@mendable/firecrawl-js 4.19.0 → 4.20.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/audit-ci.jsonc +5 -2
- package/dist/{chunk-JJY4NJXL.js → chunk-UXVHRV2G.js} +1 -1
- package/dist/index.cjs +118 -47
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +119 -48
- package/dist/{package-HMEPZJ3J.js → package-ELM7Z5VP.js} +1 -1
- package/package.json +1 -1
- package/src/__tests__/unit/v2/scrape-browser.unit.test.ts +39 -12
- package/src/v2/methods/batch.ts +89 -28
- package/src/v2/methods/browser.ts +19 -11
- package/src/v2/methods/map.ts +36 -9
- package/src/v2/methods/parse.ts +15 -9
- package/src/v2/methods/scrape.ts +32 -13
- package/src/v2/methods/search.ts +38 -10
- package/src/v2/utils/httpClient.ts +36 -16
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
require_package
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UXVHRV2G.js";
|
|
4
4
|
|
|
5
5
|
// src/v2/utils/httpClient.ts
|
|
6
6
|
import axios from "axios";
|
|
@@ -58,10 +58,10 @@ var HttpClient = class {
|
|
|
58
58
|
const isPlainObjectBody = !isFormDataBody && cfg.data != null && typeof cfg.data === "object" && !Array.isArray(cfg.data);
|
|
59
59
|
if (isPlainObjectBody && cfg.method && ["post", "put", "patch"].includes(cfg.method.toLowerCase())) {
|
|
60
60
|
const data = cfg.data ?? {};
|
|
61
|
-
cfg.data = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
61
|
+
cfg.data = {
|
|
62
|
+
...data,
|
|
63
|
+
origin: typeof data.origin === "string" && data.origin.includes("mcp") ? data.origin : `js-sdk@${version}`
|
|
64
|
+
};
|
|
65
65
|
}
|
|
66
66
|
if (isFormDataBody) {
|
|
67
67
|
cfg.headers = { ...cfg.headers || {} };
|
|
@@ -89,16 +89,22 @@ var HttpClient = class {
|
|
|
89
89
|
sleep(seconds) {
|
|
90
90
|
return new Promise((r) => setTimeout(r, seconds * 1e3));
|
|
91
91
|
}
|
|
92
|
-
post(endpoint, body,
|
|
93
|
-
return this.request({
|
|
92
|
+
post(endpoint, body, options) {
|
|
93
|
+
return this.request({
|
|
94
|
+
method: "post",
|
|
95
|
+
url: endpoint,
|
|
96
|
+
data: body,
|
|
97
|
+
headers: options?.headers,
|
|
98
|
+
timeout: options?.timeoutMs
|
|
99
|
+
});
|
|
94
100
|
}
|
|
95
|
-
postMultipart(endpoint, formData,
|
|
101
|
+
postMultipart(endpoint, formData, options) {
|
|
96
102
|
return this.request({
|
|
97
103
|
method: "post",
|
|
98
104
|
url: endpoint,
|
|
99
105
|
data: formData,
|
|
100
|
-
headers,
|
|
101
|
-
timeout: timeoutMs
|
|
106
|
+
headers: options?.headers,
|
|
107
|
+
timeout: options?.timeoutMs
|
|
102
108
|
});
|
|
103
109
|
}
|
|
104
110
|
get(endpoint, headers) {
|
|
@@ -364,7 +370,11 @@ async function scrape(http, url, options) {
|
|
|
364
370
|
const payload = { url: url.trim() };
|
|
365
371
|
if (options) Object.assign(payload, options);
|
|
366
372
|
try {
|
|
367
|
-
const res = await http.post(
|
|
373
|
+
const res = await http.post(
|
|
374
|
+
"/v2/scrape",
|
|
375
|
+
payload,
|
|
376
|
+
typeof options?.timeout === "number" ? { timeoutMs: options.timeout + 5e3 } : {}
|
|
377
|
+
);
|
|
368
378
|
if (res.status !== 200 || !res.data?.success) {
|
|
369
379
|
throwForBadResponse(res, "scrape");
|
|
370
380
|
}
|
|
@@ -392,12 +402,15 @@ async function interact(http, jobId, args) {
|
|
|
392
402
|
try {
|
|
393
403
|
const res = await http.post(
|
|
394
404
|
`/v2/scrape/${jobId}/interact`,
|
|
395
|
-
body
|
|
405
|
+
body,
|
|
406
|
+
args.timeout != null ? { timeoutMs: args.timeout * 1e3 + 5e3 } : {}
|
|
396
407
|
);
|
|
397
|
-
if (res.status !== 200)
|
|
408
|
+
if (res.status !== 200)
|
|
409
|
+
throwForBadResponse(res, "interact with scrape browser");
|
|
398
410
|
return res.data;
|
|
399
411
|
} catch (err) {
|
|
400
|
-
if (err?.isAxiosError)
|
|
412
|
+
if (err?.isAxiosError)
|
|
413
|
+
return normalizeAxiosError(err, "interact with scrape browser");
|
|
401
414
|
throw err;
|
|
402
415
|
}
|
|
403
416
|
}
|
|
@@ -435,7 +448,9 @@ function toUploadBlob(input, contentType) {
|
|
|
435
448
|
return new Blob([input], { type: contentType });
|
|
436
449
|
}
|
|
437
450
|
if (typeof input === "string") {
|
|
438
|
-
return new Blob([input], {
|
|
451
|
+
return new Blob([input], {
|
|
452
|
+
type: contentType ?? "text/plain; charset=utf-8"
|
|
453
|
+
});
|
|
439
454
|
}
|
|
440
455
|
throw new Error("Unsupported parse file data type");
|
|
441
456
|
}
|
|
@@ -463,9 +478,12 @@ async function parse(http, file, options) {
|
|
|
463
478
|
toUploadBlob(file.data, file.contentType),
|
|
464
479
|
file.filename.trim()
|
|
465
480
|
);
|
|
466
|
-
const requestTimeoutMs = typeof normalizedOptions.timeout === "number" ? normalizedOptions.timeout + 5e3 : void 0;
|
|
467
481
|
try {
|
|
468
|
-
const res = await http.postMultipart(
|
|
482
|
+
const res = await http.postMultipart(
|
|
483
|
+
"/v2/parse",
|
|
484
|
+
formData,
|
|
485
|
+
typeof normalizedOptions.timeout === "number" ? { timeoutMs: normalizedOptions.timeout + 5e3 } : {}
|
|
486
|
+
);
|
|
469
487
|
if (res.status !== 200 || !res.data?.success) {
|
|
470
488
|
throwForBadResponse(res, "parse");
|
|
471
489
|
}
|
|
@@ -479,8 +497,10 @@ async function parse(http, file, options) {
|
|
|
479
497
|
// src/v2/methods/search.ts
|
|
480
498
|
function prepareSearchPayload(req) {
|
|
481
499
|
if (!req.query || !req.query.trim()) throw new Error("Query cannot be empty");
|
|
482
|
-
if (req.limit != null && req.limit <= 0)
|
|
483
|
-
|
|
500
|
+
if (req.limit != null && req.limit <= 0)
|
|
501
|
+
throw new Error("limit must be positive");
|
|
502
|
+
if (req.timeout != null && req.timeout <= 0)
|
|
503
|
+
throw new Error("timeout must be positive");
|
|
484
504
|
const payload = {
|
|
485
505
|
query: req.query
|
|
486
506
|
};
|
|
@@ -489,9 +509,11 @@ function prepareSearchPayload(req) {
|
|
|
489
509
|
if (req.limit != null) payload.limit = req.limit;
|
|
490
510
|
if (req.tbs != null) payload.tbs = req.tbs;
|
|
491
511
|
if (req.location != null) payload.location = req.location;
|
|
492
|
-
if (req.ignoreInvalidURLs != null)
|
|
512
|
+
if (req.ignoreInvalidURLs != null)
|
|
513
|
+
payload.ignoreInvalidURLs = req.ignoreInvalidURLs;
|
|
493
514
|
if (req.timeout != null) payload.timeout = req.timeout;
|
|
494
|
-
if (req.integration && req.integration.trim())
|
|
515
|
+
if (req.integration && req.integration.trim())
|
|
516
|
+
payload.integration = req.integration.trim();
|
|
495
517
|
if (req.origin) payload.origin = req.origin;
|
|
496
518
|
if (req.scrapeOptions) {
|
|
497
519
|
ensureValidScrapeOptions(req.scrapeOptions);
|
|
@@ -517,7 +539,11 @@ function transformArray(arr) {
|
|
|
517
539
|
async function search(http, request) {
|
|
518
540
|
const payload = prepareSearchPayload(request);
|
|
519
541
|
try {
|
|
520
|
-
const res = await http.post(
|
|
542
|
+
const res = await http.post(
|
|
543
|
+
"/v2/search",
|
|
544
|
+
payload,
|
|
545
|
+
typeof request.timeout === "number" ? { timeoutMs: request.timeout + 5e3 } : {}
|
|
546
|
+
);
|
|
521
547
|
if (res.status !== 200 || !res.data?.success) {
|
|
522
548
|
throwForBadResponse(res, "search");
|
|
523
549
|
}
|
|
@@ -525,7 +551,8 @@ async function search(http, request) {
|
|
|
525
551
|
const out = {};
|
|
526
552
|
if (data.web) out.web = transformArray(data.web);
|
|
527
553
|
if (data.news) out.news = transformArray(data.news);
|
|
528
|
-
if (data.images)
|
|
554
|
+
if (data.images)
|
|
555
|
+
out.images = transformArray(data.images);
|
|
529
556
|
return out;
|
|
530
557
|
} catch (err) {
|
|
531
558
|
if (err?.isAxiosError) return normalizeAxiosError(err, "search");
|
|
@@ -540,11 +567,14 @@ function prepareMapPayload(url, options) {
|
|
|
540
567
|
if (options) {
|
|
541
568
|
if (options.sitemap != null) payload.sitemap = options.sitemap;
|
|
542
569
|
if (options.search != null) payload.search = options.search;
|
|
543
|
-
if (options.includeSubdomains != null)
|
|
544
|
-
|
|
570
|
+
if (options.includeSubdomains != null)
|
|
571
|
+
payload.includeSubdomains = options.includeSubdomains;
|
|
572
|
+
if (options.ignoreQueryParameters != null)
|
|
573
|
+
payload.ignoreQueryParameters = options.ignoreQueryParameters;
|
|
545
574
|
if (options.limit != null) payload.limit = options.limit;
|
|
546
575
|
if (options.timeout != null) payload.timeout = options.timeout;
|
|
547
|
-
if (options.integration != null && options.integration.trim())
|
|
576
|
+
if (options.integration != null && options.integration.trim())
|
|
577
|
+
payload.integration = options.integration.trim();
|
|
548
578
|
if (options.origin) payload.origin = options.origin;
|
|
549
579
|
if (options.location != null) payload.location = options.location;
|
|
550
580
|
}
|
|
@@ -553,7 +583,11 @@ function prepareMapPayload(url, options) {
|
|
|
553
583
|
async function map(http, url, options) {
|
|
554
584
|
const payload = prepareMapPayload(url, options);
|
|
555
585
|
try {
|
|
556
|
-
const res = await http.post(
|
|
586
|
+
const res = await http.post(
|
|
587
|
+
"/v2/map",
|
|
588
|
+
payload,
|
|
589
|
+
typeof options?.timeout === "number" ? { timeoutMs: options.timeout + 5e3 } : {}
|
|
590
|
+
);
|
|
557
591
|
if (res.status !== 200 || !res.data?.success) {
|
|
558
592
|
throwForBadResponse(res, "map");
|
|
559
593
|
}
|
|
@@ -561,7 +595,12 @@ async function map(http, url, options) {
|
|
|
561
595
|
const links = [];
|
|
562
596
|
for (const item of linksIn) {
|
|
563
597
|
if (typeof item === "string") links.push({ url: item });
|
|
564
|
-
else if (item && typeof item === "object")
|
|
598
|
+
else if (item && typeof item === "object")
|
|
599
|
+
links.push({
|
|
600
|
+
url: item.url,
|
|
601
|
+
title: item.title,
|
|
602
|
+
description: item.description
|
|
603
|
+
});
|
|
565
604
|
}
|
|
566
605
|
return { links };
|
|
567
606
|
} catch (err) {
|
|
@@ -773,7 +812,8 @@ async function startBatchScrape(http, urls, {
|
|
|
773
812
|
integration,
|
|
774
813
|
origin
|
|
775
814
|
} = {}) {
|
|
776
|
-
if (!Array.isArray(urls) || urls.length === 0)
|
|
815
|
+
if (!Array.isArray(urls) || urls.length === 0)
|
|
816
|
+
throw new Error("URLs list cannot be empty");
|
|
777
817
|
const payload = { urls };
|
|
778
818
|
if (options) {
|
|
779
819
|
ensureValidScrapeOptions(options);
|
|
@@ -784,22 +824,30 @@ async function startBatchScrape(http, urls, {
|
|
|
784
824
|
if (ignoreInvalidURLs != null) payload.ignoreInvalidURLs = ignoreInvalidURLs;
|
|
785
825
|
if (maxConcurrency != null) payload.maxConcurrency = maxConcurrency;
|
|
786
826
|
if (zeroDataRetention != null) payload.zeroDataRetention = zeroDataRetention;
|
|
787
|
-
if (integration != null && integration.trim())
|
|
827
|
+
if (integration != null && integration.trim())
|
|
828
|
+
payload.integration = integration.trim();
|
|
788
829
|
if (origin) payload.origin = origin;
|
|
789
830
|
try {
|
|
790
831
|
const headers = http.prepareHeaders(idempotencyKey);
|
|
791
|
-
const res = await http.post("/v2/batch/scrape", payload, headers);
|
|
792
|
-
if (res.status !== 200 || !res.data?.success)
|
|
793
|
-
|
|
832
|
+
const res = await http.post("/v2/batch/scrape", payload, { headers });
|
|
833
|
+
if (res.status !== 200 || !res.data?.success)
|
|
834
|
+
throwForBadResponse(res, "start batch scrape");
|
|
835
|
+
return {
|
|
836
|
+
id: res.data.id,
|
|
837
|
+
url: res.data.url,
|
|
838
|
+
invalidURLs: res.data.invalidURLs || void 0
|
|
839
|
+
};
|
|
794
840
|
} catch (err) {
|
|
795
|
-
if (err?.isAxiosError)
|
|
841
|
+
if (err?.isAxiosError)
|
|
842
|
+
return normalizeAxiosError(err, "start batch scrape");
|
|
796
843
|
throw err;
|
|
797
844
|
}
|
|
798
845
|
}
|
|
799
846
|
async function getBatchScrapeStatus(http, jobId, pagination) {
|
|
800
847
|
try {
|
|
801
848
|
const res = await http.get(`/v2/batch/scrape/${jobId}`);
|
|
802
|
-
if (res.status !== 200 || !res.data?.success)
|
|
849
|
+
if (res.status !== 200 || !res.data?.success)
|
|
850
|
+
throwForBadResponse(res, "get batch scrape status");
|
|
803
851
|
const body = res.data;
|
|
804
852
|
const initialDocs = body.data || [];
|
|
805
853
|
const auto = pagination?.autoPaginate ?? true;
|
|
@@ -815,7 +863,12 @@ async function getBatchScrapeStatus(http, jobId, pagination) {
|
|
|
815
863
|
data: initialDocs
|
|
816
864
|
};
|
|
817
865
|
}
|
|
818
|
-
const aggregated = await fetchAllPages(
|
|
866
|
+
const aggregated = await fetchAllPages(
|
|
867
|
+
http,
|
|
868
|
+
body.next,
|
|
869
|
+
initialDocs,
|
|
870
|
+
pagination
|
|
871
|
+
);
|
|
819
872
|
return {
|
|
820
873
|
id: jobId,
|
|
821
874
|
status: body.status,
|
|
@@ -827,17 +880,21 @@ async function getBatchScrapeStatus(http, jobId, pagination) {
|
|
|
827
880
|
data: aggregated
|
|
828
881
|
};
|
|
829
882
|
} catch (err) {
|
|
830
|
-
if (err?.isAxiosError)
|
|
883
|
+
if (err?.isAxiosError)
|
|
884
|
+
return normalizeAxiosError(err, "get batch scrape status");
|
|
831
885
|
throw err;
|
|
832
886
|
}
|
|
833
887
|
}
|
|
834
888
|
async function cancelBatchScrape(http, jobId) {
|
|
835
889
|
try {
|
|
836
|
-
const res = await http.delete(
|
|
890
|
+
const res = await http.delete(
|
|
891
|
+
`/v2/batch/scrape/${jobId}`
|
|
892
|
+
);
|
|
837
893
|
if (res.status !== 200) throwForBadResponse(res, "cancel batch scrape");
|
|
838
894
|
return res.data?.status === "cancelled";
|
|
839
895
|
} catch (err) {
|
|
840
|
-
if (err?.isAxiosError)
|
|
896
|
+
if (err?.isAxiosError)
|
|
897
|
+
return normalizeAxiosError(err, "cancel batch scrape");
|
|
841
898
|
throw err;
|
|
842
899
|
}
|
|
843
900
|
}
|
|
@@ -846,9 +903,13 @@ async function getBatchScrapeErrors(http, jobId) {
|
|
|
846
903
|
const res = await http.get(`/v2/batch/scrape/${jobId}/errors`);
|
|
847
904
|
if (res.status !== 200) throwForBadResponse(res, "get batch scrape errors");
|
|
848
905
|
const payload = res.data?.data ?? res.data;
|
|
849
|
-
return {
|
|
906
|
+
return {
|
|
907
|
+
errors: payload.errors || [],
|
|
908
|
+
robotsBlocked: payload.robotsBlocked || []
|
|
909
|
+
};
|
|
850
910
|
} catch (err) {
|
|
851
|
-
if (err?.isAxiosError)
|
|
911
|
+
if (err?.isAxiosError)
|
|
912
|
+
return normalizeAxiosError(err, "get batch scrape errors");
|
|
852
913
|
throw err;
|
|
853
914
|
}
|
|
854
915
|
}
|
|
@@ -883,7 +944,12 @@ async function waitForBatchCompletion(http, jobId, pollInterval = 2, timeout) {
|
|
|
883
944
|
}
|
|
884
945
|
async function batchScrape(http, urls, opts = {}) {
|
|
885
946
|
const start = await startBatchScrape(http, urls, opts);
|
|
886
|
-
return waitForBatchCompletion(
|
|
947
|
+
return waitForBatchCompletion(
|
|
948
|
+
http,
|
|
949
|
+
start.id,
|
|
950
|
+
opts.pollInterval ?? 2,
|
|
951
|
+
opts.timeout
|
|
952
|
+
);
|
|
887
953
|
}
|
|
888
954
|
|
|
889
955
|
// src/v2/methods/extract.ts
|
|
@@ -1022,7 +1088,8 @@ async function browser(http, args = {}) {
|
|
|
1022
1088
|
if (res.status !== 200) throwForBadResponse(res, "create browser session");
|
|
1023
1089
|
return res.data;
|
|
1024
1090
|
} catch (err) {
|
|
1025
|
-
if (err?.isAxiosError)
|
|
1091
|
+
if (err?.isAxiosError)
|
|
1092
|
+
return normalizeAxiosError(err, "create browser session");
|
|
1026
1093
|
throw err;
|
|
1027
1094
|
}
|
|
1028
1095
|
}
|
|
@@ -1035,12 +1102,14 @@ async function browserExecute(http, sessionId, args) {
|
|
|
1035
1102
|
try {
|
|
1036
1103
|
const res = await http.post(
|
|
1037
1104
|
`/v2/browser/${sessionId}/execute`,
|
|
1038
|
-
body
|
|
1105
|
+
body,
|
|
1106
|
+
args.timeout != null ? { timeoutMs: args.timeout * 1e3 + 5e3 } : {}
|
|
1039
1107
|
);
|
|
1040
1108
|
if (res.status !== 200) throwForBadResponse(res, "execute browser code");
|
|
1041
1109
|
return res.data;
|
|
1042
1110
|
} catch (err) {
|
|
1043
|
-
if (err?.isAxiosError)
|
|
1111
|
+
if (err?.isAxiosError)
|
|
1112
|
+
return normalizeAxiosError(err, "execute browser code");
|
|
1044
1113
|
throw err;
|
|
1045
1114
|
}
|
|
1046
1115
|
}
|
|
@@ -1052,7 +1121,8 @@ async function deleteBrowser(http, sessionId) {
|
|
|
1052
1121
|
if (res.status !== 200) throwForBadResponse(res, "delete browser session");
|
|
1053
1122
|
return res.data;
|
|
1054
1123
|
} catch (err) {
|
|
1055
|
-
if (err?.isAxiosError)
|
|
1124
|
+
if (err?.isAxiosError)
|
|
1125
|
+
return normalizeAxiosError(err, "delete browser session");
|
|
1056
1126
|
throw err;
|
|
1057
1127
|
}
|
|
1058
1128
|
}
|
|
@@ -1064,7 +1134,8 @@ async function listBrowsers(http, args = {}) {
|
|
|
1064
1134
|
if (res.status !== 200) throwForBadResponse(res, "list browser sessions");
|
|
1065
1135
|
return res.data;
|
|
1066
1136
|
} catch (err) {
|
|
1067
|
-
if (err?.isAxiosError)
|
|
1137
|
+
if (err?.isAxiosError)
|
|
1138
|
+
return normalizeAxiosError(err, "list browser sessions");
|
|
1068
1139
|
throw err;
|
|
1069
1140
|
}
|
|
1070
1141
|
}
|
|
@@ -1707,7 +1778,7 @@ var FirecrawlApp = class {
|
|
|
1707
1778
|
if (typeof process !== "undefined" && process.env && process.env.npm_package_version) {
|
|
1708
1779
|
return process.env.npm_package_version;
|
|
1709
1780
|
}
|
|
1710
|
-
const packageJson = await import("./package-
|
|
1781
|
+
const packageJson = await import("./package-ELM7Z5VP.js");
|
|
1711
1782
|
return packageJson.default.version;
|
|
1712
1783
|
} catch (error) {
|
|
1713
1784
|
const isTest = typeof process !== "undefined" && (process.env.JEST_WORKER_ID != null || false);
|
package/package.json
CHANGED
|
@@ -14,12 +14,15 @@ describe("JS SDK v2 scrape-browser methods", () => {
|
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
const http = { post } as any;
|
|
17
|
-
const response = await interact(http, "job-123", {
|
|
18
|
-
|
|
19
|
-
expect(post).toHaveBeenCalledWith("/v2/scrape/job-123/interact", {
|
|
17
|
+
const response = await interact(http, "job-123", {
|
|
20
18
|
code: "console.log('ok')",
|
|
21
|
-
language: "node",
|
|
22
19
|
});
|
|
20
|
+
|
|
21
|
+
expect(post).toHaveBeenCalledWith(
|
|
22
|
+
"/v2/scrape/job-123/interact",
|
|
23
|
+
{ code: "console.log('ok')", language: "node" },
|
|
24
|
+
{},
|
|
25
|
+
);
|
|
23
26
|
expect(response.success).toBe(true);
|
|
24
27
|
expect(response.exitCode).toBe(0);
|
|
25
28
|
});
|
|
@@ -38,22 +41,27 @@ describe("JS SDK v2 scrape-browser methods", () => {
|
|
|
38
41
|
}));
|
|
39
42
|
|
|
40
43
|
const http = { post } as any;
|
|
41
|
-
const response = await interact(http, "job-456", {
|
|
42
|
-
|
|
43
|
-
expect(post).toHaveBeenCalledWith("/v2/scrape/job-456/interact", {
|
|
44
|
+
const response = await interact(http, "job-456", {
|
|
44
45
|
prompt: "Click the login button",
|
|
45
|
-
language: "node",
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
expect(post).toHaveBeenCalledWith(
|
|
49
|
+
"/v2/scrape/job-456/interact",
|
|
50
|
+
{ prompt: "Click the login button", language: "node" },
|
|
51
|
+
{},
|
|
52
|
+
);
|
|
47
53
|
expect(response.success).toBe(true);
|
|
48
54
|
expect(response.output).toBe("Clicked the button");
|
|
49
55
|
expect(response.liveViewUrl).toBe("https://live.example.com/view");
|
|
50
|
-
expect(response.interactiveLiveViewUrl).toBe(
|
|
56
|
+
expect(response.interactiveLiveViewUrl).toBe(
|
|
57
|
+
"https://live.example.com/interactive",
|
|
58
|
+
);
|
|
51
59
|
});
|
|
52
60
|
|
|
53
61
|
test("interact throws when neither code nor prompt provided", async () => {
|
|
54
62
|
const http = { post: jest.fn() } as any;
|
|
55
63
|
await expect(interact(http, "job-123", {})).rejects.toThrow(
|
|
56
|
-
"Either 'code' or 'prompt' must be provided"
|
|
64
|
+
"Either 'code' or 'prompt' must be provided",
|
|
57
65
|
);
|
|
58
66
|
});
|
|
59
67
|
|
|
@@ -68,7 +76,7 @@ describe("JS SDK v2 scrape-browser methods", () => {
|
|
|
68
76
|
|
|
69
77
|
const http = { post } as any;
|
|
70
78
|
await expect(
|
|
71
|
-
interact(http, "bad-id", { code: "console.log('ok')" })
|
|
79
|
+
interact(http, "bad-id", { code: "console.log('ok')" }),
|
|
72
80
|
).rejects.toBeInstanceOf(SdkError);
|
|
73
81
|
});
|
|
74
82
|
|
|
@@ -98,7 +106,26 @@ describe("JS SDK v2 scrape-browser methods", () => {
|
|
|
98
106
|
|
|
99
107
|
const http = { delete: del } as any;
|
|
100
108
|
await expect(stopInteraction(http, "job-123")).rejects.toBeInstanceOf(
|
|
101
|
-
SdkError
|
|
109
|
+
SdkError,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("interact converts seconds-based body timeout to ms axios timeout", async () => {
|
|
114
|
+
const post = jest.fn(async () => ({
|
|
115
|
+
status: 200,
|
|
116
|
+
data: { success: true, stdout: "ok", exitCode: 0 },
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
const http = { post } as any;
|
|
120
|
+
await interact(http, "job-123", {
|
|
121
|
+
code: "console.log('ok')",
|
|
122
|
+
timeout: 150,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(post).toHaveBeenCalledWith(
|
|
126
|
+
"/v2/scrape/job-123/interact",
|
|
127
|
+
{ code: "console.log('ok')", language: "node", timeout: 150 },
|
|
128
|
+
{ timeoutMs: 155000 },
|
|
102
129
|
);
|
|
103
130
|
});
|
|
104
131
|
});
|