@mendable/firecrawl-js 4.38.0 → 4.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/{chunk-KSTNOGOD.js → chunk-4DPCCKWG.js} +1 -1
- package/dist/index.cjs +39 -9
- package/dist/index.d.cts +152 -4
- package/dist/index.d.ts +152 -4
- package/dist/index.js +40 -10
- package/dist/{package-OS7RONXY.js → package-D2QZMRW7.js} +1 -1
- package/package.json +1 -1
- package/pnpm-workspace.yaml +1 -0
- package/src/__tests__/e2e/v2/batch.test.ts +13 -11
- package/src/__tests__/e2e/v2/crawl.test.ts +24 -20
- package/src/__tests__/e2e/v2/extract.test.ts +32 -7
- package/src/__tests__/e2e/v2/map.test.ts +4 -3
- package/src/__tests__/e2e/v2/parse.test.ts +4 -3
- package/src/__tests__/e2e/v2/scrape.test.ts +10 -9
- package/src/__tests__/e2e/v2/search.test.ts +9 -8
- package/src/__tests__/e2e/v2/usage.test.ts +6 -5
- package/src/__tests__/e2e/v2/utils/rateLimit.ts +257 -0
- package/src/__tests__/e2e/v2/watcher.test.ts +4 -3
- package/src/__tests__/unit/v2/agent-effort-trace.unit.test.ts +1 -1
- package/src/__tests__/unit/v2/agent-thread.unit.test.ts +164 -0
- package/src/__tests__/unit/v2/e2eRateLimitRetry.test.ts +501 -0
- package/src/__tests__/unit/v2/extract.unit.test.ts +1 -1
- package/src/__tests__/unit/v2/scrapeAutoResume.test.ts +1 -1
- package/src/__tests__/unit/v2/search-country.unit.test.ts +32 -0
- package/src/__tests__/unit/v2/search-data-property.test.ts +1 -0
- package/src/__tests__/unit/v2/v1-aliases.test.ts +1 -0
- package/src/v2/client.ts +13 -1
- package/src/v2/methods/agent.ts +23 -1
- package/src/v2/methods/research.ts +14 -8
- package/src/v2/methods/search.ts +1 -0
- package/src/v2/types.ts +151 -34
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
require_package
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4DPCCKWG.js";
|
|
4
4
|
|
|
5
5
|
// src/v2/utils/httpClient.ts
|
|
6
6
|
import axios from "axios";
|
|
@@ -620,6 +620,7 @@ function prepareSearchPayload(req) {
|
|
|
620
620
|
if (req.limit != null) payload.limit = req.limit;
|
|
621
621
|
if (req.tbs != null) payload.tbs = req.tbs;
|
|
622
622
|
if (req.location != null) payload.location = req.location;
|
|
623
|
+
if (req.country != null) payload.country = req.country;
|
|
623
624
|
if (req.ignoreInvalidURLs != null)
|
|
624
625
|
payload.ignoreInvalidURLs = req.ignoreInvalidURLs;
|
|
625
626
|
if (req.timeout != null) payload.timeout = req.timeout;
|
|
@@ -1240,6 +1241,9 @@ function prepareAgentPayload(args) {
|
|
|
1240
1241
|
body.threatProtection = args.threatProtection;
|
|
1241
1242
|
if (args.auditMetadata != null)
|
|
1242
1243
|
body.auditMetadata = args.auditMetadata;
|
|
1244
|
+
if (args.threadId != null) body.threadId = args.threadId;
|
|
1245
|
+
if (args.mode != null) body.mode = args.mode;
|
|
1246
|
+
if (args.exchange != null) body.exchange = args.exchange;
|
|
1243
1247
|
return body;
|
|
1244
1248
|
}
|
|
1245
1249
|
async function startAgent(http, args) {
|
|
@@ -1310,6 +1314,17 @@ async function getAgentSnapshot(http, jobId, snapshotId) {
|
|
|
1310
1314
|
throw err;
|
|
1311
1315
|
}
|
|
1312
1316
|
}
|
|
1317
|
+
async function getAgentThread(http, threadId, options) {
|
|
1318
|
+
try {
|
|
1319
|
+
const query = options?.includeData ? "?includeData=true" : "";
|
|
1320
|
+
const res = await http.get(`/v2/agent/threads/${threadId}${query}`);
|
|
1321
|
+
if (res.status !== 200) throwForBadResponse(res, "agent thread");
|
|
1322
|
+
return res.data;
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
if (err?.isAxiosError) return normalizeAxiosError(err, "agent thread");
|
|
1325
|
+
throw err;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1313
1328
|
async function cancelAgent(http, jobId) {
|
|
1314
1329
|
try {
|
|
1315
1330
|
const res = await http.delete(`/v2/agent/${jobId}`);
|
|
@@ -1550,9 +1565,7 @@ var ResearchClient = class {
|
|
|
1550
1565
|
appendParam(params, "query", options.query);
|
|
1551
1566
|
appendParam(params, "k", options.k);
|
|
1552
1567
|
try {
|
|
1553
|
-
const res = await this.http.get(
|
|
1554
|
-
withQuery(`${BASE}/papers/${encodeURIComponent(id)}`, params)
|
|
1555
|
-
);
|
|
1568
|
+
const res = await this.http.get(withQuery(`${BASE}/papers/${encodeURIComponent(id)}`, params));
|
|
1556
1569
|
if (res.status !== 200) throwForBadResponse(res, "get paper");
|
|
1557
1570
|
return res.data;
|
|
1558
1571
|
} catch (err) {
|
|
@@ -1578,10 +1591,7 @@ var ResearchClient = class {
|
|
|
1578
1591
|
appendParam(params, "anchor", options.anchor);
|
|
1579
1592
|
try {
|
|
1580
1593
|
const res = await this.http.get(
|
|
1581
|
-
withQuery(
|
|
1582
|
-
`${BASE}/papers/${encodeURIComponent(id)}/similar`,
|
|
1583
|
-
params
|
|
1584
|
-
)
|
|
1594
|
+
withQuery(`${BASE}/papers/${encodeURIComponent(id)}/similar`, params)
|
|
1585
1595
|
);
|
|
1586
1596
|
if (res.status !== 200) throwForBadResponse(res, "find similar papers");
|
|
1587
1597
|
return res.data;
|
|
@@ -1590,7 +1600,16 @@ var ResearchClient = class {
|
|
|
1590
1600
|
}
|
|
1591
1601
|
}
|
|
1592
1602
|
/**
|
|
1593
|
-
* Search GitHub issue/PR history and repository
|
|
1603
|
+
* Search the research index GitHub slice: issue/PR history and repository
|
|
1604
|
+
* readmes.
|
|
1605
|
+
*
|
|
1606
|
+
* @deprecated Use `developerSearch()` on the client instead. This
|
|
1607
|
+
* endpoint stops responding after 2026-11-03. The developer index searches
|
|
1608
|
+
* GitHub issues, pull requests and readmes plus curated documentation
|
|
1609
|
+
* sources, returns matched passages, and adds filters for repo, language,
|
|
1610
|
+
* license and stars. It does not carry over the `scores` breakdown or the
|
|
1611
|
+
* `resultType: "web"` fallback results. See
|
|
1612
|
+
* https://docs.firecrawl.dev/features/developer.
|
|
1594
1613
|
* @param query Search query.
|
|
1595
1614
|
* @param options Optional `k`.
|
|
1596
1615
|
*/
|
|
@@ -2079,6 +2098,9 @@ var FirecrawlClient = class {
|
|
|
2079
2098
|
* Access the v2 research endpoints — Firecrawl's **research paper index**
|
|
2080
2099
|
* (~43M paper abstracts) plus GitHub history/readmes.
|
|
2081
2100
|
*
|
|
2101
|
+
* `research.searchGithub()` is deprecated and stops responding after
|
|
2102
|
+
* 2026-11-03. Use `developerSearch()` instead.
|
|
2103
|
+
*
|
|
2082
2104
|
* The paper corpus is roughly 90% biomedical and life sciences — PubMed,
|
|
2083
2105
|
* bioRxiv and medRxiv — with arXiv covering physics, mathematics and
|
|
2084
2106
|
* computer science.
|
|
@@ -2338,6 +2360,14 @@ var FirecrawlClient = class {
|
|
|
2338
2360
|
async getAgentSnapshot(jobId, snapshotId) {
|
|
2339
2361
|
return getAgentSnapshot(this.http, jobId, snapshotId);
|
|
2340
2362
|
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Get a thread and its runs, oldest turn first.
|
|
2365
|
+
* @param threadId Thread id, as returned by startAgent or getAgentStatus.
|
|
2366
|
+
* @param options.includeData Inline each succeeded run's data.
|
|
2367
|
+
*/
|
|
2368
|
+
async getAgentThread(threadId, options) {
|
|
2369
|
+
return getAgentThread(this.http, threadId, options);
|
|
2370
|
+
}
|
|
2341
2371
|
// Browser
|
|
2342
2372
|
/**
|
|
2343
2373
|
* Create a new browser session.
|
|
@@ -2480,7 +2510,7 @@ var FirecrawlApp = class {
|
|
|
2480
2510
|
if (typeof process !== "undefined" && process.env && process.env.npm_package_version) {
|
|
2481
2511
|
return process.env.npm_package_version;
|
|
2482
2512
|
}
|
|
2483
|
-
const packageJson = await import("./package-
|
|
2513
|
+
const packageJson = await import("./package-D2QZMRW7.js");
|
|
2484
2514
|
return packageJson.default.version;
|
|
2485
2515
|
} catch (error) {
|
|
2486
2516
|
const isTest = typeof process !== "undefined" && (process.env.JEST_WORKER_ID != null || false);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mendable/firecrawl-js",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.39.0",
|
|
4
4
|
"description": "JavaScript SDK for the Firecrawl API: web scraping, crawling, web search, and scientific literature search over a research paper index of PubMed, bioRxiv, medRxiv and arXiv abstracts",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import Firecrawl from "../../../index";
|
|
5
5
|
import { config } from "dotenv";
|
|
6
6
|
import { getIdentity, getApiUrl } from "./utils/idmux";
|
|
7
|
+
import { testTimeoutMs, waitForJob, withRateLimitRetry } from "./utils/rateLimit";
|
|
7
8
|
import { describe, test, expect, beforeAll } from "@jest/globals";
|
|
8
9
|
|
|
9
10
|
config();
|
|
@@ -13,7 +14,7 @@ let client: Firecrawl;
|
|
|
13
14
|
|
|
14
15
|
beforeAll(async () => {
|
|
15
16
|
const { apiKey } = await getIdentity({ name: "js-e2e-batch" });
|
|
16
|
-
client = new Firecrawl({ apiKey, apiUrl: API_URL });
|
|
17
|
+
client = withRateLimitRetry(new Firecrawl({ apiKey, apiUrl: API_URL }));
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
describe("v2.batch e2e", () => {
|
|
@@ -22,19 +23,21 @@ describe("v2.batch e2e", () => {
|
|
|
22
23
|
"https://docs.firecrawl.dev",
|
|
23
24
|
"https://firecrawl.dev",
|
|
24
25
|
];
|
|
25
|
-
const
|
|
26
|
+
const start = await client.startBatchScrape(urls, { options: { formats: ["markdown"] } });
|
|
27
|
+
const job = await waitForJob(() => client.getBatchScrapeStatus(start.id), { pollInterval: 1, timeout: 210 });
|
|
26
28
|
expect(["completed", "failed"]).toContain(job.status);
|
|
27
29
|
expect(job.completed).toBeGreaterThanOrEqual(0);
|
|
28
30
|
expect(job.total).toBeGreaterThanOrEqual(0);
|
|
29
31
|
expect(Array.isArray(job.data)).toBe(true);
|
|
30
|
-
},
|
|
32
|
+
}, testTimeoutMs(362_000));
|
|
31
33
|
|
|
32
34
|
test("batch scrape with wait returns job id for error retrieval", async () => {
|
|
33
35
|
const urls = [
|
|
34
36
|
"https://docs.firecrawl.dev",
|
|
35
37
|
"https://firecrawl.dev",
|
|
36
38
|
];
|
|
37
|
-
const
|
|
39
|
+
const start = await client.startBatchScrape(urls, { options: { formats: ["markdown"] } });
|
|
40
|
+
const job = await waitForJob(() => client.getBatchScrapeStatus(start.id), { pollInterval: 1, timeout: 210 });
|
|
38
41
|
// Verify job has id field
|
|
39
42
|
expect(job.id).toBeDefined();
|
|
40
43
|
expect(typeof job.id).toBe("string");
|
|
@@ -44,7 +47,7 @@ describe("v2.batch e2e", () => {
|
|
|
44
47
|
expect(errors).toHaveProperty("robotsBlocked");
|
|
45
48
|
expect(Array.isArray(errors.errors)).toBe(true);
|
|
46
49
|
expect(Array.isArray(errors.robotsBlocked)).toBe(true);
|
|
47
|
-
},
|
|
50
|
+
}, testTimeoutMs(512_000));
|
|
48
51
|
|
|
49
52
|
test("start batch minimal and status", async () => {
|
|
50
53
|
const urls = ["https://docs.firecrawl.dev", "https://firecrawl.dev"];
|
|
@@ -57,11 +60,11 @@ describe("v2.batch e2e", () => {
|
|
|
57
60
|
// Verify status includes id field
|
|
58
61
|
expect(status.id).toBeDefined();
|
|
59
62
|
expect(status.id).toBe(start.id);
|
|
60
|
-
},
|
|
63
|
+
}, testTimeoutMs(151_000));
|
|
61
64
|
|
|
62
65
|
test("wait batch with all params", async () => {
|
|
63
66
|
const urls = ["https://docs.firecrawl.dev", "https://firecrawl.dev"];
|
|
64
|
-
const
|
|
67
|
+
const start = await client.startBatchScrape(urls, {
|
|
65
68
|
options: {
|
|
66
69
|
formats: [
|
|
67
70
|
"markdown",
|
|
@@ -74,14 +77,13 @@ describe("v2.batch e2e", () => {
|
|
|
74
77
|
ignoreInvalidURLs: true,
|
|
75
78
|
maxConcurrency: 2,
|
|
76
79
|
zeroDataRetention: false,
|
|
77
|
-
pollInterval: 1,
|
|
78
|
-
timeout: 180,
|
|
79
80
|
});
|
|
81
|
+
const job = await waitForJob(() => client.getBatchScrapeStatus(start.id), { pollInterval: 1, timeout: 210 });
|
|
80
82
|
expect(["completed", "failed", "cancelled"]).toContain(job.status);
|
|
81
83
|
expect(job.completed).toBeGreaterThanOrEqual(0);
|
|
82
84
|
expect(job.total).toBeGreaterThanOrEqual(0);
|
|
83
85
|
expect(Array.isArray(job.data)).toBe(true);
|
|
84
|
-
},
|
|
86
|
+
}, testTimeoutMs(362_000));
|
|
85
87
|
|
|
86
88
|
test("cancel batch", async () => {
|
|
87
89
|
const urls = ["https://docs.firecrawl.dev", "https://firecrawl.dev"];
|
|
@@ -89,6 +91,6 @@ describe("v2.batch e2e", () => {
|
|
|
89
91
|
expect(typeof start.id).toBe("string");
|
|
90
92
|
const cancelled = await client.cancelBatchScrape(start.id);
|
|
91
93
|
expect(cancelled).toBe(true);
|
|
92
|
-
},
|
|
94
|
+
}, testTimeoutMs(151_000));
|
|
93
95
|
});
|
|
94
96
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import Firecrawl from "../../../index";
|
|
5
5
|
import { config } from "dotenv";
|
|
6
6
|
import { getIdentity, getApiUrl } from "./utils/idmux";
|
|
7
|
+
import { testTimeoutMs, waitForJob, withRateLimitRetry } from "./utils/rateLimit";
|
|
7
8
|
import { describe, test, expect, beforeAll } from "@jest/globals";
|
|
8
9
|
|
|
9
10
|
config();
|
|
@@ -13,7 +14,7 @@ let client: Firecrawl;
|
|
|
13
14
|
|
|
14
15
|
beforeAll(async () => {
|
|
15
16
|
const { apiKey } = await getIdentity({ name: "js-e2e-crawl" });
|
|
16
|
-
client = new Firecrawl({ apiKey, apiUrl: API_URL });
|
|
17
|
+
client = withRateLimitRetry(new Firecrawl({ apiKey, apiUrl: API_URL }));
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
describe("v2.crawl e2e", () => {
|
|
@@ -23,21 +24,21 @@ describe("v2.crawl e2e", () => {
|
|
|
23
24
|
const job = await client.startCrawl("https://docs.firecrawl.dev", { limit: 3 });
|
|
24
25
|
expect(typeof job.id).toBe("string");
|
|
25
26
|
expect(typeof job.url).toBe("string");
|
|
26
|
-
}, 90_000);
|
|
27
|
+
}, testTimeoutMs(90_000));
|
|
27
28
|
|
|
28
29
|
test("start crawl with options", async () => {
|
|
29
30
|
if (!client) throw new Error();
|
|
30
31
|
const job = await client.startCrawl("https://docs.firecrawl.dev", { limit: 5, maxDiscoveryDepth: 2 });
|
|
31
32
|
expect(typeof job.id).toBe("string");
|
|
32
33
|
expect(typeof job.url).toBe("string");
|
|
33
|
-
}, 90_000);
|
|
34
|
+
}, testTimeoutMs(90_000));
|
|
34
35
|
|
|
35
36
|
test("start crawl with prompt", async () => {
|
|
36
37
|
if (!client) throw new Error();
|
|
37
38
|
const job = await client.startCrawl("https://firecrawl.dev", { prompt: "Extract all blog posts", limit: 3 });
|
|
38
39
|
expect(typeof job.id).toBe("string");
|
|
39
40
|
expect(typeof job.url).toBe("string");
|
|
40
|
-
}, 90_000);
|
|
41
|
+
}, testTimeoutMs(90_000));
|
|
41
42
|
|
|
42
43
|
test("get crawl status", async () => {
|
|
43
44
|
if (!client) throw new Error();
|
|
@@ -50,14 +51,14 @@ describe("v2.crawl e2e", () => {
|
|
|
50
51
|
expect(status.id).toBe(start.id);
|
|
51
52
|
// next/expiresAt may be null/undefined depending on state; check shape
|
|
52
53
|
expect(Array.isArray(status.data)).toBe(true);
|
|
53
|
-
},
|
|
54
|
+
}, testTimeoutMs(151_000));
|
|
54
55
|
|
|
55
56
|
test("cancel crawl", async () => {
|
|
56
57
|
if (!client) throw new Error();
|
|
57
58
|
const start = await client.startCrawl("https://docs.firecrawl.dev", { limit: 3 });
|
|
58
59
|
const ok = await client.cancelCrawl(start.id);
|
|
59
60
|
expect(ok).toBe(true);
|
|
60
|
-
},
|
|
61
|
+
}, testTimeoutMs(151_000));
|
|
61
62
|
|
|
62
63
|
test("get crawl errors", async () => {
|
|
63
64
|
if (!client) throw new Error();
|
|
@@ -73,12 +74,12 @@ describe("v2.crawl e2e", () => {
|
|
|
73
74
|
expect(typeof e.url === "string" || e.url == null).toBe(true);
|
|
74
75
|
expect(typeof e.error === "string" || e.error == null).toBe(true);
|
|
75
76
|
}
|
|
76
|
-
},
|
|
77
|
+
}, testTimeoutMs(151_000));
|
|
77
78
|
|
|
78
79
|
test("get crawl errors with invalid id should throw", async () => {
|
|
79
80
|
if (!client) throw new Error();
|
|
80
81
|
await expect(client.getCrawlErrors("invalid-job-id-12345")).rejects.toThrow();
|
|
81
|
-
}, 60_000);
|
|
82
|
+
}, testTimeoutMs(60_000));
|
|
82
83
|
|
|
83
84
|
test("get active crawls", async () => {
|
|
84
85
|
if (!client) throw new Error();
|
|
@@ -93,7 +94,7 @@ describe("v2.crawl e2e", () => {
|
|
|
93
94
|
expect(typeof c.options === "object").toBe(true);
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
|
-
}, 90_000);
|
|
97
|
+
}, testTimeoutMs(90_000));
|
|
97
98
|
|
|
98
99
|
test("get active crawls with running crawl", async () => {
|
|
99
100
|
if (!client) throw new Error();
|
|
@@ -104,20 +105,22 @@ describe("v2.crawl e2e", () => {
|
|
|
104
105
|
const ids = active.crawls.map(c => c.id);
|
|
105
106
|
expect(ids.includes(start.id)).toBe(true);
|
|
106
107
|
await client.cancelCrawl(start.id);
|
|
107
|
-
},
|
|
108
|
+
}, testTimeoutMs(301_000));
|
|
108
109
|
|
|
109
110
|
test("crawl with wait", async () => {
|
|
110
111
|
if (!client) throw new Error();
|
|
111
|
-
const
|
|
112
|
+
const start = await client.startCrawl("https://docs.firecrawl.dev", { limit: 3, maxDiscoveryDepth: 2 });
|
|
113
|
+
const job = await waitForJob(() => client.getCrawlStatus(start.id), { pollInterval: 1, timeout: 120 });
|
|
112
114
|
expect(["completed", "failed"]).toContain(job.status);
|
|
113
115
|
expect(job.completed).toBeGreaterThanOrEqual(0);
|
|
114
116
|
expect(job.total).toBeGreaterThanOrEqual(0);
|
|
115
117
|
expect(Array.isArray(job.data)).toBe(true);
|
|
116
|
-
},
|
|
118
|
+
}, testTimeoutMs(272_000));
|
|
117
119
|
|
|
118
120
|
test("crawl with wait returns job id for error retrieval", async () => {
|
|
119
121
|
if (!client) throw new Error();
|
|
120
|
-
const
|
|
122
|
+
const start = await client.startCrawl("https://docs.firecrawl.dev", { limit: 3, maxDiscoveryDepth: 2 });
|
|
123
|
+
const job = await waitForJob(() => client.getCrawlStatus(start.id), { pollInterval: 1, timeout: 120 });
|
|
121
124
|
// Verify job has id field
|
|
122
125
|
expect(job.id).toBeDefined();
|
|
123
126
|
expect(typeof job.id).toBe("string");
|
|
@@ -127,16 +130,17 @@ describe("v2.crawl e2e", () => {
|
|
|
127
130
|
expect(errors).toHaveProperty("robotsBlocked");
|
|
128
131
|
expect(Array.isArray(errors.errors)).toBe(true);
|
|
129
132
|
expect(Array.isArray(errors.robotsBlocked)).toBe(true);
|
|
130
|
-
},
|
|
133
|
+
}, testTimeoutMs(422_000));
|
|
131
134
|
|
|
132
135
|
test("crawl with prompt and wait", async () => {
|
|
133
136
|
if (!client) throw new Error();
|
|
134
|
-
const
|
|
137
|
+
const start = await client.startCrawl("https://docs.firecrawl.dev", { prompt: "Extract all blog posts", limit: 3 });
|
|
138
|
+
const job = await waitForJob(() => client.getCrawlStatus(start.id), { pollInterval: 1, timeout: 120 });
|
|
135
139
|
expect(["completed", "failed"]).toContain(job.status);
|
|
136
140
|
expect(job.completed).toBeGreaterThanOrEqual(0);
|
|
137
141
|
expect(job.total).toBeGreaterThanOrEqual(0);
|
|
138
142
|
expect(Array.isArray(job.data)).toBe(true);
|
|
139
|
-
},
|
|
143
|
+
}, testTimeoutMs(272_000));
|
|
140
144
|
|
|
141
145
|
test("crawl with scrape options", async () => {
|
|
142
146
|
if (!client) throw new Error();
|
|
@@ -145,7 +149,7 @@ describe("v2.crawl e2e", () => {
|
|
|
145
149
|
scrapeOptions: { formats: ["markdown", "links"], onlyMainContent: false, mobile: true },
|
|
146
150
|
});
|
|
147
151
|
expect(typeof job.id).toBe("string");
|
|
148
|
-
}, 120_000);
|
|
152
|
+
}, testTimeoutMs(120_000));
|
|
149
153
|
|
|
150
154
|
test("crawl with json format object", async () => {
|
|
151
155
|
if (!client) throw new Error();
|
|
@@ -154,7 +158,7 @@ describe("v2.crawl e2e", () => {
|
|
|
154
158
|
scrapeOptions: { formats: [{ type: "json", prompt: "Extract page title", schema: { type: "object", properties: { title: { type: "string" } }, required: ["title"] } }] },
|
|
155
159
|
});
|
|
156
160
|
expect(typeof job.id).toBe("string");
|
|
157
|
-
}, 120_000);
|
|
161
|
+
}, testTimeoutMs(120_000));
|
|
158
162
|
|
|
159
163
|
test("crawl all parameters", async () => {
|
|
160
164
|
if (!client) throw new Error();
|
|
@@ -187,13 +191,13 @@ describe("v2.crawl e2e", () => {
|
|
|
187
191
|
zeroDataRetention: false,
|
|
188
192
|
});
|
|
189
193
|
expect(typeof job.id).toBe("string");
|
|
190
|
-
}, 180_000);
|
|
194
|
+
}, testTimeoutMs(180_000));
|
|
191
195
|
|
|
192
196
|
test("crawl params preview", async () => {
|
|
193
197
|
if (!client) throw new Error();
|
|
194
198
|
const params = await client.crawlParamsPreview("https://docs.firecrawl.dev", "Extract all blog posts and documentation");
|
|
195
199
|
expect(params && typeof params === "object").toBe(true);
|
|
196
200
|
// Optional fields may or may not be present; just assert object shape
|
|
197
|
-
}, 60_000);
|
|
201
|
+
}, testTimeoutMs(60_000));
|
|
198
202
|
});
|
|
199
203
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import Firecrawl from "../../../index";
|
|
5
5
|
import { config } from "dotenv";
|
|
6
6
|
import { getIdentity, getApiUrl } from "./utils/idmux";
|
|
7
|
+
import { testTimeoutMs, waitForJob, withRateLimitRetry } from "./utils/rateLimit";
|
|
7
8
|
import { describe, test, expect, beforeAll } from "@jest/globals";
|
|
8
9
|
import { z } from "zod";
|
|
9
10
|
|
|
@@ -14,14 +15,38 @@ let client: Firecrawl;
|
|
|
14
15
|
|
|
15
16
|
beforeAll(async () => {
|
|
16
17
|
const { apiKey } = await getIdentity({ name: "js-e2e-extract" });
|
|
17
|
-
client = new Firecrawl({ apiKey, apiUrl: API_URL });
|
|
18
|
+
client = withRateLimitRetry(new Firecrawl({ apiKey, apiUrl: API_URL }));
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
describe("v2.extract e2e", () => {
|
|
22
|
+
/**
|
|
23
|
+
* Starts an extract, then polls it. Mirrors client.extract, but keeps every
|
|
24
|
+
* request retryable: the wrapper cannot run client.extract again, because a
|
|
25
|
+
* second run would start a second job.
|
|
26
|
+
*
|
|
27
|
+
* The bound is 180s, the time an extract needs, and the same bound batch
|
|
28
|
+
* uses.
|
|
29
|
+
*
|
|
30
|
+
* The bound is not the whole cost of the test. startExtract goes through
|
|
31
|
+
* the wrapper too, so it can spend the retry budget before the poll starts:
|
|
32
|
+
* 150s for the start, plus 180 + 2 + 150 = 332s for the poll, so 482s. Each
|
|
33
|
+
* test below therefore carries a 333_000 ms base, which gives 483s. The
|
|
34
|
+
* unit test "every wrapped call in a test fits its budget" holds that sum.
|
|
35
|
+
*/
|
|
36
|
+
async function extractAndWait(
|
|
37
|
+
args: Parameters<typeof client.startExtract>[0],
|
|
38
|
+
) {
|
|
39
|
+
const started = await client.startExtract(args);
|
|
40
|
+
if (!started.id) return started;
|
|
41
|
+
return waitForJob(() => client.getExtractStatus(started.id!), {
|
|
42
|
+
timeout: 180,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
test("extract minimal with prompt", async () => {
|
|
22
|
-
const resp = await
|
|
47
|
+
const resp = await extractAndWait({ urls: ["https://docs.firecrawl.dev"], prompt: "Extract the main page title" });
|
|
23
48
|
expect(typeof resp.success === "boolean" || resp.success == null).toBe(true);
|
|
24
|
-
},
|
|
49
|
+
}, testTimeoutMs(333_000));
|
|
25
50
|
|
|
26
51
|
test("extract with schema", async () => {
|
|
27
52
|
const schema = {
|
|
@@ -29,7 +54,7 @@ describe("v2.extract e2e", () => {
|
|
|
29
54
|
properties: { title: { type: "string" } },
|
|
30
55
|
required: ["title"],
|
|
31
56
|
} as const;
|
|
32
|
-
const resp = await
|
|
57
|
+
const resp = await extractAndWait({
|
|
33
58
|
urls: ["https://docs.firecrawl.dev"],
|
|
34
59
|
schema,
|
|
35
60
|
prompt: "Extract the main page title",
|
|
@@ -44,13 +69,13 @@ describe("v2.extract e2e", () => {
|
|
|
44
69
|
expect(typeof resp.data).toBe("object");
|
|
45
70
|
expect((resp.data as any).title).toBeTruthy();
|
|
46
71
|
}
|
|
47
|
-
},
|
|
72
|
+
}, testTimeoutMs(333_000));
|
|
48
73
|
|
|
49
74
|
test("extract with zod schema", async () => {
|
|
50
75
|
const schema = z.object({
|
|
51
76
|
title: z.string(),
|
|
52
77
|
});
|
|
53
|
-
const resp = await
|
|
78
|
+
const resp = await extractAndWait({
|
|
54
79
|
urls: ["https://docs.firecrawl.dev"],
|
|
55
80
|
schema: schema,
|
|
56
81
|
prompt: "Extract the main page title",
|
|
@@ -65,6 +90,6 @@ describe("v2.extract e2e", () => {
|
|
|
65
90
|
expect(typeof resp.data).toBe("object");
|
|
66
91
|
expect(schema.safeParse(resp.data).success).toBe(true);
|
|
67
92
|
}
|
|
68
|
-
},
|
|
93
|
+
}, testTimeoutMs(333_000));
|
|
69
94
|
});
|
|
70
95
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import Firecrawl from "../../../index";
|
|
5
5
|
import { config } from "dotenv";
|
|
6
6
|
import { getIdentity, getApiUrl } from "./utils/idmux";
|
|
7
|
+
import { testTimeoutMs, withRateLimitRetry } from "./utils/rateLimit";
|
|
7
8
|
import { describe, test, expect, beforeAll } from "@jest/globals";
|
|
8
9
|
|
|
9
10
|
config();
|
|
@@ -13,7 +14,7 @@ let client: Firecrawl;
|
|
|
13
14
|
|
|
14
15
|
beforeAll(async () => {
|
|
15
16
|
const { apiKey } = await getIdentity({ name: "js-e2e-map" });
|
|
16
|
-
client = new Firecrawl({ apiKey, apiUrl: API_URL });
|
|
17
|
+
client = withRateLimitRetry(new Firecrawl({ apiKey, apiUrl: API_URL }));
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
describe("v2.map e2e", () => {
|
|
@@ -30,7 +31,7 @@ describe("v2.map e2e", () => {
|
|
|
30
31
|
expect(typeof first.url).toBe("string");
|
|
31
32
|
expect(first.url.startsWith("http")).toBe(true);
|
|
32
33
|
}
|
|
33
|
-
}, 90_000);
|
|
34
|
+
}, testTimeoutMs(90_000));
|
|
34
35
|
|
|
35
36
|
test.each(["only", "skip", "include"]) ("with options sitemap=%s", async (sitemap) => {
|
|
36
37
|
if (!client) throw new Error();
|
|
@@ -50,6 +51,6 @@ describe("v2.map e2e", () => {
|
|
|
50
51
|
expect(typeof link.url).toBe("string");
|
|
51
52
|
expect(link.url.startsWith("http")).toBe(true);
|
|
52
53
|
}
|
|
53
|
-
}, 120_000);
|
|
54
|
+
}, testTimeoutMs(120_000));
|
|
54
55
|
});
|
|
55
56
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Firecrawl from "../../../index";
|
|
2
2
|
import { config } from "dotenv";
|
|
3
3
|
import { getIdentity, getApiUrl } from "./utils/idmux";
|
|
4
|
+
import { testTimeoutMs, withRateLimitRetry } from "./utils/rateLimit";
|
|
4
5
|
import { describe, test, expect, beforeAll } from "@jest/globals";
|
|
5
6
|
|
|
6
7
|
config();
|
|
@@ -10,7 +11,7 @@ let client: Firecrawl;
|
|
|
10
11
|
|
|
11
12
|
beforeAll(async () => {
|
|
12
13
|
const { apiKey } = await getIdentity({ name: "js-e2e-parse" });
|
|
13
|
-
client = new Firecrawl({ apiKey, apiUrl: API_URL });
|
|
14
|
+
client = withRateLimitRetry(new Firecrawl({ apiKey, apiUrl: API_URL }));
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
describe("v2.parse e2e", () => {
|
|
@@ -41,7 +42,7 @@ describe("v2.parse e2e", () => {
|
|
|
41
42
|
expect(doc.markdown).toContain("JS SDK Parse E2E");
|
|
42
43
|
expect(doc.metadata?.creditsUsed).toBe(1);
|
|
43
44
|
},
|
|
44
|
-
60_000,
|
|
45
|
+
testTimeoutMs(60_000),
|
|
45
46
|
);
|
|
46
47
|
|
|
47
48
|
test(
|
|
@@ -62,6 +63,6 @@ describe("v2.parse e2e", () => {
|
|
|
62
63
|
),
|
|
63
64
|
).rejects.toThrow();
|
|
64
65
|
},
|
|
65
|
-
60_000,
|
|
66
|
+
testTimeoutMs(60_000),
|
|
66
67
|
);
|
|
67
68
|
});
|
|
@@ -5,6 +5,7 @@ import Firecrawl from "../../../index";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { config } from "dotenv";
|
|
7
7
|
import { getIdentity, getApiUrl } from "./utils/idmux";
|
|
8
|
+
import { testTimeoutMs, withRateLimitRetry } from "./utils/rateLimit";
|
|
8
9
|
import { describe, test, expect, beforeAll } from "@jest/globals";
|
|
9
10
|
|
|
10
11
|
config();
|
|
@@ -14,7 +15,7 @@ let client: Firecrawl;
|
|
|
14
15
|
|
|
15
16
|
beforeAll(async () => {
|
|
16
17
|
const { apiKey } = await getIdentity({ name: "js-e2e-scrape" });
|
|
17
|
-
client = new Firecrawl({ apiKey, apiUrl: API_URL });
|
|
18
|
+
client = withRateLimitRetry(new Firecrawl({ apiKey, apiUrl: API_URL }));
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
describe("v2.scrape e2e", () => {
|
|
@@ -30,7 +31,7 @@ describe("v2.scrape e2e", () => {
|
|
|
30
31
|
if (!client) throw new Error();
|
|
31
32
|
const doc = await client.scrape("https://docs.firecrawl.dev");
|
|
32
33
|
assertValidDocument(doc);
|
|
33
|
-
}, 60_000);
|
|
34
|
+
}, testTimeoutMs(60_000));
|
|
34
35
|
|
|
35
36
|
test("maximal: scrape with all options", async () => {
|
|
36
37
|
if (!client) throw new Error();
|
|
@@ -71,7 +72,7 @@ describe("v2.scrape e2e", () => {
|
|
|
71
72
|
maxAge: 60_000,
|
|
72
73
|
});
|
|
73
74
|
assertValidDocument(doc);
|
|
74
|
-
}, 90_000);
|
|
75
|
+
}, testTimeoutMs(90_000));
|
|
75
76
|
|
|
76
77
|
test("json format with zod schema (auto-converted internally)", async () => {
|
|
77
78
|
if (!client) throw new Error();
|
|
@@ -90,14 +91,14 @@ describe("v2.scrape e2e", () => {
|
|
|
90
91
|
],
|
|
91
92
|
});
|
|
92
93
|
expect(doc).toBeTruthy();
|
|
93
|
-
}, 90_000);
|
|
94
|
+
}, testTimeoutMs(90_000));
|
|
94
95
|
|
|
95
96
|
test("summary format returns summary string", async () => {
|
|
96
97
|
if (!client) throw new Error();
|
|
97
98
|
const doc = await client.scrape("https://firecrawl.dev", { formats: ["summary"] });
|
|
98
99
|
expect(typeof doc.summary).toBe("string");
|
|
99
100
|
expect((doc.summary || "").length).toBeGreaterThan(10);
|
|
100
|
-
}, 90_000);
|
|
101
|
+
}, testTimeoutMs(90_000));
|
|
101
102
|
|
|
102
103
|
test.each([
|
|
103
104
|
["markdown", "markdown"],
|
|
@@ -119,7 +120,7 @@ describe("v2.scrape e2e", () => {
|
|
|
119
120
|
expect(Array.isArray(doc.links)).toBe(true);
|
|
120
121
|
expect((doc.links || []).length).toBeGreaterThan(0);
|
|
121
122
|
}
|
|
122
|
-
}, 90_000);
|
|
123
|
+
}, testTimeoutMs(90_000));
|
|
123
124
|
|
|
124
125
|
test("images format: extract all images from webpage", async () => {
|
|
125
126
|
if (!client) throw new Error();
|
|
@@ -131,7 +132,7 @@ describe("v2.scrape e2e", () => {
|
|
|
131
132
|
expect(doc.images?.length).toBeGreaterThan(0);
|
|
132
133
|
// Should find firecrawl logo/branding images
|
|
133
134
|
expect(doc.images?.some(img => img.includes("firecrawl") || img.includes("logo"))).toBe(true);
|
|
134
|
-
}, 60_000);
|
|
135
|
+
}, testTimeoutMs(60_000));
|
|
135
136
|
|
|
136
137
|
test("images format: works with multiple formats", async () => {
|
|
137
138
|
if (!client) throw new Error();
|
|
@@ -152,12 +153,12 @@ describe("v2.scrape e2e", () => {
|
|
|
152
153
|
|
|
153
154
|
// Should discover additional images beyond those with obvious extensions
|
|
154
155
|
expect(doc.images?.length).toBeGreaterThanOrEqual(linkImages.length);
|
|
155
|
-
}, 60_000);
|
|
156
|
+
}, testTimeoutMs(60_000));
|
|
156
157
|
|
|
157
158
|
test("invalid url should throw", async () => {
|
|
158
159
|
if (!client) throw new Error();
|
|
159
160
|
await expect(client.scrape("")).rejects.toThrow("URL cannot be empty");
|
|
160
161
|
await expect(client.scrape(" ")).rejects.toThrow("URL cannot be empty");
|
|
161
|
-
});
|
|
162
|
+
}, testTimeoutMs(151_000));
|
|
162
163
|
});
|
|
163
164
|
|