@mendable/firecrawl-js 4.37.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.
Files changed (33) hide show
  1. package/README.md +7 -2
  2. package/dist/{chunk-LFX7WDGT.js → chunk-4DPCCKWG.js} +1 -1
  3. package/dist/index.cjs +61 -9
  4. package/dist/index.d.cts +202 -4
  5. package/dist/index.d.ts +202 -4
  6. package/dist/index.js +62 -10
  7. package/dist/{package-SHMK2LJE.js → package-D2QZMRW7.js} +1 -1
  8. package/package.json +1 -1
  9. package/pnpm-workspace.yaml +1 -0
  10. package/src/__tests__/e2e/v2/batch.test.ts +13 -11
  11. package/src/__tests__/e2e/v2/crawl.test.ts +24 -20
  12. package/src/__tests__/e2e/v2/extract.test.ts +32 -7
  13. package/src/__tests__/e2e/v2/map.test.ts +4 -3
  14. package/src/__tests__/e2e/v2/parse.test.ts +4 -3
  15. package/src/__tests__/e2e/v2/scrape.test.ts +10 -9
  16. package/src/__tests__/e2e/v2/search.test.ts +9 -8
  17. package/src/__tests__/e2e/v2/usage.test.ts +6 -5
  18. package/src/__tests__/e2e/v2/utils/rateLimit.ts +257 -0
  19. package/src/__tests__/e2e/v2/watcher.test.ts +4 -3
  20. package/src/__tests__/unit/v2/agent-effort-trace.unit.test.ts +1 -1
  21. package/src/__tests__/unit/v2/agent-list.unit.test.ts +68 -0
  22. package/src/__tests__/unit/v2/agent-thread.unit.test.ts +164 -0
  23. package/src/__tests__/unit/v2/e2eRateLimitRetry.test.ts +501 -0
  24. package/src/__tests__/unit/v2/extract.unit.test.ts +1 -1
  25. package/src/__tests__/unit/v2/scrapeAutoResume.test.ts +1 -1
  26. package/src/__tests__/unit/v2/search-country.unit.test.ts +32 -0
  27. package/src/__tests__/unit/v2/search-data-property.test.ts +1 -0
  28. package/src/__tests__/unit/v2/v1-aliases.test.ts +1 -0
  29. package/src/v2/client.ts +26 -1
  30. package/src/v2/methods/agent.ts +35 -1
  31. package/src/v2/methods/research.ts +14 -8
  32. package/src/v2/methods/search.ts +1 -0
  33. package/src/v2/types.ts +195 -34
@@ -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
- }, 120_000);
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
- }, 120_000);
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
- }, 120_000);
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
- }, 120_000);
108
+ }, testTimeoutMs(301_000));
108
109
 
109
110
  test("crawl with wait", async () => {
110
111
  if (!client) throw new Error();
111
- const job = await client.crawl("https://docs.firecrawl.dev", { limit: 3, maxDiscoveryDepth: 2, pollInterval: 1, timeout: 120 });
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
- }, 180_000);
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 job = await client.crawl("https://docs.firecrawl.dev", { limit: 3, maxDiscoveryDepth: 2, pollInterval: 1, timeout: 120 });
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
- }, 180_000);
133
+ }, testTimeoutMs(422_000));
131
134
 
132
135
  test("crawl with prompt and wait", async () => {
133
136
  if (!client) throw new Error();
134
- const job = await client.crawl("https://docs.firecrawl.dev", { prompt: "Extract all blog posts", limit: 3, pollInterval: 1, timeout: 120 });
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
- }, 180_000);
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 client.extract({ urls: ["https://docs.firecrawl.dev"], prompt: "Extract the main page title" });
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
- }, 120_000);
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 client.extract({
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
- }, 180_000);
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 client.extract({
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
- }, 180_000);
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
 
@@ -5,6 +5,7 @@ import Firecrawl from "../../../index";
5
5
  import type { Document, SearchResultWeb, SearchResultNews, SearchResultImages } from "../../../index";
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-search" });
17
- client = new Firecrawl({ apiKey, apiUrl: API_URL });
18
+ client = withRateLimitRetry(new Firecrawl({ apiKey, apiUrl: API_URL }));
18
19
  });
19
20
 
20
21
  function collectTexts(entries: any[] | undefined): string[] {
@@ -72,7 +73,7 @@ describe("v2.search e2e", () => {
72
73
 
73
74
  expect(results.news == null).toBe(true);
74
75
  expect(results.images == null).toBe(true);
75
- }, 90_000);
76
+ }, testTimeoutMs(90_000));
76
77
 
77
78
  test("with sources web+news and limit", async () => {
78
79
  if (!client) throw new Error();
@@ -93,7 +94,7 @@ describe("v2.search e2e", () => {
93
94
  .map(r => (r.description || "").toString().toLowerCase());
94
95
  const allWebText = (webTitles.concat(webDescriptions)).join(" ");
95
96
  expect(allWebText.includes("firecrawl")).toBe(true);
96
- }, 90_000);
97
+ }, testTimeoutMs(90_000));
97
98
 
98
99
  test("result structure", async () => {
99
100
  if (!client) throw new Error();
@@ -108,7 +109,7 @@ describe("v2.search e2e", () => {
108
109
  expect(typeof result.description === "string" || result.description == null).toBe(true);
109
110
  expect(result.url.startsWith("http")).toBe(true);
110
111
  }
111
- }, 90_000);
112
+ }, testTimeoutMs(90_000));
112
113
 
113
114
  test("all parameters (comprehensive)", async () => {
114
115
  if (!client) throw new Error();
@@ -196,7 +197,7 @@ describe("v2.search e2e", () => {
196
197
  expect(result.url?.startsWith("http")).toBe(true);
197
198
  }
198
199
  }
199
- }, 120_000);
200
+ }, testTimeoutMs(120_000));
200
201
 
201
202
  test("formats flexibility: list vs object", async () => {
202
203
  if (!client) throw new Error();
@@ -212,7 +213,7 @@ describe("v2.search e2e", () => {
212
213
  expect(results2).toBeTruthy();
213
214
  expect(results1.web).toBeTruthy();
214
215
  expect(results2.web).toBeTruthy();
215
- }, 90_000);
216
+ }, testTimeoutMs(151_000));
216
217
 
217
218
  test("with json format object", async () => {
218
219
  if (!client) throw new Error();
@@ -229,7 +230,7 @@ describe("v2.search e2e", () => {
229
230
  });
230
231
  expect(results).toBeTruthy();
231
232
  expect(Array.isArray(results.web) || results.web == null).toBe(true);
232
- }, 90_000);
233
+ }, testTimeoutMs(90_000));
233
234
 
234
235
  test("with summary format, documents include summary when present", async () => {
235
236
  if (!client) throw new Error();
@@ -242,6 +243,6 @@ describe("v2.search e2e", () => {
242
243
  expect(typeof docs[0].summary).toBe("string");
243
244
  expect((docs[0].summary || "").length).toBeGreaterThan(5);
244
245
  }
245
- }, 90_000);
246
+ }, testTimeoutMs(90_000));
246
247
  });
247
248
 
@@ -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-usage" });
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.usage e2e", () => {
@@ -21,17 +22,17 @@ describe("v2.usage e2e", () => {
21
22
  const resp = await client.getConcurrency();
22
23
  expect(typeof resp.concurrency).toBe("number");
23
24
  expect(typeof resp.maxConcurrency).toBe("number");
24
- }, 60_000);
25
+ }, testTimeoutMs(60_000));
25
26
 
26
27
  test("get_credit_usage", async () => {
27
28
  const resp = await client.getCreditUsage();
28
29
  expect(typeof resp.remainingCredits).toBe("number");
29
- }, 60_000);
30
+ }, testTimeoutMs(60_000));
30
31
 
31
32
  test("get_token_usage", async () => {
32
33
  const resp = await client.getTokenUsage();
33
34
  expect(typeof resp.remainingTokens).toBe("number");
34
- }, 60_000);
35
+ }, testTimeoutMs(60_000));
35
36
 
36
37
  test("get_queue_status", async () => {
37
38
  const resp = await client.getQueueStatus();
@@ -39,6 +40,6 @@ describe("v2.usage e2e", () => {
39
40
  expect(typeof resp.activeJobsInQueue).toBe("number");
40
41
  expect(typeof resp.waitingJobsInQueue).toBe("number");
41
42
  expect(typeof resp.maxConcurrency).toBe("number");
42
- }, 60_000);
43
+ }, testTimeoutMs(60_000));
43
44
  });
44
45