From 972996d6ee79b80064537621bdccb40c85f5cf33 Mon Sep 17 00:00:00 2001 From: rafaelsideguide <150964962+rafaelsideguide@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:04:17 -0300 Subject: [PATCH 1/3] wip --- firecrawl.go | 173 ++++++++++++++++++++----- firecrawl_test.go | 80 ++++++------ firecrawl_test_v1.go | 292 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 473 insertions(+), 72 deletions(-) create mode 100644 firecrawl_test_v1.go diff --git a/firecrawl.go b/firecrawl.go index 9a9dcfe..8d01c0d 100644 --- a/firecrawl.go +++ b/firecrawl.go @@ -12,8 +12,8 @@ import ( "time" ) -// FirecrawlDocumentMetadata represents metadata for a Firecrawl document -type FirecrawlDocumentMetadata struct { +// FirecrawlDocumentMetadataV0 represents metadata for a Firecrawl document for v0 +type FirecrawlDocumentMetadataV0 struct { Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` Language string `json:"language,omitempty"` @@ -48,8 +48,44 @@ type FirecrawlDocumentMetadata struct { PageError string `json:"pageError,omitempty"` } -// FirecrawlDocument represents a document in Firecrawl -type FirecrawlDocument struct { +// FirecrawlDocumentMetadata represents metadata for a Firecrawl document for v1 +type FirecrawlDocumentMetadata struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Language string `json:"language,omitempty"` + Keywords string `json:"keywords,omitempty"` + Robots string `json:"robots,omitempty"` + OGTitle string `json:"ogTitle,omitempty"` + OGDescription string `json:"ogDescription,omitempty"` + OGURL string `json:"ogUrl,omitempty"` + OGImage string `json:"ogImage,omitempty"` + OGAudio string `json:"ogAudio,omitempty"` + OGDeterminer string `json:"ogDeterminer,omitempty"` + OGLocale string `json:"ogLocale,omitempty"` + OGLocaleAlternate []string `json:"ogLocaleAlternate,omitempty"` + OGSiteName string `json:"ogSiteName,omitempty"` + OGVideo string `json:"ogVideo,omitempty"` + DCTermsCreated string `json:"dctermsCreated,omitempty"` + DCDateCreated string `json:"dcDateCreated,omitempty"` + DCDate string `json:"dcDate,omitempty"` + DCTermsType string `json:"dctermsType,omitempty"` + DCType string `json:"dcType,omitempty"` + DCTermsAudience string `json:"dctermsAudience,omitempty"` + DCTermsSubject string `json:"dctermsSubject,omitempty"` + DCSubject string `json:"dcSubject,omitempty"` + DCDescription string `json:"dcDescription,omitempty"` + DCTermsKeywords string `json:"dctermsKeywords,omitempty"` + ModifiedTime string `json:"modifiedTime,omitempty"` + PublishedTime string `json:"publishedTime,omitempty"` + ArticleTag string `json:"articleTag,omitempty"` + ArticleSection string `json:"articleSection,omitempty"` + SourceURL string `json:"sourceURL,omitempty"` + StatusCode int `json:"statusCode,omitempty"` + Error string `json:"error,omitempty"` +} + +// FirecrawlDocumentV0 represents a document in Firecrawl for v0 +type FirecrawlDocumentV0 struct { ID string `json:"id,omitempty"` URL string `json:"url,omitempty"` Content string `json:"content"` @@ -66,6 +102,23 @@ type FirecrawlDocument struct { Index int `json:"index,omitempty"` } +// FirecrawlDocument represents a document in Firecrawl for v1 +type FirecrawlDocument struct { + ID string `json:"id,omitempty"` + URL string `json:"url,omitempty"` + Markdown string `json:"markdown,omitempty"` + HTML string `json:"html,omitempty"` + LLMExtraction map[string]any `json:"llm_extraction,omitempty"` + CreatedAt *time.Time `json:"createdAt,omitempty"` + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + Type string `json:"type,omitempty"` + Metadata *FirecrawlDocumentMetadata `json:"metadata,omitempty"` + ChildrenLinks []string `json:"childrenLinks,omitempty"` + Provider string `json:"provider,omitempty"` + Warning string `json:"warning,omitempty"` + Index int `json:"index,omitempty"` +} + // ExtractorOptions represents options for extraction. type ExtractorOptions struct { Mode string `json:"mode,omitempty"` @@ -73,39 +126,74 @@ type ExtractorOptions struct { ExtractionSchema any `json:"extractionSchema,omitempty"` } +// ScrapeResponseV0 represents the response for scraping operations for v0 +type ScrapeResponseV0 struct { + Success bool `json:"success"` + Data *FirecrawlDocumentV0 `json:"data,omitempty"` +} + // ScrapeResponse represents the response for scraping operations type ScrapeResponse struct { Success bool `json:"success"` Data *FirecrawlDocument `json:"data,omitempty"` } +// SearchResponseV0 represents the response for searching operations for v0 +type SearchResponseV0 struct { + Success bool `json:"success"` + Data []*FirecrawlDocumentV0 `json:"data,omitempty"` +} + // SearchResponse represents the response for searching operations type SearchResponse struct { Success bool `json:"success"` Data []*FirecrawlDocument `json:"data,omitempty"` } -// CrawlResponse represents the response for crawling operations +// CrawlResponseV0 represents the response for crawling operations for v0 +type CrawlResponseV0 struct { + Success bool `json:"success"` + JobID string `json:"jobId,omitempty"` + Data []*FirecrawlDocumentV0 `json:"data,omitempty"` +} + +// CrawlResponse represents the response for crawling operations for v1 type CrawlResponse struct { Success bool `json:"success"` - JobID string `json:"jobId,omitempty"` + ID string `json:"id,omitempty"` Data []*FirecrawlDocument `json:"data,omitempty"` } -// JobStatusResponse represents the response for checking crawl job status -type JobStatusResponse struct { - Success bool `json:"success"` +// JobStatusResponseV0 represents the response for checking crawl job status for v0 +type JobStatusResponseV0 struct { + Success bool `json:"success"` + Status string `json:"status"` + Current int `json:"current,omitempty"` + CurrentURL string `json:"current_url,omitempty"` + CurrentStep string `json:"current_step,omitempty"` + Total int `json:"total,omitempty"` + JobID string `json:"jobId,omitempty"` + Data []*FirecrawlDocumentV0 `json:"data,omitempty"` + PartialData []*FirecrawlDocumentV0 `json:"partial_data,omitempty"` +} + +// CrawlStatusResponse (old JobStatusResponse) represents the response for checking crawl job status for v1 +type CrawlStatusResponse struct { Status string `json:"status"` - Current int `json:"current,omitempty"` - CurrentURL string `json:"current_url,omitempty"` - CurrentStep string `json:"current_step,omitempty"` - Total int `json:"total,omitempty"` - JobID string `json:"jobId,omitempty"` + TotalCount int `json:"total_count,omitempty"` + CreditsUsed int `json:"credits_used,omitempty"` + ExpiresAt string `json:"expires_at,omitempty"` + Next string `json:"next,omitempty"` Data []*FirecrawlDocument `json:"data,omitempty"` - PartialData []*FirecrawlDocument `json:"partial_data,omitempty"` } -// CancelCrawlJobResponse represents the response for canceling a crawl job +// CancelCrawlJobResponseV0 represents the response for canceling a crawl job for v0 +type CancelCrawlJobResponseV0 struct { + Success bool `json:"success"` + Status string `json:"status"` +} + +// CancelCrawlJobResponse represents the response for canceling a crawl job for v1 type CancelCrawlJobResponse struct { Success bool `json:"success"` Status string `json:"status"` @@ -163,9 +251,10 @@ func withBackoff(backoff int) requestOption { // FirecrawlApp represents a client for the Firecrawl API. type FirecrawlApp struct { - APIKey string - APIURL string - Client *http.Client + APIKey string + APIURL string + Client *http.Client + Version string } // NewFirecrawlApp creates a new instance of FirecrawlApp with the provided API key and API URL. @@ -179,7 +268,7 @@ type FirecrawlApp struct { // Returns: // - *FirecrawlApp: A new instance of FirecrawlApp configured with the provided or retrieved API key and API URL. // - error: An error if the API key is not provided or retrieved. -func NewFirecrawlApp(apiKey, apiURL string) (*FirecrawlApp, error) { +func NewFirecrawlApp(apiKey, apiURL string, version string) (*FirecrawlApp, error) { if apiKey == "" { apiKey = os.Getenv("FIRECRAWL_API_KEY") if apiKey == "" { @@ -194,14 +283,19 @@ func NewFirecrawlApp(apiKey, apiURL string) (*FirecrawlApp, error) { } } + if version == "" { + version = "v1" + } + client := &http.Client{ Timeout: 60 * time.Second, } return &FirecrawlApp{ - APIKey: apiKey, - APIURL: apiURL, - Client: client, + APIKey: apiKey, + APIURL: apiURL, + Client: client, + Version: version, }, nil } @@ -212,9 +306,9 @@ func NewFirecrawlApp(apiKey, apiURL string) (*FirecrawlApp, error) { // - params: Optional parameters for the scrape request, including extractor options for LLM extraction. // // Returns: -// - *FirecrawlDocument: The scraped document data. +// - *FirecrawlDocument or *FirecrawlDocumentV0: The scraped document data depending on the API version. // - error: An error if the scrape request fails. -func (app *FirecrawlApp) ScrapeURL(url string, params map[string]any) (*FirecrawlDocument, error) { +func (app *FirecrawlApp) ScrapeURL(url string, params map[string]any) (any, error) { headers := app.prepareHeaders("") scrapeBody := map[string]any{"url": url} @@ -238,7 +332,7 @@ func (app *FirecrawlApp) ScrapeURL(url string, params map[string]any) (*Firecraw resp, err := app.makeRequest( http.MethodPost, - fmt.Sprintf("%s/v0/scrape", app.APIURL), + fmt.Sprintf("%s/%s/scrape", app.APIURL, app.Version), scrapeBody, headers, "scrape URL", @@ -247,14 +341,24 @@ func (app *FirecrawlApp) ScrapeURL(url string, params map[string]any) (*Firecraw return nil, err } - var scrapeResponse ScrapeResponse - err = json.Unmarshal(resp, &scrapeResponse) - if err != nil { - return nil, err + if app.Version == "v0" { + var scrapeResponseV0 ScrapeResponseV0 + err = json.Unmarshal(resp, &scrapeResponseV0) + + if scrapeResponseV0.Success { + return scrapeResponseV0.Data, nil + } + } else if app.Version == "v1" { + var scrapeResponse ScrapeResponse + err = json.Unmarshal(resp, &scrapeResponse) + + if scrapeResponse.Success { + return scrapeResponse.Data, nil + } } - if scrapeResponse.Success { - return scrapeResponse.Data, nil + if err != nil { + return nil, err } return nil, fmt.Errorf("failed to scrape URL") @@ -271,6 +375,11 @@ func (app *FirecrawlApp) ScrapeURL(url string, params map[string]any) (*Firecraw // - error: An error if the search request fails. func (app *FirecrawlApp) Search(query string, params map[string]any) ([]*FirecrawlDocument, error) { headers := app.prepareHeaders("") + + if app.Version == "v1" { + return nil, fmt.Errorf("Search is not supported in v1") + } + searchBody := map[string]any{"query": query} for k, v := range params { searchBody[k] = v diff --git a/firecrawl_test.go b/firecrawl_test.go index 9d56c7a..cba6c12 100644 --- a/firecrawl_test.go +++ b/firecrawl_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/require" ) -var API_URL string -var TEST_API_KEY string +var API_URL_V0 string +var TEST_API_KEY_V0 string func init() { err := godotenv.Load("../.env") @@ -24,14 +24,14 @@ func init() { TEST_API_KEY = os.Getenv("TEST_API_KEY") } -func TestNoAPIKey(t *testing.T) { - _, err := NewFirecrawlApp("", API_URL) +func TestNoAPIKeyV0(t *testing.T) { + _, err := NewFirecrawlApp("", API_URL, "v0") assert.Error(t, err) assert.Contains(t, err.Error(), "no API key provided") } -func TestScrapeURLInvalidAPIKey(t *testing.T) { - app, err := NewFirecrawlApp("invalid_api_key", API_URL) +func TestScrapeURLInvalidAPIKeyV0(t *testing.T) { + app, err := NewFirecrawlApp("invalid_api_key", API_URL, "v0") require.NoError(t, err) _, err = app.ScrapeURL("https://firecrawl.dev", nil) @@ -39,8 +39,8 @@ func TestScrapeURLInvalidAPIKey(t *testing.T) { assert.Contains(t, err.Error(), "Unexpected error during scrape URL: Status code 401. Unauthorized: Invalid token") } -func TestBlocklistedURL(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestBlocklistedURLV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) _, err = app.ScrapeURL("https://facebook.com/fake-test", nil) @@ -48,8 +48,8 @@ func TestBlocklistedURL(t *testing.T) { assert.Contains(t, err.Error(), "Unexpected error during scrape URL: Status code 403. Firecrawl currently does not support social media scraping due to policy restrictions.") } -func TestSuccessfulResponseWithValidPreviewToken(t *testing.T) { - app, err := NewFirecrawlApp("this_is_just_a_preview_token", API_URL) +func TestSuccessfulResponseWithValidPreviewTokenV0(t *testing.T) { + app, err := NewFirecrawlApp("this_is_just_a_preview_token", API_URL, "v0") require.NoError(t, err) response, err := app.ScrapeURL("https://roastmywebsite.ai", nil) @@ -59,8 +59,8 @@ func TestSuccessfulResponseWithValidPreviewToken(t *testing.T) { assert.Contains(t, response.Content, "_Roast_") } -func TestScrapeURLE2E(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestScrapeURLE2EV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) response, err := app.ScrapeURL("https://roastmywebsite.ai", nil) @@ -73,8 +73,8 @@ func TestScrapeURLE2E(t *testing.T) { assert.Equal(t, response.HTML, "") } -func TestSuccessfulResponseWithValidAPIKeyAndIncludeHTML(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestSuccessfulResponseWithValidAPIKeyAndIncludeHTMLV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) params := map[string]any{ @@ -92,8 +92,8 @@ func TestSuccessfulResponseWithValidAPIKeyAndIncludeHTML(t *testing.T) { assert.NotNil(t, response.Metadata) } -func TestSuccessfulResponseForValidScrapeWithPDFFile(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestSuccessfulResponseForValidScrapeWithPDFFileV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) response, err := app.ScrapeURL("https://arxiv.org/pdf/astro-ph/9301001.pdf", nil) @@ -104,8 +104,8 @@ func TestSuccessfulResponseForValidScrapeWithPDFFile(t *testing.T) { assert.NotNil(t, response.Metadata) } -func TestSuccessfulResponseForValidScrapeWithPDFFileWithoutExplicitExtension(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestSuccessfulResponseForValidScrapeWithPDFFileWithoutExplicitExtensionV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) response, err := app.ScrapeURL("https://arxiv.org/pdf/astro-ph/9301001", nil) @@ -117,8 +117,8 @@ func TestSuccessfulResponseForValidScrapeWithPDFFileWithoutExplicitExtension(t * assert.NotNil(t, response.Metadata) } -func TestCrawlURLInvalidAPIKey(t *testing.T) { - app, err := NewFirecrawlApp("invalid_api_key", API_URL) +func TestCrawlURLInvalidAPIKeyV0(t *testing.T) { + app, err := NewFirecrawlApp("invalid_api_key", API_URL, "v0") require.NoError(t, err) _, err = app.CrawlURL("https://firecrawl.dev", nil, false, 2, "") @@ -126,8 +126,8 @@ func TestCrawlURLInvalidAPIKey(t *testing.T) { assert.Contains(t, err.Error(), "Unexpected error during start crawl job: Status code 401. Unauthorized: Invalid token") } -func TestShouldReturnErrorForBlocklistedURL(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestShouldReturnErrorForBlocklistedURLV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) _, err = app.CrawlURL("https://twitter.com/fake-test", nil, false, 2, "") @@ -135,8 +135,8 @@ func TestShouldReturnErrorForBlocklistedURL(t *testing.T) { assert.Contains(t, err.Error(), "Unexpected error during start crawl job: Status code 403. Firecrawl currently does not support social media scraping due to policy restrictions.") } -func TestCrawlURLWaitForCompletionE2E(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestCrawlURLWaitForCompletionE2EV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) params := map[string]any{ @@ -154,8 +154,8 @@ func TestCrawlURLWaitForCompletionE2E(t *testing.T) { assert.Contains(t, data[0].Content, "_Roast_") } -func TestCrawlURLWithIdempotencyKeyE2E(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestCrawlURLWithIdempotencyKeyE2EV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) uniqueIdempotencyKey := uuid.New().String() @@ -178,8 +178,8 @@ func TestCrawlURLWithIdempotencyKeyE2E(t *testing.T) { assert.Contains(t, err.Error(), "Conflict: Failed to start crawl job due to a conflict. Idempotency key already used") } -func TestCheckCrawlStatusE2E(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestCheckCrawlStatusE2EV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) params := map[string]any{ @@ -205,8 +205,8 @@ func TestCheckCrawlStatusE2E(t *testing.T) { assert.Greater(t, len(statusResponse.Data), 0) } -func TestSearchE2E(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestSearchE2EV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) response, err := app.Search("test query", nil) @@ -217,8 +217,8 @@ func TestSearchE2E(t *testing.T) { assert.NotEqual(t, response[0].Content, "") } -func TestSearchInvalidAPIKey(t *testing.T) { - app, err := NewFirecrawlApp("invalid_api_key", API_URL) +func TestSearchInvalidAPIKeyV0(t *testing.T) { + app, err := NewFirecrawlApp("invalid_api_key", API_URL, "v0") require.NoError(t, err) _, err = app.Search("test query", nil) @@ -226,8 +226,8 @@ func TestSearchInvalidAPIKey(t *testing.T) { assert.Contains(t, err.Error(), "Unexpected error during search: Status code 401. Unauthorized: Invalid token") } -func TestLLMExtraction(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestLLMExtractionV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) params := map[string]any{ @@ -255,8 +255,8 @@ func TestLLMExtraction(t *testing.T) { assert.IsType(t, true, response.LLMExtraction["is_open_source"]) } -func TestCancelCrawlJobInvalidAPIKey(t *testing.T) { - app, err := NewFirecrawlApp("invalid_api_key", API_URL) +func TestCancelCrawlJobInvalidAPIKeyV0(t *testing.T) { + app, err := NewFirecrawlApp("invalid_api_key", API_URL, "v0") require.NoError(t, err) _, err = app.CancelCrawlJob("test query") @@ -264,8 +264,8 @@ func TestCancelCrawlJobInvalidAPIKey(t *testing.T) { assert.Contains(t, err.Error(), "Unexpected error during cancel crawl job: Status code 401. Unauthorized: Invalid token") } -func TestCancelNonExistingCrawlJob(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestCancelNonExistingCrawlJobV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) jobID := uuid.New().String() @@ -274,8 +274,8 @@ func TestCancelNonExistingCrawlJob(t *testing.T) { assert.Contains(t, err.Error(), "Job not found") } -func TestCancelCrawlJobE2E(t *testing.T) { - app, err := NewFirecrawlApp(TEST_API_KEY, API_URL) +func TestCancelCrawlJobE2EV0(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v0") require.NoError(t, err) response, err := app.CrawlURL("https://firecrawl.dev", nil, false, 2, "") diff --git a/firecrawl_test_v1.go b/firecrawl_test_v1.go new file mode 100644 index 0000000..0a0832f --- /dev/null +++ b/firecrawl_test_v1.go @@ -0,0 +1,292 @@ +package firecrawl + +import ( + "log" + "os" + "testing" + "time" + + "github.com/google/uuid" + "github.com/joho/godotenv" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var API_URL string +var TEST_API_KEY string + +func init() { + err := godotenv.Load("../.env") + if err != nil { + log.Fatalf("Error loading .env file: %v", err) + } + API_URL = os.Getenv("API_URL") + TEST_API_KEY = os.Getenv("TEST_API_KEY") +} + +func TestNoAPIKey(t *testing.T) { + _, err := NewFirecrawlApp("", API_URL, "v1") + assert.Error(t, err) + assert.Contains(t, err.Error(), "no API key provided") +} + +func TestScrapeURLInvalidAPIKey(t *testing.T) { + app, err := NewFirecrawlApp("invalid_api_key", API_URL, "v1") + require.NoError(t, err) + + _, err = app.ScrapeURL("https://firecrawl.dev", nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Unexpected error during scrape URL: Status code 401. Unauthorized: Invalid token") +} + +func TestBlocklistedURL(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v1") + require.NoError(t, err) + + _, err = app.ScrapeURL("https://facebook.com/fake-test", nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Unexpected error during scrape URL: Status code 403. Firecrawl currently does not support social media scraping due to policy restrictions.") +} + +func TestSuccessfulResponseWithValidPreviewToken(t *testing.T) { + app, err := NewFirecrawlApp("this_is_just_a_preview_token", API_URL, "v1") + require.NoError(t, err) + + response, err := app.ScrapeURL("https://roastmywebsite.ai", nil) + require.NoError(t, err) + assert.NotNil(t, response) + + assert.Contains(t, response.Content, "_Roast_") +} + +func TestScrapeURLE2E(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v1") + require.NoError(t, err) + + response, err := app.ScrapeURL("https://roastmywebsite.ai", nil) + require.NoError(t, err) + assert.NotNil(t, response) + + assert.Contains(t, response.Content, "_Roast_") + assert.NotEqual(t, response.Markdown, "") + assert.NotNil(t, response.Metadata) + assert.Equal(t, response.HTML, "") +} + +func TestSuccessfulResponseWithValidAPIKeyAndIncludeHTML(t *testing.T) { + app, err := NewFirecrawlApp(TEST_API_KEY, API_URL, "v1") + require.NoError(t, err) + + params := map[string]any{ + "pageOptions": map[string]any{ + "includeHtml": true, + }, + } + response, err := app.ScrapeURL("https://roastmywebsite.ai", params) + require.NoError(t, err) + assert.NotNil(t, response) + + assert.Contains(t, response.Content, "_Roast_") + assert.Contains(t, response.Markdown, "_Roast_") + assert.Contains(t, response.HTML, "