@ricsam/isolate-playwright 0.1.12 → 0.1.13

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.
@@ -1,5 +1,8 @@
1
1
  // packages/playwright/src/index.ts
2
2
  import ivm from "isolated-vm";
3
+ import {
4
+ DEFAULT_PLAYWRIGHT_HANDLER_META
5
+ } from "./types.mjs";
3
6
  function getLocator(page, selectorType, selectorValue, optionsJson) {
4
7
  const options = optionsJson ? JSON.parse(optionsJson) : undefined;
5
8
  const nthIndex = options?.nth;
@@ -38,6 +41,138 @@ function getLocator(page, selectorType, selectorValue, optionsJson) {
38
41
  locator = first.or(second);
39
42
  break;
40
43
  }
44
+ case "and": {
45
+ const [firstInfo, secondInfo] = JSON.parse(selectorValue);
46
+ const first = getLocator(page, firstInfo[0], firstInfo[1], firstInfo[2]);
47
+ const second = getLocator(page, secondInfo[0], secondInfo[1], secondInfo[2]);
48
+ locator = first.and(second);
49
+ break;
50
+ }
51
+ case "chained": {
52
+ const [parentInfo, childInfo] = JSON.parse(selectorValue);
53
+ const parent = getLocator(page, parentInfo[0], parentInfo[1], parentInfo[2]);
54
+ const childType = childInfo[0];
55
+ const childValue = childInfo[1];
56
+ const childOptionsJson = childInfo[2];
57
+ const childOptions = childOptionsJson ? JSON.parse(childOptionsJson) : undefined;
58
+ switch (childType) {
59
+ case "css":
60
+ locator = parent.locator(childValue);
61
+ break;
62
+ case "role": {
63
+ const roleOpts = childOptions ? { ...childOptions } : undefined;
64
+ if (roleOpts) {
65
+ delete roleOpts.nth;
66
+ delete roleOpts.filter;
67
+ if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
68
+ roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
69
+ }
70
+ }
71
+ locator = parent.getByRole(childValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
72
+ break;
73
+ }
74
+ case "text":
75
+ locator = parent.getByText(childValue);
76
+ break;
77
+ case "label":
78
+ locator = parent.getByLabel(childValue);
79
+ break;
80
+ case "placeholder":
81
+ locator = parent.getByPlaceholder(childValue);
82
+ break;
83
+ case "testId":
84
+ locator = parent.getByTestId(childValue);
85
+ break;
86
+ case "altText":
87
+ locator = parent.getByAltText(childValue);
88
+ break;
89
+ case "title":
90
+ locator = parent.getByTitle(childValue);
91
+ break;
92
+ default:
93
+ locator = parent.locator(childValue);
94
+ }
95
+ if (childOptions?.nth !== undefined) {
96
+ locator = locator.nth(childOptions.nth);
97
+ }
98
+ if (childOptions?.filter) {
99
+ const filterOpts = { ...childOptions.filter };
100
+ if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
101
+ filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
102
+ }
103
+ if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
104
+ filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
105
+ }
106
+ locator = locator.filter(filterOpts);
107
+ }
108
+ break;
109
+ }
110
+ case "altText":
111
+ locator = page.getByAltText(selectorValue);
112
+ break;
113
+ case "title":
114
+ locator = page.getByTitle(selectorValue);
115
+ break;
116
+ case "frame": {
117
+ const [frameSelectorInfo, innerLocatorInfo] = JSON.parse(selectorValue);
118
+ const frameSelector = frameSelectorInfo[1];
119
+ const frame = page.frameLocator(frameSelector);
120
+ const innerType = innerLocatorInfo[0];
121
+ const innerValue = innerLocatorInfo[1];
122
+ const innerOptionsJson = innerLocatorInfo[2];
123
+ const innerOptions = innerOptionsJson ? JSON.parse(innerOptionsJson) : undefined;
124
+ switch (innerType) {
125
+ case "css":
126
+ locator = frame.locator(innerValue);
127
+ break;
128
+ case "role": {
129
+ const roleOpts = innerOptions ? { ...innerOptions } : undefined;
130
+ if (roleOpts) {
131
+ delete roleOpts.nth;
132
+ delete roleOpts.filter;
133
+ if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
134
+ roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
135
+ }
136
+ }
137
+ locator = frame.getByRole(innerValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
138
+ break;
139
+ }
140
+ case "text":
141
+ locator = frame.getByText(innerValue);
142
+ break;
143
+ case "label":
144
+ locator = frame.getByLabel(innerValue);
145
+ break;
146
+ case "placeholder":
147
+ locator = frame.getByPlaceholder(innerValue);
148
+ break;
149
+ case "testId":
150
+ locator = frame.getByTestId(innerValue);
151
+ break;
152
+ case "altText":
153
+ locator = frame.getByAltText(innerValue);
154
+ break;
155
+ case "title":
156
+ locator = frame.getByTitle(innerValue);
157
+ break;
158
+ default:
159
+ locator = frame.locator(innerValue);
160
+ }
161
+ if (innerOptions?.nth !== undefined) {
162
+ locator = locator.nth(innerOptions.nth);
163
+ }
164
+ if (innerOptions?.filter) {
165
+ const filterOpts = { ...innerOptions.filter };
166
+ if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
167
+ filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
168
+ }
169
+ if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
170
+ filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
171
+ }
172
+ locator = locator.filter(filterOpts);
173
+ }
174
+ break;
175
+ }
41
176
  default:
42
177
  locator = page.locator(selectorValue);
43
178
  }
@@ -56,7 +191,7 @@ function getLocator(page, selectorType, selectorValue, optionsJson) {
56
191
  }
57
192
  return locator;
58
193
  }
59
- async function executeLocatorAction(locator, action, actionArg, timeout) {
194
+ async function executeLocatorAction(locator, action, actionArg, timeout, fileIO) {
60
195
  switch (action) {
61
196
  case "click":
62
197
  await locator.click({ timeout });
@@ -124,6 +259,69 @@ async function executeLocatorAction(locator, action, actionArg, timeout) {
124
259
  }
125
260
  case "boundingBox":
126
261
  return await locator.boundingBox({ timeout });
262
+ case "setInputFiles": {
263
+ const files = actionArg;
264
+ if (Array.isArray(files) && files.length > 0 && typeof files[0] === "object" && "buffer" in files[0]) {
265
+ const fileBuffers2 = files.map((f) => ({
266
+ name: f.name,
267
+ mimeType: f.mimeType,
268
+ buffer: Buffer.from(f.buffer, "base64")
269
+ }));
270
+ await locator.setInputFiles(fileBuffers2, { timeout });
271
+ return null;
272
+ }
273
+ const filePaths = Array.isArray(files) ? files : [files];
274
+ if (!fileIO?.readFile) {
275
+ 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.");
276
+ }
277
+ const fileBuffers = await Promise.all(filePaths.map(async (filePath) => {
278
+ const fileData = await fileIO.readFile(filePath);
279
+ return {
280
+ name: fileData.name,
281
+ mimeType: fileData.mimeType,
282
+ buffer: fileData.buffer
283
+ };
284
+ }));
285
+ await locator.setInputFiles(fileBuffers, { timeout });
286
+ return null;
287
+ }
288
+ case "screenshot": {
289
+ const opts = actionArg;
290
+ const buffer = await locator.screenshot({
291
+ timeout,
292
+ type: opts?.type,
293
+ quality: opts?.quality
294
+ });
295
+ if (opts?.path) {
296
+ if (!fileIO?.writeFile) {
297
+ 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.");
298
+ }
299
+ await fileIO.writeFile(opts.path, buffer);
300
+ }
301
+ return buffer.toString("base64");
302
+ }
303
+ case "dragTo": {
304
+ const targetInfo = actionArg;
305
+ const targetLocator = getLocator(locator.page(), targetInfo[0], targetInfo[1], targetInfo[2]);
306
+ await locator.dragTo(targetLocator, { timeout });
307
+ return null;
308
+ }
309
+ case "scrollIntoViewIfNeeded":
310
+ await locator.scrollIntoViewIfNeeded({ timeout });
311
+ return null;
312
+ case "highlight":
313
+ await locator.highlight();
314
+ return null;
315
+ case "evaluate": {
316
+ const [fnString, arg] = actionArg;
317
+ const fn = new Function("return (" + fnString + ")")();
318
+ return await locator.evaluate(fn, arg);
319
+ }
320
+ case "evaluateAll": {
321
+ const [fnString, arg] = actionArg;
322
+ const fn = new Function("return (" + fnString + ")")();
323
+ return await locator.evaluateAll(fn, arg);
324
+ }
127
325
  default:
128
326
  throw new Error(`Unknown action: ${action}`);
129
327
  }
@@ -300,71 +498,362 @@ async function executeExpectAssertion(locator, matcher, expected, negated, timeo
300
498
  }
301
499
  break;
302
500
  }
501
+ case "toBeAttached": {
502
+ const count = await locator.count();
503
+ const isAttached = count > 0;
504
+ if (negated) {
505
+ if (isAttached)
506
+ throw new Error("Expected element to not be attached to DOM, but it was");
507
+ } else {
508
+ if (!isAttached)
509
+ throw new Error("Expected element to be attached to DOM, but it was not");
510
+ }
511
+ break;
512
+ }
513
+ case "toBeEditable": {
514
+ const isEditable = await locator.isEditable({ timeout });
515
+ if (negated) {
516
+ if (isEditable)
517
+ throw new Error("Expected element to not be editable, but it was");
518
+ } else {
519
+ if (!isEditable)
520
+ throw new Error("Expected element to be editable, but it was not");
521
+ }
522
+ break;
523
+ }
524
+ case "toHaveClass": {
525
+ const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
526
+ const classes = classAttr.split(/\s+/).filter(Boolean);
527
+ let matches;
528
+ let expectedDisplay;
529
+ if (expected && typeof expected === "object" && expected.$regex) {
530
+ const regex = new RegExp(expected.$regex, expected.$flags);
531
+ matches = regex.test(classAttr);
532
+ expectedDisplay = String(regex);
533
+ } else if (Array.isArray(expected)) {
534
+ matches = expected.every((c) => classes.includes(c));
535
+ expectedDisplay = JSON.stringify(expected);
536
+ } else {
537
+ matches = classAttr === String(expected) || classes.includes(String(expected));
538
+ expectedDisplay = String(expected);
539
+ }
540
+ if (negated) {
541
+ if (matches)
542
+ throw new Error(`Expected class to not match ${expectedDisplay}, but got "${classAttr}"`);
543
+ } else {
544
+ if (!matches)
545
+ throw new Error(`Expected class to match ${expectedDisplay}, but got "${classAttr}"`);
546
+ }
547
+ break;
548
+ }
549
+ case "toContainClass": {
550
+ const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
551
+ const classes = classAttr.split(/\s+/).filter(Boolean);
552
+ const expectedClass = String(expected);
553
+ const hasClass = classes.includes(expectedClass);
554
+ if (negated) {
555
+ if (hasClass)
556
+ throw new Error(`Expected element to not contain class "${expectedClass}", but it does`);
557
+ } else {
558
+ if (!hasClass)
559
+ throw new Error(`Expected element to contain class "${expectedClass}", but classes are "${classAttr}"`);
560
+ }
561
+ break;
562
+ }
563
+ case "toHaveId": {
564
+ const id = await locator.getAttribute("id", { timeout });
565
+ const matches = id === String(expected);
566
+ if (negated) {
567
+ if (matches)
568
+ throw new Error(`Expected id to not be "${expected}", but it was`);
569
+ } else {
570
+ if (!matches)
571
+ throw new Error(`Expected id to be "${expected}", but got "${id}"`);
572
+ }
573
+ break;
574
+ }
575
+ case "toBeInViewport": {
576
+ const isInViewport = await locator.evaluate((el) => {
577
+ const rect = el.getBoundingClientRect();
578
+ return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
579
+ });
580
+ if (negated) {
581
+ if (isInViewport)
582
+ throw new Error("Expected element to not be in viewport, but it was");
583
+ } else {
584
+ if (!isInViewport)
585
+ throw new Error("Expected element to be in viewport, but it was not");
586
+ }
587
+ break;
588
+ }
589
+ case "toHaveCSS": {
590
+ const { name, value } = expected;
591
+ const actual = await locator.evaluate((el, propName) => {
592
+ return getComputedStyle(el).getPropertyValue(propName);
593
+ }, name);
594
+ let matches;
595
+ if (value && typeof value === "object" && value.$regex) {
596
+ const regex = new RegExp(value.$regex, value.$flags);
597
+ matches = regex.test(actual);
598
+ } else {
599
+ matches = actual === String(value);
600
+ }
601
+ if (negated) {
602
+ if (matches)
603
+ throw new Error(`Expected CSS "${name}" to not be "${value}", but it was`);
604
+ } else {
605
+ if (!matches)
606
+ throw new Error(`Expected CSS "${name}" to be "${value}", but got "${actual}"`);
607
+ }
608
+ break;
609
+ }
610
+ case "toHaveJSProperty": {
611
+ const { name, value } = expected;
612
+ const actual = await locator.evaluate((el, propName) => {
613
+ return el[propName];
614
+ }, name);
615
+ const matches = JSON.stringify(actual) === JSON.stringify(value);
616
+ if (negated) {
617
+ if (matches)
618
+ throw new Error(`Expected JS property "${name}" to not be ${JSON.stringify(value)}, but it was`);
619
+ } else {
620
+ if (!matches)
621
+ throw new Error(`Expected JS property "${name}" to be ${JSON.stringify(value)}, but got ${JSON.stringify(actual)}`);
622
+ }
623
+ break;
624
+ }
625
+ case "toHaveAccessibleName": {
626
+ const accessibleName = await locator.evaluate((el) => {
627
+ return el.getAttribute("aria-label") || el.getAttribute("aria-labelledby") || el.innerText || "";
628
+ });
629
+ let matches;
630
+ if (expected && typeof expected === "object" && expected.$regex) {
631
+ const regex = new RegExp(expected.$regex, expected.$flags);
632
+ matches = regex.test(accessibleName);
633
+ } else {
634
+ matches = accessibleName === String(expected);
635
+ }
636
+ if (negated) {
637
+ if (matches)
638
+ throw new Error(`Expected accessible name to not be "${expected}", but it was`);
639
+ } else {
640
+ if (!matches)
641
+ throw new Error(`Expected accessible name to be "${expected}", but got "${accessibleName}"`);
642
+ }
643
+ break;
644
+ }
645
+ case "toHaveAccessibleDescription": {
646
+ const accessibleDesc = await locator.evaluate((el) => {
647
+ const describedby = el.getAttribute("aria-describedby");
648
+ if (describedby) {
649
+ const descEl = document.getElementById(describedby);
650
+ return descEl?.textContent || "";
651
+ }
652
+ return el.getAttribute("aria-description") || "";
653
+ });
654
+ let matches;
655
+ if (expected && typeof expected === "object" && expected.$regex) {
656
+ const regex = new RegExp(expected.$regex, expected.$flags);
657
+ matches = regex.test(accessibleDesc);
658
+ } else {
659
+ matches = accessibleDesc === String(expected);
660
+ }
661
+ if (negated) {
662
+ if (matches)
663
+ throw new Error(`Expected accessible description to not be "${expected}", but it was`);
664
+ } else {
665
+ if (!matches)
666
+ throw new Error(`Expected accessible description to be "${expected}", but got "${accessibleDesc}"`);
667
+ }
668
+ break;
669
+ }
670
+ case "toHaveRole": {
671
+ const role = await locator.evaluate((el) => {
672
+ return el.getAttribute("role") || el.tagName.toLowerCase();
673
+ });
674
+ const matches = role === String(expected);
675
+ if (negated) {
676
+ if (matches)
677
+ throw new Error(`Expected role to not be "${expected}", but it was`);
678
+ } else {
679
+ if (!matches)
680
+ throw new Error(`Expected role to be "${expected}", but got "${role}"`);
681
+ }
682
+ break;
683
+ }
303
684
  default:
304
685
  throw new Error(`Unknown matcher: ${matcher}`);
305
686
  }
306
687
  }
688
+ async function executePageExpectAssertion(page, matcher, expected, negated, timeout) {
689
+ let expectedValue = expected;
690
+ if (expected && typeof expected === "object" && expected.$regex) {
691
+ expectedValue = new RegExp(expected.$regex, expected.$flags);
692
+ }
693
+ switch (matcher) {
694
+ case "toHaveURL": {
695
+ const expectedUrl = expectedValue;
696
+ const startTime = Date.now();
697
+ let lastUrl = "";
698
+ while (Date.now() - startTime < timeout) {
699
+ lastUrl = page.url();
700
+ const matches = expectedUrl instanceof RegExp ? expectedUrl.test(lastUrl) : lastUrl === expectedUrl;
701
+ if (negated ? !matches : matches)
702
+ return;
703
+ await new Promise((r) => setTimeout(r, 100));
704
+ }
705
+ if (negated) {
706
+ throw new Error(`Expected URL to not match "${expectedUrl}", but got "${lastUrl}"`);
707
+ } else {
708
+ throw new Error(`Expected URL to be "${expectedUrl}", but got "${lastUrl}"`);
709
+ }
710
+ }
711
+ case "toHaveTitle": {
712
+ const expectedTitle = expectedValue;
713
+ const startTime = Date.now();
714
+ let lastTitle = "";
715
+ while (Date.now() - startTime < timeout) {
716
+ lastTitle = await page.title();
717
+ const matches = expectedTitle instanceof RegExp ? expectedTitle.test(lastTitle) : lastTitle === expectedTitle;
718
+ if (negated ? !matches : matches)
719
+ return;
720
+ await new Promise((r) => setTimeout(r, 100));
721
+ }
722
+ if (negated) {
723
+ throw new Error(`Expected title to not match "${expectedTitle}", but got "${lastTitle}"`);
724
+ } else {
725
+ throw new Error(`Expected title to be "${expectedTitle}", but got "${lastTitle}"`);
726
+ }
727
+ }
728
+ default:
729
+ throw new Error(`Unknown page matcher: ${matcher}`);
730
+ }
731
+ }
307
732
  function createPlaywrightHandler(page, options) {
308
733
  const timeout = options?.timeout ?? 30000;
734
+ const fileIO = {
735
+ readFile: options?.readFile,
736
+ writeFile: options?.writeFile
737
+ };
738
+ const registry = {
739
+ pages: new Map([["page_0", page]]),
740
+ contexts: new Map([["ctx_0", page.context()]]),
741
+ nextPageId: 1,
742
+ nextContextId: 1
743
+ };
309
744
  return async (op) => {
310
745
  try {
746
+ switch (op.type) {
747
+ case "newContext": {
748
+ if (!options?.createContext) {
749
+ return { ok: false, error: { name: "Error", message: "createContext callback not provided. Configure createContext in playwright options to enable browser.newContext()." } };
750
+ }
751
+ const [contextOptions] = op.args;
752
+ const newContext = await options.createContext(contextOptions);
753
+ const contextId2 = `ctx_${registry.nextContextId++}`;
754
+ registry.contexts.set(contextId2, newContext);
755
+ return { ok: true, value: { contextId: contextId2 } };
756
+ }
757
+ case "newPage": {
758
+ if (!options?.createPage) {
759
+ return { ok: false, error: { name: "Error", message: "createPage callback not provided. Configure createPage in playwright options to enable context.newPage()." } };
760
+ }
761
+ const contextId2 = op.contextId ?? "ctx_0";
762
+ const targetContext2 = registry.contexts.get(contextId2);
763
+ if (!targetContext2) {
764
+ return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
765
+ }
766
+ const newPage = await options.createPage(targetContext2);
767
+ const pageId2 = `page_${registry.nextPageId++}`;
768
+ registry.pages.set(pageId2, newPage);
769
+ return { ok: true, value: { pageId: pageId2 } };
770
+ }
771
+ case "closeContext": {
772
+ const contextId2 = op.contextId ?? "ctx_0";
773
+ const context = registry.contexts.get(contextId2);
774
+ if (!context) {
775
+ return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
776
+ }
777
+ await context.close();
778
+ registry.contexts.delete(contextId2);
779
+ for (const [pid, p] of registry.pages) {
780
+ if (p.context() === context) {
781
+ registry.pages.delete(pid);
782
+ }
783
+ }
784
+ return { ok: true };
785
+ }
786
+ }
787
+ const pageId = op.pageId ?? "page_0";
788
+ const targetPage = registry.pages.get(pageId);
789
+ if (!targetPage) {
790
+ return { ok: false, error: { name: "Error", message: `Page ${pageId} not found` } };
791
+ }
792
+ const contextId = op.contextId ?? "ctx_0";
793
+ const targetContext = registry.contexts.get(contextId);
311
794
  switch (op.type) {
312
795
  case "goto": {
313
796
  const [url, waitUntil] = op.args;
314
- await page.goto(url, {
797
+ await targetPage.goto(url, {
315
798
  timeout,
316
799
  waitUntil: waitUntil ?? "load"
317
800
  });
318
801
  return { ok: true };
319
802
  }
320
803
  case "reload":
321
- await page.reload({ timeout });
804
+ await targetPage.reload({ timeout });
322
805
  return { ok: true };
323
806
  case "url":
324
- return { ok: true, value: page.url() };
807
+ return { ok: true, value: targetPage.url() };
325
808
  case "title":
326
- return { ok: true, value: await page.title() };
809
+ return { ok: true, value: await targetPage.title() };
327
810
  case "content":
328
- return { ok: true, value: await page.content() };
811
+ return { ok: true, value: await targetPage.content() };
329
812
  case "waitForSelector": {
330
813
  const [selector, optionsJson] = op.args;
331
814
  const opts = optionsJson ? JSON.parse(optionsJson) : {};
332
- await page.waitForSelector(selector, { timeout, ...opts });
815
+ await targetPage.waitForSelector(selector, { timeout, ...opts });
333
816
  return { ok: true };
334
817
  }
335
818
  case "waitForTimeout": {
336
819
  const [ms] = op.args;
337
- await page.waitForTimeout(ms);
820
+ await targetPage.waitForTimeout(ms);
338
821
  return { ok: true };
339
822
  }
340
823
  case "waitForLoadState": {
341
824
  const [state] = op.args;
342
- await page.waitForLoadState(state ?? "load", { timeout });
825
+ await targetPage.waitForLoadState(state ?? "load", { timeout });
343
826
  return { ok: true };
344
827
  }
345
828
  case "evaluate": {
346
829
  const [script, arg] = op.args;
347
830
  if (op.args.length > 1) {
348
831
  const fn = new Function("return (" + script + ")")();
349
- const result2 = await page.evaluate(fn, arg);
832
+ const result2 = await targetPage.evaluate(fn, arg);
350
833
  return { ok: true, value: result2 };
351
834
  }
352
- const result = await page.evaluate(script);
835
+ const result = await targetPage.evaluate(script);
353
836
  return { ok: true, value: result };
354
837
  }
355
838
  case "locatorAction": {
356
839
  const [selectorType, selectorValue, roleOptions, action, actionArg] = op.args;
357
- const locator = getLocator(page, selectorType, selectorValue, roleOptions);
358
- const result = await executeLocatorAction(locator, action, actionArg, timeout);
840
+ const locator = getLocator(targetPage, selectorType, selectorValue, roleOptions);
841
+ const result = await executeLocatorAction(locator, action, actionArg, timeout, fileIO);
359
842
  return { ok: true, value: result };
360
843
  }
361
844
  case "expectLocator": {
362
845
  const [selectorType, selectorValue, roleOptions, matcher, expected, negated, customTimeout] = op.args;
363
- const locator = getLocator(page, selectorType, selectorValue, roleOptions);
846
+ const locator = getLocator(targetPage, selectorType, selectorValue, roleOptions);
364
847
  const effectiveTimeout = customTimeout ?? timeout;
365
848
  await executeExpectAssertion(locator, matcher, expected, negated ?? false, effectiveTimeout);
366
849
  return { ok: true };
367
850
  }
851
+ case "expectPage": {
852
+ const [matcher, expected, negated, customTimeout] = op.args;
853
+ const effectiveTimeout = customTimeout ?? timeout;
854
+ await executePageExpectAssertion(targetPage, matcher, expected, negated ?? false, effectiveTimeout);
855
+ return { ok: true };
856
+ }
368
857
  case "request": {
369
858
  const [url, method, data, headers] = op.args;
370
859
  const requestOptions = {
@@ -376,7 +865,7 @@ function createPlaywrightHandler(page, options) {
376
865
  if (data !== undefined && data !== null) {
377
866
  requestOptions.data = data;
378
867
  }
379
- const response = await page.request.fetch(url, {
868
+ const response = await targetPage.request.fetch(url, {
380
869
  method,
381
870
  ...requestOptions
382
871
  });
@@ -399,7 +888,7 @@ function createPlaywrightHandler(page, options) {
399
888
  }
400
889
  case "goBack": {
401
890
  const [waitUntil] = op.args;
402
- await page.goBack({
891
+ await targetPage.goBack({
403
892
  timeout,
404
893
  waitUntil: waitUntil ?? "load"
405
894
  });
@@ -407,22 +896,156 @@ function createPlaywrightHandler(page, options) {
407
896
  }
408
897
  case "goForward": {
409
898
  const [waitUntil] = op.args;
410
- await page.goForward({
899
+ await targetPage.goForward({
411
900
  timeout,
412
901
  waitUntil: waitUntil ?? "load"
413
902
  });
414
903
  return { ok: true };
415
904
  }
416
905
  case "waitForURL": {
417
- const [url, customTimeout, waitUntil] = op.args;
418
- await page.waitForURL(url, {
906
+ const [urlArg, customTimeout, waitUntil] = op.args;
907
+ const url = urlArg && typeof urlArg === "object" && "$regex" in urlArg ? new RegExp(urlArg.$regex, urlArg.$flags) : urlArg;
908
+ await targetPage.waitForURL(url, {
419
909
  timeout: customTimeout ?? timeout,
420
910
  waitUntil: waitUntil ?? undefined
421
911
  });
422
912
  return { ok: true };
423
913
  }
424
914
  case "clearCookies": {
425
- await page.context().clearCookies();
915
+ const ctx = targetContext ?? targetPage.context();
916
+ await ctx.clearCookies();
917
+ return { ok: true };
918
+ }
919
+ case "screenshot": {
920
+ const [screenshotOptions] = op.args;
921
+ const buffer = await targetPage.screenshot({
922
+ type: screenshotOptions?.type,
923
+ quality: screenshotOptions?.quality,
924
+ fullPage: screenshotOptions?.fullPage,
925
+ clip: screenshotOptions?.clip
926
+ });
927
+ if (screenshotOptions?.path) {
928
+ if (!fileIO.writeFile) {
929
+ throw new Error("screenshot() 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.");
930
+ }
931
+ await fileIO.writeFile(screenshotOptions.path, buffer);
932
+ }
933
+ return { ok: true, value: buffer.toString("base64") };
934
+ }
935
+ case "setViewportSize": {
936
+ const [size] = op.args;
937
+ await targetPage.setViewportSize(size);
938
+ return { ok: true };
939
+ }
940
+ case "viewportSize": {
941
+ return { ok: true, value: targetPage.viewportSize() };
942
+ }
943
+ case "keyboardType": {
944
+ const [text, typeOptions] = op.args;
945
+ await targetPage.keyboard.type(text, typeOptions);
946
+ return { ok: true };
947
+ }
948
+ case "keyboardPress": {
949
+ const [key, pressOptions] = op.args;
950
+ await targetPage.keyboard.press(key, pressOptions);
951
+ return { ok: true };
952
+ }
953
+ case "keyboardDown": {
954
+ const [key] = op.args;
955
+ await targetPage.keyboard.down(key);
956
+ return { ok: true };
957
+ }
958
+ case "keyboardUp": {
959
+ const [key] = op.args;
960
+ await targetPage.keyboard.up(key);
961
+ return { ok: true };
962
+ }
963
+ case "keyboardInsertText": {
964
+ const [text] = op.args;
965
+ await targetPage.keyboard.insertText(text);
966
+ return { ok: true };
967
+ }
968
+ case "mouseMove": {
969
+ const [x, y, moveOptions] = op.args;
970
+ await targetPage.mouse.move(x, y, moveOptions);
971
+ return { ok: true };
972
+ }
973
+ case "mouseClick": {
974
+ const [x, y, clickOptions] = op.args;
975
+ await targetPage.mouse.click(x, y, clickOptions);
976
+ return { ok: true };
977
+ }
978
+ case "mouseDown": {
979
+ const [downOptions] = op.args;
980
+ await targetPage.mouse.down(downOptions);
981
+ return { ok: true };
982
+ }
983
+ case "mouseUp": {
984
+ const [upOptions] = op.args;
985
+ await targetPage.mouse.up(upOptions);
986
+ return { ok: true };
987
+ }
988
+ case "mouseWheel": {
989
+ const [deltaX, deltaY] = op.args;
990
+ await targetPage.mouse.wheel(deltaX, deltaY);
991
+ return { ok: true };
992
+ }
993
+ case "frames": {
994
+ const frames = targetPage.frames();
995
+ return { ok: true, value: frames.map((f) => ({ name: f.name(), url: f.url() })) };
996
+ }
997
+ case "mainFrame": {
998
+ const mainFrame = targetPage.mainFrame();
999
+ return { ok: true, value: { name: mainFrame.name(), url: mainFrame.url() } };
1000
+ }
1001
+ case "bringToFront": {
1002
+ await targetPage.bringToFront();
1003
+ return { ok: true };
1004
+ }
1005
+ case "close": {
1006
+ await targetPage.close();
1007
+ registry.pages.delete(pageId);
1008
+ return { ok: true };
1009
+ }
1010
+ case "isClosed": {
1011
+ return { ok: true, value: targetPage.isClosed() };
1012
+ }
1013
+ case "pdf": {
1014
+ const [pdfOptions] = op.args;
1015
+ const { path: pdfPath, ...restPdfOptions } = pdfOptions ?? {};
1016
+ const buffer = await targetPage.pdf(restPdfOptions);
1017
+ if (pdfPath) {
1018
+ if (!fileIO.writeFile) {
1019
+ 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.");
1020
+ }
1021
+ await fileIO.writeFile(pdfPath, buffer);
1022
+ }
1023
+ return { ok: true, value: buffer.toString("base64") };
1024
+ }
1025
+ case "emulateMedia": {
1026
+ const [mediaOptions] = op.args;
1027
+ await targetPage.emulateMedia(mediaOptions);
1028
+ return { ok: true };
1029
+ }
1030
+ case "addCookies": {
1031
+ const [cookies] = op.args;
1032
+ const ctx = targetContext ?? targetPage.context();
1033
+ await ctx.addCookies(cookies);
1034
+ return { ok: true };
1035
+ }
1036
+ case "cookies": {
1037
+ const [urls] = op.args;
1038
+ const ctx = targetContext ?? targetPage.context();
1039
+ const cookies = await ctx.cookies(urls);
1040
+ return { ok: true, value: cookies };
1041
+ }
1042
+ case "setExtraHTTPHeaders": {
1043
+ const [headers] = op.args;
1044
+ await targetPage.setExtraHTTPHeaders(headers);
1045
+ return { ok: true };
1046
+ }
1047
+ case "pause": {
1048
+ await targetPage.pause();
426
1049
  return { ok: true };
427
1050
  }
428
1051
  default:
@@ -434,11 +1057,27 @@ function createPlaywrightHandler(page, options) {
434
1057
  }
435
1058
  };
436
1059
  }
1060
+ function defaultPlaywrightHandler(page, options) {
1061
+ const handler = createPlaywrightHandler(page, options);
1062
+ handler[DEFAULT_PLAYWRIGHT_HANDLER_META] = { page, options };
1063
+ return handler;
1064
+ }
1065
+ function getDefaultPlaywrightHandlerMetadata(handler) {
1066
+ return handler[DEFAULT_PLAYWRIGHT_HANDLER_META];
1067
+ }
437
1068
  async function setupPlaywright(context, options) {
438
1069
  const timeout = options.timeout ?? 30000;
439
- const page = "page" in options ? options.page : undefined;
1070
+ const explicitPage = "page" in options ? options.page : undefined;
440
1071
  const handler = "handler" in options ? options.handler : undefined;
441
- const effectiveHandler = handler ?? (page ? createPlaywrightHandler(page, { timeout }) : undefined);
1072
+ const handlerMetadata = handler ? getDefaultPlaywrightHandlerMetadata(handler) : undefined;
1073
+ const page = explicitPage ?? handlerMetadata?.page;
1074
+ const createPage = "createPage" in options ? options.createPage : undefined;
1075
+ const createContext = "createContext" in options ? options.createContext : undefined;
1076
+ const effectiveHandler = handler ?? (page ? createPlaywrightHandler(page, {
1077
+ timeout,
1078
+ createPage,
1079
+ createContext
1080
+ }) : undefined);
442
1081
  if (!effectiveHandler) {
443
1082
  throw new Error("Either page or handler must be provided to setupPlaywright");
444
1083
  }
@@ -525,8 +1164,8 @@ async function setupPlaywright(context, options) {
525
1164
  }));
526
1165
  context.evalSync(`
527
1166
  (function() {
528
- globalThis.__pw_invoke = async function(type, args) {
529
- const op = JSON.stringify({ type, args });
1167
+ globalThis.__pw_invoke = async function(type, args, options) {
1168
+ const op = JSON.stringify({ type, args, pageId: options?.pageId, contextId: options?.contextId });
530
1169
  const resultJson = __Playwright_handler_ref.applySyncPromise(undefined, [op]);
531
1170
  const result = JSON.parse(resultJson);
532
1171
  if (result.ok) {
@@ -541,211 +1180,362 @@ async function setupPlaywright(context, options) {
541
1180
  `);
542
1181
  context.evalSync(`
543
1182
  (function() {
544
- let __pw_currentUrl = '';
545
- globalThis.page = {
1183
+ // IsolatePage class - represents a page with a specific pageId
1184
+ class IsolatePage {
1185
+ #pageId; #contextId; #currentUrl = '';
1186
+ constructor(pageId, contextId) {
1187
+ this.#pageId = pageId;
1188
+ this.#contextId = contextId;
1189
+ }
1190
+ get __isPage() { return true; }
1191
+ get __pageId() { return this.#pageId; }
1192
+ get __contextId() { return this.#contextId; }
1193
+
546
1194
  async goto(url, options) {
547
- const result = await __pw_invoke("goto", [url, options?.waitUntil || null]);
548
- const resolvedUrl = await __pw_invoke("url", []);
549
- __pw_currentUrl = resolvedUrl || url;
550
- return result;
551
- },
1195
+ await __pw_invoke("goto", [url, options?.waitUntil || null], { pageId: this.#pageId });
1196
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1197
+ this.#currentUrl = resolvedUrl || url;
1198
+ }
552
1199
  async reload() {
553
- const result = await __pw_invoke("reload", []);
554
- const resolvedUrl = await __pw_invoke("url", []);
555
- if (resolvedUrl) __pw_currentUrl = resolvedUrl;
556
- return result;
557
- },
558
- url() {
559
- return __pw_currentUrl;
560
- },
561
- async title() {
562
- return __pw_invoke("title", []);
563
- },
564
- async content() {
565
- return __pw_invoke("content", []);
566
- },
1200
+ await __pw_invoke("reload", [], { pageId: this.#pageId });
1201
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1202
+ if (resolvedUrl) this.#currentUrl = resolvedUrl;
1203
+ }
1204
+ url() { return this.#currentUrl; }
1205
+ async title() { return __pw_invoke("title", [], { pageId: this.#pageId }); }
1206
+ async content() { return __pw_invoke("content", [], { pageId: this.#pageId }); }
567
1207
  async waitForSelector(selector, options) {
568
- return __pw_invoke("waitForSelector", [selector, options ? JSON.stringify(options) : null]);
569
- },
570
- async waitForTimeout(ms) {
571
- return __pw_invoke("waitForTimeout", [ms]);
572
- },
573
- async waitForLoadState(state) {
574
- return __pw_invoke("waitForLoadState", [state || null]);
575
- },
1208
+ return __pw_invoke("waitForSelector", [selector, options ? JSON.stringify(options) : null], { pageId: this.#pageId });
1209
+ }
1210
+ async waitForTimeout(ms) { return __pw_invoke("waitForTimeout", [ms], { pageId: this.#pageId }); }
1211
+ async waitForLoadState(state) { return __pw_invoke("waitForLoadState", [state || null], { pageId: this.#pageId }); }
576
1212
  async evaluate(script, arg) {
577
1213
  const hasArg = arguments.length > 1;
578
1214
  if (hasArg) {
579
1215
  const serialized = typeof script === "function" ? script.toString() : script;
580
- return __pw_invoke("evaluate", [serialized, arg]);
1216
+ return __pw_invoke("evaluate", [serialized, arg], { pageId: this.#pageId });
581
1217
  }
582
1218
  const serialized = typeof script === "function" ? "(" + script.toString() + ")()" : script;
583
- return __pw_invoke("evaluate", [serialized]);
584
- },
585
- locator(selector) { return new Locator("css", selector, null); },
1219
+ return __pw_invoke("evaluate", [serialized], { pageId: this.#pageId });
1220
+ }
1221
+ locator(selector) { return new Locator("css", selector, null, this.#pageId); }
586
1222
  getByRole(role, options) {
587
1223
  if (options) {
588
1224
  const serialized = { ...options };
589
- // Use duck-typing RegExp detection (instanceof fails across isolated-vm boundary)
590
1225
  const name = options.name;
591
1226
  if (name && typeof name === 'object' && typeof name.source === 'string' && typeof name.flags === 'string') {
592
1227
  serialized.name = { $regex: name.source, $flags: name.flags };
593
1228
  }
594
- return new Locator("role", role, JSON.stringify(serialized));
1229
+ return new Locator("role", role, JSON.stringify(serialized), this.#pageId);
595
1230
  }
596
- return new Locator("role", role, null);
597
- },
598
- getByText(text) { return new Locator("text", text, null); },
599
- getByLabel(label) { return new Locator("label", label, null); },
600
- getByPlaceholder(p) { return new Locator("placeholder", p, null); },
601
- getByTestId(id) { return new Locator("testId", id, null); },
1231
+ return new Locator("role", role, null, this.#pageId);
1232
+ }
1233
+ getByText(text) { return new Locator("text", text, null, this.#pageId); }
1234
+ getByLabel(label) { return new Locator("label", label, null, this.#pageId); }
1235
+ getByPlaceholder(p) { return new Locator("placeholder", p, null, this.#pageId); }
1236
+ getByTestId(id) { return new Locator("testId", id, null, this.#pageId); }
1237
+ getByAltText(alt) { return new Locator("altText", alt, null, this.#pageId); }
1238
+ getByTitle(title) { return new Locator("title", title, null, this.#pageId); }
1239
+ frameLocator(selector) {
1240
+ const pageId = this.#pageId;
1241
+ return {
1242
+ locator(innerSelector) { return new Locator("frame", JSON.stringify([["css", selector, null], ["css", innerSelector, null]]), null, pageId); },
1243
+ getByRole(role, options) { return new Locator("frame", JSON.stringify([["css", selector, null], ["role", role, options ? JSON.stringify(options) : null]]), null, pageId); },
1244
+ getByText(text) { return new Locator("frame", JSON.stringify([["css", selector, null], ["text", text, null]]), null, pageId); },
1245
+ getByLabel(label) { return new Locator("frame", JSON.stringify([["css", selector, null], ["label", label, null]]), null, pageId); },
1246
+ getByPlaceholder(placeholder) { return new Locator("frame", JSON.stringify([["css", selector, null], ["placeholder", placeholder, null]]), null, pageId); },
1247
+ getByTestId(testId) { return new Locator("frame", JSON.stringify([["css", selector, null], ["testId", testId, null]]), null, pageId); },
1248
+ getByAltText(alt) { return new Locator("frame", JSON.stringify([["css", selector, null], ["altText", alt, null]]), null, pageId); },
1249
+ getByTitle(title) { return new Locator("frame", JSON.stringify([["css", selector, null], ["title", title, null]]), null, pageId); },
1250
+ };
1251
+ }
602
1252
  async goBack(options) {
603
- await __pw_invoke("goBack", [options?.waitUntil || null]);
604
- const resolvedUrl = await __pw_invoke("url", []);
605
- if (resolvedUrl) __pw_currentUrl = resolvedUrl;
606
- },
1253
+ await __pw_invoke("goBack", [options?.waitUntil || null], { pageId: this.#pageId });
1254
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1255
+ if (resolvedUrl) this.#currentUrl = resolvedUrl;
1256
+ }
607
1257
  async goForward(options) {
608
- await __pw_invoke("goForward", [options?.waitUntil || null]);
609
- const resolvedUrl = await __pw_invoke("url", []);
610
- if (resolvedUrl) __pw_currentUrl = resolvedUrl;
611
- },
1258
+ await __pw_invoke("goForward", [options?.waitUntil || null], { pageId: this.#pageId });
1259
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1260
+ if (resolvedUrl) this.#currentUrl = resolvedUrl;
1261
+ }
612
1262
  async waitForURL(url, options) {
613
- return __pw_invoke("waitForURL", [url, options?.timeout || null, options?.waitUntil || null]);
614
- },
1263
+ let serializedUrl = url;
1264
+ if (url && typeof url === 'object' && typeof url.source === 'string' && typeof url.flags === 'string') {
1265
+ serializedUrl = { $regex: url.source, $flags: url.flags };
1266
+ }
1267
+ return __pw_invoke("waitForURL", [serializedUrl, options?.timeout || null, options?.waitUntil || null], { pageId: this.#pageId });
1268
+ }
615
1269
  context() {
1270
+ const contextId = this.#contextId;
1271
+ return new IsolateContext(contextId);
1272
+ }
1273
+ async click(selector) { return this.locator(selector).click(); }
1274
+ async fill(selector, value) { return this.locator(selector).fill(value); }
1275
+ async textContent(selector) { return this.locator(selector).textContent(); }
1276
+ async innerText(selector) { return this.locator(selector).innerText(); }
1277
+ async innerHTML(selector) { return this.locator(selector).innerHTML(); }
1278
+ async getAttribute(selector, name) { return this.locator(selector).getAttribute(name); }
1279
+ async inputValue(selector) { return this.locator(selector).inputValue(); }
1280
+ async isVisible(selector) { return this.locator(selector).isVisible(); }
1281
+ async isEnabled(selector) { return this.locator(selector).isEnabled(); }
1282
+ async isChecked(selector) { return this.locator(selector).isChecked(); }
1283
+ async isHidden(selector) { return this.locator(selector).isHidden(); }
1284
+ async isDisabled(selector) { return this.locator(selector).isDisabled(); }
1285
+ async screenshot(options) { return __pw_invoke("screenshot", [options || {}], { pageId: this.#pageId }); }
1286
+ async setViewportSize(size) { return __pw_invoke("setViewportSize", [size], { pageId: this.#pageId }); }
1287
+ async viewportSize() { return __pw_invoke("viewportSize", [], { pageId: this.#pageId }); }
1288
+ async emulateMedia(options) { return __pw_invoke("emulateMedia", [options], { pageId: this.#pageId }); }
1289
+ async setExtraHTTPHeaders(headers) { return __pw_invoke("setExtraHTTPHeaders", [headers], { pageId: this.#pageId }); }
1290
+ async bringToFront() { return __pw_invoke("bringToFront", [], { pageId: this.#pageId }); }
1291
+ async close() { return __pw_invoke("close", [], { pageId: this.#pageId }); }
1292
+ async isClosed() { return __pw_invoke("isClosed", [], { pageId: this.#pageId }); }
1293
+ async pdf(options) { return __pw_invoke("pdf", [options || {}], { pageId: this.#pageId }); }
1294
+ async pause() { return __pw_invoke("pause", [], { pageId: this.#pageId }); }
1295
+ async frames() { return __pw_invoke("frames", [], { pageId: this.#pageId }); }
1296
+ async mainFrame() { return __pw_invoke("mainFrame", [], { pageId: this.#pageId }); }
1297
+ get keyboard() {
1298
+ const pageId = this.#pageId;
616
1299
  return {
617
- async clearCookies() {
618
- return __pw_invoke("clearCookies", []);
619
- }
1300
+ async type(text, options) { return __pw_invoke("keyboardType", [text, options], { pageId }); },
1301
+ async press(key, options) { return __pw_invoke("keyboardPress", [key, options], { pageId }); },
1302
+ async down(key) { return __pw_invoke("keyboardDown", [key], { pageId }); },
1303
+ async up(key) { return __pw_invoke("keyboardUp", [key], { pageId }); },
1304
+ async insertText(text) { return __pw_invoke("keyboardInsertText", [text], { pageId }); }
620
1305
  };
621
- },
622
- async click(selector) { return this.locator(selector).click(); },
623
- async fill(selector, value) { return this.locator(selector).fill(value); },
624
- request: {
625
- async fetch(url, options) {
626
- const result = await __pw_invoke("request", [url, options?.method || "GET", options?.data, options?.headers]);
627
- return {
628
- status: () => result.status,
629
- ok: () => result.ok,
630
- headers: () => result.headers,
631
- json: async () => result.json,
632
- text: async () => result.text,
633
- body: async () => result.body,
634
- };
635
- },
636
- async get(url, options) {
637
- return this.fetch(url, { ...options, method: "GET" });
638
- },
639
- async post(url, options) {
640
- return this.fetch(url, { ...options, method: "POST" });
641
- },
642
- async put(url, options) {
643
- return this.fetch(url, { ...options, method: "PUT" });
644
- },
645
- async delete(url, options) {
646
- return this.fetch(url, { ...options, method: "DELETE" });
647
- },
648
- },
1306
+ }
1307
+ get mouse() {
1308
+ const pageId = this.#pageId;
1309
+ return {
1310
+ async move(x, y, options) { return __pw_invoke("mouseMove", [x, y, options], { pageId }); },
1311
+ async click(x, y, options) { return __pw_invoke("mouseClick", [x, y, options], { pageId }); },
1312
+ async down(options) { return __pw_invoke("mouseDown", [options], { pageId }); },
1313
+ async up(options) { return __pw_invoke("mouseUp", [options], { pageId }); },
1314
+ async wheel(deltaX, deltaY) { return __pw_invoke("mouseWheel", [deltaX, deltaY], { pageId }); }
1315
+ };
1316
+ }
1317
+ get request() {
1318
+ const pageId = this.#pageId;
1319
+ return {
1320
+ async fetch(url, options) {
1321
+ const result = await __pw_invoke("request", [url, options?.method || "GET", options?.data, options?.headers], { pageId });
1322
+ return {
1323
+ status: () => result.status,
1324
+ ok: () => result.ok,
1325
+ headers: () => result.headers,
1326
+ json: async () => result.json,
1327
+ text: async () => result.text,
1328
+ body: async () => result.body,
1329
+ };
1330
+ },
1331
+ async get(url, options) { return this.fetch(url, { ...options, method: "GET" }); },
1332
+ async post(url, options) { return this.fetch(url, { ...options, method: "POST" }); },
1333
+ async put(url, options) { return this.fetch(url, { ...options, method: "PUT" }); },
1334
+ async delete(url, options) { return this.fetch(url, { ...options, method: "DELETE" }); },
1335
+ };
1336
+ }
1337
+ }
1338
+ globalThis.IsolatePage = IsolatePage;
1339
+
1340
+ // IsolateContext class - represents a browser context with a specific contextId
1341
+ class IsolateContext {
1342
+ #contextId;
1343
+ constructor(contextId) { this.#contextId = contextId; }
1344
+ get __contextId() { return this.#contextId; }
1345
+
1346
+ async newPage() {
1347
+ const result = await __pw_invoke("newPage", [], { contextId: this.#contextId });
1348
+ return new IsolatePage(result.pageId, this.#contextId);
1349
+ }
1350
+ async close() { return __pw_invoke("closeContext", [], { contextId: this.#contextId }); }
1351
+ async clearCookies() { return __pw_invoke("clearCookies", [], { contextId: this.#contextId }); }
1352
+ async addCookies(cookies) { return __pw_invoke("addCookies", [cookies], { contextId: this.#contextId }); }
1353
+ async cookies(urls) { return __pw_invoke("cookies", [urls], { contextId: this.#contextId }); }
1354
+ }
1355
+ globalThis.IsolateContext = IsolateContext;
1356
+
1357
+ // browser global - for creating new contexts
1358
+ globalThis.browser = {
1359
+ async newContext(options) {
1360
+ const result = await __pw_invoke("newContext", [options || null]);
1361
+ return new IsolateContext(result.contextId);
1362
+ }
649
1363
  };
1364
+
1365
+ // context global - represents the default context
1366
+ globalThis.context = new IsolateContext("ctx_0");
1367
+
1368
+ // page global - represents the default page
1369
+ globalThis.page = new IsolatePage("page_0", "ctx_0");
650
1370
  })();
651
1371
  `);
652
1372
  context.evalSync(`
653
1373
  (function() {
1374
+ // Helper to serialize options including RegExp
1375
+ function serializeOptions(options) {
1376
+ if (!options) return null;
1377
+ const serialized = { ...options };
1378
+ if (options.name && typeof options.name === 'object' && typeof options.name.source === 'string' && typeof options.name.flags === 'string') {
1379
+ serialized.name = { $regex: options.name.source, $flags: options.name.flags };
1380
+ }
1381
+ return JSON.stringify(serialized);
1382
+ }
1383
+
654
1384
  class Locator {
655
- #type; #value; #options;
656
- constructor(type, value, options) {
1385
+ #type; #value; #options; #pageId;
1386
+ constructor(type, value, options, pageId) {
657
1387
  this.#type = type;
658
1388
  this.#value = value;
659
1389
  this.#options = options;
1390
+ this.#pageId = pageId || "page_0";
660
1391
  }
661
1392
 
662
1393
  _getInfo() { return [this.#type, this.#value, this.#options]; }
1394
+ _getPageId() { return this.#pageId; }
1395
+
1396
+ // Helper to create a chained locator
1397
+ _chain(childType, childValue, childOptions) {
1398
+ const parentInfo = this._getInfo();
1399
+ const childInfo = [childType, childValue, childOptions];
1400
+ return new Locator("chained", JSON.stringify([parentInfo, childInfo]), null, this.#pageId);
1401
+ }
663
1402
 
664
1403
  async click() {
665
- return __pw_invoke("locatorAction", [...this._getInfo(), "click", null]);
1404
+ return __pw_invoke("locatorAction", [...this._getInfo(), "click", null], { pageId: this.#pageId });
666
1405
  }
667
1406
  async dblclick() {
668
- return __pw_invoke("locatorAction", [...this._getInfo(), "dblclick", null]);
1407
+ return __pw_invoke("locatorAction", [...this._getInfo(), "dblclick", null], { pageId: this.#pageId });
669
1408
  }
670
1409
  async fill(text) {
671
- return __pw_invoke("locatorAction", [...this._getInfo(), "fill", text]);
1410
+ return __pw_invoke("locatorAction", [...this._getInfo(), "fill", text], { pageId: this.#pageId });
672
1411
  }
673
1412
  async type(text) {
674
- return __pw_invoke("locatorAction", [...this._getInfo(), "type", text]);
1413
+ return __pw_invoke("locatorAction", [...this._getInfo(), "type", text], { pageId: this.#pageId });
675
1414
  }
676
1415
  async check() {
677
- return __pw_invoke("locatorAction", [...this._getInfo(), "check", null]);
1416
+ return __pw_invoke("locatorAction", [...this._getInfo(), "check", null], { pageId: this.#pageId });
678
1417
  }
679
1418
  async uncheck() {
680
- return __pw_invoke("locatorAction", [...this._getInfo(), "uncheck", null]);
1419
+ return __pw_invoke("locatorAction", [...this._getInfo(), "uncheck", null], { pageId: this.#pageId });
681
1420
  }
682
1421
  async selectOption(value) {
683
- return __pw_invoke("locatorAction", [...this._getInfo(), "selectOption", value]);
1422
+ return __pw_invoke("locatorAction", [...this._getInfo(), "selectOption", value], { pageId: this.#pageId });
684
1423
  }
685
1424
  async clear() {
686
- return __pw_invoke("locatorAction", [...this._getInfo(), "clear", null]);
1425
+ return __pw_invoke("locatorAction", [...this._getInfo(), "clear", null], { pageId: this.#pageId });
687
1426
  }
688
1427
  async press(key) {
689
- return __pw_invoke("locatorAction", [...this._getInfo(), "press", key]);
1428
+ return __pw_invoke("locatorAction", [...this._getInfo(), "press", key], { pageId: this.#pageId });
690
1429
  }
691
1430
  async hover() {
692
- return __pw_invoke("locatorAction", [...this._getInfo(), "hover", null]);
1431
+ return __pw_invoke("locatorAction", [...this._getInfo(), "hover", null], { pageId: this.#pageId });
693
1432
  }
694
1433
  async focus() {
695
- return __pw_invoke("locatorAction", [...this._getInfo(), "focus", null]);
1434
+ return __pw_invoke("locatorAction", [...this._getInfo(), "focus", null], { pageId: this.#pageId });
696
1435
  }
697
1436
  async textContent() {
698
- return __pw_invoke("locatorAction", [...this._getInfo(), "getText", null]);
1437
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getText", null], { pageId: this.#pageId });
699
1438
  }
700
1439
  async inputValue() {
701
- return __pw_invoke("locatorAction", [...this._getInfo(), "getValue", null]);
1440
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getValue", null], { pageId: this.#pageId });
702
1441
  }
703
1442
  async isVisible() {
704
- return __pw_invoke("locatorAction", [...this._getInfo(), "isVisible", null]);
1443
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isVisible", null], { pageId: this.#pageId });
705
1444
  }
706
1445
  async isEnabled() {
707
- return __pw_invoke("locatorAction", [...this._getInfo(), "isEnabled", null]);
1446
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isEnabled", null], { pageId: this.#pageId });
708
1447
  }
709
1448
  async isChecked() {
710
- return __pw_invoke("locatorAction", [...this._getInfo(), "isChecked", null]);
1449
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isChecked", null], { pageId: this.#pageId });
711
1450
  }
712
1451
  async count() {
713
- return __pw_invoke("locatorAction", [...this._getInfo(), "count", null]);
1452
+ return __pw_invoke("locatorAction", [...this._getInfo(), "count", null], { pageId: this.#pageId });
714
1453
  }
715
1454
  async getAttribute(name) {
716
- return __pw_invoke("locatorAction", [...this._getInfo(), "getAttribute", name]);
1455
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getAttribute", name], { pageId: this.#pageId });
717
1456
  }
718
1457
  async isDisabled() {
719
- return __pw_invoke("locatorAction", [...this._getInfo(), "isDisabled", null]);
1458
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isDisabled", null], { pageId: this.#pageId });
720
1459
  }
721
1460
  async isHidden() {
722
- return __pw_invoke("locatorAction", [...this._getInfo(), "isHidden", null]);
1461
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isHidden", null], { pageId: this.#pageId });
723
1462
  }
724
1463
  async innerHTML() {
725
- return __pw_invoke("locatorAction", [...this._getInfo(), "innerHTML", null]);
1464
+ return __pw_invoke("locatorAction", [...this._getInfo(), "innerHTML", null], { pageId: this.#pageId });
726
1465
  }
727
1466
  async innerText() {
728
- return __pw_invoke("locatorAction", [...this._getInfo(), "innerText", null]);
1467
+ return __pw_invoke("locatorAction", [...this._getInfo(), "innerText", null], { pageId: this.#pageId });
729
1468
  }
730
1469
  async allTextContents() {
731
- return __pw_invoke("locatorAction", [...this._getInfo(), "allTextContents", null]);
1470
+ return __pw_invoke("locatorAction", [...this._getInfo(), "allTextContents", null], { pageId: this.#pageId });
732
1471
  }
733
1472
  async allInnerTexts() {
734
- return __pw_invoke("locatorAction", [...this._getInfo(), "allInnerTexts", null]);
1473
+ return __pw_invoke("locatorAction", [...this._getInfo(), "allInnerTexts", null], { pageId: this.#pageId });
735
1474
  }
736
1475
  async waitFor(options) {
737
- return __pw_invoke("locatorAction", [...this._getInfo(), "waitFor", options || {}]);
1476
+ return __pw_invoke("locatorAction", [...this._getInfo(), "waitFor", options || {}], { pageId: this.#pageId });
738
1477
  }
739
1478
  async boundingBox() {
740
- return __pw_invoke("locatorAction", [...this._getInfo(), "boundingBox", null]);
1479
+ return __pw_invoke("locatorAction", [...this._getInfo(), "boundingBox", null], { pageId: this.#pageId });
741
1480
  }
742
- locator(selector) {
743
- const parentSelector = this.#type === 'css' ? this.#value : null;
744
- if (parentSelector) {
745
- return new Locator("css", parentSelector + " " + selector, this.#options);
1481
+ async setInputFiles(files) {
1482
+ // Serialize files - if they have buffers, convert to base64
1483
+ let serializedFiles = files;
1484
+ if (Array.isArray(files) && files.length > 0 && typeof files[0] === 'object' && files[0].buffer) {
1485
+ serializedFiles = files.map(f => ({
1486
+ name: f.name,
1487
+ mimeType: f.mimeType,
1488
+ buffer: typeof f.buffer === 'string' ? f.buffer : btoa(String.fromCharCode(...new Uint8Array(f.buffer)))
1489
+ }));
746
1490
  }
747
- // For non-css locators, use css with the combined approach
748
- return new Locator("css", selector, this.#options);
1491
+ return __pw_invoke("locatorAction", [...this._getInfo(), "setInputFiles", serializedFiles], { pageId: this.#pageId });
1492
+ }
1493
+ async screenshot(options) {
1494
+ const base64 = await __pw_invoke("locatorAction", [...this._getInfo(), "screenshot", options || {}], { pageId: this.#pageId });
1495
+ return base64;
1496
+ }
1497
+ async dragTo(target) {
1498
+ const targetInfo = target._getInfo();
1499
+ return __pw_invoke("locatorAction", [...this._getInfo(), "dragTo", targetInfo], { pageId: this.#pageId });
1500
+ }
1501
+ async scrollIntoViewIfNeeded() {
1502
+ return __pw_invoke("locatorAction", [...this._getInfo(), "scrollIntoViewIfNeeded", null], { pageId: this.#pageId });
1503
+ }
1504
+ async highlight() {
1505
+ return __pw_invoke("locatorAction", [...this._getInfo(), "highlight", null], { pageId: this.#pageId });
1506
+ }
1507
+ async evaluate(fn, arg) {
1508
+ const fnString = typeof fn === 'function' ? fn.toString() : fn;
1509
+ return __pw_invoke("locatorAction", [...this._getInfo(), "evaluate", [fnString, arg]], { pageId: this.#pageId });
1510
+ }
1511
+ async evaluateAll(fn, arg) {
1512
+ const fnString = typeof fn === 'function' ? fn.toString() : fn;
1513
+ return __pw_invoke("locatorAction", [...this._getInfo(), "evaluateAll", [fnString, arg]], { pageId: this.#pageId });
1514
+ }
1515
+ locator(selector) {
1516
+ return this._chain("css", selector, null);
1517
+ }
1518
+ // Chaining: getBy* methods within a locator
1519
+ getByRole(role, options) {
1520
+ return this._chain("role", role, serializeOptions(options));
1521
+ }
1522
+ getByText(text) {
1523
+ return this._chain("text", text, null);
1524
+ }
1525
+ getByLabel(label) {
1526
+ return this._chain("label", label, null);
1527
+ }
1528
+ getByPlaceholder(placeholder) {
1529
+ return this._chain("placeholder", placeholder, null);
1530
+ }
1531
+ getByTestId(testId) {
1532
+ return this._chain("testId", testId, null);
1533
+ }
1534
+ getByAltText(altText) {
1535
+ return this._chain("altText", altText, null);
1536
+ }
1537
+ getByTitle(title) {
1538
+ return this._chain("title", title, null);
749
1539
  }
750
1540
  async all() {
751
1541
  const n = await this.count();
@@ -757,7 +1547,7 @@ async function setupPlaywright(context, options) {
757
1547
  }
758
1548
  nth(index) {
759
1549
  const existingOpts = this.#options ? JSON.parse(this.#options) : {};
760
- return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, nth: index }));
1550
+ return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, nth: index }), this.#pageId);
761
1551
  }
762
1552
  first() {
763
1553
  return this.nth(0);
@@ -777,13 +1567,28 @@ async function setupPlaywright(context, options) {
777
1567
  if (hasNotText && typeof hasNotText === 'object' && typeof hasNotText.source === 'string' && typeof hasNotText.flags === 'string') {
778
1568
  serializedFilter.hasNotText = { $regex: hasNotText.source, $flags: hasNotText.flags };
779
1569
  }
780
- return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, filter: serializedFilter }));
1570
+ // Serialize has/hasNot locators using duck-typing
1571
+ const has = options.has;
1572
+ if (has && typeof has === 'object' && typeof has._getInfo === 'function') {
1573
+ serializedFilter.has = { $locator: has._getInfo() };
1574
+ }
1575
+ const hasNot = options.hasNot;
1576
+ if (hasNot && typeof hasNot === 'object' && typeof hasNot._getInfo === 'function') {
1577
+ serializedFilter.hasNot = { $locator: hasNot._getInfo() };
1578
+ }
1579
+ return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, filter: serializedFilter }), this.#pageId);
781
1580
  }
782
1581
  or(other) {
783
1582
  // Create a composite locator that matches either this or other
784
1583
  const thisInfo = this._getInfo();
785
1584
  const otherInfo = other._getInfo();
786
- return new Locator("or", JSON.stringify([thisInfo, otherInfo]), null);
1585
+ return new Locator("or", JSON.stringify([thisInfo, otherInfo]), null, this.#pageId);
1586
+ }
1587
+ and(other) {
1588
+ // Create a composite locator that matches both this and other
1589
+ const thisInfo = this._getInfo();
1590
+ const otherInfo = other._getInfo();
1591
+ return new Locator("and", JSON.stringify([thisInfo, otherInfo]), null, this.#pageId);
787
1592
  }
788
1593
  }
789
1594
  globalThis.Locator = Locator;
@@ -794,84 +1599,157 @@ async function setupPlaywright(context, options) {
794
1599
  // Helper to create locator matchers
795
1600
  function createLocatorMatchers(locator, baseMatchers) {
796
1601
  const info = locator._getInfo();
1602
+ const pageId = locator._getPageId ? locator._getPageId() : "page_0";
1603
+
1604
+ // Helper for serializing regex values
1605
+ function serializeExpected(expected) {
1606
+ if (expected instanceof RegExp) {
1607
+ return { $regex: expected.source, $flags: expected.flags };
1608
+ }
1609
+ return expected;
1610
+ }
797
1611
 
798
1612
  const locatorMatchers = {
799
1613
  async toBeVisible(options) {
800
- return __pw_invoke("expectLocator", [...info, "toBeVisible", null, false, options?.timeout]);
1614
+ return __pw_invoke("expectLocator", [...info, "toBeVisible", null, false, options?.timeout], { pageId });
801
1615
  },
802
1616
  async toContainText(expected, options) {
803
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
804
- return __pw_invoke("expectLocator", [...info, "toContainText", serialized, false, options?.timeout]);
1617
+ return __pw_invoke("expectLocator", [...info, "toContainText", serializeExpected(expected), false, options?.timeout], { pageId });
805
1618
  },
806
1619
  async toHaveValue(expected, options) {
807
- return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, false, options?.timeout]);
1620
+ return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, false, options?.timeout], { pageId });
808
1621
  },
809
1622
  async toBeEnabled(options) {
810
- return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, false, options?.timeout]);
1623
+ return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, false, options?.timeout], { pageId });
811
1624
  },
812
1625
  async toBeChecked(options) {
813
- return __pw_invoke("expectLocator", [...info, "toBeChecked", null, false, options?.timeout]);
1626
+ return __pw_invoke("expectLocator", [...info, "toBeChecked", null, false, options?.timeout], { pageId });
814
1627
  },
815
1628
  async toHaveAttribute(name, value, options) {
816
- return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value }, false, options?.timeout]);
1629
+ return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value: serializeExpected(value) }, false, options?.timeout], { pageId });
817
1630
  },
818
1631
  async toHaveText(expected, options) {
819
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
820
- return __pw_invoke("expectLocator", [...info, "toHaveText", serialized, false, options?.timeout]);
1632
+ return __pw_invoke("expectLocator", [...info, "toHaveText", serializeExpected(expected), false, options?.timeout], { pageId });
821
1633
  },
822
1634
  async toHaveCount(count, options) {
823
- return __pw_invoke("expectLocator", [...info, "toHaveCount", count, false, options?.timeout]);
1635
+ return __pw_invoke("expectLocator", [...info, "toHaveCount", count, false, options?.timeout], { pageId });
824
1636
  },
825
1637
  async toBeHidden(options) {
826
- return __pw_invoke("expectLocator", [...info, "toBeHidden", null, false, options?.timeout]);
1638
+ return __pw_invoke("expectLocator", [...info, "toBeHidden", null, false, options?.timeout], { pageId });
827
1639
  },
828
1640
  async toBeDisabled(options) {
829
- return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, false, options?.timeout]);
1641
+ return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, false, options?.timeout], { pageId });
830
1642
  },
831
1643
  async toBeFocused(options) {
832
- return __pw_invoke("expectLocator", [...info, "toBeFocused", null, false, options?.timeout]);
1644
+ return __pw_invoke("expectLocator", [...info, "toBeFocused", null, false, options?.timeout], { pageId });
833
1645
  },
834
1646
  async toBeEmpty(options) {
835
- return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, false, options?.timeout]);
1647
+ return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, false, options?.timeout], { pageId });
1648
+ },
1649
+ // New matchers
1650
+ async toBeAttached(options) {
1651
+ return __pw_invoke("expectLocator", [...info, "toBeAttached", null, false, options?.timeout], { pageId });
1652
+ },
1653
+ async toBeEditable(options) {
1654
+ return __pw_invoke("expectLocator", [...info, "toBeEditable", null, false, options?.timeout], { pageId });
1655
+ },
1656
+ async toHaveClass(expected, options) {
1657
+ return __pw_invoke("expectLocator", [...info, "toHaveClass", serializeExpected(expected), false, options?.timeout], { pageId });
1658
+ },
1659
+ async toContainClass(expected, options) {
1660
+ return __pw_invoke("expectLocator", [...info, "toContainClass", expected, false, options?.timeout], { pageId });
1661
+ },
1662
+ async toHaveId(expected, options) {
1663
+ return __pw_invoke("expectLocator", [...info, "toHaveId", expected, false, options?.timeout], { pageId });
1664
+ },
1665
+ async toBeInViewport(options) {
1666
+ return __pw_invoke("expectLocator", [...info, "toBeInViewport", null, false, options?.timeout], { pageId });
1667
+ },
1668
+ async toHaveCSS(name, value, options) {
1669
+ return __pw_invoke("expectLocator", [...info, "toHaveCSS", { name, value: serializeExpected(value) }, false, options?.timeout], { pageId });
1670
+ },
1671
+ async toHaveJSProperty(name, value, options) {
1672
+ return __pw_invoke("expectLocator", [...info, "toHaveJSProperty", { name, value }, false, options?.timeout], { pageId });
1673
+ },
1674
+ async toHaveAccessibleName(expected, options) {
1675
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleName", serializeExpected(expected), false, options?.timeout], { pageId });
1676
+ },
1677
+ async toHaveAccessibleDescription(expected, options) {
1678
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleDescription", serializeExpected(expected), false, options?.timeout], { pageId });
1679
+ },
1680
+ async toHaveRole(expected, options) {
1681
+ return __pw_invoke("expectLocator", [...info, "toHaveRole", expected, false, options?.timeout], { pageId });
836
1682
  },
837
1683
  not: {
838
1684
  async toBeVisible(options) {
839
- return __pw_invoke("expectLocator", [...info, "toBeVisible", null, true, options?.timeout]);
1685
+ return __pw_invoke("expectLocator", [...info, "toBeVisible", null, true, options?.timeout], { pageId });
840
1686
  },
841
1687
  async toContainText(expected, options) {
842
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
843
- return __pw_invoke("expectLocator", [...info, "toContainText", serialized, true, options?.timeout]);
1688
+ return __pw_invoke("expectLocator", [...info, "toContainText", serializeExpected(expected), true, options?.timeout], { pageId });
844
1689
  },
845
1690
  async toHaveValue(expected, options) {
846
- return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, true, options?.timeout]);
1691
+ return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, true, options?.timeout], { pageId });
847
1692
  },
848
1693
  async toBeEnabled(options) {
849
- return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, true, options?.timeout]);
1694
+ return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, true, options?.timeout], { pageId });
850
1695
  },
851
1696
  async toBeChecked(options) {
852
- return __pw_invoke("expectLocator", [...info, "toBeChecked", null, true, options?.timeout]);
1697
+ return __pw_invoke("expectLocator", [...info, "toBeChecked", null, true, options?.timeout], { pageId });
853
1698
  },
854
1699
  async toHaveAttribute(name, value, options) {
855
- return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value }, true, options?.timeout]);
1700
+ return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value: serializeExpected(value) }, true, options?.timeout], { pageId });
856
1701
  },
857
1702
  async toHaveText(expected, options) {
858
- const serialized = expected instanceof RegExp ? { $regex: expected.source, $flags: expected.flags } : expected;
859
- return __pw_invoke("expectLocator", [...info, "toHaveText", serialized, true, options?.timeout]);
1703
+ return __pw_invoke("expectLocator", [...info, "toHaveText", serializeExpected(expected), true, options?.timeout], { pageId });
860
1704
  },
861
1705
  async toHaveCount(count, options) {
862
- return __pw_invoke("expectLocator", [...info, "toHaveCount", count, true, options?.timeout]);
1706
+ return __pw_invoke("expectLocator", [...info, "toHaveCount", count, true, options?.timeout], { pageId });
863
1707
  },
864
1708
  async toBeHidden(options) {
865
- return __pw_invoke("expectLocator", [...info, "toBeHidden", null, true, options?.timeout]);
1709
+ return __pw_invoke("expectLocator", [...info, "toBeHidden", null, true, options?.timeout], { pageId });
866
1710
  },
867
1711
  async toBeDisabled(options) {
868
- return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, true, options?.timeout]);
1712
+ return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, true, options?.timeout], { pageId });
869
1713
  },
870
1714
  async toBeFocused(options) {
871
- return __pw_invoke("expectLocator", [...info, "toBeFocused", null, true, options?.timeout]);
1715
+ return __pw_invoke("expectLocator", [...info, "toBeFocused", null, true, options?.timeout], { pageId });
872
1716
  },
873
1717
  async toBeEmpty(options) {
874
- return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, true, options?.timeout]);
1718
+ return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, true, options?.timeout], { pageId });
1719
+ },
1720
+ // New negated matchers
1721
+ async toBeAttached(options) {
1722
+ return __pw_invoke("expectLocator", [...info, "toBeAttached", null, true, options?.timeout], { pageId });
1723
+ },
1724
+ async toBeEditable(options) {
1725
+ return __pw_invoke("expectLocator", [...info, "toBeEditable", null, true, options?.timeout], { pageId });
1726
+ },
1727
+ async toHaveClass(expected, options) {
1728
+ return __pw_invoke("expectLocator", [...info, "toHaveClass", serializeExpected(expected), true, options?.timeout], { pageId });
1729
+ },
1730
+ async toContainClass(expected, options) {
1731
+ return __pw_invoke("expectLocator", [...info, "toContainClass", expected, true, options?.timeout], { pageId });
1732
+ },
1733
+ async toHaveId(expected, options) {
1734
+ return __pw_invoke("expectLocator", [...info, "toHaveId", expected, true, options?.timeout], { pageId });
1735
+ },
1736
+ async toBeInViewport(options) {
1737
+ return __pw_invoke("expectLocator", [...info, "toBeInViewport", null, true, options?.timeout], { pageId });
1738
+ },
1739
+ async toHaveCSS(name, value, options) {
1740
+ return __pw_invoke("expectLocator", [...info, "toHaveCSS", { name, value: serializeExpected(value) }, true, options?.timeout], { pageId });
1741
+ },
1742
+ async toHaveJSProperty(name, value, options) {
1743
+ return __pw_invoke("expectLocator", [...info, "toHaveJSProperty", { name, value }, true, options?.timeout], { pageId });
1744
+ },
1745
+ async toHaveAccessibleName(expected, options) {
1746
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleName", serializeExpected(expected), true, options?.timeout], { pageId });
1747
+ },
1748
+ async toHaveAccessibleDescription(expected, options) {
1749
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleDescription", serializeExpected(expected), true, options?.timeout], { pageId });
1750
+ },
1751
+ async toHaveRole(expected, options) {
1752
+ return __pw_invoke("expectLocator", [...info, "toHaveRole", expected, true, options?.timeout], { pageId });
875
1753
  },
876
1754
  }
877
1755
  };
@@ -887,6 +1765,44 @@ async function setupPlaywright(context, options) {
887
1765
  return locatorMatchers;
888
1766
  }
889
1767
 
1768
+ // Helper to create page matchers
1769
+ function createPageMatchers(page, baseMatchers) {
1770
+ const pageId = page.__pageId || "page_0";
1771
+
1772
+ function serializeExpected(expected) {
1773
+ if (expected instanceof RegExp) {
1774
+ return { $regex: expected.source, $flags: expected.flags };
1775
+ }
1776
+ return expected;
1777
+ }
1778
+
1779
+ const pageMatchers = {
1780
+ async toHaveURL(expected, options) {
1781
+ return __pw_invoke("expectPage", ["toHaveURL", serializeExpected(expected), false, options?.timeout], { pageId });
1782
+ },
1783
+ async toHaveTitle(expected, options) {
1784
+ return __pw_invoke("expectPage", ["toHaveTitle", serializeExpected(expected), false, options?.timeout], { pageId });
1785
+ },
1786
+ not: {
1787
+ async toHaveURL(expected, options) {
1788
+ return __pw_invoke("expectPage", ["toHaveURL", serializeExpected(expected), true, options?.timeout], { pageId });
1789
+ },
1790
+ async toHaveTitle(expected, options) {
1791
+ return __pw_invoke("expectPage", ["toHaveTitle", serializeExpected(expected), true, options?.timeout], { pageId });
1792
+ },
1793
+ }
1794
+ };
1795
+
1796
+ if (baseMatchers) {
1797
+ return {
1798
+ ...baseMatchers,
1799
+ ...pageMatchers,
1800
+ not: { ...baseMatchers.not, ...pageMatchers.not }
1801
+ };
1802
+ }
1803
+ return pageMatchers;
1804
+ }
1805
+
890
1806
  // Only extend expect if test-environment already defined it
891
1807
  if (typeof globalThis.expect === 'function') {
892
1808
  const originalExpect = globalThis.expect;
@@ -896,6 +1812,10 @@ async function setupPlaywright(context, options) {
896
1812
  if (actual && actual.constructor && actual.constructor.name === 'Locator') {
897
1813
  return createLocatorMatchers(actual, baseMatchers);
898
1814
  }
1815
+ // If actual is the page object (IsolatePage), add page-specific matchers
1816
+ if (actual && actual.__isPage === true) {
1817
+ return createPageMatchers(actual, baseMatchers);
1818
+ }
899
1819
  return baseMatchers;
900
1820
  };
901
1821
  }
@@ -931,7 +1851,10 @@ async function setupPlaywright(context, options) {
931
1851
  }
932
1852
  export {
933
1853
  setupPlaywright,
934
- createPlaywrightHandler
1854
+ getDefaultPlaywrightHandlerMetadata,
1855
+ defaultPlaywrightHandler,
1856
+ createPlaywrightHandler,
1857
+ DEFAULT_PLAYWRIGHT_HANDLER_META
935
1858
  };
936
1859
 
937
- //# debugId=6B90A150912AD17864756E2164756E21
1860
+ //# debugId=AFE0563DA4EAC06264756E2164756E21