@ricsam/isolate-playwright 0.1.13 → 0.1.14
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 +47 -2
- package/dist/cjs/client.cjs +5 -1095
- package/dist/cjs/client.cjs.map +3 -3
- package/dist/cjs/handler.cjs +1406 -0
- package/dist/cjs/handler.cjs.map +10 -0
- package/dist/cjs/index.cjs +378 -1082
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/types.cjs.map +1 -1
- package/dist/mjs/client.mjs +5 -1093
- package/dist/mjs/client.mjs.map +3 -3
- package/dist/mjs/handler.mjs +1378 -0
- package/dist/mjs/handler.mjs.map +10 -0
- package/dist/mjs/index.mjs +382 -1079
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/types.mjs.map +1 -1
- package/dist/types/client.d.ts +2 -19
- package/dist/types/handler.d.ts +44 -0
- package/dist/types/index.d.ts +4 -86
- package/dist/types/types.d.ts +3 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -43,1090 +43,150 @@ var __export = (target, all) => {
|
|
|
43
43
|
var exports_src = {};
|
|
44
44
|
__export(exports_src, {
|
|
45
45
|
setupPlaywright: () => setupPlaywright,
|
|
46
|
-
getDefaultPlaywrightHandlerMetadata: () => getDefaultPlaywrightHandlerMetadata,
|
|
47
|
-
defaultPlaywrightHandler: () => defaultPlaywrightHandler,
|
|
48
|
-
createPlaywrightHandler: () => createPlaywrightHandler,
|
|
46
|
+
getDefaultPlaywrightHandlerMetadata: () => import_handler.getDefaultPlaywrightHandlerMetadata,
|
|
47
|
+
defaultPlaywrightHandler: () => import_handler.defaultPlaywrightHandler,
|
|
48
|
+
createPlaywrightHandler: () => import_handler.createPlaywrightHandler,
|
|
49
49
|
DEFAULT_PLAYWRIGHT_HANDLER_META: () => import_types.DEFAULT_PLAYWRIGHT_HANDLER_META
|
|
50
50
|
});
|
|
51
51
|
module.exports = __toCommonJS(exports_src);
|
|
52
52
|
var import_isolated_vm = __toESM(require("isolated-vm"));
|
|
53
53
|
var import_types = require("./types.cjs");
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const roleOptions = options ? { ...options } : undefined;
|
|
58
|
-
if (roleOptions) {
|
|
59
|
-
delete roleOptions.nth;
|
|
60
|
-
delete roleOptions.filter;
|
|
61
|
-
if (roleOptions.name && typeof roleOptions.name === "object" && roleOptions.name.$regex) {
|
|
62
|
-
roleOptions.name = new RegExp(roleOptions.name.$regex, roleOptions.name.$flags);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
let locator;
|
|
66
|
-
switch (selectorType) {
|
|
67
|
-
case "css":
|
|
68
|
-
locator = page.locator(selectorValue);
|
|
69
|
-
break;
|
|
70
|
-
case "role":
|
|
71
|
-
locator = page.getByRole(selectorValue, roleOptions && Object.keys(roleOptions).length > 0 ? roleOptions : undefined);
|
|
72
|
-
break;
|
|
73
|
-
case "text":
|
|
74
|
-
locator = page.getByText(selectorValue);
|
|
75
|
-
break;
|
|
76
|
-
case "label":
|
|
77
|
-
locator = page.getByLabel(selectorValue);
|
|
78
|
-
break;
|
|
79
|
-
case "placeholder":
|
|
80
|
-
locator = page.getByPlaceholder(selectorValue);
|
|
81
|
-
break;
|
|
82
|
-
case "testId":
|
|
83
|
-
locator = page.getByTestId(selectorValue);
|
|
84
|
-
break;
|
|
85
|
-
case "or": {
|
|
86
|
-
const [firstInfo, secondInfo] = JSON.parse(selectorValue);
|
|
87
|
-
const first = getLocator(page, firstInfo[0], firstInfo[1], firstInfo[2]);
|
|
88
|
-
const second = getLocator(page, secondInfo[0], secondInfo[1], secondInfo[2]);
|
|
89
|
-
locator = first.or(second);
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
case "and": {
|
|
93
|
-
const [firstInfo, secondInfo] = JSON.parse(selectorValue);
|
|
94
|
-
const first = getLocator(page, firstInfo[0], firstInfo[1], firstInfo[2]);
|
|
95
|
-
const second = getLocator(page, secondInfo[0], secondInfo[1], secondInfo[2]);
|
|
96
|
-
locator = first.and(second);
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
case "chained": {
|
|
100
|
-
const [parentInfo, childInfo] = JSON.parse(selectorValue);
|
|
101
|
-
const parent = getLocator(page, parentInfo[0], parentInfo[1], parentInfo[2]);
|
|
102
|
-
const childType = childInfo[0];
|
|
103
|
-
const childValue = childInfo[1];
|
|
104
|
-
const childOptionsJson = childInfo[2];
|
|
105
|
-
const childOptions = childOptionsJson ? JSON.parse(childOptionsJson) : undefined;
|
|
106
|
-
switch (childType) {
|
|
107
|
-
case "css":
|
|
108
|
-
locator = parent.locator(childValue);
|
|
109
|
-
break;
|
|
110
|
-
case "role": {
|
|
111
|
-
const roleOpts = childOptions ? { ...childOptions } : undefined;
|
|
112
|
-
if (roleOpts) {
|
|
113
|
-
delete roleOpts.nth;
|
|
114
|
-
delete roleOpts.filter;
|
|
115
|
-
if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
|
|
116
|
-
roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
locator = parent.getByRole(childValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
case "text":
|
|
123
|
-
locator = parent.getByText(childValue);
|
|
124
|
-
break;
|
|
125
|
-
case "label":
|
|
126
|
-
locator = parent.getByLabel(childValue);
|
|
127
|
-
break;
|
|
128
|
-
case "placeholder":
|
|
129
|
-
locator = parent.getByPlaceholder(childValue);
|
|
130
|
-
break;
|
|
131
|
-
case "testId":
|
|
132
|
-
locator = parent.getByTestId(childValue);
|
|
133
|
-
break;
|
|
134
|
-
case "altText":
|
|
135
|
-
locator = parent.getByAltText(childValue);
|
|
136
|
-
break;
|
|
137
|
-
case "title":
|
|
138
|
-
locator = parent.getByTitle(childValue);
|
|
139
|
-
break;
|
|
140
|
-
default:
|
|
141
|
-
locator = parent.locator(childValue);
|
|
142
|
-
}
|
|
143
|
-
if (childOptions?.nth !== undefined) {
|
|
144
|
-
locator = locator.nth(childOptions.nth);
|
|
145
|
-
}
|
|
146
|
-
if (childOptions?.filter) {
|
|
147
|
-
const filterOpts = { ...childOptions.filter };
|
|
148
|
-
if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
|
|
149
|
-
filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
|
|
150
|
-
}
|
|
151
|
-
if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
|
|
152
|
-
filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
|
|
153
|
-
}
|
|
154
|
-
locator = locator.filter(filterOpts);
|
|
155
|
-
}
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
case "altText":
|
|
159
|
-
locator = page.getByAltText(selectorValue);
|
|
160
|
-
break;
|
|
161
|
-
case "title":
|
|
162
|
-
locator = page.getByTitle(selectorValue);
|
|
163
|
-
break;
|
|
164
|
-
case "frame": {
|
|
165
|
-
const [frameSelectorInfo, innerLocatorInfo] = JSON.parse(selectorValue);
|
|
166
|
-
const frameSelector = frameSelectorInfo[1];
|
|
167
|
-
const frame = page.frameLocator(frameSelector);
|
|
168
|
-
const innerType = innerLocatorInfo[0];
|
|
169
|
-
const innerValue = innerLocatorInfo[1];
|
|
170
|
-
const innerOptionsJson = innerLocatorInfo[2];
|
|
171
|
-
const innerOptions = innerOptionsJson ? JSON.parse(innerOptionsJson) : undefined;
|
|
172
|
-
switch (innerType) {
|
|
173
|
-
case "css":
|
|
174
|
-
locator = frame.locator(innerValue);
|
|
175
|
-
break;
|
|
176
|
-
case "role": {
|
|
177
|
-
const roleOpts = innerOptions ? { ...innerOptions } : undefined;
|
|
178
|
-
if (roleOpts) {
|
|
179
|
-
delete roleOpts.nth;
|
|
180
|
-
delete roleOpts.filter;
|
|
181
|
-
if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
|
|
182
|
-
roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
locator = frame.getByRole(innerValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
case "text":
|
|
189
|
-
locator = frame.getByText(innerValue);
|
|
190
|
-
break;
|
|
191
|
-
case "label":
|
|
192
|
-
locator = frame.getByLabel(innerValue);
|
|
193
|
-
break;
|
|
194
|
-
case "placeholder":
|
|
195
|
-
locator = frame.getByPlaceholder(innerValue);
|
|
196
|
-
break;
|
|
197
|
-
case "testId":
|
|
198
|
-
locator = frame.getByTestId(innerValue);
|
|
199
|
-
break;
|
|
200
|
-
case "altText":
|
|
201
|
-
locator = frame.getByAltText(innerValue);
|
|
202
|
-
break;
|
|
203
|
-
case "title":
|
|
204
|
-
locator = frame.getByTitle(innerValue);
|
|
205
|
-
break;
|
|
206
|
-
default:
|
|
207
|
-
locator = frame.locator(innerValue);
|
|
208
|
-
}
|
|
209
|
-
if (innerOptions?.nth !== undefined) {
|
|
210
|
-
locator = locator.nth(innerOptions.nth);
|
|
211
|
-
}
|
|
212
|
-
if (innerOptions?.filter) {
|
|
213
|
-
const filterOpts = { ...innerOptions.filter };
|
|
214
|
-
if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
|
|
215
|
-
filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
|
|
216
|
-
}
|
|
217
|
-
if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
|
|
218
|
-
filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
|
|
219
|
-
}
|
|
220
|
-
locator = locator.filter(filterOpts);
|
|
221
|
-
}
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
|
-
default:
|
|
225
|
-
locator = page.locator(selectorValue);
|
|
226
|
-
}
|
|
227
|
-
if (nthIndex !== undefined) {
|
|
228
|
-
locator = locator.nth(nthIndex);
|
|
229
|
-
}
|
|
230
|
-
if (options?.filter) {
|
|
231
|
-
const filterOpts = { ...options.filter };
|
|
232
|
-
if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
|
|
233
|
-
filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
|
|
234
|
-
}
|
|
235
|
-
if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
|
|
236
|
-
filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
|
|
237
|
-
}
|
|
238
|
-
locator = locator.filter(filterOpts);
|
|
239
|
-
}
|
|
240
|
-
return locator;
|
|
241
|
-
}
|
|
242
|
-
async function executeLocatorAction(locator, action, actionArg, timeout, fileIO) {
|
|
243
|
-
switch (action) {
|
|
244
|
-
case "click":
|
|
245
|
-
await locator.click({ timeout });
|
|
246
|
-
return null;
|
|
247
|
-
case "dblclick":
|
|
248
|
-
await locator.dblclick({ timeout });
|
|
249
|
-
return null;
|
|
250
|
-
case "fill":
|
|
251
|
-
await locator.fill(String(actionArg ?? ""), { timeout });
|
|
252
|
-
return null;
|
|
253
|
-
case "type":
|
|
254
|
-
await locator.pressSequentially(String(actionArg ?? ""), { timeout });
|
|
255
|
-
return null;
|
|
256
|
-
case "check":
|
|
257
|
-
await locator.check({ timeout });
|
|
258
|
-
return null;
|
|
259
|
-
case "uncheck":
|
|
260
|
-
await locator.uncheck({ timeout });
|
|
261
|
-
return null;
|
|
262
|
-
case "selectOption":
|
|
263
|
-
await locator.selectOption(String(actionArg ?? ""), { timeout });
|
|
264
|
-
return null;
|
|
265
|
-
case "clear":
|
|
266
|
-
await locator.clear({ timeout });
|
|
267
|
-
return null;
|
|
268
|
-
case "press":
|
|
269
|
-
await locator.press(String(actionArg ?? ""), { timeout });
|
|
270
|
-
return null;
|
|
271
|
-
case "hover":
|
|
272
|
-
await locator.hover({ timeout });
|
|
273
|
-
return null;
|
|
274
|
-
case "focus":
|
|
275
|
-
await locator.focus({ timeout });
|
|
276
|
-
return null;
|
|
277
|
-
case "getText":
|
|
278
|
-
return await locator.textContent({ timeout });
|
|
279
|
-
case "getValue":
|
|
280
|
-
return await locator.inputValue({ timeout });
|
|
281
|
-
case "isVisible":
|
|
282
|
-
return await locator.isVisible();
|
|
283
|
-
case "isEnabled":
|
|
284
|
-
return await locator.isEnabled();
|
|
285
|
-
case "isChecked":
|
|
286
|
-
return await locator.isChecked();
|
|
287
|
-
case "count":
|
|
288
|
-
return await locator.count();
|
|
289
|
-
case "getAttribute":
|
|
290
|
-
return await locator.getAttribute(String(actionArg ?? ""), { timeout });
|
|
291
|
-
case "isDisabled":
|
|
292
|
-
return await locator.isDisabled();
|
|
293
|
-
case "isHidden":
|
|
294
|
-
return await locator.isHidden();
|
|
295
|
-
case "innerHTML":
|
|
296
|
-
return await locator.innerHTML({ timeout });
|
|
297
|
-
case "innerText":
|
|
298
|
-
return await locator.innerText({ timeout });
|
|
299
|
-
case "allTextContents":
|
|
300
|
-
return await locator.allTextContents();
|
|
301
|
-
case "allInnerTexts":
|
|
302
|
-
return await locator.allInnerTexts();
|
|
303
|
-
case "waitFor": {
|
|
304
|
-
const opts = actionArg && typeof actionArg === "object" ? actionArg : {};
|
|
305
|
-
await locator.waitFor({ state: opts.state, timeout: opts.timeout ?? timeout });
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
case "boundingBox":
|
|
309
|
-
return await locator.boundingBox({ timeout });
|
|
310
|
-
case "setInputFiles": {
|
|
311
|
-
const files = actionArg;
|
|
312
|
-
if (Array.isArray(files) && files.length > 0 && typeof files[0] === "object" && "buffer" in files[0]) {
|
|
313
|
-
const fileBuffers2 = files.map((f) => ({
|
|
314
|
-
name: f.name,
|
|
315
|
-
mimeType: f.mimeType,
|
|
316
|
-
buffer: Buffer.from(f.buffer, "base64")
|
|
317
|
-
}));
|
|
318
|
-
await locator.setInputFiles(fileBuffers2, { timeout });
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
const filePaths = Array.isArray(files) ? files : [files];
|
|
322
|
-
if (!fileIO?.readFile) {
|
|
323
|
-
throw new Error("setInputFiles() with file paths requires a readFile callback in defaultPlaywrightHandler options. " + "Either provide file data directly using { name, mimeType, buffer } format, or " + "configure a readFile callback to control file access from the isolate.");
|
|
324
|
-
}
|
|
325
|
-
const fileBuffers = await Promise.all(filePaths.map(async (filePath) => {
|
|
326
|
-
const fileData = await fileIO.readFile(filePath);
|
|
327
|
-
return {
|
|
328
|
-
name: fileData.name,
|
|
329
|
-
mimeType: fileData.mimeType,
|
|
330
|
-
buffer: fileData.buffer
|
|
331
|
-
};
|
|
332
|
-
}));
|
|
333
|
-
await locator.setInputFiles(fileBuffers, { timeout });
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
case "screenshot": {
|
|
337
|
-
const opts = actionArg;
|
|
338
|
-
const buffer = await locator.screenshot({
|
|
339
|
-
timeout,
|
|
340
|
-
type: opts?.type,
|
|
341
|
-
quality: opts?.quality
|
|
342
|
-
});
|
|
343
|
-
if (opts?.path) {
|
|
344
|
-
if (!fileIO?.writeFile) {
|
|
345
|
-
throw new Error("screenshot() with path option requires a writeFile callback in defaultPlaywrightHandler options. " + "Either omit the path option (screenshot returns base64 data), or " + "configure a writeFile callback to control file writing from the isolate.");
|
|
346
|
-
}
|
|
347
|
-
await fileIO.writeFile(opts.path, buffer);
|
|
348
|
-
}
|
|
349
|
-
return buffer.toString("base64");
|
|
350
|
-
}
|
|
351
|
-
case "dragTo": {
|
|
352
|
-
const targetInfo = actionArg;
|
|
353
|
-
const targetLocator = getLocator(locator.page(), targetInfo[0], targetInfo[1], targetInfo[2]);
|
|
354
|
-
await locator.dragTo(targetLocator, { timeout });
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
case "scrollIntoViewIfNeeded":
|
|
358
|
-
await locator.scrollIntoViewIfNeeded({ timeout });
|
|
359
|
-
return null;
|
|
360
|
-
case "highlight":
|
|
361
|
-
await locator.highlight();
|
|
362
|
-
return null;
|
|
363
|
-
case "evaluate": {
|
|
364
|
-
const [fnString, arg] = actionArg;
|
|
365
|
-
const fn = new Function("return (" + fnString + ")")();
|
|
366
|
-
return await locator.evaluate(fn, arg);
|
|
367
|
-
}
|
|
368
|
-
case "evaluateAll": {
|
|
369
|
-
const [fnString, arg] = actionArg;
|
|
370
|
-
const fn = new Function("return (" + fnString + ")")();
|
|
371
|
-
return await locator.evaluateAll(fn, arg);
|
|
372
|
-
}
|
|
373
|
-
default:
|
|
374
|
-
throw new Error(`Unknown action: ${action}`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
async function executeExpectAssertion(locator, matcher, expected, negated, timeout) {
|
|
378
|
-
switch (matcher) {
|
|
379
|
-
case "toBeVisible": {
|
|
380
|
-
const isVisible = await locator.isVisible();
|
|
381
|
-
if (negated) {
|
|
382
|
-
if (isVisible)
|
|
383
|
-
throw new Error("Expected element to not be visible, but it was visible");
|
|
384
|
-
} else {
|
|
385
|
-
if (!isVisible)
|
|
386
|
-
throw new Error("Expected element to be visible, but it was not");
|
|
387
|
-
}
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
case "toContainText": {
|
|
391
|
-
const text = await locator.textContent({ timeout });
|
|
392
|
-
let matches;
|
|
393
|
-
let expectedDisplay;
|
|
394
|
-
if (expected && typeof expected === "object" && expected.$regex) {
|
|
395
|
-
const regex = new RegExp(expected.$regex, expected.$flags);
|
|
396
|
-
matches = regex.test(text ?? "");
|
|
397
|
-
expectedDisplay = String(regex);
|
|
398
|
-
} else {
|
|
399
|
-
matches = text?.includes(String(expected)) ?? false;
|
|
400
|
-
expectedDisplay = String(expected);
|
|
401
|
-
}
|
|
402
|
-
if (negated) {
|
|
403
|
-
if (matches)
|
|
404
|
-
throw new Error(`Expected text to not contain ${expectedDisplay}, but got "${text}"`);
|
|
405
|
-
} else {
|
|
406
|
-
if (!matches)
|
|
407
|
-
throw new Error(`Expected text to contain ${expectedDisplay}, but got "${text}"`);
|
|
408
|
-
}
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
case "toHaveValue": {
|
|
412
|
-
const value = await locator.inputValue({ timeout });
|
|
413
|
-
const matches = value === String(expected);
|
|
414
|
-
if (negated) {
|
|
415
|
-
if (matches)
|
|
416
|
-
throw new Error(`Expected value to not be "${expected}", but it was`);
|
|
417
|
-
} else {
|
|
418
|
-
if (!matches)
|
|
419
|
-
throw new Error(`Expected value to be "${expected}", but got "${value}"`);
|
|
420
|
-
}
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
case "toBeEnabled": {
|
|
424
|
-
const isEnabled = await locator.isEnabled();
|
|
425
|
-
if (negated) {
|
|
426
|
-
if (isEnabled)
|
|
427
|
-
throw new Error("Expected element to be disabled, but it was enabled");
|
|
428
|
-
} else {
|
|
429
|
-
if (!isEnabled)
|
|
430
|
-
throw new Error("Expected element to be enabled, but it was disabled");
|
|
431
|
-
}
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
case "toBeChecked": {
|
|
435
|
-
const isChecked = await locator.isChecked();
|
|
436
|
-
if (negated) {
|
|
437
|
-
if (isChecked)
|
|
438
|
-
throw new Error("Expected element to not be checked, but it was checked");
|
|
439
|
-
} else {
|
|
440
|
-
if (!isChecked)
|
|
441
|
-
throw new Error("Expected element to be checked, but it was not");
|
|
442
|
-
}
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
case "toHaveAttribute": {
|
|
446
|
-
const { name, value } = expected;
|
|
447
|
-
const actual = await locator.getAttribute(name, { timeout });
|
|
448
|
-
if (value instanceof RegExp || value && typeof value === "object" && value.$regex) {
|
|
449
|
-
const regex = value.$regex ? new RegExp(value.$regex, value.$flags) : value;
|
|
450
|
-
const matches = regex.test(actual ?? "");
|
|
451
|
-
if (negated) {
|
|
452
|
-
if (matches)
|
|
453
|
-
throw new Error(`Expected attribute "${name}" to not match ${regex}, but got "${actual}"`);
|
|
454
|
-
} else {
|
|
455
|
-
if (!matches)
|
|
456
|
-
throw new Error(`Expected attribute "${name}" to match ${regex}, but got "${actual}"`);
|
|
457
|
-
}
|
|
458
|
-
} else {
|
|
459
|
-
const matches = actual === String(value);
|
|
460
|
-
if (negated) {
|
|
461
|
-
if (matches)
|
|
462
|
-
throw new Error(`Expected attribute "${name}" to not be "${value}", but it was`);
|
|
463
|
-
} else {
|
|
464
|
-
if (!matches)
|
|
465
|
-
throw new Error(`Expected attribute "${name}" to be "${value}", but got "${actual}"`);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
break;
|
|
469
|
-
}
|
|
470
|
-
case "toHaveText": {
|
|
471
|
-
const text = await locator.textContent({ timeout }) ?? "";
|
|
472
|
-
let matches;
|
|
473
|
-
let expectedDisplay;
|
|
474
|
-
if (expected && typeof expected === "object" && expected.$regex) {
|
|
475
|
-
const regex = new RegExp(expected.$regex, expected.$flags);
|
|
476
|
-
matches = regex.test(text);
|
|
477
|
-
expectedDisplay = String(regex);
|
|
478
|
-
} else {
|
|
479
|
-
matches = text === String(expected);
|
|
480
|
-
expectedDisplay = JSON.stringify(expected);
|
|
481
|
-
}
|
|
482
|
-
if (negated) {
|
|
483
|
-
if (matches)
|
|
484
|
-
throw new Error(`Expected text to not be ${expectedDisplay}, but got "${text}"`);
|
|
485
|
-
} else {
|
|
486
|
-
if (!matches)
|
|
487
|
-
throw new Error(`Expected text to be ${expectedDisplay}, but got "${text}"`);
|
|
488
|
-
}
|
|
489
|
-
break;
|
|
490
|
-
}
|
|
491
|
-
case "toHaveCount": {
|
|
492
|
-
const count = await locator.count();
|
|
493
|
-
const expectedCount = Number(expected);
|
|
494
|
-
if (negated) {
|
|
495
|
-
if (count === expectedCount)
|
|
496
|
-
throw new Error(`Expected count to not be ${expectedCount}, but it was`);
|
|
497
|
-
} else {
|
|
498
|
-
if (count !== expectedCount)
|
|
499
|
-
throw new Error(`Expected count to be ${expectedCount}, but got ${count}`);
|
|
500
|
-
}
|
|
501
|
-
break;
|
|
502
|
-
}
|
|
503
|
-
case "toBeHidden": {
|
|
504
|
-
const isHidden = await locator.isHidden();
|
|
505
|
-
if (negated) {
|
|
506
|
-
if (isHidden)
|
|
507
|
-
throw new Error("Expected element to not be hidden, but it was hidden");
|
|
508
|
-
} else {
|
|
509
|
-
if (!isHidden)
|
|
510
|
-
throw new Error("Expected element to be hidden, but it was not");
|
|
511
|
-
}
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
514
|
-
case "toBeDisabled": {
|
|
515
|
-
const isDisabled = await locator.isDisabled();
|
|
516
|
-
if (negated) {
|
|
517
|
-
if (isDisabled)
|
|
518
|
-
throw new Error("Expected element to not be disabled, but it was disabled");
|
|
519
|
-
} else {
|
|
520
|
-
if (!isDisabled)
|
|
521
|
-
throw new Error("Expected element to be disabled, but it was not");
|
|
522
|
-
}
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
case "toBeFocused": {
|
|
526
|
-
const isFocused = await locator.evaluate((el) => document.activeElement === el).catch(() => false);
|
|
527
|
-
if (negated) {
|
|
528
|
-
if (isFocused)
|
|
529
|
-
throw new Error("Expected element to not be focused, but it was focused");
|
|
530
|
-
} else {
|
|
531
|
-
if (!isFocused)
|
|
532
|
-
throw new Error("Expected element to be focused, but it was not");
|
|
533
|
-
}
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
case "toBeEmpty": {
|
|
537
|
-
const text = await locator.textContent({ timeout });
|
|
538
|
-
const value = await locator.inputValue({ timeout }).catch(() => null);
|
|
539
|
-
const isEmpty = value !== null ? value === "" : (text ?? "") === "";
|
|
540
|
-
if (negated) {
|
|
541
|
-
if (isEmpty)
|
|
542
|
-
throw new Error("Expected element to not be empty, but it was");
|
|
543
|
-
} else {
|
|
544
|
-
if (!isEmpty)
|
|
545
|
-
throw new Error("Expected element to be empty, but it was not");
|
|
546
|
-
}
|
|
547
|
-
break;
|
|
548
|
-
}
|
|
549
|
-
case "toBeAttached": {
|
|
550
|
-
const count = await locator.count();
|
|
551
|
-
const isAttached = count > 0;
|
|
552
|
-
if (negated) {
|
|
553
|
-
if (isAttached)
|
|
554
|
-
throw new Error("Expected element to not be attached to DOM, but it was");
|
|
555
|
-
} else {
|
|
556
|
-
if (!isAttached)
|
|
557
|
-
throw new Error("Expected element to be attached to DOM, but it was not");
|
|
558
|
-
}
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
case "toBeEditable": {
|
|
562
|
-
const isEditable = await locator.isEditable({ timeout });
|
|
563
|
-
if (negated) {
|
|
564
|
-
if (isEditable)
|
|
565
|
-
throw new Error("Expected element to not be editable, but it was");
|
|
566
|
-
} else {
|
|
567
|
-
if (!isEditable)
|
|
568
|
-
throw new Error("Expected element to be editable, but it was not");
|
|
569
|
-
}
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
case "toHaveClass": {
|
|
573
|
-
const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
|
|
574
|
-
const classes = classAttr.split(/\s+/).filter(Boolean);
|
|
575
|
-
let matches;
|
|
576
|
-
let expectedDisplay;
|
|
577
|
-
if (expected && typeof expected === "object" && expected.$regex) {
|
|
578
|
-
const regex = new RegExp(expected.$regex, expected.$flags);
|
|
579
|
-
matches = regex.test(classAttr);
|
|
580
|
-
expectedDisplay = String(regex);
|
|
581
|
-
} else if (Array.isArray(expected)) {
|
|
582
|
-
matches = expected.every((c) => classes.includes(c));
|
|
583
|
-
expectedDisplay = JSON.stringify(expected);
|
|
584
|
-
} else {
|
|
585
|
-
matches = classAttr === String(expected) || classes.includes(String(expected));
|
|
586
|
-
expectedDisplay = String(expected);
|
|
587
|
-
}
|
|
588
|
-
if (negated) {
|
|
589
|
-
if (matches)
|
|
590
|
-
throw new Error(`Expected class to not match ${expectedDisplay}, but got "${classAttr}"`);
|
|
591
|
-
} else {
|
|
592
|
-
if (!matches)
|
|
593
|
-
throw new Error(`Expected class to match ${expectedDisplay}, but got "${classAttr}"`);
|
|
594
|
-
}
|
|
595
|
-
break;
|
|
596
|
-
}
|
|
597
|
-
case "toContainClass": {
|
|
598
|
-
const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
|
|
599
|
-
const classes = classAttr.split(/\s+/).filter(Boolean);
|
|
600
|
-
const expectedClass = String(expected);
|
|
601
|
-
const hasClass = classes.includes(expectedClass);
|
|
602
|
-
if (negated) {
|
|
603
|
-
if (hasClass)
|
|
604
|
-
throw new Error(`Expected element to not contain class "${expectedClass}", but it does`);
|
|
605
|
-
} else {
|
|
606
|
-
if (!hasClass)
|
|
607
|
-
throw new Error(`Expected element to contain class "${expectedClass}", but classes are "${classAttr}"`);
|
|
608
|
-
}
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
case "toHaveId": {
|
|
612
|
-
const id = await locator.getAttribute("id", { timeout });
|
|
613
|
-
const matches = id === String(expected);
|
|
614
|
-
if (negated) {
|
|
615
|
-
if (matches)
|
|
616
|
-
throw new Error(`Expected id to not be "${expected}", but it was`);
|
|
617
|
-
} else {
|
|
618
|
-
if (!matches)
|
|
619
|
-
throw new Error(`Expected id to be "${expected}", but got "${id}"`);
|
|
620
|
-
}
|
|
621
|
-
break;
|
|
622
|
-
}
|
|
623
|
-
case "toBeInViewport": {
|
|
624
|
-
const isInViewport = await locator.evaluate((el) => {
|
|
625
|
-
const rect = el.getBoundingClientRect();
|
|
626
|
-
return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
627
|
-
});
|
|
628
|
-
if (negated) {
|
|
629
|
-
if (isInViewport)
|
|
630
|
-
throw new Error("Expected element to not be in viewport, but it was");
|
|
631
|
-
} else {
|
|
632
|
-
if (!isInViewport)
|
|
633
|
-
throw new Error("Expected element to be in viewport, but it was not");
|
|
634
|
-
}
|
|
635
|
-
break;
|
|
636
|
-
}
|
|
637
|
-
case "toHaveCSS": {
|
|
638
|
-
const { name, value } = expected;
|
|
639
|
-
const actual = await locator.evaluate((el, propName) => {
|
|
640
|
-
return getComputedStyle(el).getPropertyValue(propName);
|
|
641
|
-
}, name);
|
|
642
|
-
let matches;
|
|
643
|
-
if (value && typeof value === "object" && value.$regex) {
|
|
644
|
-
const regex = new RegExp(value.$regex, value.$flags);
|
|
645
|
-
matches = regex.test(actual);
|
|
646
|
-
} else {
|
|
647
|
-
matches = actual === String(value);
|
|
648
|
-
}
|
|
649
|
-
if (negated) {
|
|
650
|
-
if (matches)
|
|
651
|
-
throw new Error(`Expected CSS "${name}" to not be "${value}", but it was`);
|
|
652
|
-
} else {
|
|
653
|
-
if (!matches)
|
|
654
|
-
throw new Error(`Expected CSS "${name}" to be "${value}", but got "${actual}"`);
|
|
655
|
-
}
|
|
656
|
-
break;
|
|
657
|
-
}
|
|
658
|
-
case "toHaveJSProperty": {
|
|
659
|
-
const { name, value } = expected;
|
|
660
|
-
const actual = await locator.evaluate((el, propName) => {
|
|
661
|
-
return el[propName];
|
|
662
|
-
}, name);
|
|
663
|
-
const matches = JSON.stringify(actual) === JSON.stringify(value);
|
|
664
|
-
if (negated) {
|
|
665
|
-
if (matches)
|
|
666
|
-
throw new Error(`Expected JS property "${name}" to not be ${JSON.stringify(value)}, but it was`);
|
|
667
|
-
} else {
|
|
668
|
-
if (!matches)
|
|
669
|
-
throw new Error(`Expected JS property "${name}" to be ${JSON.stringify(value)}, but got ${JSON.stringify(actual)}`);
|
|
670
|
-
}
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
|
-
case "toHaveAccessibleName": {
|
|
674
|
-
const accessibleName = await locator.evaluate((el) => {
|
|
675
|
-
return el.getAttribute("aria-label") || el.getAttribute("aria-labelledby") || el.innerText || "";
|
|
676
|
-
});
|
|
677
|
-
let matches;
|
|
678
|
-
if (expected && typeof expected === "object" && expected.$regex) {
|
|
679
|
-
const regex = new RegExp(expected.$regex, expected.$flags);
|
|
680
|
-
matches = regex.test(accessibleName);
|
|
681
|
-
} else {
|
|
682
|
-
matches = accessibleName === String(expected);
|
|
683
|
-
}
|
|
684
|
-
if (negated) {
|
|
685
|
-
if (matches)
|
|
686
|
-
throw new Error(`Expected accessible name to not be "${expected}", but it was`);
|
|
687
|
-
} else {
|
|
688
|
-
if (!matches)
|
|
689
|
-
throw new Error(`Expected accessible name to be "${expected}", but got "${accessibleName}"`);
|
|
690
|
-
}
|
|
691
|
-
break;
|
|
692
|
-
}
|
|
693
|
-
case "toHaveAccessibleDescription": {
|
|
694
|
-
const accessibleDesc = await locator.evaluate((el) => {
|
|
695
|
-
const describedby = el.getAttribute("aria-describedby");
|
|
696
|
-
if (describedby) {
|
|
697
|
-
const descEl = document.getElementById(describedby);
|
|
698
|
-
return descEl?.textContent || "";
|
|
699
|
-
}
|
|
700
|
-
return el.getAttribute("aria-description") || "";
|
|
701
|
-
});
|
|
702
|
-
let matches;
|
|
703
|
-
if (expected && typeof expected === "object" && expected.$regex) {
|
|
704
|
-
const regex = new RegExp(expected.$regex, expected.$flags);
|
|
705
|
-
matches = regex.test(accessibleDesc);
|
|
706
|
-
} else {
|
|
707
|
-
matches = accessibleDesc === String(expected);
|
|
708
|
-
}
|
|
709
|
-
if (negated) {
|
|
710
|
-
if (matches)
|
|
711
|
-
throw new Error(`Expected accessible description to not be "${expected}", but it was`);
|
|
712
|
-
} else {
|
|
713
|
-
if (!matches)
|
|
714
|
-
throw new Error(`Expected accessible description to be "${expected}", but got "${accessibleDesc}"`);
|
|
715
|
-
}
|
|
716
|
-
break;
|
|
717
|
-
}
|
|
718
|
-
case "toHaveRole": {
|
|
719
|
-
const role = await locator.evaluate((el) => {
|
|
720
|
-
return el.getAttribute("role") || el.tagName.toLowerCase();
|
|
721
|
-
});
|
|
722
|
-
const matches = role === String(expected);
|
|
723
|
-
if (negated) {
|
|
724
|
-
if (matches)
|
|
725
|
-
throw new Error(`Expected role to not be "${expected}", but it was`);
|
|
726
|
-
} else {
|
|
727
|
-
if (!matches)
|
|
728
|
-
throw new Error(`Expected role to be "${expected}", but got "${role}"`);
|
|
729
|
-
}
|
|
730
|
-
break;
|
|
731
|
-
}
|
|
732
|
-
default:
|
|
733
|
-
throw new Error(`Unknown matcher: ${matcher}`);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
async function executePageExpectAssertion(page, matcher, expected, negated, timeout) {
|
|
737
|
-
let expectedValue = expected;
|
|
738
|
-
if (expected && typeof expected === "object" && expected.$regex) {
|
|
739
|
-
expectedValue = new RegExp(expected.$regex, expected.$flags);
|
|
740
|
-
}
|
|
741
|
-
switch (matcher) {
|
|
742
|
-
case "toHaveURL": {
|
|
743
|
-
const expectedUrl = expectedValue;
|
|
744
|
-
const startTime = Date.now();
|
|
745
|
-
let lastUrl = "";
|
|
746
|
-
while (Date.now() - startTime < timeout) {
|
|
747
|
-
lastUrl = page.url();
|
|
748
|
-
const matches = expectedUrl instanceof RegExp ? expectedUrl.test(lastUrl) : lastUrl === expectedUrl;
|
|
749
|
-
if (negated ? !matches : matches)
|
|
750
|
-
return;
|
|
751
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
752
|
-
}
|
|
753
|
-
if (negated) {
|
|
754
|
-
throw new Error(`Expected URL to not match "${expectedUrl}", but got "${lastUrl}"`);
|
|
755
|
-
} else {
|
|
756
|
-
throw new Error(`Expected URL to be "${expectedUrl}", but got "${lastUrl}"`);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
case "toHaveTitle": {
|
|
760
|
-
const expectedTitle = expectedValue;
|
|
761
|
-
const startTime = Date.now();
|
|
762
|
-
let lastTitle = "";
|
|
763
|
-
while (Date.now() - startTime < timeout) {
|
|
764
|
-
lastTitle = await page.title();
|
|
765
|
-
const matches = expectedTitle instanceof RegExp ? expectedTitle.test(lastTitle) : lastTitle === expectedTitle;
|
|
766
|
-
if (negated ? !matches : matches)
|
|
767
|
-
return;
|
|
768
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
769
|
-
}
|
|
770
|
-
if (negated) {
|
|
771
|
-
throw new Error(`Expected title to not match "${expectedTitle}", but got "${lastTitle}"`);
|
|
772
|
-
} else {
|
|
773
|
-
throw new Error(`Expected title to be "${expectedTitle}", but got "${lastTitle}"`);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
default:
|
|
777
|
-
throw new Error(`Unknown page matcher: ${matcher}`);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
function createPlaywrightHandler(page, options) {
|
|
781
|
-
const timeout = options?.timeout ?? 30000;
|
|
782
|
-
const fileIO = {
|
|
783
|
-
readFile: options?.readFile,
|
|
784
|
-
writeFile: options?.writeFile
|
|
785
|
-
};
|
|
786
|
-
const registry = {
|
|
787
|
-
pages: new Map([["page_0", page]]),
|
|
788
|
-
contexts: new Map([["ctx_0", page.context()]]),
|
|
789
|
-
nextPageId: 1,
|
|
790
|
-
nextContextId: 1
|
|
791
|
-
};
|
|
54
|
+
var import_handler = require("./handler.cjs");
|
|
55
|
+
var import_handler2 = require("./handler.cjs");
|
|
56
|
+
function wrapHandlerWithPredicateSupport(handler, evaluatePredicate, defaultTimeout) {
|
|
792
57
|
return async (op) => {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
const contextId2 = op.contextId ?? "ctx_0";
|
|
810
|
-
const targetContext2 = registry.contexts.get(contextId2);
|
|
811
|
-
if (!targetContext2) {
|
|
812
|
-
return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
|
|
813
|
-
}
|
|
814
|
-
const newPage = await options.createPage(targetContext2);
|
|
815
|
-
const pageId2 = `page_${registry.nextPageId++}`;
|
|
816
|
-
registry.pages.set(pageId2, newPage);
|
|
817
|
-
return { ok: true, value: { pageId: pageId2 } };
|
|
818
|
-
}
|
|
819
|
-
case "closeContext": {
|
|
820
|
-
const contextId2 = op.contextId ?? "ctx_0";
|
|
821
|
-
const context = registry.contexts.get(contextId2);
|
|
822
|
-
if (!context) {
|
|
823
|
-
return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
|
|
824
|
-
}
|
|
825
|
-
await context.close();
|
|
826
|
-
registry.contexts.delete(contextId2);
|
|
827
|
-
for (const [pid, p] of registry.pages) {
|
|
828
|
-
if (p.context() === context) {
|
|
829
|
-
registry.pages.delete(pid);
|
|
58
|
+
switch (op.type) {
|
|
59
|
+
case "waitForURLPredicate": {
|
|
60
|
+
const [predicateId, customTimeout, waitUntil] = op.args;
|
|
61
|
+
const effectiveTimeout = customTimeout ?? defaultTimeout;
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
const pollInterval = 100;
|
|
64
|
+
while (true) {
|
|
65
|
+
const urlResult = await handler({ type: "url", args: [], pageId: op.pageId, contextId: op.contextId });
|
|
66
|
+
if (urlResult.ok) {
|
|
67
|
+
try {
|
|
68
|
+
if (evaluatePredicate(predicateId, urlResult.value)) {
|
|
69
|
+
return { ok: true };
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
const error = e;
|
|
73
|
+
return { ok: false, error: { name: error.name, message: error.message } };
|
|
830
74
|
}
|
|
831
75
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
const pageId = op.pageId ?? "page_0";
|
|
836
|
-
const targetPage = registry.pages.get(pageId);
|
|
837
|
-
if (!targetPage) {
|
|
838
|
-
return { ok: false, error: { name: "Error", message: `Page ${pageId} not found` } };
|
|
839
|
-
}
|
|
840
|
-
const contextId = op.contextId ?? "ctx_0";
|
|
841
|
-
const targetContext = registry.contexts.get(contextId);
|
|
842
|
-
switch (op.type) {
|
|
843
|
-
case "goto": {
|
|
844
|
-
const [url, waitUntil] = op.args;
|
|
845
|
-
await targetPage.goto(url, {
|
|
846
|
-
timeout,
|
|
847
|
-
waitUntil: waitUntil ?? "load"
|
|
848
|
-
});
|
|
849
|
-
return { ok: true };
|
|
850
|
-
}
|
|
851
|
-
case "reload":
|
|
852
|
-
await targetPage.reload({ timeout });
|
|
853
|
-
return { ok: true };
|
|
854
|
-
case "url":
|
|
855
|
-
return { ok: true, value: targetPage.url() };
|
|
856
|
-
case "title":
|
|
857
|
-
return { ok: true, value: await targetPage.title() };
|
|
858
|
-
case "content":
|
|
859
|
-
return { ok: true, value: await targetPage.content() };
|
|
860
|
-
case "waitForSelector": {
|
|
861
|
-
const [selector, optionsJson] = op.args;
|
|
862
|
-
const opts = optionsJson ? JSON.parse(optionsJson) : {};
|
|
863
|
-
await targetPage.waitForSelector(selector, { timeout, ...opts });
|
|
864
|
-
return { ok: true };
|
|
865
|
-
}
|
|
866
|
-
case "waitForTimeout": {
|
|
867
|
-
const [ms] = op.args;
|
|
868
|
-
await targetPage.waitForTimeout(ms);
|
|
869
|
-
return { ok: true };
|
|
870
|
-
}
|
|
871
|
-
case "waitForLoadState": {
|
|
872
|
-
const [state] = op.args;
|
|
873
|
-
await targetPage.waitForLoadState(state ?? "load", { timeout });
|
|
874
|
-
return { ok: true };
|
|
875
|
-
}
|
|
876
|
-
case "evaluate": {
|
|
877
|
-
const [script, arg] = op.args;
|
|
878
|
-
if (op.args.length > 1) {
|
|
879
|
-
const fn = new Function("return (" + script + ")")();
|
|
880
|
-
const result2 = await targetPage.evaluate(fn, arg);
|
|
881
|
-
return { ok: true, value: result2 };
|
|
76
|
+
if (effectiveTimeout > 0 && Date.now() - startTime >= effectiveTimeout) {
|
|
77
|
+
return { ok: false, error: { name: "Error", message: `Timeout ${effectiveTimeout}ms exceeded waiting for URL` } };
|
|
882
78
|
}
|
|
883
|
-
|
|
884
|
-
return { ok: true, value: result };
|
|
79
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
885
80
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
case "expectPage": {
|
|
900
|
-
const [matcher, expected, negated, customTimeout] = op.args;
|
|
901
|
-
const effectiveTimeout = customTimeout ?? timeout;
|
|
902
|
-
await executePageExpectAssertion(targetPage, matcher, expected, negated ?? false, effectiveTimeout);
|
|
903
|
-
return { ok: true };
|
|
904
|
-
}
|
|
905
|
-
case "request": {
|
|
906
|
-
const [url, method, data, headers] = op.args;
|
|
907
|
-
const requestOptions = {
|
|
908
|
-
timeout
|
|
909
|
-
};
|
|
910
|
-
if (headers) {
|
|
911
|
-
requestOptions.headers = headers;
|
|
912
|
-
}
|
|
913
|
-
if (data !== undefined && data !== null) {
|
|
914
|
-
requestOptions.data = data;
|
|
915
|
-
}
|
|
916
|
-
const response = await targetPage.request.fetch(url, {
|
|
917
|
-
method,
|
|
918
|
-
...requestOptions
|
|
81
|
+
}
|
|
82
|
+
case "waitForResponsePredicateFinish": {
|
|
83
|
+
const [initialListenerId, predicateId, customTimeout] = op.args;
|
|
84
|
+
const effectiveTimeout = customTimeout ?? defaultTimeout;
|
|
85
|
+
const broadMatcher = { type: "regex", value: { $regex: ".*", $flags: "" } };
|
|
86
|
+
const startTime = Date.now();
|
|
87
|
+
let currentListenerId = initialListenerId;
|
|
88
|
+
while (true) {
|
|
89
|
+
const finishResult = await handler({
|
|
90
|
+
type: "waitForResponseFinish",
|
|
91
|
+
args: [currentListenerId],
|
|
92
|
+
pageId: op.pageId,
|
|
93
|
+
contextId: op.contextId
|
|
919
94
|
});
|
|
920
|
-
|
|
921
|
-
|
|
95
|
+
if (!finishResult.ok)
|
|
96
|
+
return finishResult;
|
|
97
|
+
const responseData = finishResult.value;
|
|
922
98
|
try {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
body: null
|
|
99
|
+
const serialized = {
|
|
100
|
+
method: "",
|
|
101
|
+
headers: Object.entries(responseData.headers || {}),
|
|
102
|
+
url: responseData.url,
|
|
103
|
+
status: responseData.status,
|
|
104
|
+
statusText: responseData.statusText,
|
|
105
|
+
body: responseData.text || ""
|
|
106
|
+
};
|
|
107
|
+
if (evaluatePredicate(predicateId, serialized)) {
|
|
108
|
+
return finishResult;
|
|
934
109
|
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
timeout,
|
|
949
|
-
waitUntil: waitUntil ?? "load"
|
|
950
|
-
});
|
|
951
|
-
return { ok: true };
|
|
952
|
-
}
|
|
953
|
-
case "waitForURL": {
|
|
954
|
-
const [urlArg, customTimeout, waitUntil] = op.args;
|
|
955
|
-
const url = urlArg && typeof urlArg === "object" && "$regex" in urlArg ? new RegExp(urlArg.$regex, urlArg.$flags) : urlArg;
|
|
956
|
-
await targetPage.waitForURL(url, {
|
|
957
|
-
timeout: customTimeout ?? timeout,
|
|
958
|
-
waitUntil: waitUntil ?? undefined
|
|
110
|
+
} catch (e) {
|
|
111
|
+
const error = e;
|
|
112
|
+
return { ok: false, error: { name: error.name, message: error.message } };
|
|
113
|
+
}
|
|
114
|
+
if (effectiveTimeout > 0 && Date.now() - startTime >= effectiveTimeout) {
|
|
115
|
+
return { ok: false, error: { name: "Error", message: `Timeout ${effectiveTimeout}ms exceeded waiting for response` } };
|
|
116
|
+
}
|
|
117
|
+
const remainingTimeout = effectiveTimeout > 0 ? Math.max(1, effectiveTimeout - (Date.now() - startTime)) : effectiveTimeout;
|
|
118
|
+
const nextStartResult = await handler({
|
|
119
|
+
type: "waitForResponseStart",
|
|
120
|
+
args: [broadMatcher, remainingTimeout],
|
|
121
|
+
pageId: op.pageId,
|
|
122
|
+
contextId: op.contextId
|
|
959
123
|
});
|
|
960
|
-
|
|
124
|
+
if (!nextStartResult.ok)
|
|
125
|
+
return nextStartResult;
|
|
126
|
+
currentListenerId = nextStartResult.value.listenerId;
|
|
961
127
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
128
|
+
}
|
|
129
|
+
case "waitForRequestPredicateFinish": {
|
|
130
|
+
const [initialListenerId, predicateId, customTimeout] = op.args;
|
|
131
|
+
const effectiveTimeout = customTimeout ?? defaultTimeout;
|
|
132
|
+
const broadMatcher = { type: "regex", value: { $regex: ".*", $flags: "" } };
|
|
133
|
+
const startTime = Date.now();
|
|
134
|
+
let currentListenerId = initialListenerId;
|
|
135
|
+
while (true) {
|
|
136
|
+
const finishResult = await handler({
|
|
137
|
+
type: "waitForRequestFinish",
|
|
138
|
+
args: [currentListenerId],
|
|
139
|
+
pageId: op.pageId,
|
|
140
|
+
contextId: op.contextId
|
|
974
141
|
});
|
|
975
|
-
if (
|
|
976
|
-
|
|
977
|
-
|
|
142
|
+
if (!finishResult.ok)
|
|
143
|
+
return finishResult;
|
|
144
|
+
const requestData = finishResult.value;
|
|
145
|
+
try {
|
|
146
|
+
const serialized = {
|
|
147
|
+
method: requestData.method,
|
|
148
|
+
headers: Object.entries(requestData.headers || {}),
|
|
149
|
+
url: requestData.url,
|
|
150
|
+
body: requestData.postData || ""
|
|
151
|
+
};
|
|
152
|
+
if (evaluatePredicate(predicateId, serialized)) {
|
|
153
|
+
return finishResult;
|
|
978
154
|
}
|
|
979
|
-
|
|
155
|
+
} catch (e) {
|
|
156
|
+
const error = e;
|
|
157
|
+
return { ok: false, error: { name: error.name, message: error.message } };
|
|
980
158
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
case "setViewportSize": {
|
|
984
|
-
const [size] = op.args;
|
|
985
|
-
await targetPage.setViewportSize(size);
|
|
986
|
-
return { ok: true };
|
|
987
|
-
}
|
|
988
|
-
case "viewportSize": {
|
|
989
|
-
return { ok: true, value: targetPage.viewportSize() };
|
|
990
|
-
}
|
|
991
|
-
case "keyboardType": {
|
|
992
|
-
const [text, typeOptions] = op.args;
|
|
993
|
-
await targetPage.keyboard.type(text, typeOptions);
|
|
994
|
-
return { ok: true };
|
|
995
|
-
}
|
|
996
|
-
case "keyboardPress": {
|
|
997
|
-
const [key, pressOptions] = op.args;
|
|
998
|
-
await targetPage.keyboard.press(key, pressOptions);
|
|
999
|
-
return { ok: true };
|
|
1000
|
-
}
|
|
1001
|
-
case "keyboardDown": {
|
|
1002
|
-
const [key] = op.args;
|
|
1003
|
-
await targetPage.keyboard.down(key);
|
|
1004
|
-
return { ok: true };
|
|
1005
|
-
}
|
|
1006
|
-
case "keyboardUp": {
|
|
1007
|
-
const [key] = op.args;
|
|
1008
|
-
await targetPage.keyboard.up(key);
|
|
1009
|
-
return { ok: true };
|
|
1010
|
-
}
|
|
1011
|
-
case "keyboardInsertText": {
|
|
1012
|
-
const [text] = op.args;
|
|
1013
|
-
await targetPage.keyboard.insertText(text);
|
|
1014
|
-
return { ok: true };
|
|
1015
|
-
}
|
|
1016
|
-
case "mouseMove": {
|
|
1017
|
-
const [x, y, moveOptions] = op.args;
|
|
1018
|
-
await targetPage.mouse.move(x, y, moveOptions);
|
|
1019
|
-
return { ok: true };
|
|
1020
|
-
}
|
|
1021
|
-
case "mouseClick": {
|
|
1022
|
-
const [x, y, clickOptions] = op.args;
|
|
1023
|
-
await targetPage.mouse.click(x, y, clickOptions);
|
|
1024
|
-
return { ok: true };
|
|
1025
|
-
}
|
|
1026
|
-
case "mouseDown": {
|
|
1027
|
-
const [downOptions] = op.args;
|
|
1028
|
-
await targetPage.mouse.down(downOptions);
|
|
1029
|
-
return { ok: true };
|
|
1030
|
-
}
|
|
1031
|
-
case "mouseUp": {
|
|
1032
|
-
const [upOptions] = op.args;
|
|
1033
|
-
await targetPage.mouse.up(upOptions);
|
|
1034
|
-
return { ok: true };
|
|
1035
|
-
}
|
|
1036
|
-
case "mouseWheel": {
|
|
1037
|
-
const [deltaX, deltaY] = op.args;
|
|
1038
|
-
await targetPage.mouse.wheel(deltaX, deltaY);
|
|
1039
|
-
return { ok: true };
|
|
1040
|
-
}
|
|
1041
|
-
case "frames": {
|
|
1042
|
-
const frames = targetPage.frames();
|
|
1043
|
-
return { ok: true, value: frames.map((f) => ({ name: f.name(), url: f.url() })) };
|
|
1044
|
-
}
|
|
1045
|
-
case "mainFrame": {
|
|
1046
|
-
const mainFrame = targetPage.mainFrame();
|
|
1047
|
-
return { ok: true, value: { name: mainFrame.name(), url: mainFrame.url() } };
|
|
1048
|
-
}
|
|
1049
|
-
case "bringToFront": {
|
|
1050
|
-
await targetPage.bringToFront();
|
|
1051
|
-
return { ok: true };
|
|
1052
|
-
}
|
|
1053
|
-
case "close": {
|
|
1054
|
-
await targetPage.close();
|
|
1055
|
-
registry.pages.delete(pageId);
|
|
1056
|
-
return { ok: true };
|
|
1057
|
-
}
|
|
1058
|
-
case "isClosed": {
|
|
1059
|
-
return { ok: true, value: targetPage.isClosed() };
|
|
1060
|
-
}
|
|
1061
|
-
case "pdf": {
|
|
1062
|
-
const [pdfOptions] = op.args;
|
|
1063
|
-
const { path: pdfPath, ...restPdfOptions } = pdfOptions ?? {};
|
|
1064
|
-
const buffer = await targetPage.pdf(restPdfOptions);
|
|
1065
|
-
if (pdfPath) {
|
|
1066
|
-
if (!fileIO.writeFile) {
|
|
1067
|
-
throw new Error("pdf() with path option requires a writeFile callback to be provided. " + "Either provide a writeFile callback in defaultPlaywrightHandler options, or omit the path option " + "and handle the returned base64 data yourself.");
|
|
1068
|
-
}
|
|
1069
|
-
await fileIO.writeFile(pdfPath, buffer);
|
|
159
|
+
if (effectiveTimeout > 0 && Date.now() - startTime >= effectiveTimeout) {
|
|
160
|
+
return { ok: false, error: { name: "Error", message: `Timeout ${effectiveTimeout}ms exceeded waiting for request` } };
|
|
1070
161
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
await ctx.addCookies(cookies);
|
|
1082
|
-
return { ok: true };
|
|
1083
|
-
}
|
|
1084
|
-
case "cookies": {
|
|
1085
|
-
const [urls] = op.args;
|
|
1086
|
-
const ctx = targetContext ?? targetPage.context();
|
|
1087
|
-
const cookies = await ctx.cookies(urls);
|
|
1088
|
-
return { ok: true, value: cookies };
|
|
1089
|
-
}
|
|
1090
|
-
case "setExtraHTTPHeaders": {
|
|
1091
|
-
const [headers] = op.args;
|
|
1092
|
-
await targetPage.setExtraHTTPHeaders(headers);
|
|
1093
|
-
return { ok: true };
|
|
1094
|
-
}
|
|
1095
|
-
case "pause": {
|
|
1096
|
-
await targetPage.pause();
|
|
1097
|
-
return { ok: true };
|
|
162
|
+
const remainingTimeout = effectiveTimeout > 0 ? Math.max(1, effectiveTimeout - (Date.now() - startTime)) : effectiveTimeout;
|
|
163
|
+
const nextStartResult = await handler({
|
|
164
|
+
type: "waitForRequestStart",
|
|
165
|
+
args: [broadMatcher, remainingTimeout],
|
|
166
|
+
pageId: op.pageId,
|
|
167
|
+
contextId: op.contextId
|
|
168
|
+
});
|
|
169
|
+
if (!nextStartResult.ok)
|
|
170
|
+
return nextStartResult;
|
|
171
|
+
currentListenerId = nextStartResult.value.listenerId;
|
|
1098
172
|
}
|
|
1099
|
-
default:
|
|
1100
|
-
return { ok: false, error: { name: "Error", message: `Unknown operation: ${op.type}` } };
|
|
1101
173
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
return { ok: false, error: { name: error.name, message: error.message } };
|
|
174
|
+
default:
|
|
175
|
+
return handler(op);
|
|
1105
176
|
}
|
|
1106
177
|
};
|
|
1107
178
|
}
|
|
1108
|
-
function defaultPlaywrightHandler(page, options) {
|
|
1109
|
-
const handler = createPlaywrightHandler(page, options);
|
|
1110
|
-
handler[import_types.DEFAULT_PLAYWRIGHT_HANDLER_META] = { page, options };
|
|
1111
|
-
return handler;
|
|
1112
|
-
}
|
|
1113
|
-
function getDefaultPlaywrightHandlerMetadata(handler) {
|
|
1114
|
-
return handler[import_types.DEFAULT_PLAYWRIGHT_HANDLER_META];
|
|
1115
|
-
}
|
|
1116
179
|
async function setupPlaywright(context, options) {
|
|
1117
180
|
const timeout = options.timeout ?? 30000;
|
|
1118
181
|
const explicitPage = "page" in options ? options.page : undefined;
|
|
1119
182
|
const handler = "handler" in options ? options.handler : undefined;
|
|
1120
|
-
const handlerMetadata = handler ? getDefaultPlaywrightHandlerMetadata(handler) : undefined;
|
|
183
|
+
const handlerMetadata = handler ? import_handler2.getDefaultPlaywrightHandlerMetadata(handler) : undefined;
|
|
1121
184
|
const page = explicitPage ?? handlerMetadata?.page;
|
|
1122
185
|
const createPage = "createPage" in options ? options.createPage : undefined;
|
|
1123
186
|
const createContext = "createContext" in options ? options.createContext : undefined;
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
createContext
|
|
1128
|
-
}) : undefined);
|
|
1129
|
-
if (!effectiveHandler) {
|
|
187
|
+
const readFile = "readFile" in options ? options.readFile : undefined;
|
|
188
|
+
const writeFile = "writeFile" in options ? options.writeFile : undefined;
|
|
189
|
+
if (!handler && !page) {
|
|
1130
190
|
throw new Error("Either page or handler must be provided to setupPlaywright");
|
|
1131
191
|
}
|
|
1132
192
|
const browserConsoleLogs = [];
|
|
@@ -1205,14 +265,9 @@ async function setupPlaywright(context, options) {
|
|
|
1205
265
|
page.on("response", responseHandler);
|
|
1206
266
|
page.on("console", consoleHandler);
|
|
1207
267
|
}
|
|
1208
|
-
global.setSync("__Playwright_handler_ref", new import_isolated_vm.default.Reference(async (opJson) => {
|
|
1209
|
-
const op = JSON.parse(opJson);
|
|
1210
|
-
const result = await effectiveHandler(op);
|
|
1211
|
-
return JSON.stringify(result);
|
|
1212
|
-
}));
|
|
1213
268
|
context.evalSync(`
|
|
1214
269
|
(function() {
|
|
1215
|
-
globalThis.
|
|
270
|
+
globalThis.__pw_invoke_sync = function(type, args, options) {
|
|
1216
271
|
const op = JSON.stringify({ type, args, pageId: options?.pageId, contextId: options?.contextId });
|
|
1217
272
|
const resultJson = __Playwright_handler_ref.applySyncPromise(undefined, [op]);
|
|
1218
273
|
const result = JSON.parse(resultJson);
|
|
@@ -1224,13 +279,68 @@ async function setupPlaywright(context, options) {
|
|
|
1224
279
|
throw error;
|
|
1225
280
|
}
|
|
1226
281
|
};
|
|
282
|
+
globalThis.__pw_invoke = async function(type, args, options) {
|
|
283
|
+
return globalThis.__pw_invoke_sync(type, args, options);
|
|
284
|
+
};
|
|
285
|
+
})();
|
|
286
|
+
`);
|
|
287
|
+
context.evalSync(`
|
|
288
|
+
(function() {
|
|
289
|
+
const __pw_predicates = new Map();
|
|
290
|
+
let __pw_next_id = 0;
|
|
291
|
+
globalThis.__pw_register_predicate = function(fn) {
|
|
292
|
+
const id = __pw_next_id++;
|
|
293
|
+
__pw_predicates.set(id, fn);
|
|
294
|
+
return id;
|
|
295
|
+
};
|
|
296
|
+
globalThis.__pw_unregister_predicate = function(id) {
|
|
297
|
+
__pw_predicates.delete(id);
|
|
298
|
+
};
|
|
299
|
+
globalThis.__pw_evaluate_predicate = function(id, data) {
|
|
300
|
+
const fn = __pw_predicates.get(id);
|
|
301
|
+
if (!fn) throw new Error('Predicate not found: ' + id);
|
|
302
|
+
const result = fn(data);
|
|
303
|
+
if (result && typeof result === 'object' && typeof result.then === 'function') {
|
|
304
|
+
throw new Error('Async predicates are not supported. Use a synchronous predicate function.');
|
|
305
|
+
}
|
|
306
|
+
return !!result;
|
|
307
|
+
};
|
|
1227
308
|
})();
|
|
1228
309
|
`);
|
|
310
|
+
const evaluatePredicateRef = context.global.getSync("__pw_evaluate_predicate", { reference: true });
|
|
311
|
+
const evaluatePredicateFn = (predicateId, data) => {
|
|
312
|
+
return evaluatePredicateRef.applySync(undefined, [new import_isolated_vm.default.ExternalCopy(predicateId).copyInto(), new import_isolated_vm.default.ExternalCopy(data).copyInto()]);
|
|
313
|
+
};
|
|
314
|
+
let effectiveHandler;
|
|
315
|
+
if (handler && handlerMetadata?.page) {
|
|
316
|
+
effectiveHandler = import_handler2.createPlaywrightHandler(handlerMetadata.page, {
|
|
317
|
+
...handlerMetadata.options,
|
|
318
|
+
evaluatePredicate: evaluatePredicateFn
|
|
319
|
+
});
|
|
320
|
+
} else if (handler) {
|
|
321
|
+
effectiveHandler = wrapHandlerWithPredicateSupport(handler, evaluatePredicateFn, timeout);
|
|
322
|
+
} else if (page) {
|
|
323
|
+
effectiveHandler = import_handler2.createPlaywrightHandler(page, {
|
|
324
|
+
timeout,
|
|
325
|
+
readFile,
|
|
326
|
+
writeFile,
|
|
327
|
+
createPage,
|
|
328
|
+
createContext,
|
|
329
|
+
evaluatePredicate: evaluatePredicateFn
|
|
330
|
+
});
|
|
331
|
+
} else {
|
|
332
|
+
throw new Error("Either page or handler must be provided to setupPlaywright");
|
|
333
|
+
}
|
|
334
|
+
global.setSync("__Playwright_handler_ref", new import_isolated_vm.default.Reference(async (opJson) => {
|
|
335
|
+
const op = JSON.parse(opJson);
|
|
336
|
+
const result = await effectiveHandler(op);
|
|
337
|
+
return JSON.stringify(result);
|
|
338
|
+
}));
|
|
1229
339
|
context.evalSync(`
|
|
1230
340
|
(function() {
|
|
1231
341
|
// IsolatePage class - represents a page with a specific pageId
|
|
1232
342
|
class IsolatePage {
|
|
1233
|
-
#pageId; #contextId;
|
|
343
|
+
#pageId; #contextId;
|
|
1234
344
|
constructor(pageId, contextId) {
|
|
1235
345
|
this.#pageId = pageId;
|
|
1236
346
|
this.#contextId = contextId;
|
|
@@ -1241,15 +351,11 @@ async function setupPlaywright(context, options) {
|
|
|
1241
351
|
|
|
1242
352
|
async goto(url, options) {
|
|
1243
353
|
await __pw_invoke("goto", [url, options?.waitUntil || null], { pageId: this.#pageId });
|
|
1244
|
-
const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
|
|
1245
|
-
this.#currentUrl = resolvedUrl || url;
|
|
1246
354
|
}
|
|
1247
355
|
async reload() {
|
|
1248
356
|
await __pw_invoke("reload", [], { pageId: this.#pageId });
|
|
1249
|
-
const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
|
|
1250
|
-
if (resolvedUrl) this.#currentUrl = resolvedUrl;
|
|
1251
357
|
}
|
|
1252
|
-
url() { return this.#
|
|
358
|
+
url() { return __pw_invoke_sync("url", [], { pageId: this.#pageId }); }
|
|
1253
359
|
async title() { return __pw_invoke("title", [], { pageId: this.#pageId }); }
|
|
1254
360
|
async content() { return __pw_invoke("content", [], { pageId: this.#pageId }); }
|
|
1255
361
|
async waitForSelector(selector, options) {
|
|
@@ -1299,21 +405,139 @@ async function setupPlaywright(context, options) {
|
|
|
1299
405
|
}
|
|
1300
406
|
async goBack(options) {
|
|
1301
407
|
await __pw_invoke("goBack", [options?.waitUntil || null], { pageId: this.#pageId });
|
|
1302
|
-
const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
|
|
1303
|
-
if (resolvedUrl) this.#currentUrl = resolvedUrl;
|
|
1304
408
|
}
|
|
1305
409
|
async goForward(options) {
|
|
1306
410
|
await __pw_invoke("goForward", [options?.waitUntil || null], { pageId: this.#pageId });
|
|
1307
|
-
const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
|
|
1308
|
-
if (resolvedUrl) this.#currentUrl = resolvedUrl;
|
|
1309
411
|
}
|
|
1310
412
|
async waitForURL(url, options) {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
413
|
+
if (typeof url === 'function') {
|
|
414
|
+
const predicateId = __pw_register_predicate(url);
|
|
415
|
+
try {
|
|
416
|
+
await __pw_invoke("waitForURLPredicate", [predicateId, options?.timeout || null, options?.waitUntil || null], { pageId: this.#pageId });
|
|
417
|
+
} finally {
|
|
418
|
+
__pw_unregister_predicate(predicateId);
|
|
419
|
+
}
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
let serializedUrl;
|
|
423
|
+
if (typeof url === 'string') {
|
|
424
|
+
serializedUrl = { type: 'string', value: url };
|
|
425
|
+
} else if (url && typeof url === 'object' && typeof url.source === 'string' && typeof url.flags === 'string') {
|
|
426
|
+
serializedUrl = { type: 'regex', value: { $regex: url.source, $flags: url.flags } };
|
|
427
|
+
} else {
|
|
428
|
+
serializedUrl = url;
|
|
1314
429
|
}
|
|
1315
430
|
return __pw_invoke("waitForURL", [serializedUrl, options?.timeout || null, options?.waitUntil || null], { pageId: this.#pageId });
|
|
1316
431
|
}
|
|
432
|
+
waitForRequest(urlOrPredicate, options) {
|
|
433
|
+
if (typeof urlOrPredicate === 'function') {
|
|
434
|
+
const userPredicate = urlOrPredicate;
|
|
435
|
+
const wrappedPredicate = (data) => {
|
|
436
|
+
const requestLike = {
|
|
437
|
+
url: () => data.url,
|
|
438
|
+
method: () => data.method,
|
|
439
|
+
headers: () => Object.fromEntries(data.headers),
|
|
440
|
+
headersArray: () => data.headers.map(h => ({ name: h[0], value: h[1] })),
|
|
441
|
+
postData: () => data.body || null,
|
|
442
|
+
};
|
|
443
|
+
return userPredicate(requestLike);
|
|
444
|
+
};
|
|
445
|
+
const predicateId = __pw_register_predicate(wrappedPredicate);
|
|
446
|
+
const pageId = this.#pageId;
|
|
447
|
+
// Start listening immediately (before the user triggers the request)
|
|
448
|
+
const broadMatcher = { type: 'regex', value: { $regex: '.*', $flags: '' } };
|
|
449
|
+
const startResult = __pw_invoke_sync("waitForRequestStart", [broadMatcher, options?.timeout || null], { pageId });
|
|
450
|
+
const listenerId = startResult.listenerId;
|
|
451
|
+
return {
|
|
452
|
+
then(resolve, reject) {
|
|
453
|
+
try {
|
|
454
|
+
const r = __pw_invoke_sync("waitForRequestPredicateFinish", [listenerId, predicateId, options?.timeout || null], { pageId });
|
|
455
|
+
resolve({ url: () => r.url, method: () => r.method, headers: () => r.headers, postData: () => r.postData });
|
|
456
|
+
} catch(e) { reject(e); } finally { __pw_unregister_predicate(predicateId); }
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
let serializedMatcher;
|
|
461
|
+
if (typeof urlOrPredicate === 'string') {
|
|
462
|
+
serializedMatcher = { type: 'string', value: urlOrPredicate };
|
|
463
|
+
} else if (urlOrPredicate && typeof urlOrPredicate === 'object'
|
|
464
|
+
&& typeof urlOrPredicate.source === 'string'
|
|
465
|
+
&& typeof urlOrPredicate.flags === 'string') {
|
|
466
|
+
serializedMatcher = { type: 'regex', value: { $regex: urlOrPredicate.source, $flags: urlOrPredicate.flags } };
|
|
467
|
+
} else {
|
|
468
|
+
throw new Error('waitForRequest requires a URL string, RegExp, or predicate function');
|
|
469
|
+
}
|
|
470
|
+
const startResult = __pw_invoke_sync("waitForRequestStart", [serializedMatcher, options?.timeout || null], { pageId: this.#pageId });
|
|
471
|
+
const listenerId = startResult.listenerId;
|
|
472
|
+
const pageId = this.#pageId;
|
|
473
|
+
return {
|
|
474
|
+
then(resolve, reject) {
|
|
475
|
+
try {
|
|
476
|
+
const r = __pw_invoke_sync("waitForRequestFinish", [listenerId], { pageId });
|
|
477
|
+
resolve({ url: () => r.url, method: () => r.method, headers: () => r.headers, postData: () => r.postData });
|
|
478
|
+
} catch(e) { reject(e); }
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
waitForResponse(urlOrPredicate, options) {
|
|
483
|
+
if (typeof urlOrPredicate === 'function') {
|
|
484
|
+
const userPredicate = urlOrPredicate;
|
|
485
|
+
const wrappedPredicate = (data) => {
|
|
486
|
+
const responseLike = {
|
|
487
|
+
url: () => data.url,
|
|
488
|
+
status: () => data.status,
|
|
489
|
+
statusText: () => data.statusText,
|
|
490
|
+
headers: () => Object.fromEntries(data.headers),
|
|
491
|
+
headersArray: () => data.headers.map(h => ({ name: h[0], value: h[1] })),
|
|
492
|
+
ok: () => data.status >= 200 && data.status < 300,
|
|
493
|
+
};
|
|
494
|
+
return userPredicate(responseLike);
|
|
495
|
+
};
|
|
496
|
+
const predicateId = __pw_register_predicate(wrappedPredicate);
|
|
497
|
+
const pageId = this.#pageId;
|
|
498
|
+
// Start listening immediately (before the user triggers the response)
|
|
499
|
+
const broadMatcher = { type: 'regex', value: { $regex: '.*', $flags: '' } };
|
|
500
|
+
const startResult = __pw_invoke_sync("waitForResponseStart", [broadMatcher, options?.timeout || null], { pageId });
|
|
501
|
+
const listenerId = startResult.listenerId;
|
|
502
|
+
return {
|
|
503
|
+
then(resolve, reject) {
|
|
504
|
+
try {
|
|
505
|
+
const r = __pw_invoke_sync("waitForResponsePredicateFinish", [listenerId, predicateId, options?.timeout || null], { pageId });
|
|
506
|
+
resolve({
|
|
507
|
+
url: () => r.url, status: () => r.status, statusText: () => r.statusText,
|
|
508
|
+
headers: () => r.headers, headersArray: () => r.headersArray,
|
|
509
|
+
ok: () => r.ok, json: async () => r.json, text: async () => r.text, body: async () => r.body,
|
|
510
|
+
});
|
|
511
|
+
} catch(e) { reject(e); } finally { __pw_unregister_predicate(predicateId); }
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
let serializedMatcher;
|
|
516
|
+
if (typeof urlOrPredicate === 'string') {
|
|
517
|
+
serializedMatcher = { type: 'string', value: urlOrPredicate };
|
|
518
|
+
} else if (urlOrPredicate && typeof urlOrPredicate === 'object'
|
|
519
|
+
&& typeof urlOrPredicate.source === 'string'
|
|
520
|
+
&& typeof urlOrPredicate.flags === 'string') {
|
|
521
|
+
serializedMatcher = { type: 'regex', value: { $regex: urlOrPredicate.source, $flags: urlOrPredicate.flags } };
|
|
522
|
+
} else {
|
|
523
|
+
throw new Error('waitForResponse requires a URL string, RegExp, or predicate function');
|
|
524
|
+
}
|
|
525
|
+
const startResult = __pw_invoke_sync("waitForResponseStart", [serializedMatcher, options?.timeout || null], { pageId: this.#pageId });
|
|
526
|
+
const listenerId = startResult.listenerId;
|
|
527
|
+
const pageId = this.#pageId;
|
|
528
|
+
return {
|
|
529
|
+
then(resolve, reject) {
|
|
530
|
+
try {
|
|
531
|
+
const r = __pw_invoke_sync("waitForResponseFinish", [listenerId], { pageId });
|
|
532
|
+
resolve({
|
|
533
|
+
url: () => r.url, status: () => r.status, statusText: () => r.statusText,
|
|
534
|
+
headers: () => r.headers, headersArray: () => r.headersArray,
|
|
535
|
+
ok: () => r.ok, json: async () => r.json, text: async () => r.text, body: async () => r.body,
|
|
536
|
+
});
|
|
537
|
+
} catch(e) { reject(e); }
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
1317
541
|
context() {
|
|
1318
542
|
const contextId = this.#contextId;
|
|
1319
543
|
return new IsolateContext(contextId);
|
|
@@ -1429,6 +653,86 @@ async function setupPlaywright(context, options) {
|
|
|
1429
653
|
return JSON.stringify(serialized);
|
|
1430
654
|
}
|
|
1431
655
|
|
|
656
|
+
const INPUT_FILES_VALIDATION_ERROR =
|
|
657
|
+
"setInputFiles() expects a file path string, an array of file path strings, " +
|
|
658
|
+
"a single inline file object ({ name, mimeType, buffer }), or an array of inline file objects.";
|
|
659
|
+
|
|
660
|
+
function isInlineFileObject(value) {
|
|
661
|
+
return !!value
|
|
662
|
+
&& typeof value === 'object'
|
|
663
|
+
&& typeof value.name === 'string'
|
|
664
|
+
&& typeof value.mimeType === 'string'
|
|
665
|
+
&& 'buffer' in value;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function encodeInlineFileBuffer(buffer) {
|
|
669
|
+
if (typeof buffer === 'string') {
|
|
670
|
+
return buffer;
|
|
671
|
+
}
|
|
672
|
+
let bytes;
|
|
673
|
+
if (buffer instanceof ArrayBuffer) {
|
|
674
|
+
bytes = new Uint8Array(buffer);
|
|
675
|
+
} else if (ArrayBuffer.isView(buffer)) {
|
|
676
|
+
bytes = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
677
|
+
} else {
|
|
678
|
+
throw new Error(
|
|
679
|
+
"setInputFiles() inline file buffer must be a base64 string, ArrayBuffer, or TypedArray."
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
let binary = '';
|
|
683
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
684
|
+
binary += String.fromCharCode(bytes[i]);
|
|
685
|
+
}
|
|
686
|
+
return btoa(binary);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function serializeInlineFile(file) {
|
|
690
|
+
return {
|
|
691
|
+
name: file.name,
|
|
692
|
+
mimeType: file.mimeType,
|
|
693
|
+
buffer: encodeInlineFileBuffer(file.buffer),
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function normalizeSetInputFilesArg(files) {
|
|
698
|
+
if (typeof files === 'string') {
|
|
699
|
+
return files;
|
|
700
|
+
}
|
|
701
|
+
if (isInlineFileObject(files)) {
|
|
702
|
+
return serializeInlineFile(files);
|
|
703
|
+
}
|
|
704
|
+
if (!Array.isArray(files)) {
|
|
705
|
+
throw new Error(INPUT_FILES_VALIDATION_ERROR);
|
|
706
|
+
}
|
|
707
|
+
if (files.length === 0) {
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let hasPaths = false;
|
|
712
|
+
let hasInline = false;
|
|
713
|
+
const inlineFiles = [];
|
|
714
|
+
|
|
715
|
+
for (const file of files) {
|
|
716
|
+
if (typeof file === 'string') {
|
|
717
|
+
hasPaths = true;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (isInlineFileObject(file)) {
|
|
721
|
+
hasInline = true;
|
|
722
|
+
inlineFiles.push(serializeInlineFile(file));
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
throw new Error(INPUT_FILES_VALIDATION_ERROR);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (hasPaths && hasInline) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
"setInputFiles() does not support mixing file paths and inline file objects in the same array."
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
return hasInline ? inlineFiles : files;
|
|
734
|
+
}
|
|
735
|
+
|
|
1432
736
|
class Locator {
|
|
1433
737
|
#type; #value; #options; #pageId;
|
|
1434
738
|
constructor(type, value, options, pageId) {
|
|
@@ -1527,15 +831,7 @@ async function setupPlaywright(context, options) {
|
|
|
1527
831
|
return __pw_invoke("locatorAction", [...this._getInfo(), "boundingBox", null], { pageId: this.#pageId });
|
|
1528
832
|
}
|
|
1529
833
|
async setInputFiles(files) {
|
|
1530
|
-
|
|
1531
|
-
let serializedFiles = files;
|
|
1532
|
-
if (Array.isArray(files) && files.length > 0 && typeof files[0] === 'object' && files[0].buffer) {
|
|
1533
|
-
serializedFiles = files.map(f => ({
|
|
1534
|
-
name: f.name,
|
|
1535
|
-
mimeType: f.mimeType,
|
|
1536
|
-
buffer: typeof f.buffer === 'string' ? f.buffer : btoa(String.fromCharCode(...new Uint8Array(f.buffer)))
|
|
1537
|
-
}));
|
|
1538
|
-
}
|
|
834
|
+
const serializedFiles = normalizeSetInputFilesArg(files);
|
|
1539
835
|
return __pw_invoke("locatorAction", [...this._getInfo(), "setInputFiles", serializedFiles], { pageId: this.#pageId });
|
|
1540
836
|
}
|
|
1541
837
|
async screenshot(options) {
|
|
@@ -1898,4 +1194,4 @@ async function setupPlaywright(context, options) {
|
|
|
1898
1194
|
};
|
|
1899
1195
|
}
|
|
1900
1196
|
|
|
1901
|
-
//# debugId=
|
|
1197
|
+
//# debugId=2C9A0105748F8D9364756E2164756E21
|