@link-assistant/web-capture 1.7.16 → 1.7.17
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/CHANGELOG.md +6 -0
- package/README.md +5 -0
- package/package.json +1 -1
- package/src/gdocs-model-wait.js +186 -0
- package/src/gdocs.js +25 -57
- package/tests/integration/gdocs-public-doc.test.js +1 -1
- package/tests/unit/gdocs.test.js +80 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -102,6 +102,11 @@ web-capture https://docs.google.com/document/d/DOC_ID/edit --capture api
|
|
|
102
102
|
web-capture https://docs.google.com/document/d/DOC_ID/edit --capture api --apiToken YOUR_TOKEN
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
Browser-model capture waits until `DOCS_modelChunk` data stops changing before
|
|
106
|
+
parsing. For very large or slow documents, tune the wait with
|
|
107
|
+
`WEB_CAPTURE_GDOCS_STABILITY_MS` (default `1500`) and
|
|
108
|
+
`WEB_CAPTURE_GDOCS_MAX_WAIT_MS` (default `30000`).
|
|
109
|
+
|
|
105
110
|
## API Endpoints (Server Mode)
|
|
106
111
|
|
|
107
112
|
Start the server with `web-capture --serve` and use the endpoints below.
|
package/package.json
CHANGED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* global document, window */
|
|
2
|
+
import { googleDocsBrowserModelUnavailableError } from './gdocs-fallback.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_GDOCS_MODEL_STABILITY_MS = 1500;
|
|
5
|
+
const DEFAULT_GDOCS_MODEL_MAX_WAIT_MS = 30000;
|
|
6
|
+
const DEFAULT_GDOCS_MODEL_POLL_MS = 250;
|
|
7
|
+
|
|
8
|
+
export async function waitForGoogleDocsModelQuiescence(
|
|
9
|
+
page,
|
|
10
|
+
{ documentId, maxWaitMs, stabilityMs, pollMs, log }
|
|
11
|
+
) {
|
|
12
|
+
const started = Date.now();
|
|
13
|
+
let lastFingerprint = null;
|
|
14
|
+
let stableSince = null;
|
|
15
|
+
let lastModelData = null;
|
|
16
|
+
let lastStableForMs = 0;
|
|
17
|
+
let pollCount = 0;
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
const modelData = await readGoogleDocsModelData(page);
|
|
21
|
+
pollCount += 1;
|
|
22
|
+
const fingerprint = googleDocsModelFingerprint(modelData);
|
|
23
|
+
lastModelData = modelData;
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
fingerprint.chunks > 0 &&
|
|
27
|
+
sameModelFingerprint(fingerprint, lastFingerprint)
|
|
28
|
+
) {
|
|
29
|
+
if (stableSince === null) {
|
|
30
|
+
stableSince = Date.now();
|
|
31
|
+
}
|
|
32
|
+
lastStableForMs = Date.now() - stableSince;
|
|
33
|
+
if (lastStableForMs >= stabilityMs) {
|
|
34
|
+
log?.debug?.(() => ({
|
|
35
|
+
event: 'gdocs.browser-model.quiesced',
|
|
36
|
+
documentId,
|
|
37
|
+
chunks: fingerprint.chunks,
|
|
38
|
+
chunkPayloadBytes: fingerprint.payloadBytes,
|
|
39
|
+
cidUrls: Object.keys(modelData.cidUrlMap || {}).length,
|
|
40
|
+
pollCount,
|
|
41
|
+
stableForMs: lastStableForMs,
|
|
42
|
+
elapsedMs: Date.now() - started,
|
|
43
|
+
}));
|
|
44
|
+
return {
|
|
45
|
+
...modelData,
|
|
46
|
+
chunkPayloadBytes: fingerprint.payloadBytes,
|
|
47
|
+
pollCount,
|
|
48
|
+
stableForMs: lastStableForMs,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
lastFingerprint = fingerprint;
|
|
53
|
+
stableSince = null;
|
|
54
|
+
lastStableForMs = 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const elapsedMs = Date.now() - started;
|
|
58
|
+
if (elapsedMs >= maxWaitMs) {
|
|
59
|
+
const lastChunks = lastModelData?.chunks?.length || 0;
|
|
60
|
+
const lastCidUrls = Object.keys(lastModelData?.cidUrlMap || {}).length;
|
|
61
|
+
throw googleDocsBrowserModelUnavailableError(
|
|
62
|
+
`Google Docs DOCS_modelChunk stream did not quiesce within ${maxWaitMs} ms for document ${documentId} (last chunks=${lastChunks}, payload_bytes=${fingerprint.payloadBytes}, cid_urls=${lastCidUrls}, poll_count=${pollCount}, stable_for_ms=${lastStableForMs})`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await waitForPage(page, Math.min(pollMs, maxWaitMs - elapsedMs));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveGoogleDocsModelWaitOptions(options) {
|
|
71
|
+
return {
|
|
72
|
+
stabilityMs: readDurationMs(
|
|
73
|
+
options.modelStabilityMs,
|
|
74
|
+
'WEB_CAPTURE_GDOCS_STABILITY_MS',
|
|
75
|
+
DEFAULT_GDOCS_MODEL_STABILITY_MS
|
|
76
|
+
),
|
|
77
|
+
maxWaitMs: readDurationMs(
|
|
78
|
+
options.modelMaxWaitMs ?? options.waitMs,
|
|
79
|
+
'WEB_CAPTURE_GDOCS_MAX_WAIT_MS',
|
|
80
|
+
DEFAULT_GDOCS_MODEL_MAX_WAIT_MS
|
|
81
|
+
),
|
|
82
|
+
pollMs: Math.max(
|
|
83
|
+
1,
|
|
84
|
+
readDurationMs(
|
|
85
|
+
options.modelPollMs,
|
|
86
|
+
'WEB_CAPTURE_GDOCS_POLL_MS',
|
|
87
|
+
DEFAULT_GDOCS_MODEL_POLL_MS
|
|
88
|
+
)
|
|
89
|
+
),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function readGoogleDocsModelData(page) {
|
|
94
|
+
return await evaluateOnPage(page, () => {
|
|
95
|
+
const chunks = [...(window.__captured_chunks || [])];
|
|
96
|
+
if (
|
|
97
|
+
window.DOCS_modelChunk &&
|
|
98
|
+
chunks.length === 0 &&
|
|
99
|
+
!chunks.includes(window.DOCS_modelChunk)
|
|
100
|
+
) {
|
|
101
|
+
chunks.push(window.DOCS_modelChunk);
|
|
102
|
+
}
|
|
103
|
+
const cidUrlMap = {};
|
|
104
|
+
const scripts = document.querySelectorAll('script');
|
|
105
|
+
for (const script of scripts) {
|
|
106
|
+
const text = script.textContent || '';
|
|
107
|
+
if (!text.includes('docs-images-rt')) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const regex =
|
|
111
|
+
/"([A-Za-z0-9_-]{20,})"\s*:\s*"(https:\/\/docs\.google\.com\/docs-images-rt\/[^"]+)"/g;
|
|
112
|
+
let match;
|
|
113
|
+
while ((match = regex.exec(text)) !== null) {
|
|
114
|
+
cidUrlMap[match[1]] = match[2]
|
|
115
|
+
.replace(/\\u003d/g, '=')
|
|
116
|
+
.replace(/\\u0026/g, '&')
|
|
117
|
+
.replace(/\\\//g, '/');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { chunks, cidUrlMap };
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function googleDocsModelFingerprint(modelData) {
|
|
125
|
+
const chunks = modelData?.chunks || [];
|
|
126
|
+
return {
|
|
127
|
+
chunks: chunks.length,
|
|
128
|
+
payloadBytes: chunks.reduce(
|
|
129
|
+
(total, chunk) => total + jsonPayloadByteLength(chunk),
|
|
130
|
+
0
|
|
131
|
+
),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function sameModelFingerprint(left, right) {
|
|
136
|
+
return (
|
|
137
|
+
Boolean(left) &&
|
|
138
|
+
Boolean(right) &&
|
|
139
|
+
left.chunks === right.chunks &&
|
|
140
|
+
left.payloadBytes === right.payloadBytes
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function jsonPayloadByteLength(value) {
|
|
145
|
+
try {
|
|
146
|
+
return Buffer.byteLength(JSON.stringify(value) || '');
|
|
147
|
+
} catch {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readDurationMs(optionValue, envName, defaultValue) {
|
|
153
|
+
const rawValue = optionValue ?? process.env[envName];
|
|
154
|
+
if (rawValue === undefined || rawValue === null || rawValue === '') {
|
|
155
|
+
return defaultValue;
|
|
156
|
+
}
|
|
157
|
+
const parsed = Number(rawValue);
|
|
158
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
159
|
+
return defaultValue;
|
|
160
|
+
}
|
|
161
|
+
return Math.floor(parsed);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function waitForPage(page, waitMs) {
|
|
165
|
+
if (waitMs <= 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const rawPage = page.rawPage || page;
|
|
169
|
+
if (typeof rawPage.waitForTimeout === 'function') {
|
|
170
|
+
await rawPage.waitForTimeout(waitMs);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (typeof page.waitForTimeout === 'function') {
|
|
174
|
+
await page.waitForTimeout(waitMs);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function evaluateOnPage(page, fn) {
|
|
181
|
+
const rawPage = page.rawPage || page;
|
|
182
|
+
if (rawPage !== page && typeof rawPage.evaluate === 'function') {
|
|
183
|
+
return await rawPage.evaluate(fn);
|
|
184
|
+
}
|
|
185
|
+
return await page.evaluate(fn);
|
|
186
|
+
}
|
package/src/gdocs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global
|
|
1
|
+
/* global window */
|
|
2
2
|
// Google Docs capture module.
|
|
3
3
|
//
|
|
4
4
|
// Public export capture uses:
|
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
fetchGoogleDocByExportFormat as fetchGoogleDocByExportFormatImpl,
|
|
24
24
|
captureGoogleDocWithBrowserOrFallback as captureGoogleDocWithBrowserOrFallbackImpl,
|
|
25
25
|
} from './gdocs-fallback.js';
|
|
26
|
+
import {
|
|
27
|
+
resolveGoogleDocsModelWaitOptions,
|
|
28
|
+
waitForGoogleDocsModelQuiescence,
|
|
29
|
+
} from './gdocs-model-wait.js';
|
|
26
30
|
|
|
27
31
|
export {
|
|
28
32
|
normalizeGoogleDocsExportMarkdown,
|
|
@@ -369,7 +373,10 @@ export function captureGoogleDocWithBrowserOrFallback(url, options = {}) {
|
|
|
369
373
|
* @param {Object} [options] - Capture options
|
|
370
374
|
* @param {string} [options.engine='playwright'] - Browser engine
|
|
371
375
|
* @param {string} [options.apiToken] - Optional Authorization header token
|
|
372
|
-
* @param {number} [options.waitMs
|
|
376
|
+
* @param {number} [options.waitMs] - Deprecated alias for modelMaxWaitMs
|
|
377
|
+
* @param {number} [options.modelStabilityMs=1500] - Required quiet period after the last model change
|
|
378
|
+
* @param {number} [options.modelMaxWaitMs=30000] - Hard timeout for model quiescence
|
|
379
|
+
* @param {number} [options.modelPollMs=250] - Poll interval while waiting for model quiescence
|
|
373
380
|
* @param {Function} [options.createBrowser] - Browser factory for tests
|
|
374
381
|
* @returns {Promise<{capture: Object, markdown: string, html: string, text: string, documentId: string, exportUrl: string}>}
|
|
375
382
|
*/
|
|
@@ -377,10 +384,10 @@ export async function captureGoogleDocWithBrowser(url, options = {}) {
|
|
|
377
384
|
const {
|
|
378
385
|
engine = 'playwright',
|
|
379
386
|
apiToken,
|
|
380
|
-
waitMs = 8000,
|
|
381
387
|
createBrowser = defaultCreateBrowser,
|
|
382
388
|
log,
|
|
383
389
|
} = options;
|
|
390
|
+
const modelWait = resolveGoogleDocsModelWaitOptions(options);
|
|
384
391
|
const documentId = extractDocumentId(url);
|
|
385
392
|
if (!documentId) {
|
|
386
393
|
throw new Error(`Not a valid Google Docs URL: ${url}`);
|
|
@@ -392,7 +399,9 @@ export async function captureGoogleDocWithBrowser(url, options = {}) {
|
|
|
392
399
|
documentId,
|
|
393
400
|
editUrl,
|
|
394
401
|
engine,
|
|
395
|
-
|
|
402
|
+
modelStabilityMs: modelWait.stabilityMs,
|
|
403
|
+
modelMaxWaitMs: modelWait.maxWaitMs,
|
|
404
|
+
modelPollMs: modelWait.pollMs,
|
|
396
405
|
hasApiToken: Boolean(apiToken),
|
|
397
406
|
}));
|
|
398
407
|
const browser = await createBrowser(engine);
|
|
@@ -416,35 +425,10 @@ export async function captureGoogleDocWithBrowser(url, options = {}) {
|
|
|
416
425
|
documentId,
|
|
417
426
|
editUrl,
|
|
418
427
|
}));
|
|
419
|
-
await
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
window.DOCS_modelChunk &&
|
|
425
|
-
chunks.length === 0 &&
|
|
426
|
-
!chunks.includes(window.DOCS_modelChunk)
|
|
427
|
-
) {
|
|
428
|
-
chunks.push(window.DOCS_modelChunk);
|
|
429
|
-
}
|
|
430
|
-
const cidUrlMap = {};
|
|
431
|
-
const scripts = document.querySelectorAll('script');
|
|
432
|
-
for (const script of scripts) {
|
|
433
|
-
const text = script.textContent || '';
|
|
434
|
-
if (!text.includes('docs-images-rt')) {
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
const regex =
|
|
438
|
-
/"([A-Za-z0-9_-]{20,})"\s*:\s*"(https:\/\/docs\.google\.com\/docs-images-rt\/[^"]+)"/g;
|
|
439
|
-
let match;
|
|
440
|
-
while ((match = regex.exec(text)) !== null) {
|
|
441
|
-
cidUrlMap[match[1]] = match[2]
|
|
442
|
-
.replace(/\\u003d/g, '=')
|
|
443
|
-
.replace(/\\u0026/g, '&')
|
|
444
|
-
.replace(/\\\//g, '/');
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return { chunks, cidUrlMap };
|
|
428
|
+
const modelData = await waitForGoogleDocsModelQuiescence(page, {
|
|
429
|
+
...modelWait,
|
|
430
|
+
documentId,
|
|
431
|
+
log,
|
|
448
432
|
});
|
|
449
433
|
|
|
450
434
|
const capture = parseGoogleDocsModelChunks(
|
|
@@ -457,6 +441,9 @@ export async function captureGoogleDocWithBrowser(url, options = {}) {
|
|
|
457
441
|
documentId,
|
|
458
442
|
chunks: modelData.chunks?.length || 0,
|
|
459
443
|
cidUrls: Object.keys(modelData.cidUrlMap || {}).length,
|
|
444
|
+
chunkPayloadBytes: modelData.chunkPayloadBytes,
|
|
445
|
+
pollCount: modelData.pollCount,
|
|
446
|
+
stableForMs: modelData.stableForMs,
|
|
460
447
|
blocks: capture.blocks.length,
|
|
461
448
|
tables: capture.tables.length,
|
|
462
449
|
images: capture.images.length,
|
|
@@ -474,6 +461,11 @@ export async function captureGoogleDocWithBrowser(url, options = {}) {
|
|
|
474
461
|
text: renderGoogleDocsCapture(capture, 'txt'),
|
|
475
462
|
documentId,
|
|
476
463
|
exportUrl: editUrl,
|
|
464
|
+
diagnostics: {
|
|
465
|
+
modelChunkPayloadBytes: modelData.chunkPayloadBytes,
|
|
466
|
+
modelPollCount: modelData.pollCount,
|
|
467
|
+
modelStableForMs: modelData.stableForMs,
|
|
468
|
+
},
|
|
477
469
|
};
|
|
478
470
|
} finally {
|
|
479
471
|
if (page) {
|
|
@@ -548,30 +540,6 @@ async function installDocsModelCapture(page) {
|
|
|
548
540
|
}
|
|
549
541
|
}
|
|
550
542
|
|
|
551
|
-
async function waitForPage(page, waitMs) {
|
|
552
|
-
if (waitMs <= 0) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
const rawPage = page.rawPage || page;
|
|
556
|
-
if (typeof rawPage.waitForTimeout === 'function') {
|
|
557
|
-
await rawPage.waitForTimeout(waitMs);
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
if (typeof page.waitForTimeout === 'function') {
|
|
561
|
-
await page.waitForTimeout(waitMs);
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
async function evaluateOnPage(page, fn) {
|
|
568
|
-
const rawPage = page.rawPage || page;
|
|
569
|
-
if (rawPage !== page && typeof rawPage.evaluate === 'function') {
|
|
570
|
-
return await rawPage.evaluate(fn);
|
|
571
|
-
}
|
|
572
|
-
return await page.evaluate(fn);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
543
|
/**
|
|
576
544
|
* Extract a CID-to-image URL map from Google Docs editor HTML.
|
|
577
545
|
*
|
|
@@ -182,7 +182,7 @@ describe('Google Docs public test document (issue #90)', () => {
|
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
const captureBrowserWithRetry = (url) =>
|
|
185
|
-
retry(() => captureGoogleDocWithBrowser(url
|
|
185
|
+
retry(() => captureGoogleDocWithBrowser(url), {
|
|
186
186
|
retries: 2,
|
|
187
187
|
baseDelay: 2000,
|
|
188
188
|
});
|
package/tests/unit/gdocs.test.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
buildDocsApiUrl,
|
|
8
8
|
extractBase64Images,
|
|
9
9
|
captureGoogleDocWithBrowserOrFallback,
|
|
10
|
+
captureGoogleDocWithBrowser,
|
|
10
11
|
isGoogleDocsBrowserModelUnavailableError,
|
|
11
12
|
parseGoogleDocsModelChunks,
|
|
12
13
|
renderGoogleDocsCapture,
|
|
@@ -948,6 +949,57 @@ describe('gdocs', () => {
|
|
|
948
949
|
});
|
|
949
950
|
});
|
|
950
951
|
|
|
952
|
+
describe('captureGoogleDocWithBrowser (issue #106)', () => {
|
|
953
|
+
it('waits for DOCS_modelChunk data to stop changing before parsing', async () => {
|
|
954
|
+
const browser = fakeBrowserReturningModelSequence([
|
|
955
|
+
{
|
|
956
|
+
chunks: [{ chunk: [{ ty: 'is', s: 'First paragraph\n' }] }],
|
|
957
|
+
cidUrlMap: {},
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
chunks: [{ chunk: [{ ty: 'is', s: 'First paragraph\n' }] }],
|
|
961
|
+
cidUrlMap: {},
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
chunks: [
|
|
965
|
+
{ chunk: [{ ty: 'is', s: 'First paragraph\n' }] },
|
|
966
|
+
{ chunk: [{ ty: 'is', s: 'Second paragraph\n' }] },
|
|
967
|
+
],
|
|
968
|
+
cidUrlMap: {},
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
chunks: [
|
|
972
|
+
{ chunk: [{ ty: 'is', s: 'First paragraph\n' }] },
|
|
973
|
+
{ chunk: [{ ty: 'is', s: 'Second paragraph\n' }] },
|
|
974
|
+
],
|
|
975
|
+
cidUrlMap: {},
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
chunks: [
|
|
979
|
+
{ chunk: [{ ty: 'is', s: 'First paragraph\n' }] },
|
|
980
|
+
{ chunk: [{ ty: 'is', s: 'Second paragraph\n' }] },
|
|
981
|
+
],
|
|
982
|
+
cidUrlMap: {},
|
|
983
|
+
},
|
|
984
|
+
]);
|
|
985
|
+
|
|
986
|
+
const result = await captureGoogleDocWithBrowser(
|
|
987
|
+
'https://docs.google.com/document/d/quiescence-doc/edit',
|
|
988
|
+
{
|
|
989
|
+
waitMs: 0,
|
|
990
|
+
modelMaxWaitMs: 200,
|
|
991
|
+
modelPollMs: 2,
|
|
992
|
+
modelStabilityMs: 2,
|
|
993
|
+
createBrowser: async () => browser,
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
expect(result.markdown).toContain('First paragraph');
|
|
998
|
+
expect(result.markdown).toContain('Second paragraph');
|
|
999
|
+
expect(browser.evaluateCalls).toBeGreaterThan(2);
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
|
|
951
1003
|
describe('GDOCS_EXPORT_FORMATS', () => {
|
|
952
1004
|
it('contains all expected formats', () => {
|
|
953
1005
|
expect(GDOCS_EXPORT_FORMATS).toEqual({
|
|
@@ -1303,3 +1355,31 @@ function fakeBrowserReturningModel(modelData) {
|
|
|
1303
1355
|
async close() {},
|
|
1304
1356
|
};
|
|
1305
1357
|
}
|
|
1358
|
+
|
|
1359
|
+
function fakeBrowserReturningModelSequence(modelDataSequence) {
|
|
1360
|
+
let evaluateCalls = 0;
|
|
1361
|
+
return {
|
|
1362
|
+
get evaluateCalls() {
|
|
1363
|
+
return evaluateCalls;
|
|
1364
|
+
},
|
|
1365
|
+
async newPage() {
|
|
1366
|
+
return {
|
|
1367
|
+
async addInitScript() {},
|
|
1368
|
+
async setUserAgent() {},
|
|
1369
|
+
async setExtraHTTPHeaders() {},
|
|
1370
|
+
async setViewport() {},
|
|
1371
|
+
async goto() {},
|
|
1372
|
+
async waitForTimeout(waitMs) {
|
|
1373
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
1374
|
+
},
|
|
1375
|
+
async evaluate() {
|
|
1376
|
+
const index = Math.min(evaluateCalls, modelDataSequence.length - 1);
|
|
1377
|
+
evaluateCalls += 1;
|
|
1378
|
+
return JSON.parse(JSON.stringify(modelDataSequence[index]));
|
|
1379
|
+
},
|
|
1380
|
+
async close() {},
|
|
1381
|
+
};
|
|
1382
|
+
},
|
|
1383
|
+
async close() {},
|
|
1384
|
+
};
|
|
1385
|
+
}
|