@ricsam/isolate-playwright 0.1.12 → 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.
@@ -43,450 +43,150 @@ var __export = (target, all) => {
43
43
  var exports_src = {};
44
44
  __export(exports_src, {
45
45
  setupPlaywright: () => setupPlaywright,
46
- createPlaywrightHandler: () => createPlaywrightHandler
46
+ getDefaultPlaywrightHandlerMetadata: () => import_handler.getDefaultPlaywrightHandlerMetadata,
47
+ defaultPlaywrightHandler: () => import_handler.defaultPlaywrightHandler,
48
+ createPlaywrightHandler: () => import_handler.createPlaywrightHandler,
49
+ DEFAULT_PLAYWRIGHT_HANDLER_META: () => import_types.DEFAULT_PLAYWRIGHT_HANDLER_META
47
50
  });
48
51
  module.exports = __toCommonJS(exports_src);
49
52
  var import_isolated_vm = __toESM(require("isolated-vm"));
50
- function getLocator(page, selectorType, selectorValue, optionsJson) {
51
- const options = optionsJson ? JSON.parse(optionsJson) : undefined;
52
- const nthIndex = options?.nth;
53
- const roleOptions = options ? { ...options } : undefined;
54
- if (roleOptions) {
55
- delete roleOptions.nth;
56
- delete roleOptions.filter;
57
- if (roleOptions.name && typeof roleOptions.name === "object" && roleOptions.name.$regex) {
58
- roleOptions.name = new RegExp(roleOptions.name.$regex, roleOptions.name.$flags);
59
- }
60
- }
61
- let locator;
62
- switch (selectorType) {
63
- case "css":
64
- locator = page.locator(selectorValue);
65
- break;
66
- case "role":
67
- locator = page.getByRole(selectorValue, roleOptions && Object.keys(roleOptions).length > 0 ? roleOptions : undefined);
68
- break;
69
- case "text":
70
- locator = page.getByText(selectorValue);
71
- break;
72
- case "label":
73
- locator = page.getByLabel(selectorValue);
74
- break;
75
- case "placeholder":
76
- locator = page.getByPlaceholder(selectorValue);
77
- break;
78
- case "testId":
79
- locator = page.getByTestId(selectorValue);
80
- break;
81
- case "or": {
82
- const [firstInfo, secondInfo] = JSON.parse(selectorValue);
83
- const first = getLocator(page, firstInfo[0], firstInfo[1], firstInfo[2]);
84
- const second = getLocator(page, secondInfo[0], secondInfo[1], secondInfo[2]);
85
- locator = first.or(second);
86
- break;
87
- }
88
- default:
89
- locator = page.locator(selectorValue);
90
- }
91
- if (nthIndex !== undefined) {
92
- locator = locator.nth(nthIndex);
93
- }
94
- if (options?.filter) {
95
- const filterOpts = { ...options.filter };
96
- if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
97
- filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
98
- }
99
- if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
100
- filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
101
- }
102
- locator = locator.filter(filterOpts);
103
- }
104
- return locator;
105
- }
106
- async function executeLocatorAction(locator, action, actionArg, timeout) {
107
- switch (action) {
108
- case "click":
109
- await locator.click({ timeout });
110
- return null;
111
- case "dblclick":
112
- await locator.dblclick({ timeout });
113
- return null;
114
- case "fill":
115
- await locator.fill(String(actionArg ?? ""), { timeout });
116
- return null;
117
- case "type":
118
- await locator.pressSequentially(String(actionArg ?? ""), { timeout });
119
- return null;
120
- case "check":
121
- await locator.check({ timeout });
122
- return null;
123
- case "uncheck":
124
- await locator.uncheck({ timeout });
125
- return null;
126
- case "selectOption":
127
- await locator.selectOption(String(actionArg ?? ""), { timeout });
128
- return null;
129
- case "clear":
130
- await locator.clear({ timeout });
131
- return null;
132
- case "press":
133
- await locator.press(String(actionArg ?? ""), { timeout });
134
- return null;
135
- case "hover":
136
- await locator.hover({ timeout });
137
- return null;
138
- case "focus":
139
- await locator.focus({ timeout });
140
- return null;
141
- case "getText":
142
- return await locator.textContent({ timeout });
143
- case "getValue":
144
- return await locator.inputValue({ timeout });
145
- case "isVisible":
146
- return await locator.isVisible();
147
- case "isEnabled":
148
- return await locator.isEnabled();
149
- case "isChecked":
150
- return await locator.isChecked();
151
- case "count":
152
- return await locator.count();
153
- case "getAttribute":
154
- return await locator.getAttribute(String(actionArg ?? ""), { timeout });
155
- case "isDisabled":
156
- return await locator.isDisabled();
157
- case "isHidden":
158
- return await locator.isHidden();
159
- case "innerHTML":
160
- return await locator.innerHTML({ timeout });
161
- case "innerText":
162
- return await locator.innerText({ timeout });
163
- case "allTextContents":
164
- return await locator.allTextContents();
165
- case "allInnerTexts":
166
- return await locator.allInnerTexts();
167
- case "waitFor": {
168
- const opts = actionArg && typeof actionArg === "object" ? actionArg : {};
169
- await locator.waitFor({ state: opts.state, timeout: opts.timeout ?? timeout });
170
- return null;
171
- }
172
- case "boundingBox":
173
- return await locator.boundingBox({ timeout });
174
- default:
175
- throw new Error(`Unknown action: ${action}`);
176
- }
177
- }
178
- async function executeExpectAssertion(locator, matcher, expected, negated, timeout) {
179
- switch (matcher) {
180
- case "toBeVisible": {
181
- const isVisible = await locator.isVisible();
182
- if (negated) {
183
- if (isVisible)
184
- throw new Error("Expected element to not be visible, but it was visible");
185
- } else {
186
- if (!isVisible)
187
- throw new Error("Expected element to be visible, but it was not");
188
- }
189
- break;
190
- }
191
- case "toContainText": {
192
- const text = await locator.textContent({ timeout });
193
- let matches;
194
- let expectedDisplay;
195
- if (expected && typeof expected === "object" && expected.$regex) {
196
- const regex = new RegExp(expected.$regex, expected.$flags);
197
- matches = regex.test(text ?? "");
198
- expectedDisplay = String(regex);
199
- } else {
200
- matches = text?.includes(String(expected)) ?? false;
201
- expectedDisplay = String(expected);
202
- }
203
- if (negated) {
204
- if (matches)
205
- throw new Error(`Expected text to not contain ${expectedDisplay}, but got "${text}"`);
206
- } else {
207
- if (!matches)
208
- throw new Error(`Expected text to contain ${expectedDisplay}, but got "${text}"`);
209
- }
210
- break;
211
- }
212
- case "toHaveValue": {
213
- const value = await locator.inputValue({ timeout });
214
- const matches = value === String(expected);
215
- if (negated) {
216
- if (matches)
217
- throw new Error(`Expected value to not be "${expected}", but it was`);
218
- } else {
219
- if (!matches)
220
- throw new Error(`Expected value to be "${expected}", but got "${value}"`);
221
- }
222
- break;
223
- }
224
- case "toBeEnabled": {
225
- const isEnabled = await locator.isEnabled();
226
- if (negated) {
227
- if (isEnabled)
228
- throw new Error("Expected element to be disabled, but it was enabled");
229
- } else {
230
- if (!isEnabled)
231
- throw new Error("Expected element to be enabled, but it was disabled");
232
- }
233
- break;
234
- }
235
- case "toBeChecked": {
236
- const isChecked = await locator.isChecked();
237
- if (negated) {
238
- if (isChecked)
239
- throw new Error("Expected element to not be checked, but it was checked");
240
- } else {
241
- if (!isChecked)
242
- throw new Error("Expected element to be checked, but it was not");
243
- }
244
- break;
245
- }
246
- case "toHaveAttribute": {
247
- const { name, value } = expected;
248
- const actual = await locator.getAttribute(name, { timeout });
249
- if (value instanceof RegExp || value && typeof value === "object" && value.$regex) {
250
- const regex = value.$regex ? new RegExp(value.$regex, value.$flags) : value;
251
- const matches = regex.test(actual ?? "");
252
- if (negated) {
253
- if (matches)
254
- throw new Error(`Expected attribute "${name}" to not match ${regex}, but got "${actual}"`);
255
- } else {
256
- if (!matches)
257
- throw new Error(`Expected attribute "${name}" to match ${regex}, but got "${actual}"`);
258
- }
259
- } else {
260
- const matches = actual === String(value);
261
- if (negated) {
262
- if (matches)
263
- throw new Error(`Expected attribute "${name}" to not be "${value}", but it was`);
264
- } else {
265
- if (!matches)
266
- throw new Error(`Expected attribute "${name}" to be "${value}", but got "${actual}"`);
267
- }
268
- }
269
- break;
270
- }
271
- case "toHaveText": {
272
- const text = await locator.textContent({ timeout }) ?? "";
273
- let matches;
274
- let expectedDisplay;
275
- if (expected && typeof expected === "object" && expected.$regex) {
276
- const regex = new RegExp(expected.$regex, expected.$flags);
277
- matches = regex.test(text);
278
- expectedDisplay = String(regex);
279
- } else {
280
- matches = text === String(expected);
281
- expectedDisplay = JSON.stringify(expected);
282
- }
283
- if (negated) {
284
- if (matches)
285
- throw new Error(`Expected text to not be ${expectedDisplay}, but got "${text}"`);
286
- } else {
287
- if (!matches)
288
- throw new Error(`Expected text to be ${expectedDisplay}, but got "${text}"`);
289
- }
290
- break;
291
- }
292
- case "toHaveCount": {
293
- const count = await locator.count();
294
- const expectedCount = Number(expected);
295
- if (negated) {
296
- if (count === expectedCount)
297
- throw new Error(`Expected count to not be ${expectedCount}, but it was`);
298
- } else {
299
- if (count !== expectedCount)
300
- throw new Error(`Expected count to be ${expectedCount}, but got ${count}`);
301
- }
302
- break;
303
- }
304
- case "toBeHidden": {
305
- const isHidden = await locator.isHidden();
306
- if (negated) {
307
- if (isHidden)
308
- throw new Error("Expected element to not be hidden, but it was hidden");
309
- } else {
310
- if (!isHidden)
311
- throw new Error("Expected element to be hidden, but it was not");
312
- }
313
- break;
314
- }
315
- case "toBeDisabled": {
316
- const isDisabled = await locator.isDisabled();
317
- if (negated) {
318
- if (isDisabled)
319
- throw new Error("Expected element to not be disabled, but it was disabled");
320
- } else {
321
- if (!isDisabled)
322
- throw new Error("Expected element to be disabled, but it was not");
323
- }
324
- break;
325
- }
326
- case "toBeFocused": {
327
- const isFocused = await locator.evaluate((el) => document.activeElement === el).catch(() => false);
328
- if (negated) {
329
- if (isFocused)
330
- throw new Error("Expected element to not be focused, but it was focused");
331
- } else {
332
- if (!isFocused)
333
- throw new Error("Expected element to be focused, but it was not");
334
- }
335
- break;
336
- }
337
- case "toBeEmpty": {
338
- const text = await locator.textContent({ timeout });
339
- const value = await locator.inputValue({ timeout }).catch(() => null);
340
- const isEmpty = value !== null ? value === "" : (text ?? "") === "";
341
- if (negated) {
342
- if (isEmpty)
343
- throw new Error("Expected element to not be empty, but it was");
344
- } else {
345
- if (!isEmpty)
346
- throw new Error("Expected element to be empty, but it was not");
347
- }
348
- break;
349
- }
350
- default:
351
- throw new Error(`Unknown matcher: ${matcher}`);
352
- }
353
- }
354
- function createPlaywrightHandler(page, options) {
355
- const timeout = options?.timeout ?? 30000;
53
+ var import_types = require("./types.cjs");
54
+ var import_handler = require("./handler.cjs");
55
+ var import_handler2 = require("./handler.cjs");
56
+ function wrapHandlerWithPredicateSupport(handler, evaluatePredicate, defaultTimeout) {
356
57
  return async (op) => {
357
- try {
358
- switch (op.type) {
359
- case "goto": {
360
- const [url, waitUntil] = op.args;
361
- await page.goto(url, {
362
- timeout,
363
- waitUntil: waitUntil ?? "load"
364
- });
365
- return { ok: true };
366
- }
367
- case "reload":
368
- await page.reload({ timeout });
369
- return { ok: true };
370
- case "url":
371
- return { ok: true, value: page.url() };
372
- case "title":
373
- return { ok: true, value: await page.title() };
374
- case "content":
375
- return { ok: true, value: await page.content() };
376
- case "waitForSelector": {
377
- const [selector, optionsJson] = op.args;
378
- const opts = optionsJson ? JSON.parse(optionsJson) : {};
379
- await page.waitForSelector(selector, { timeout, ...opts });
380
- return { ok: true };
381
- }
382
- case "waitForTimeout": {
383
- const [ms] = op.args;
384
- await page.waitForTimeout(ms);
385
- return { ok: true };
386
- }
387
- case "waitForLoadState": {
388
- const [state] = op.args;
389
- await page.waitForLoadState(state ?? "load", { timeout });
390
- return { ok: true };
391
- }
392
- case "evaluate": {
393
- const [script, arg] = op.args;
394
- if (op.args.length > 1) {
395
- const fn = new Function("return (" + script + ")")();
396
- const result2 = await page.evaluate(fn, arg);
397
- return { ok: true, value: result2 };
398
- }
399
- const result = await page.evaluate(script);
400
- return { ok: true, value: result };
401
- }
402
- case "locatorAction": {
403
- const [selectorType, selectorValue, roleOptions, action, actionArg] = op.args;
404
- const locator = getLocator(page, selectorType, selectorValue, roleOptions);
405
- const result = await executeLocatorAction(locator, action, actionArg, timeout);
406
- return { ok: true, value: result };
407
- }
408
- case "expectLocator": {
409
- const [selectorType, selectorValue, roleOptions, matcher, expected, negated, customTimeout] = op.args;
410
- const locator = getLocator(page, selectorType, selectorValue, roleOptions);
411
- const effectiveTimeout = customTimeout ?? timeout;
412
- await executeExpectAssertion(locator, matcher, expected, negated ?? false, effectiveTimeout);
413
- return { ok: true };
414
- }
415
- case "request": {
416
- const [url, method, data, headers] = op.args;
417
- const requestOptions = {
418
- timeout
419
- };
420
- if (headers) {
421
- requestOptions.headers = headers;
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 } };
74
+ }
422
75
  }
423
- if (data !== undefined && data !== null) {
424
- requestOptions.data = data;
76
+ if (effectiveTimeout > 0 && Date.now() - startTime >= effectiveTimeout) {
77
+ return { ok: false, error: { name: "Error", message: `Timeout ${effectiveTimeout}ms exceeded waiting for URL` } };
425
78
  }
426
- const response = await page.request.fetch(url, {
427
- method,
428
- ...requestOptions
79
+ await new Promise((r) => setTimeout(r, pollInterval));
80
+ }
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
429
94
  });
430
- const text = await response.text();
431
- let json = null;
95
+ if (!finishResult.ok)
96
+ return finishResult;
97
+ const responseData = finishResult.value;
432
98
  try {
433
- json = JSON.parse(text);
434
- } catch {}
435
- return {
436
- ok: true,
437
- value: {
438
- status: response.status(),
439
- ok: response.ok(),
440
- headers: response.headers(),
441
- text,
442
- json,
443
- 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;
444
109
  }
445
- };
446
- }
447
- case "goBack": {
448
- const [waitUntil] = op.args;
449
- await page.goBack({
450
- timeout,
451
- waitUntil: waitUntil ?? "load"
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
452
123
  });
453
- return { ok: true };
124
+ if (!nextStartResult.ok)
125
+ return nextStartResult;
126
+ currentListenerId = nextStartResult.value.listenerId;
454
127
  }
455
- case "goForward": {
456
- const [waitUntil] = op.args;
457
- await page.goForward({
458
- timeout,
459
- waitUntil: waitUntil ?? "load"
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
460
141
  });
461
- return { ok: true };
462
- }
463
- case "waitForURL": {
464
- const [url, customTimeout, waitUntil] = op.args;
465
- await page.waitForURL(url, {
466
- timeout: customTimeout ?? timeout,
467
- waitUntil: waitUntil ?? undefined
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;
154
+ }
155
+ } catch (e) {
156
+ const error = e;
157
+ return { ok: false, error: { name: error.name, message: error.message } };
158
+ }
159
+ if (effectiveTimeout > 0 && Date.now() - startTime >= effectiveTimeout) {
160
+ return { ok: false, error: { name: "Error", message: `Timeout ${effectiveTimeout}ms exceeded waiting for request` } };
161
+ }
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
468
168
  });
469
- return { ok: true };
470
- }
471
- case "clearCookies": {
472
- await page.context().clearCookies();
473
- return { ok: true };
169
+ if (!nextStartResult.ok)
170
+ return nextStartResult;
171
+ currentListenerId = nextStartResult.value.listenerId;
474
172
  }
475
- default:
476
- return { ok: false, error: { name: "Error", message: `Unknown operation: ${op.type}` } };
477
173
  }
478
- } catch (err) {
479
- const error = err;
480
- return { ok: false, error: { name: error.name, message: error.message } };
174
+ default:
175
+ return handler(op);
481
176
  }
482
177
  };
483
178
  }
484
179
  async function setupPlaywright(context, options) {
485
180
  const timeout = options.timeout ?? 30000;
486
- const page = "page" in options ? options.page : undefined;
181
+ const explicitPage = "page" in options ? options.page : undefined;
487
182
  const handler = "handler" in options ? options.handler : undefined;
488
- const effectiveHandler = handler ?? (page ? createPlaywrightHandler(page, { timeout }) : undefined);
489
- if (!effectiveHandler) {
183
+ const handlerMetadata = handler ? import_handler2.getDefaultPlaywrightHandlerMetadata(handler) : undefined;
184
+ const page = explicitPage ?? handlerMetadata?.page;
185
+ const createPage = "createPage" in options ? options.createPage : undefined;
186
+ const createContext = "createContext" in options ? options.createContext : undefined;
187
+ const readFile = "readFile" in options ? options.readFile : undefined;
188
+ const writeFile = "writeFile" in options ? options.writeFile : undefined;
189
+ if (!handler && !page) {
490
190
  throw new Error("Either page or handler must be provided to setupPlaywright");
491
191
  }
492
192
  const browserConsoleLogs = [];
@@ -565,15 +265,10 @@ async function setupPlaywright(context, options) {
565
265
  page.on("response", responseHandler);
566
266
  page.on("console", consoleHandler);
567
267
  }
568
- global.setSync("__Playwright_handler_ref", new import_isolated_vm.default.Reference(async (opJson) => {
569
- const op = JSON.parse(opJson);
570
- const result = await effectiveHandler(op);
571
- return JSON.stringify(result);
572
- }));
573
268
  context.evalSync(`
574
269
  (function() {
575
- globalThis.__pw_invoke = async function(type, args) {
576
- const op = JSON.stringify({ type, args });
270
+ globalThis.__pw_invoke_sync = function(type, args, options) {
271
+ const op = JSON.stringify({ type, args, pageId: options?.pageId, contextId: options?.contextId });
577
272
  const resultJson = __Playwright_handler_ref.applySyncPromise(undefined, [op]);
578
273
  const result = JSON.parse(resultJson);
579
274
  if (result.ok) {
@@ -584,215 +279,607 @@ async function setupPlaywright(context, options) {
584
279
  throw error;
585
280
  }
586
281
  };
282
+ globalThis.__pw_invoke = async function(type, args, options) {
283
+ return globalThis.__pw_invoke_sync(type, args, options);
284
+ };
587
285
  })();
588
286
  `);
589
287
  context.evalSync(`
590
288
  (function() {
591
- let __pw_currentUrl = '';
592
- globalThis.page = {
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
+ };
308
+ })();
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
+ }));
339
+ context.evalSync(`
340
+ (function() {
341
+ // IsolatePage class - represents a page with a specific pageId
342
+ class IsolatePage {
343
+ #pageId; #contextId;
344
+ constructor(pageId, contextId) {
345
+ this.#pageId = pageId;
346
+ this.#contextId = contextId;
347
+ }
348
+ get __isPage() { return true; }
349
+ get __pageId() { return this.#pageId; }
350
+ get __contextId() { return this.#contextId; }
351
+
593
352
  async goto(url, options) {
594
- const result = await __pw_invoke("goto", [url, options?.waitUntil || null]);
595
- const resolvedUrl = await __pw_invoke("url", []);
596
- __pw_currentUrl = resolvedUrl || url;
597
- return result;
598
- },
353
+ await __pw_invoke("goto", [url, options?.waitUntil || null], { pageId: this.#pageId });
354
+ }
599
355
  async reload() {
600
- const result = await __pw_invoke("reload", []);
601
- const resolvedUrl = await __pw_invoke("url", []);
602
- if (resolvedUrl) __pw_currentUrl = resolvedUrl;
603
- return result;
604
- },
605
- url() {
606
- return __pw_currentUrl;
607
- },
608
- async title() {
609
- return __pw_invoke("title", []);
610
- },
611
- async content() {
612
- return __pw_invoke("content", []);
613
- },
356
+ await __pw_invoke("reload", [], { pageId: this.#pageId });
357
+ }
358
+ url() { return __pw_invoke_sync("url", [], { pageId: this.#pageId }); }
359
+ async title() { return __pw_invoke("title", [], { pageId: this.#pageId }); }
360
+ async content() { return __pw_invoke("content", [], { pageId: this.#pageId }); }
614
361
  async waitForSelector(selector, options) {
615
- return __pw_invoke("waitForSelector", [selector, options ? JSON.stringify(options) : null]);
616
- },
617
- async waitForTimeout(ms) {
618
- return __pw_invoke("waitForTimeout", [ms]);
619
- },
620
- async waitForLoadState(state) {
621
- return __pw_invoke("waitForLoadState", [state || null]);
622
- },
362
+ return __pw_invoke("waitForSelector", [selector, options ? JSON.stringify(options) : null], { pageId: this.#pageId });
363
+ }
364
+ async waitForTimeout(ms) { return __pw_invoke("waitForTimeout", [ms], { pageId: this.#pageId }); }
365
+ async waitForLoadState(state) { return __pw_invoke("waitForLoadState", [state || null], { pageId: this.#pageId }); }
623
366
  async evaluate(script, arg) {
624
367
  const hasArg = arguments.length > 1;
625
368
  if (hasArg) {
626
369
  const serialized = typeof script === "function" ? script.toString() : script;
627
- return __pw_invoke("evaluate", [serialized, arg]);
370
+ return __pw_invoke("evaluate", [serialized, arg], { pageId: this.#pageId });
628
371
  }
629
372
  const serialized = typeof script === "function" ? "(" + script.toString() + ")()" : script;
630
- return __pw_invoke("evaluate", [serialized]);
631
- },
632
- locator(selector) { return new Locator("css", selector, null); },
373
+ return __pw_invoke("evaluate", [serialized], { pageId: this.#pageId });
374
+ }
375
+ locator(selector) { return new Locator("css", selector, null, this.#pageId); }
633
376
  getByRole(role, options) {
634
377
  if (options) {
635
378
  const serialized = { ...options };
636
- // Use duck-typing RegExp detection (instanceof fails across isolated-vm boundary)
637
379
  const name = options.name;
638
380
  if (name && typeof name === 'object' && typeof name.source === 'string' && typeof name.flags === 'string') {
639
381
  serialized.name = { $regex: name.source, $flags: name.flags };
640
382
  }
641
- return new Locator("role", role, JSON.stringify(serialized));
383
+ return new Locator("role", role, JSON.stringify(serialized), this.#pageId);
642
384
  }
643
- return new Locator("role", role, null);
644
- },
645
- getByText(text) { return new Locator("text", text, null); },
646
- getByLabel(label) { return new Locator("label", label, null); },
647
- getByPlaceholder(p) { return new Locator("placeholder", p, null); },
648
- getByTestId(id) { return new Locator("testId", id, null); },
385
+ return new Locator("role", role, null, this.#pageId);
386
+ }
387
+ getByText(text) { return new Locator("text", text, null, this.#pageId); }
388
+ getByLabel(label) { return new Locator("label", label, null, this.#pageId); }
389
+ getByPlaceholder(p) { return new Locator("placeholder", p, null, this.#pageId); }
390
+ getByTestId(id) { return new Locator("testId", id, null, this.#pageId); }
391
+ getByAltText(alt) { return new Locator("altText", alt, null, this.#pageId); }
392
+ getByTitle(title) { return new Locator("title", title, null, this.#pageId); }
393
+ frameLocator(selector) {
394
+ const pageId = this.#pageId;
395
+ return {
396
+ locator(innerSelector) { return new Locator("frame", JSON.stringify([["css", selector, null], ["css", innerSelector, null]]), null, pageId); },
397
+ getByRole(role, options) { return new Locator("frame", JSON.stringify([["css", selector, null], ["role", role, options ? JSON.stringify(options) : null]]), null, pageId); },
398
+ getByText(text) { return new Locator("frame", JSON.stringify([["css", selector, null], ["text", text, null]]), null, pageId); },
399
+ getByLabel(label) { return new Locator("frame", JSON.stringify([["css", selector, null], ["label", label, null]]), null, pageId); },
400
+ getByPlaceholder(placeholder) { return new Locator("frame", JSON.stringify([["css", selector, null], ["placeholder", placeholder, null]]), null, pageId); },
401
+ getByTestId(testId) { return new Locator("frame", JSON.stringify([["css", selector, null], ["testId", testId, null]]), null, pageId); },
402
+ getByAltText(alt) { return new Locator("frame", JSON.stringify([["css", selector, null], ["altText", alt, null]]), null, pageId); },
403
+ getByTitle(title) { return new Locator("frame", JSON.stringify([["css", selector, null], ["title", title, null]]), null, pageId); },
404
+ };
405
+ }
649
406
  async goBack(options) {
650
- await __pw_invoke("goBack", [options?.waitUntil || null]);
651
- const resolvedUrl = await __pw_invoke("url", []);
652
- if (resolvedUrl) __pw_currentUrl = resolvedUrl;
653
- },
407
+ await __pw_invoke("goBack", [options?.waitUntil || null], { pageId: this.#pageId });
408
+ }
654
409
  async goForward(options) {
655
- await __pw_invoke("goForward", [options?.waitUntil || null]);
656
- const resolvedUrl = await __pw_invoke("url", []);
657
- if (resolvedUrl) __pw_currentUrl = resolvedUrl;
658
- },
410
+ await __pw_invoke("goForward", [options?.waitUntil || null], { pageId: this.#pageId });
411
+ }
659
412
  async waitForURL(url, options) {
660
- return __pw_invoke("waitForURL", [url, options?.timeout || null, options?.waitUntil || null]);
661
- },
662
- context() {
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;
429
+ }
430
+ return __pw_invoke("waitForURL", [serializedUrl, options?.timeout || null, options?.waitUntil || null], { pageId: this.#pageId });
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;
663
473
  return {
664
- async clearCookies() {
665
- return __pw_invoke("clearCookies", []);
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); }
666
479
  }
667
480
  };
668
- },
669
- async click(selector) { return this.locator(selector).click(); },
670
- async fill(selector, value) { return this.locator(selector).fill(value); },
671
- request: {
672
- async fetch(url, options) {
673
- const result = await __pw_invoke("request", [url, options?.method || "GET", options?.data, options?.headers]);
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;
674
502
  return {
675
- status: () => result.status,
676
- ok: () => result.ok,
677
- headers: () => result.headers,
678
- json: async () => result.json,
679
- text: async () => result.text,
680
- body: async () => result.body,
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
+ }
681
513
  };
682
- },
683
- async get(url, options) {
684
- return this.fetch(url, { ...options, method: "GET" });
685
- },
686
- async post(url, options) {
687
- return this.fetch(url, { ...options, method: "POST" });
688
- },
689
- async put(url, options) {
690
- return this.fetch(url, { ...options, method: "PUT" });
691
- },
692
- async delete(url, options) {
693
- return this.fetch(url, { ...options, method: "DELETE" });
694
- },
695
- },
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
+ }
541
+ context() {
542
+ const contextId = this.#contextId;
543
+ return new IsolateContext(contextId);
544
+ }
545
+ async click(selector) { return this.locator(selector).click(); }
546
+ async fill(selector, value) { return this.locator(selector).fill(value); }
547
+ async textContent(selector) { return this.locator(selector).textContent(); }
548
+ async innerText(selector) { return this.locator(selector).innerText(); }
549
+ async innerHTML(selector) { return this.locator(selector).innerHTML(); }
550
+ async getAttribute(selector, name) { return this.locator(selector).getAttribute(name); }
551
+ async inputValue(selector) { return this.locator(selector).inputValue(); }
552
+ async isVisible(selector) { return this.locator(selector).isVisible(); }
553
+ async isEnabled(selector) { return this.locator(selector).isEnabled(); }
554
+ async isChecked(selector) { return this.locator(selector).isChecked(); }
555
+ async isHidden(selector) { return this.locator(selector).isHidden(); }
556
+ async isDisabled(selector) { return this.locator(selector).isDisabled(); }
557
+ async screenshot(options) { return __pw_invoke("screenshot", [options || {}], { pageId: this.#pageId }); }
558
+ async setViewportSize(size) { return __pw_invoke("setViewportSize", [size], { pageId: this.#pageId }); }
559
+ async viewportSize() { return __pw_invoke("viewportSize", [], { pageId: this.#pageId }); }
560
+ async emulateMedia(options) { return __pw_invoke("emulateMedia", [options], { pageId: this.#pageId }); }
561
+ async setExtraHTTPHeaders(headers) { return __pw_invoke("setExtraHTTPHeaders", [headers], { pageId: this.#pageId }); }
562
+ async bringToFront() { return __pw_invoke("bringToFront", [], { pageId: this.#pageId }); }
563
+ async close() { return __pw_invoke("close", [], { pageId: this.#pageId }); }
564
+ async isClosed() { return __pw_invoke("isClosed", [], { pageId: this.#pageId }); }
565
+ async pdf(options) { return __pw_invoke("pdf", [options || {}], { pageId: this.#pageId }); }
566
+ async pause() { return __pw_invoke("pause", [], { pageId: this.#pageId }); }
567
+ async frames() { return __pw_invoke("frames", [], { pageId: this.#pageId }); }
568
+ async mainFrame() { return __pw_invoke("mainFrame", [], { pageId: this.#pageId }); }
569
+ get keyboard() {
570
+ const pageId = this.#pageId;
571
+ return {
572
+ async type(text, options) { return __pw_invoke("keyboardType", [text, options], { pageId }); },
573
+ async press(key, options) { return __pw_invoke("keyboardPress", [key, options], { pageId }); },
574
+ async down(key) { return __pw_invoke("keyboardDown", [key], { pageId }); },
575
+ async up(key) { return __pw_invoke("keyboardUp", [key], { pageId }); },
576
+ async insertText(text) { return __pw_invoke("keyboardInsertText", [text], { pageId }); }
577
+ };
578
+ }
579
+ get mouse() {
580
+ const pageId = this.#pageId;
581
+ return {
582
+ async move(x, y, options) { return __pw_invoke("mouseMove", [x, y, options], { pageId }); },
583
+ async click(x, y, options) { return __pw_invoke("mouseClick", [x, y, options], { pageId }); },
584
+ async down(options) { return __pw_invoke("mouseDown", [options], { pageId }); },
585
+ async up(options) { return __pw_invoke("mouseUp", [options], { pageId }); },
586
+ async wheel(deltaX, deltaY) { return __pw_invoke("mouseWheel", [deltaX, deltaY], { pageId }); }
587
+ };
588
+ }
589
+ get request() {
590
+ const pageId = this.#pageId;
591
+ return {
592
+ async fetch(url, options) {
593
+ const result = await __pw_invoke("request", [url, options?.method || "GET", options?.data, options?.headers], { pageId });
594
+ return {
595
+ status: () => result.status,
596
+ ok: () => result.ok,
597
+ headers: () => result.headers,
598
+ json: async () => result.json,
599
+ text: async () => result.text,
600
+ body: async () => result.body,
601
+ };
602
+ },
603
+ async get(url, options) { return this.fetch(url, { ...options, method: "GET" }); },
604
+ async post(url, options) { return this.fetch(url, { ...options, method: "POST" }); },
605
+ async put(url, options) { return this.fetch(url, { ...options, method: "PUT" }); },
606
+ async delete(url, options) { return this.fetch(url, { ...options, method: "DELETE" }); },
607
+ };
608
+ }
609
+ }
610
+ globalThis.IsolatePage = IsolatePage;
611
+
612
+ // IsolateContext class - represents a browser context with a specific contextId
613
+ class IsolateContext {
614
+ #contextId;
615
+ constructor(contextId) { this.#contextId = contextId; }
616
+ get __contextId() { return this.#contextId; }
617
+
618
+ async newPage() {
619
+ const result = await __pw_invoke("newPage", [], { contextId: this.#contextId });
620
+ return new IsolatePage(result.pageId, this.#contextId);
621
+ }
622
+ async close() { return __pw_invoke("closeContext", [], { contextId: this.#contextId }); }
623
+ async clearCookies() { return __pw_invoke("clearCookies", [], { contextId: this.#contextId }); }
624
+ async addCookies(cookies) { return __pw_invoke("addCookies", [cookies], { contextId: this.#contextId }); }
625
+ async cookies(urls) { return __pw_invoke("cookies", [urls], { contextId: this.#contextId }); }
626
+ }
627
+ globalThis.IsolateContext = IsolateContext;
628
+
629
+ // browser global - for creating new contexts
630
+ globalThis.browser = {
631
+ async newContext(options) {
632
+ const result = await __pw_invoke("newContext", [options || null]);
633
+ return new IsolateContext(result.contextId);
634
+ }
696
635
  };
636
+
637
+ // context global - represents the default context
638
+ globalThis.context = new IsolateContext("ctx_0");
639
+
640
+ // page global - represents the default page
641
+ globalThis.page = new IsolatePage("page_0", "ctx_0");
697
642
  })();
698
643
  `);
699
644
  context.evalSync(`
700
645
  (function() {
646
+ // Helper to serialize options including RegExp
647
+ function serializeOptions(options) {
648
+ if (!options) return null;
649
+ const serialized = { ...options };
650
+ if (options.name && typeof options.name === 'object' && typeof options.name.source === 'string' && typeof options.name.flags === 'string') {
651
+ serialized.name = { $regex: options.name.source, $flags: options.name.flags };
652
+ }
653
+ return JSON.stringify(serialized);
654
+ }
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
+
701
736
  class Locator {
702
- #type; #value; #options;
703
- constructor(type, value, options) {
737
+ #type; #value; #options; #pageId;
738
+ constructor(type, value, options, pageId) {
704
739
  this.#type = type;
705
740
  this.#value = value;
706
741
  this.#options = options;
742
+ this.#pageId = pageId || "page_0";
707
743
  }
708
744
 
709
745
  _getInfo() { return [this.#type, this.#value, this.#options]; }
746
+ _getPageId() { return this.#pageId; }
747
+
748
+ // Helper to create a chained locator
749
+ _chain(childType, childValue, childOptions) {
750
+ const parentInfo = this._getInfo();
751
+ const childInfo = [childType, childValue, childOptions];
752
+ return new Locator("chained", JSON.stringify([parentInfo, childInfo]), null, this.#pageId);
753
+ }
710
754
 
711
755
  async click() {
712
- return __pw_invoke("locatorAction", [...this._getInfo(), "click", null]);
756
+ return __pw_invoke("locatorAction", [...this._getInfo(), "click", null], { pageId: this.#pageId });
713
757
  }
714
758
  async dblclick() {
715
- return __pw_invoke("locatorAction", [...this._getInfo(), "dblclick", null]);
759
+ return __pw_invoke("locatorAction", [...this._getInfo(), "dblclick", null], { pageId: this.#pageId });
716
760
  }
717
761
  async fill(text) {
718
- return __pw_invoke("locatorAction", [...this._getInfo(), "fill", text]);
762
+ return __pw_invoke("locatorAction", [...this._getInfo(), "fill", text], { pageId: this.#pageId });
719
763
  }
720
764
  async type(text) {
721
- return __pw_invoke("locatorAction", [...this._getInfo(), "type", text]);
765
+ return __pw_invoke("locatorAction", [...this._getInfo(), "type", text], { pageId: this.#pageId });
722
766
  }
723
767
  async check() {
724
- return __pw_invoke("locatorAction", [...this._getInfo(), "check", null]);
768
+ return __pw_invoke("locatorAction", [...this._getInfo(), "check", null], { pageId: this.#pageId });
725
769
  }
726
770
  async uncheck() {
727
- return __pw_invoke("locatorAction", [...this._getInfo(), "uncheck", null]);
771
+ return __pw_invoke("locatorAction", [...this._getInfo(), "uncheck", null], { pageId: this.#pageId });
728
772
  }
729
773
  async selectOption(value) {
730
- return __pw_invoke("locatorAction", [...this._getInfo(), "selectOption", value]);
774
+ return __pw_invoke("locatorAction", [...this._getInfo(), "selectOption", value], { pageId: this.#pageId });
731
775
  }
732
776
  async clear() {
733
- return __pw_invoke("locatorAction", [...this._getInfo(), "clear", null]);
777
+ return __pw_invoke("locatorAction", [...this._getInfo(), "clear", null], { pageId: this.#pageId });
734
778
  }
735
779
  async press(key) {
736
- return __pw_invoke("locatorAction", [...this._getInfo(), "press", key]);
780
+ return __pw_invoke("locatorAction", [...this._getInfo(), "press", key], { pageId: this.#pageId });
737
781
  }
738
782
  async hover() {
739
- return __pw_invoke("locatorAction", [...this._getInfo(), "hover", null]);
783
+ return __pw_invoke("locatorAction", [...this._getInfo(), "hover", null], { pageId: this.#pageId });
740
784
  }
741
785
  async focus() {
742
- return __pw_invoke("locatorAction", [...this._getInfo(), "focus", null]);
786
+ return __pw_invoke("locatorAction", [...this._getInfo(), "focus", null], { pageId: this.#pageId });
743
787
  }
744
788
  async textContent() {
745
- return __pw_invoke("locatorAction", [...this._getInfo(), "getText", null]);
789
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getText", null], { pageId: this.#pageId });
746
790
  }
747
791
  async inputValue() {
748
- return __pw_invoke("locatorAction", [...this._getInfo(), "getValue", null]);
792
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getValue", null], { pageId: this.#pageId });
749
793
  }
750
794
  async isVisible() {
751
- return __pw_invoke("locatorAction", [...this._getInfo(), "isVisible", null]);
795
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isVisible", null], { pageId: this.#pageId });
752
796
  }
753
797
  async isEnabled() {
754
- return __pw_invoke("locatorAction", [...this._getInfo(), "isEnabled", null]);
798
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isEnabled", null], { pageId: this.#pageId });
755
799
  }
756
800
  async isChecked() {
757
- return __pw_invoke("locatorAction", [...this._getInfo(), "isChecked", null]);
801
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isChecked", null], { pageId: this.#pageId });
758
802
  }
759
803
  async count() {
760
- return __pw_invoke("locatorAction", [...this._getInfo(), "count", null]);
804
+ return __pw_invoke("locatorAction", [...this._getInfo(), "count", null], { pageId: this.#pageId });
761
805
  }
762
806
  async getAttribute(name) {
763
- return __pw_invoke("locatorAction", [...this._getInfo(), "getAttribute", name]);
807
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getAttribute", name], { pageId: this.#pageId });
764
808
  }
765
809
  async isDisabled() {
766
- return __pw_invoke("locatorAction", [...this._getInfo(), "isDisabled", null]);
810
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isDisabled", null], { pageId: this.#pageId });
767
811
  }
768
812
  async isHidden() {
769
- return __pw_invoke("locatorAction", [...this._getInfo(), "isHidden", null]);
813
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isHidden", null], { pageId: this.#pageId });
770
814
  }
771
815
  async innerHTML() {
772
- return __pw_invoke("locatorAction", [...this._getInfo(), "innerHTML", null]);
816
+ return __pw_invoke("locatorAction", [...this._getInfo(), "innerHTML", null], { pageId: this.#pageId });
773
817
  }
774
818
  async innerText() {
775
- return __pw_invoke("locatorAction", [...this._getInfo(), "innerText", null]);
819
+ return __pw_invoke("locatorAction", [...this._getInfo(), "innerText", null], { pageId: this.#pageId });
776
820
  }
777
821
  async allTextContents() {
778
- return __pw_invoke("locatorAction", [...this._getInfo(), "allTextContents", null]);
822
+ return __pw_invoke("locatorAction", [...this._getInfo(), "allTextContents", null], { pageId: this.#pageId });
779
823
  }
780
824
  async allInnerTexts() {
781
- return __pw_invoke("locatorAction", [...this._getInfo(), "allInnerTexts", null]);
825
+ return __pw_invoke("locatorAction", [...this._getInfo(), "allInnerTexts", null], { pageId: this.#pageId });
782
826
  }
783
827
  async waitFor(options) {
784
- return __pw_invoke("locatorAction", [...this._getInfo(), "waitFor", options || {}]);
828
+ return __pw_invoke("locatorAction", [...this._getInfo(), "waitFor", options || {}], { pageId: this.#pageId });
785
829
  }
786
830
  async boundingBox() {
787
- return __pw_invoke("locatorAction", [...this._getInfo(), "boundingBox", null]);
831
+ return __pw_invoke("locatorAction", [...this._getInfo(), "boundingBox", null], { pageId: this.#pageId });
832
+ }
833
+ async setInputFiles(files) {
834
+ const serializedFiles = normalizeSetInputFilesArg(files);
835
+ return __pw_invoke("locatorAction", [...this._getInfo(), "setInputFiles", serializedFiles], { pageId: this.#pageId });
836
+ }
837
+ async screenshot(options) {
838
+ const base64 = await __pw_invoke("locatorAction", [...this._getInfo(), "screenshot", options || {}], { pageId: this.#pageId });
839
+ return base64;
840
+ }
841
+ async dragTo(target) {
842
+ const targetInfo = target._getInfo();
843
+ return __pw_invoke("locatorAction", [...this._getInfo(), "dragTo", targetInfo], { pageId: this.#pageId });
844
+ }
845
+ async scrollIntoViewIfNeeded() {
846
+ return __pw_invoke("locatorAction", [...this._getInfo(), "scrollIntoViewIfNeeded", null], { pageId: this.#pageId });
847
+ }
848
+ async highlight() {
849
+ return __pw_invoke("locatorAction", [...this._getInfo(), "highlight", null], { pageId: this.#pageId });
850
+ }
851
+ async evaluate(fn, arg) {
852
+ const fnString = typeof fn === 'function' ? fn.toString() : fn;
853
+ return __pw_invoke("locatorAction", [...this._getInfo(), "evaluate", [fnString, arg]], { pageId: this.#pageId });
854
+ }
855
+ async evaluateAll(fn, arg) {
856
+ const fnString = typeof fn === 'function' ? fn.toString() : fn;
857
+ return __pw_invoke("locatorAction", [...this._getInfo(), "evaluateAll", [fnString, arg]], { pageId: this.#pageId });
788
858
  }
789
859
  locator(selector) {
790
- const parentSelector = this.#type === 'css' ? this.#value : null;
791
- if (parentSelector) {
792
- return new Locator("css", parentSelector + " " + selector, this.#options);
793
- }
794
- // For non-css locators, use css with the combined approach
795
- return new Locator("css", selector, this.#options);
860
+ return this._chain("css", selector, null);
861
+ }
862
+ // Chaining: getBy* methods within a locator
863
+ getByRole(role, options) {
864
+ return this._chain("role", role, serializeOptions(options));
865
+ }
866
+ getByText(text) {
867
+ return this._chain("text", text, null);
868
+ }
869
+ getByLabel(label) {
870
+ return this._chain("label", label, null);
871
+ }
872
+ getByPlaceholder(placeholder) {
873
+ return this._chain("placeholder", placeholder, null);
874
+ }
875
+ getByTestId(testId) {
876
+ return this._chain("testId", testId, null);
877
+ }
878
+ getByAltText(altText) {
879
+ return this._chain("altText", altText, null);
880
+ }
881
+ getByTitle(title) {
882
+ return this._chain("title", title, null);
796
883
  }
797
884
  async all() {
798
885
  const n = await this.count();
@@ -804,7 +891,7 @@ async function setupPlaywright(context, options) {
804
891
  }
805
892
  nth(index) {
806
893
  const existingOpts = this.#options ? JSON.parse(this.#options) : {};
807
- return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, nth: index }));
894
+ return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, nth: index }), this.#pageId);
808
895
  }
809
896
  first() {
810
897
  return this.nth(0);
@@ -824,13 +911,28 @@ async function setupPlaywright(context, options) {
824
911
  if (hasNotText && typeof hasNotText === 'object' && typeof hasNotText.source === 'string' && typeof hasNotText.flags === 'string') {
825
912
  serializedFilter.hasNotText = { $regex: hasNotText.source, $flags: hasNotText.flags };
826
913
  }
827
- return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, filter: serializedFilter }));
914
+ // Serialize has/hasNot locators using duck-typing
915
+ const has = options.has;
916
+ if (has && typeof has === 'object' && typeof has._getInfo === 'function') {
917
+ serializedFilter.has = { $locator: has._getInfo() };
918
+ }
919
+ const hasNot = options.hasNot;
920
+ if (hasNot && typeof hasNot === 'object' && typeof hasNot._getInfo === 'function') {
921
+ serializedFilter.hasNot = { $locator: hasNot._getInfo() };
922
+ }
923
+ return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, filter: serializedFilter }), this.#pageId);
828
924
  }
829
925
  or(other) {
830
926
  // Create a composite locator that matches either this or other
831
927
  const thisInfo = this._getInfo();
832
928
  const otherInfo = other._getInfo();
833
- return new Locator("or", JSON.stringify([thisInfo, otherInfo]), null);
929
+ return new Locator("or", JSON.stringify([thisInfo, otherInfo]), null, this.#pageId);
930
+ }
931
+ and(other) {
932
+ // Create a composite locator that matches both this and other
933
+ const thisInfo = this._getInfo();
934
+ const otherInfo = other._getInfo();
935
+ return new Locator("and", JSON.stringify([thisInfo, otherInfo]), null, this.#pageId);
834
936
  }
835
937
  }
836
938
  globalThis.Locator = Locator;
@@ -841,84 +943,157 @@ async function setupPlaywright(context, options) {
841
943
  // Helper to create locator matchers
842
944
  function createLocatorMatchers(locator, baseMatchers) {
843
945
  const info = locator._getInfo();
946
+ const pageId = locator._getPageId ? locator._getPageId() : "page_0";
947
+
948
+ // Helper for serializing regex values
949
+ function serializeExpected(expected) {
950
+ if (expected instanceof RegExp) {
951
+ return { $regex: expected.source, $flags: expected.flags };
952
+ }
953
+ return expected;
954
+ }
844
955
 
845
956
  const locatorMatchers = {
846
957
  async toBeVisible(options) {
847
- return __pw_invoke("expectLocator", [...info, "toBeVisible", null, false, options?.timeout]);
958
+ return __pw_invoke("expectLocator", [...info, "toBeVisible", null, false, options?.timeout], { pageId });
848
959
  },
849
960
  async toContainText(expected, options) {
850
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
851
- return __pw_invoke("expectLocator", [...info, "toContainText", serialized, false, options?.timeout]);
961
+ return __pw_invoke("expectLocator", [...info, "toContainText", serializeExpected(expected), false, options?.timeout], { pageId });
852
962
  },
853
963
  async toHaveValue(expected, options) {
854
- return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, false, options?.timeout]);
964
+ return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, false, options?.timeout], { pageId });
855
965
  },
856
966
  async toBeEnabled(options) {
857
- return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, false, options?.timeout]);
967
+ return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, false, options?.timeout], { pageId });
858
968
  },
859
969
  async toBeChecked(options) {
860
- return __pw_invoke("expectLocator", [...info, "toBeChecked", null, false, options?.timeout]);
970
+ return __pw_invoke("expectLocator", [...info, "toBeChecked", null, false, options?.timeout], { pageId });
861
971
  },
862
972
  async toHaveAttribute(name, value, options) {
863
- return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value }, false, options?.timeout]);
973
+ return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value: serializeExpected(value) }, false, options?.timeout], { pageId });
864
974
  },
865
975
  async toHaveText(expected, options) {
866
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
867
- return __pw_invoke("expectLocator", [...info, "toHaveText", serialized, false, options?.timeout]);
976
+ return __pw_invoke("expectLocator", [...info, "toHaveText", serializeExpected(expected), false, options?.timeout], { pageId });
868
977
  },
869
978
  async toHaveCount(count, options) {
870
- return __pw_invoke("expectLocator", [...info, "toHaveCount", count, false, options?.timeout]);
979
+ return __pw_invoke("expectLocator", [...info, "toHaveCount", count, false, options?.timeout], { pageId });
871
980
  },
872
981
  async toBeHidden(options) {
873
- return __pw_invoke("expectLocator", [...info, "toBeHidden", null, false, options?.timeout]);
982
+ return __pw_invoke("expectLocator", [...info, "toBeHidden", null, false, options?.timeout], { pageId });
874
983
  },
875
984
  async toBeDisabled(options) {
876
- return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, false, options?.timeout]);
985
+ return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, false, options?.timeout], { pageId });
877
986
  },
878
987
  async toBeFocused(options) {
879
- return __pw_invoke("expectLocator", [...info, "toBeFocused", null, false, options?.timeout]);
988
+ return __pw_invoke("expectLocator", [...info, "toBeFocused", null, false, options?.timeout], { pageId });
880
989
  },
881
990
  async toBeEmpty(options) {
882
- return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, false, options?.timeout]);
991
+ return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, false, options?.timeout], { pageId });
992
+ },
993
+ // New matchers
994
+ async toBeAttached(options) {
995
+ return __pw_invoke("expectLocator", [...info, "toBeAttached", null, false, options?.timeout], { pageId });
996
+ },
997
+ async toBeEditable(options) {
998
+ return __pw_invoke("expectLocator", [...info, "toBeEditable", null, false, options?.timeout], { pageId });
999
+ },
1000
+ async toHaveClass(expected, options) {
1001
+ return __pw_invoke("expectLocator", [...info, "toHaveClass", serializeExpected(expected), false, options?.timeout], { pageId });
1002
+ },
1003
+ async toContainClass(expected, options) {
1004
+ return __pw_invoke("expectLocator", [...info, "toContainClass", expected, false, options?.timeout], { pageId });
1005
+ },
1006
+ async toHaveId(expected, options) {
1007
+ return __pw_invoke("expectLocator", [...info, "toHaveId", expected, false, options?.timeout], { pageId });
1008
+ },
1009
+ async toBeInViewport(options) {
1010
+ return __pw_invoke("expectLocator", [...info, "toBeInViewport", null, false, options?.timeout], { pageId });
1011
+ },
1012
+ async toHaveCSS(name, value, options) {
1013
+ return __pw_invoke("expectLocator", [...info, "toHaveCSS", { name, value: serializeExpected(value) }, false, options?.timeout], { pageId });
1014
+ },
1015
+ async toHaveJSProperty(name, value, options) {
1016
+ return __pw_invoke("expectLocator", [...info, "toHaveJSProperty", { name, value }, false, options?.timeout], { pageId });
1017
+ },
1018
+ async toHaveAccessibleName(expected, options) {
1019
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleName", serializeExpected(expected), false, options?.timeout], { pageId });
1020
+ },
1021
+ async toHaveAccessibleDescription(expected, options) {
1022
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleDescription", serializeExpected(expected), false, options?.timeout], { pageId });
1023
+ },
1024
+ async toHaveRole(expected, options) {
1025
+ return __pw_invoke("expectLocator", [...info, "toHaveRole", expected, false, options?.timeout], { pageId });
883
1026
  },
884
1027
  not: {
885
1028
  async toBeVisible(options) {
886
- return __pw_invoke("expectLocator", [...info, "toBeVisible", null, true, options?.timeout]);
1029
+ return __pw_invoke("expectLocator", [...info, "toBeVisible", null, true, options?.timeout], { pageId });
887
1030
  },
888
1031
  async toContainText(expected, options) {
889
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
890
- return __pw_invoke("expectLocator", [...info, "toContainText", serialized, true, options?.timeout]);
1032
+ return __pw_invoke("expectLocator", [...info, "toContainText", serializeExpected(expected), true, options?.timeout], { pageId });
891
1033
  },
892
1034
  async toHaveValue(expected, options) {
893
- return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, true, options?.timeout]);
1035
+ return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, true, options?.timeout], { pageId });
894
1036
  },
895
1037
  async toBeEnabled(options) {
896
- return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, true, options?.timeout]);
1038
+ return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, true, options?.timeout], { pageId });
897
1039
  },
898
1040
  async toBeChecked(options) {
899
- return __pw_invoke("expectLocator", [...info, "toBeChecked", null, true, options?.timeout]);
1041
+ return __pw_invoke("expectLocator", [...info, "toBeChecked", null, true, options?.timeout], { pageId });
900
1042
  },
901
1043
  async toHaveAttribute(name, value, options) {
902
- return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value }, true, options?.timeout]);
1044
+ return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value: serializeExpected(value) }, true, options?.timeout], { pageId });
903
1045
  },
904
1046
  async toHaveText(expected, options) {
905
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
906
- return __pw_invoke("expectLocator", [...info, "toHaveText", serialized, true, options?.timeout]);
1047
+ return __pw_invoke("expectLocator", [...info, "toHaveText", serializeExpected(expected), true, options?.timeout], { pageId });
907
1048
  },
908
1049
  async toHaveCount(count, options) {
909
- return __pw_invoke("expectLocator", [...info, "toHaveCount", count, true, options?.timeout]);
1050
+ return __pw_invoke("expectLocator", [...info, "toHaveCount", count, true, options?.timeout], { pageId });
910
1051
  },
911
1052
  async toBeHidden(options) {
912
- return __pw_invoke("expectLocator", [...info, "toBeHidden", null, true, options?.timeout]);
1053
+ return __pw_invoke("expectLocator", [...info, "toBeHidden", null, true, options?.timeout], { pageId });
913
1054
  },
914
1055
  async toBeDisabled(options) {
915
- return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, true, options?.timeout]);
1056
+ return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, true, options?.timeout], { pageId });
916
1057
  },
917
1058
  async toBeFocused(options) {
918
- return __pw_invoke("expectLocator", [...info, "toBeFocused", null, true, options?.timeout]);
1059
+ return __pw_invoke("expectLocator", [...info, "toBeFocused", null, true, options?.timeout], { pageId });
919
1060
  },
920
1061
  async toBeEmpty(options) {
921
- return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, true, options?.timeout]);
1062
+ return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, true, options?.timeout], { pageId });
1063
+ },
1064
+ // New negated matchers
1065
+ async toBeAttached(options) {
1066
+ return __pw_invoke("expectLocator", [...info, "toBeAttached", null, true, options?.timeout], { pageId });
1067
+ },
1068
+ async toBeEditable(options) {
1069
+ return __pw_invoke("expectLocator", [...info, "toBeEditable", null, true, options?.timeout], { pageId });
1070
+ },
1071
+ async toHaveClass(expected, options) {
1072
+ return __pw_invoke("expectLocator", [...info, "toHaveClass", serializeExpected(expected), true, options?.timeout], { pageId });
1073
+ },
1074
+ async toContainClass(expected, options) {
1075
+ return __pw_invoke("expectLocator", [...info, "toContainClass", expected, true, options?.timeout], { pageId });
1076
+ },
1077
+ async toHaveId(expected, options) {
1078
+ return __pw_invoke("expectLocator", [...info, "toHaveId", expected, true, options?.timeout], { pageId });
1079
+ },
1080
+ async toBeInViewport(options) {
1081
+ return __pw_invoke("expectLocator", [...info, "toBeInViewport", null, true, options?.timeout], { pageId });
1082
+ },
1083
+ async toHaveCSS(name, value, options) {
1084
+ return __pw_invoke("expectLocator", [...info, "toHaveCSS", { name, value: serializeExpected(value) }, true, options?.timeout], { pageId });
1085
+ },
1086
+ async toHaveJSProperty(name, value, options) {
1087
+ return __pw_invoke("expectLocator", [...info, "toHaveJSProperty", { name, value }, true, options?.timeout], { pageId });
1088
+ },
1089
+ async toHaveAccessibleName(expected, options) {
1090
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleName", serializeExpected(expected), true, options?.timeout], { pageId });
1091
+ },
1092
+ async toHaveAccessibleDescription(expected, options) {
1093
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleDescription", serializeExpected(expected), true, options?.timeout], { pageId });
1094
+ },
1095
+ async toHaveRole(expected, options) {
1096
+ return __pw_invoke("expectLocator", [...info, "toHaveRole", expected, true, options?.timeout], { pageId });
922
1097
  },
923
1098
  }
924
1099
  };
@@ -934,6 +1109,44 @@ async function setupPlaywright(context, options) {
934
1109
  return locatorMatchers;
935
1110
  }
936
1111
 
1112
+ // Helper to create page matchers
1113
+ function createPageMatchers(page, baseMatchers) {
1114
+ const pageId = page.__pageId || "page_0";
1115
+
1116
+ function serializeExpected(expected) {
1117
+ if (expected instanceof RegExp) {
1118
+ return { $regex: expected.source, $flags: expected.flags };
1119
+ }
1120
+ return expected;
1121
+ }
1122
+
1123
+ const pageMatchers = {
1124
+ async toHaveURL(expected, options) {
1125
+ return __pw_invoke("expectPage", ["toHaveURL", serializeExpected(expected), false, options?.timeout], { pageId });
1126
+ },
1127
+ async toHaveTitle(expected, options) {
1128
+ return __pw_invoke("expectPage", ["toHaveTitle", serializeExpected(expected), false, options?.timeout], { pageId });
1129
+ },
1130
+ not: {
1131
+ async toHaveURL(expected, options) {
1132
+ return __pw_invoke("expectPage", ["toHaveURL", serializeExpected(expected), true, options?.timeout], { pageId });
1133
+ },
1134
+ async toHaveTitle(expected, options) {
1135
+ return __pw_invoke("expectPage", ["toHaveTitle", serializeExpected(expected), true, options?.timeout], { pageId });
1136
+ },
1137
+ }
1138
+ };
1139
+
1140
+ if (baseMatchers) {
1141
+ return {
1142
+ ...baseMatchers,
1143
+ ...pageMatchers,
1144
+ not: { ...baseMatchers.not, ...pageMatchers.not }
1145
+ };
1146
+ }
1147
+ return pageMatchers;
1148
+ }
1149
+
937
1150
  // Only extend expect if test-environment already defined it
938
1151
  if (typeof globalThis.expect === 'function') {
939
1152
  const originalExpect = globalThis.expect;
@@ -943,6 +1156,10 @@ async function setupPlaywright(context, options) {
943
1156
  if (actual && actual.constructor && actual.constructor.name === 'Locator') {
944
1157
  return createLocatorMatchers(actual, baseMatchers);
945
1158
  }
1159
+ // If actual is the page object (IsolatePage), add page-specific matchers
1160
+ if (actual && actual.__isPage === true) {
1161
+ return createPageMatchers(actual, baseMatchers);
1162
+ }
946
1163
  return baseMatchers;
947
1164
  };
948
1165
  }
@@ -977,4 +1194,4 @@ async function setupPlaywright(context, options) {
977
1194
  };
978
1195
  }
979
1196
 
980
- //# debugId=6C45722AECDACEA764756E2164756E21
1197
+ //# debugId=2C9A0105748F8D9364756E2164756E21