@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.
@@ -43,10 +43,14 @@ var __export = (target, all) => {
43
43
  var exports_src = {};
44
44
  __export(exports_src, {
45
45
  setupPlaywright: () => setupPlaywright,
46
- createPlaywrightHandler: () => createPlaywrightHandler
46
+ getDefaultPlaywrightHandlerMetadata: () => getDefaultPlaywrightHandlerMetadata,
47
+ defaultPlaywrightHandler: () => defaultPlaywrightHandler,
48
+ createPlaywrightHandler: () => 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"));
53
+ var import_types = require("./types.cjs");
50
54
  function getLocator(page, selectorType, selectorValue, optionsJson) {
51
55
  const options = optionsJson ? JSON.parse(optionsJson) : undefined;
52
56
  const nthIndex = options?.nth;
@@ -85,6 +89,138 @@ function getLocator(page, selectorType, selectorValue, optionsJson) {
85
89
  locator = first.or(second);
86
90
  break;
87
91
  }
92
+ case "and": {
93
+ const [firstInfo, secondInfo] = JSON.parse(selectorValue);
94
+ const first = getLocator(page, firstInfo[0], firstInfo[1], firstInfo[2]);
95
+ const second = getLocator(page, secondInfo[0], secondInfo[1], secondInfo[2]);
96
+ locator = first.and(second);
97
+ break;
98
+ }
99
+ case "chained": {
100
+ const [parentInfo, childInfo] = JSON.parse(selectorValue);
101
+ const parent = getLocator(page, parentInfo[0], parentInfo[1], parentInfo[2]);
102
+ const childType = childInfo[0];
103
+ const childValue = childInfo[1];
104
+ const childOptionsJson = childInfo[2];
105
+ const childOptions = childOptionsJson ? JSON.parse(childOptionsJson) : undefined;
106
+ switch (childType) {
107
+ case "css":
108
+ locator = parent.locator(childValue);
109
+ break;
110
+ case "role": {
111
+ const roleOpts = childOptions ? { ...childOptions } : undefined;
112
+ if (roleOpts) {
113
+ delete roleOpts.nth;
114
+ delete roleOpts.filter;
115
+ if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
116
+ roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
117
+ }
118
+ }
119
+ locator = parent.getByRole(childValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
120
+ break;
121
+ }
122
+ case "text":
123
+ locator = parent.getByText(childValue);
124
+ break;
125
+ case "label":
126
+ locator = parent.getByLabel(childValue);
127
+ break;
128
+ case "placeholder":
129
+ locator = parent.getByPlaceholder(childValue);
130
+ break;
131
+ case "testId":
132
+ locator = parent.getByTestId(childValue);
133
+ break;
134
+ case "altText":
135
+ locator = parent.getByAltText(childValue);
136
+ break;
137
+ case "title":
138
+ locator = parent.getByTitle(childValue);
139
+ break;
140
+ default:
141
+ locator = parent.locator(childValue);
142
+ }
143
+ if (childOptions?.nth !== undefined) {
144
+ locator = locator.nth(childOptions.nth);
145
+ }
146
+ if (childOptions?.filter) {
147
+ const filterOpts = { ...childOptions.filter };
148
+ if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
149
+ filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
150
+ }
151
+ if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
152
+ filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
153
+ }
154
+ locator = locator.filter(filterOpts);
155
+ }
156
+ break;
157
+ }
158
+ case "altText":
159
+ locator = page.getByAltText(selectorValue);
160
+ break;
161
+ case "title":
162
+ locator = page.getByTitle(selectorValue);
163
+ break;
164
+ case "frame": {
165
+ const [frameSelectorInfo, innerLocatorInfo] = JSON.parse(selectorValue);
166
+ const frameSelector = frameSelectorInfo[1];
167
+ const frame = page.frameLocator(frameSelector);
168
+ const innerType = innerLocatorInfo[0];
169
+ const innerValue = innerLocatorInfo[1];
170
+ const innerOptionsJson = innerLocatorInfo[2];
171
+ const innerOptions = innerOptionsJson ? JSON.parse(innerOptionsJson) : undefined;
172
+ switch (innerType) {
173
+ case "css":
174
+ locator = frame.locator(innerValue);
175
+ break;
176
+ case "role": {
177
+ const roleOpts = innerOptions ? { ...innerOptions } : undefined;
178
+ if (roleOpts) {
179
+ delete roleOpts.nth;
180
+ delete roleOpts.filter;
181
+ if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
182
+ roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
183
+ }
184
+ }
185
+ locator = frame.getByRole(innerValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
186
+ break;
187
+ }
188
+ case "text":
189
+ locator = frame.getByText(innerValue);
190
+ break;
191
+ case "label":
192
+ locator = frame.getByLabel(innerValue);
193
+ break;
194
+ case "placeholder":
195
+ locator = frame.getByPlaceholder(innerValue);
196
+ break;
197
+ case "testId":
198
+ locator = frame.getByTestId(innerValue);
199
+ break;
200
+ case "altText":
201
+ locator = frame.getByAltText(innerValue);
202
+ break;
203
+ case "title":
204
+ locator = frame.getByTitle(innerValue);
205
+ break;
206
+ default:
207
+ locator = frame.locator(innerValue);
208
+ }
209
+ if (innerOptions?.nth !== undefined) {
210
+ locator = locator.nth(innerOptions.nth);
211
+ }
212
+ if (innerOptions?.filter) {
213
+ const filterOpts = { ...innerOptions.filter };
214
+ if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
215
+ filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
216
+ }
217
+ if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
218
+ filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
219
+ }
220
+ locator = locator.filter(filterOpts);
221
+ }
222
+ break;
223
+ }
88
224
  default:
89
225
  locator = page.locator(selectorValue);
90
226
  }
@@ -103,7 +239,7 @@ function getLocator(page, selectorType, selectorValue, optionsJson) {
103
239
  }
104
240
  return locator;
105
241
  }
106
- async function executeLocatorAction(locator, action, actionArg, timeout) {
242
+ async function executeLocatorAction(locator, action, actionArg, timeout, fileIO) {
107
243
  switch (action) {
108
244
  case "click":
109
245
  await locator.click({ timeout });
@@ -171,6 +307,69 @@ async function executeLocatorAction(locator, action, actionArg, timeout) {
171
307
  }
172
308
  case "boundingBox":
173
309
  return await locator.boundingBox({ timeout });
310
+ case "setInputFiles": {
311
+ const files = actionArg;
312
+ if (Array.isArray(files) && files.length > 0 && typeof files[0] === "object" && "buffer" in files[0]) {
313
+ const fileBuffers2 = files.map((f) => ({
314
+ name: f.name,
315
+ mimeType: f.mimeType,
316
+ buffer: Buffer.from(f.buffer, "base64")
317
+ }));
318
+ await locator.setInputFiles(fileBuffers2, { timeout });
319
+ return null;
320
+ }
321
+ const filePaths = Array.isArray(files) ? files : [files];
322
+ if (!fileIO?.readFile) {
323
+ throw new Error("setInputFiles() with file paths requires a readFile callback in defaultPlaywrightHandler options. " + "Either provide file data directly using { name, mimeType, buffer } format, or " + "configure a readFile callback to control file access from the isolate.");
324
+ }
325
+ const fileBuffers = await Promise.all(filePaths.map(async (filePath) => {
326
+ const fileData = await fileIO.readFile(filePath);
327
+ return {
328
+ name: fileData.name,
329
+ mimeType: fileData.mimeType,
330
+ buffer: fileData.buffer
331
+ };
332
+ }));
333
+ await locator.setInputFiles(fileBuffers, { timeout });
334
+ return null;
335
+ }
336
+ case "screenshot": {
337
+ const opts = actionArg;
338
+ const buffer = await locator.screenshot({
339
+ timeout,
340
+ type: opts?.type,
341
+ quality: opts?.quality
342
+ });
343
+ if (opts?.path) {
344
+ if (!fileIO?.writeFile) {
345
+ throw new Error("screenshot() with path option requires a writeFile callback in defaultPlaywrightHandler options. " + "Either omit the path option (screenshot returns base64 data), or " + "configure a writeFile callback to control file writing from the isolate.");
346
+ }
347
+ await fileIO.writeFile(opts.path, buffer);
348
+ }
349
+ return buffer.toString("base64");
350
+ }
351
+ case "dragTo": {
352
+ const targetInfo = actionArg;
353
+ const targetLocator = getLocator(locator.page(), targetInfo[0], targetInfo[1], targetInfo[2]);
354
+ await locator.dragTo(targetLocator, { timeout });
355
+ return null;
356
+ }
357
+ case "scrollIntoViewIfNeeded":
358
+ await locator.scrollIntoViewIfNeeded({ timeout });
359
+ return null;
360
+ case "highlight":
361
+ await locator.highlight();
362
+ return null;
363
+ case "evaluate": {
364
+ const [fnString, arg] = actionArg;
365
+ const fn = new Function("return (" + fnString + ")")();
366
+ return await locator.evaluate(fn, arg);
367
+ }
368
+ case "evaluateAll": {
369
+ const [fnString, arg] = actionArg;
370
+ const fn = new Function("return (" + fnString + ")")();
371
+ return await locator.evaluateAll(fn, arg);
372
+ }
174
373
  default:
175
374
  throw new Error(`Unknown action: ${action}`);
176
375
  }
@@ -347,71 +546,362 @@ async function executeExpectAssertion(locator, matcher, expected, negated, timeo
347
546
  }
348
547
  break;
349
548
  }
549
+ case "toBeAttached": {
550
+ const count = await locator.count();
551
+ const isAttached = count > 0;
552
+ if (negated) {
553
+ if (isAttached)
554
+ throw new Error("Expected element to not be attached to DOM, but it was");
555
+ } else {
556
+ if (!isAttached)
557
+ throw new Error("Expected element to be attached to DOM, but it was not");
558
+ }
559
+ break;
560
+ }
561
+ case "toBeEditable": {
562
+ const isEditable = await locator.isEditable({ timeout });
563
+ if (negated) {
564
+ if (isEditable)
565
+ throw new Error("Expected element to not be editable, but it was");
566
+ } else {
567
+ if (!isEditable)
568
+ throw new Error("Expected element to be editable, but it was not");
569
+ }
570
+ break;
571
+ }
572
+ case "toHaveClass": {
573
+ const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
574
+ const classes = classAttr.split(/\s+/).filter(Boolean);
575
+ let matches;
576
+ let expectedDisplay;
577
+ if (expected && typeof expected === "object" && expected.$regex) {
578
+ const regex = new RegExp(expected.$regex, expected.$flags);
579
+ matches = regex.test(classAttr);
580
+ expectedDisplay = String(regex);
581
+ } else if (Array.isArray(expected)) {
582
+ matches = expected.every((c) => classes.includes(c));
583
+ expectedDisplay = JSON.stringify(expected);
584
+ } else {
585
+ matches = classAttr === String(expected) || classes.includes(String(expected));
586
+ expectedDisplay = String(expected);
587
+ }
588
+ if (negated) {
589
+ if (matches)
590
+ throw new Error(`Expected class to not match ${expectedDisplay}, but got "${classAttr}"`);
591
+ } else {
592
+ if (!matches)
593
+ throw new Error(`Expected class to match ${expectedDisplay}, but got "${classAttr}"`);
594
+ }
595
+ break;
596
+ }
597
+ case "toContainClass": {
598
+ const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
599
+ const classes = classAttr.split(/\s+/).filter(Boolean);
600
+ const expectedClass = String(expected);
601
+ const hasClass = classes.includes(expectedClass);
602
+ if (negated) {
603
+ if (hasClass)
604
+ throw new Error(`Expected element to not contain class "${expectedClass}", but it does`);
605
+ } else {
606
+ if (!hasClass)
607
+ throw new Error(`Expected element to contain class "${expectedClass}", but classes are "${classAttr}"`);
608
+ }
609
+ break;
610
+ }
611
+ case "toHaveId": {
612
+ const id = await locator.getAttribute("id", { timeout });
613
+ const matches = id === String(expected);
614
+ if (negated) {
615
+ if (matches)
616
+ throw new Error(`Expected id to not be "${expected}", but it was`);
617
+ } else {
618
+ if (!matches)
619
+ throw new Error(`Expected id to be "${expected}", but got "${id}"`);
620
+ }
621
+ break;
622
+ }
623
+ case "toBeInViewport": {
624
+ const isInViewport = await locator.evaluate((el) => {
625
+ const rect = el.getBoundingClientRect();
626
+ return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
627
+ });
628
+ if (negated) {
629
+ if (isInViewport)
630
+ throw new Error("Expected element to not be in viewport, but it was");
631
+ } else {
632
+ if (!isInViewport)
633
+ throw new Error("Expected element to be in viewport, but it was not");
634
+ }
635
+ break;
636
+ }
637
+ case "toHaveCSS": {
638
+ const { name, value } = expected;
639
+ const actual = await locator.evaluate((el, propName) => {
640
+ return getComputedStyle(el).getPropertyValue(propName);
641
+ }, name);
642
+ let matches;
643
+ if (value && typeof value === "object" && value.$regex) {
644
+ const regex = new RegExp(value.$regex, value.$flags);
645
+ matches = regex.test(actual);
646
+ } else {
647
+ matches = actual === String(value);
648
+ }
649
+ if (negated) {
650
+ if (matches)
651
+ throw new Error(`Expected CSS "${name}" to not be "${value}", but it was`);
652
+ } else {
653
+ if (!matches)
654
+ throw new Error(`Expected CSS "${name}" to be "${value}", but got "${actual}"`);
655
+ }
656
+ break;
657
+ }
658
+ case "toHaveJSProperty": {
659
+ const { name, value } = expected;
660
+ const actual = await locator.evaluate((el, propName) => {
661
+ return el[propName];
662
+ }, name);
663
+ const matches = JSON.stringify(actual) === JSON.stringify(value);
664
+ if (negated) {
665
+ if (matches)
666
+ throw new Error(`Expected JS property "${name}" to not be ${JSON.stringify(value)}, but it was`);
667
+ } else {
668
+ if (!matches)
669
+ throw new Error(`Expected JS property "${name}" to be ${JSON.stringify(value)}, but got ${JSON.stringify(actual)}`);
670
+ }
671
+ break;
672
+ }
673
+ case "toHaveAccessibleName": {
674
+ const accessibleName = await locator.evaluate((el) => {
675
+ return el.getAttribute("aria-label") || el.getAttribute("aria-labelledby") || el.innerText || "";
676
+ });
677
+ let matches;
678
+ if (expected && typeof expected === "object" && expected.$regex) {
679
+ const regex = new RegExp(expected.$regex, expected.$flags);
680
+ matches = regex.test(accessibleName);
681
+ } else {
682
+ matches = accessibleName === String(expected);
683
+ }
684
+ if (negated) {
685
+ if (matches)
686
+ throw new Error(`Expected accessible name to not be "${expected}", but it was`);
687
+ } else {
688
+ if (!matches)
689
+ throw new Error(`Expected accessible name to be "${expected}", but got "${accessibleName}"`);
690
+ }
691
+ break;
692
+ }
693
+ case "toHaveAccessibleDescription": {
694
+ const accessibleDesc = await locator.evaluate((el) => {
695
+ const describedby = el.getAttribute("aria-describedby");
696
+ if (describedby) {
697
+ const descEl = document.getElementById(describedby);
698
+ return descEl?.textContent || "";
699
+ }
700
+ return el.getAttribute("aria-description") || "";
701
+ });
702
+ let matches;
703
+ if (expected && typeof expected === "object" && expected.$regex) {
704
+ const regex = new RegExp(expected.$regex, expected.$flags);
705
+ matches = regex.test(accessibleDesc);
706
+ } else {
707
+ matches = accessibleDesc === String(expected);
708
+ }
709
+ if (negated) {
710
+ if (matches)
711
+ throw new Error(`Expected accessible description to not be "${expected}", but it was`);
712
+ } else {
713
+ if (!matches)
714
+ throw new Error(`Expected accessible description to be "${expected}", but got "${accessibleDesc}"`);
715
+ }
716
+ break;
717
+ }
718
+ case "toHaveRole": {
719
+ const role = await locator.evaluate((el) => {
720
+ return el.getAttribute("role") || el.tagName.toLowerCase();
721
+ });
722
+ const matches = role === String(expected);
723
+ if (negated) {
724
+ if (matches)
725
+ throw new Error(`Expected role to not be "${expected}", but it was`);
726
+ } else {
727
+ if (!matches)
728
+ throw new Error(`Expected role to be "${expected}", but got "${role}"`);
729
+ }
730
+ break;
731
+ }
350
732
  default:
351
733
  throw new Error(`Unknown matcher: ${matcher}`);
352
734
  }
353
735
  }
736
+ async function executePageExpectAssertion(page, matcher, expected, negated, timeout) {
737
+ let expectedValue = expected;
738
+ if (expected && typeof expected === "object" && expected.$regex) {
739
+ expectedValue = new RegExp(expected.$regex, expected.$flags);
740
+ }
741
+ switch (matcher) {
742
+ case "toHaveURL": {
743
+ const expectedUrl = expectedValue;
744
+ const startTime = Date.now();
745
+ let lastUrl = "";
746
+ while (Date.now() - startTime < timeout) {
747
+ lastUrl = page.url();
748
+ const matches = expectedUrl instanceof RegExp ? expectedUrl.test(lastUrl) : lastUrl === expectedUrl;
749
+ if (negated ? !matches : matches)
750
+ return;
751
+ await new Promise((r) => setTimeout(r, 100));
752
+ }
753
+ if (negated) {
754
+ throw new Error(`Expected URL to not match "${expectedUrl}", but got "${lastUrl}"`);
755
+ } else {
756
+ throw new Error(`Expected URL to be "${expectedUrl}", but got "${lastUrl}"`);
757
+ }
758
+ }
759
+ case "toHaveTitle": {
760
+ const expectedTitle = expectedValue;
761
+ const startTime = Date.now();
762
+ let lastTitle = "";
763
+ while (Date.now() - startTime < timeout) {
764
+ lastTitle = await page.title();
765
+ const matches = expectedTitle instanceof RegExp ? expectedTitle.test(lastTitle) : lastTitle === expectedTitle;
766
+ if (negated ? !matches : matches)
767
+ return;
768
+ await new Promise((r) => setTimeout(r, 100));
769
+ }
770
+ if (negated) {
771
+ throw new Error(`Expected title to not match "${expectedTitle}", but got "${lastTitle}"`);
772
+ } else {
773
+ throw new Error(`Expected title to be "${expectedTitle}", but got "${lastTitle}"`);
774
+ }
775
+ }
776
+ default:
777
+ throw new Error(`Unknown page matcher: ${matcher}`);
778
+ }
779
+ }
354
780
  function createPlaywrightHandler(page, options) {
355
781
  const timeout = options?.timeout ?? 30000;
782
+ const fileIO = {
783
+ readFile: options?.readFile,
784
+ writeFile: options?.writeFile
785
+ };
786
+ const registry = {
787
+ pages: new Map([["page_0", page]]),
788
+ contexts: new Map([["ctx_0", page.context()]]),
789
+ nextPageId: 1,
790
+ nextContextId: 1
791
+ };
356
792
  return async (op) => {
357
793
  try {
794
+ switch (op.type) {
795
+ case "newContext": {
796
+ if (!options?.createContext) {
797
+ return { ok: false, error: { name: "Error", message: "createContext callback not provided. Configure createContext in playwright options to enable browser.newContext()." } };
798
+ }
799
+ const [contextOptions] = op.args;
800
+ const newContext = await options.createContext(contextOptions);
801
+ const contextId2 = `ctx_${registry.nextContextId++}`;
802
+ registry.contexts.set(contextId2, newContext);
803
+ return { ok: true, value: { contextId: contextId2 } };
804
+ }
805
+ case "newPage": {
806
+ if (!options?.createPage) {
807
+ return { ok: false, error: { name: "Error", message: "createPage callback not provided. Configure createPage in playwright options to enable context.newPage()." } };
808
+ }
809
+ const contextId2 = op.contextId ?? "ctx_0";
810
+ const targetContext2 = registry.contexts.get(contextId2);
811
+ if (!targetContext2) {
812
+ return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
813
+ }
814
+ const newPage = await options.createPage(targetContext2);
815
+ const pageId2 = `page_${registry.nextPageId++}`;
816
+ registry.pages.set(pageId2, newPage);
817
+ return { ok: true, value: { pageId: pageId2 } };
818
+ }
819
+ case "closeContext": {
820
+ const contextId2 = op.contextId ?? "ctx_0";
821
+ const context = registry.contexts.get(contextId2);
822
+ if (!context) {
823
+ return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
824
+ }
825
+ await context.close();
826
+ registry.contexts.delete(contextId2);
827
+ for (const [pid, p] of registry.pages) {
828
+ if (p.context() === context) {
829
+ registry.pages.delete(pid);
830
+ }
831
+ }
832
+ return { ok: true };
833
+ }
834
+ }
835
+ const pageId = op.pageId ?? "page_0";
836
+ const targetPage = registry.pages.get(pageId);
837
+ if (!targetPage) {
838
+ return { ok: false, error: { name: "Error", message: `Page ${pageId} not found` } };
839
+ }
840
+ const contextId = op.contextId ?? "ctx_0";
841
+ const targetContext = registry.contexts.get(contextId);
358
842
  switch (op.type) {
359
843
  case "goto": {
360
844
  const [url, waitUntil] = op.args;
361
- await page.goto(url, {
845
+ await targetPage.goto(url, {
362
846
  timeout,
363
847
  waitUntil: waitUntil ?? "load"
364
848
  });
365
849
  return { ok: true };
366
850
  }
367
851
  case "reload":
368
- await page.reload({ timeout });
852
+ await targetPage.reload({ timeout });
369
853
  return { ok: true };
370
854
  case "url":
371
- return { ok: true, value: page.url() };
855
+ return { ok: true, value: targetPage.url() };
372
856
  case "title":
373
- return { ok: true, value: await page.title() };
857
+ return { ok: true, value: await targetPage.title() };
374
858
  case "content":
375
- return { ok: true, value: await page.content() };
859
+ return { ok: true, value: await targetPage.content() };
376
860
  case "waitForSelector": {
377
861
  const [selector, optionsJson] = op.args;
378
862
  const opts = optionsJson ? JSON.parse(optionsJson) : {};
379
- await page.waitForSelector(selector, { timeout, ...opts });
863
+ await targetPage.waitForSelector(selector, { timeout, ...opts });
380
864
  return { ok: true };
381
865
  }
382
866
  case "waitForTimeout": {
383
867
  const [ms] = op.args;
384
- await page.waitForTimeout(ms);
868
+ await targetPage.waitForTimeout(ms);
385
869
  return { ok: true };
386
870
  }
387
871
  case "waitForLoadState": {
388
872
  const [state] = op.args;
389
- await page.waitForLoadState(state ?? "load", { timeout });
873
+ await targetPage.waitForLoadState(state ?? "load", { timeout });
390
874
  return { ok: true };
391
875
  }
392
876
  case "evaluate": {
393
877
  const [script, arg] = op.args;
394
878
  if (op.args.length > 1) {
395
879
  const fn = new Function("return (" + script + ")")();
396
- const result2 = await page.evaluate(fn, arg);
880
+ const result2 = await targetPage.evaluate(fn, arg);
397
881
  return { ok: true, value: result2 };
398
882
  }
399
- const result = await page.evaluate(script);
883
+ const result = await targetPage.evaluate(script);
400
884
  return { ok: true, value: result };
401
885
  }
402
886
  case "locatorAction": {
403
887
  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);
888
+ const locator = getLocator(targetPage, selectorType, selectorValue, roleOptions);
889
+ const result = await executeLocatorAction(locator, action, actionArg, timeout, fileIO);
406
890
  return { ok: true, value: result };
407
891
  }
408
892
  case "expectLocator": {
409
893
  const [selectorType, selectorValue, roleOptions, matcher, expected, negated, customTimeout] = op.args;
410
- const locator = getLocator(page, selectorType, selectorValue, roleOptions);
894
+ const locator = getLocator(targetPage, selectorType, selectorValue, roleOptions);
411
895
  const effectiveTimeout = customTimeout ?? timeout;
412
896
  await executeExpectAssertion(locator, matcher, expected, negated ?? false, effectiveTimeout);
413
897
  return { ok: true };
414
898
  }
899
+ case "expectPage": {
900
+ const [matcher, expected, negated, customTimeout] = op.args;
901
+ const effectiveTimeout = customTimeout ?? timeout;
902
+ await executePageExpectAssertion(targetPage, matcher, expected, negated ?? false, effectiveTimeout);
903
+ return { ok: true };
904
+ }
415
905
  case "request": {
416
906
  const [url, method, data, headers] = op.args;
417
907
  const requestOptions = {
@@ -423,7 +913,7 @@ function createPlaywrightHandler(page, options) {
423
913
  if (data !== undefined && data !== null) {
424
914
  requestOptions.data = data;
425
915
  }
426
- const response = await page.request.fetch(url, {
916
+ const response = await targetPage.request.fetch(url, {
427
917
  method,
428
918
  ...requestOptions
429
919
  });
@@ -446,7 +936,7 @@ function createPlaywrightHandler(page, options) {
446
936
  }
447
937
  case "goBack": {
448
938
  const [waitUntil] = op.args;
449
- await page.goBack({
939
+ await targetPage.goBack({
450
940
  timeout,
451
941
  waitUntil: waitUntil ?? "load"
452
942
  });
@@ -454,22 +944,156 @@ function createPlaywrightHandler(page, options) {
454
944
  }
455
945
  case "goForward": {
456
946
  const [waitUntil] = op.args;
457
- await page.goForward({
947
+ await targetPage.goForward({
458
948
  timeout,
459
949
  waitUntil: waitUntil ?? "load"
460
950
  });
461
951
  return { ok: true };
462
952
  }
463
953
  case "waitForURL": {
464
- const [url, customTimeout, waitUntil] = op.args;
465
- await page.waitForURL(url, {
954
+ const [urlArg, customTimeout, waitUntil] = op.args;
955
+ const url = urlArg && typeof urlArg === "object" && "$regex" in urlArg ? new RegExp(urlArg.$regex, urlArg.$flags) : urlArg;
956
+ await targetPage.waitForURL(url, {
466
957
  timeout: customTimeout ?? timeout,
467
958
  waitUntil: waitUntil ?? undefined
468
959
  });
469
960
  return { ok: true };
470
961
  }
471
962
  case "clearCookies": {
472
- await page.context().clearCookies();
963
+ const ctx = targetContext ?? targetPage.context();
964
+ await ctx.clearCookies();
965
+ return { ok: true };
966
+ }
967
+ case "screenshot": {
968
+ const [screenshotOptions] = op.args;
969
+ const buffer = await targetPage.screenshot({
970
+ type: screenshotOptions?.type,
971
+ quality: screenshotOptions?.quality,
972
+ fullPage: screenshotOptions?.fullPage,
973
+ clip: screenshotOptions?.clip
974
+ });
975
+ if (screenshotOptions?.path) {
976
+ if (!fileIO.writeFile) {
977
+ 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.");
978
+ }
979
+ await fileIO.writeFile(screenshotOptions.path, buffer);
980
+ }
981
+ return { ok: true, value: buffer.toString("base64") };
982
+ }
983
+ case "setViewportSize": {
984
+ const [size] = op.args;
985
+ await targetPage.setViewportSize(size);
986
+ return { ok: true };
987
+ }
988
+ case "viewportSize": {
989
+ return { ok: true, value: targetPage.viewportSize() };
990
+ }
991
+ case "keyboardType": {
992
+ const [text, typeOptions] = op.args;
993
+ await targetPage.keyboard.type(text, typeOptions);
994
+ return { ok: true };
995
+ }
996
+ case "keyboardPress": {
997
+ const [key, pressOptions] = op.args;
998
+ await targetPage.keyboard.press(key, pressOptions);
999
+ return { ok: true };
1000
+ }
1001
+ case "keyboardDown": {
1002
+ const [key] = op.args;
1003
+ await targetPage.keyboard.down(key);
1004
+ return { ok: true };
1005
+ }
1006
+ case "keyboardUp": {
1007
+ const [key] = op.args;
1008
+ await targetPage.keyboard.up(key);
1009
+ return { ok: true };
1010
+ }
1011
+ case "keyboardInsertText": {
1012
+ const [text] = op.args;
1013
+ await targetPage.keyboard.insertText(text);
1014
+ return { ok: true };
1015
+ }
1016
+ case "mouseMove": {
1017
+ const [x, y, moveOptions] = op.args;
1018
+ await targetPage.mouse.move(x, y, moveOptions);
1019
+ return { ok: true };
1020
+ }
1021
+ case "mouseClick": {
1022
+ const [x, y, clickOptions] = op.args;
1023
+ await targetPage.mouse.click(x, y, clickOptions);
1024
+ return { ok: true };
1025
+ }
1026
+ case "mouseDown": {
1027
+ const [downOptions] = op.args;
1028
+ await targetPage.mouse.down(downOptions);
1029
+ return { ok: true };
1030
+ }
1031
+ case "mouseUp": {
1032
+ const [upOptions] = op.args;
1033
+ await targetPage.mouse.up(upOptions);
1034
+ return { ok: true };
1035
+ }
1036
+ case "mouseWheel": {
1037
+ const [deltaX, deltaY] = op.args;
1038
+ await targetPage.mouse.wheel(deltaX, deltaY);
1039
+ return { ok: true };
1040
+ }
1041
+ case "frames": {
1042
+ const frames = targetPage.frames();
1043
+ return { ok: true, value: frames.map((f) => ({ name: f.name(), url: f.url() })) };
1044
+ }
1045
+ case "mainFrame": {
1046
+ const mainFrame = targetPage.mainFrame();
1047
+ return { ok: true, value: { name: mainFrame.name(), url: mainFrame.url() } };
1048
+ }
1049
+ case "bringToFront": {
1050
+ await targetPage.bringToFront();
1051
+ return { ok: true };
1052
+ }
1053
+ case "close": {
1054
+ await targetPage.close();
1055
+ registry.pages.delete(pageId);
1056
+ return { ok: true };
1057
+ }
1058
+ case "isClosed": {
1059
+ return { ok: true, value: targetPage.isClosed() };
1060
+ }
1061
+ case "pdf": {
1062
+ const [pdfOptions] = op.args;
1063
+ const { path: pdfPath, ...restPdfOptions } = pdfOptions ?? {};
1064
+ const buffer = await targetPage.pdf(restPdfOptions);
1065
+ if (pdfPath) {
1066
+ if (!fileIO.writeFile) {
1067
+ throw new Error("pdf() with path option requires a writeFile callback to be provided. " + "Either provide a writeFile callback in defaultPlaywrightHandler options, or omit the path option " + "and handle the returned base64 data yourself.");
1068
+ }
1069
+ await fileIO.writeFile(pdfPath, buffer);
1070
+ }
1071
+ return { ok: true, value: buffer.toString("base64") };
1072
+ }
1073
+ case "emulateMedia": {
1074
+ const [mediaOptions] = op.args;
1075
+ await targetPage.emulateMedia(mediaOptions);
1076
+ return { ok: true };
1077
+ }
1078
+ case "addCookies": {
1079
+ const [cookies] = op.args;
1080
+ const ctx = targetContext ?? targetPage.context();
1081
+ await ctx.addCookies(cookies);
1082
+ return { ok: true };
1083
+ }
1084
+ case "cookies": {
1085
+ const [urls] = op.args;
1086
+ const ctx = targetContext ?? targetPage.context();
1087
+ const cookies = await ctx.cookies(urls);
1088
+ return { ok: true, value: cookies };
1089
+ }
1090
+ case "setExtraHTTPHeaders": {
1091
+ const [headers] = op.args;
1092
+ await targetPage.setExtraHTTPHeaders(headers);
1093
+ return { ok: true };
1094
+ }
1095
+ case "pause": {
1096
+ await targetPage.pause();
473
1097
  return { ok: true };
474
1098
  }
475
1099
  default:
@@ -481,11 +1105,27 @@ function createPlaywrightHandler(page, options) {
481
1105
  }
482
1106
  };
483
1107
  }
1108
+ function defaultPlaywrightHandler(page, options) {
1109
+ const handler = createPlaywrightHandler(page, options);
1110
+ handler[import_types.DEFAULT_PLAYWRIGHT_HANDLER_META] = { page, options };
1111
+ return handler;
1112
+ }
1113
+ function getDefaultPlaywrightHandlerMetadata(handler) {
1114
+ return handler[import_types.DEFAULT_PLAYWRIGHT_HANDLER_META];
1115
+ }
484
1116
  async function setupPlaywright(context, options) {
485
1117
  const timeout = options.timeout ?? 30000;
486
- const page = "page" in options ? options.page : undefined;
1118
+ const explicitPage = "page" in options ? options.page : undefined;
487
1119
  const handler = "handler" in options ? options.handler : undefined;
488
- const effectiveHandler = handler ?? (page ? createPlaywrightHandler(page, { timeout }) : undefined);
1120
+ const handlerMetadata = handler ? getDefaultPlaywrightHandlerMetadata(handler) : undefined;
1121
+ const page = explicitPage ?? handlerMetadata?.page;
1122
+ const createPage = "createPage" in options ? options.createPage : undefined;
1123
+ const createContext = "createContext" in options ? options.createContext : undefined;
1124
+ const effectiveHandler = handler ?? (page ? createPlaywrightHandler(page, {
1125
+ timeout,
1126
+ createPage,
1127
+ createContext
1128
+ }) : undefined);
489
1129
  if (!effectiveHandler) {
490
1130
  throw new Error("Either page or handler must be provided to setupPlaywright");
491
1131
  }
@@ -572,8 +1212,8 @@ async function setupPlaywright(context, options) {
572
1212
  }));
573
1213
  context.evalSync(`
574
1214
  (function() {
575
- globalThis.__pw_invoke = async function(type, args) {
576
- const op = JSON.stringify({ type, args });
1215
+ globalThis.__pw_invoke = async function(type, args, options) {
1216
+ const op = JSON.stringify({ type, args, pageId: options?.pageId, contextId: options?.contextId });
577
1217
  const resultJson = __Playwright_handler_ref.applySyncPromise(undefined, [op]);
578
1218
  const result = JSON.parse(resultJson);
579
1219
  if (result.ok) {
@@ -588,211 +1228,362 @@ async function setupPlaywright(context, options) {
588
1228
  `);
589
1229
  context.evalSync(`
590
1230
  (function() {
591
- let __pw_currentUrl = '';
592
- globalThis.page = {
1231
+ // IsolatePage class - represents a page with a specific pageId
1232
+ class IsolatePage {
1233
+ #pageId; #contextId; #currentUrl = '';
1234
+ constructor(pageId, contextId) {
1235
+ this.#pageId = pageId;
1236
+ this.#contextId = contextId;
1237
+ }
1238
+ get __isPage() { return true; }
1239
+ get __pageId() { return this.#pageId; }
1240
+ get __contextId() { return this.#contextId; }
1241
+
593
1242
  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
- },
1243
+ await __pw_invoke("goto", [url, options?.waitUntil || null], { pageId: this.#pageId });
1244
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1245
+ this.#currentUrl = resolvedUrl || url;
1246
+ }
599
1247
  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
- },
1248
+ await __pw_invoke("reload", [], { pageId: this.#pageId });
1249
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1250
+ if (resolvedUrl) this.#currentUrl = resolvedUrl;
1251
+ }
1252
+ url() { return this.#currentUrl; }
1253
+ async title() { return __pw_invoke("title", [], { pageId: this.#pageId }); }
1254
+ async content() { return __pw_invoke("content", [], { pageId: this.#pageId }); }
614
1255
  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
- },
1256
+ return __pw_invoke("waitForSelector", [selector, options ? JSON.stringify(options) : null], { pageId: this.#pageId });
1257
+ }
1258
+ async waitForTimeout(ms) { return __pw_invoke("waitForTimeout", [ms], { pageId: this.#pageId }); }
1259
+ async waitForLoadState(state) { return __pw_invoke("waitForLoadState", [state || null], { pageId: this.#pageId }); }
623
1260
  async evaluate(script, arg) {
624
1261
  const hasArg = arguments.length > 1;
625
1262
  if (hasArg) {
626
1263
  const serialized = typeof script === "function" ? script.toString() : script;
627
- return __pw_invoke("evaluate", [serialized, arg]);
1264
+ return __pw_invoke("evaluate", [serialized, arg], { pageId: this.#pageId });
628
1265
  }
629
1266
  const serialized = typeof script === "function" ? "(" + script.toString() + ")()" : script;
630
- return __pw_invoke("evaluate", [serialized]);
631
- },
632
- locator(selector) { return new Locator("css", selector, null); },
1267
+ return __pw_invoke("evaluate", [serialized], { pageId: this.#pageId });
1268
+ }
1269
+ locator(selector) { return new Locator("css", selector, null, this.#pageId); }
633
1270
  getByRole(role, options) {
634
1271
  if (options) {
635
1272
  const serialized = { ...options };
636
- // Use duck-typing RegExp detection (instanceof fails across isolated-vm boundary)
637
1273
  const name = options.name;
638
1274
  if (name && typeof name === 'object' && typeof name.source === 'string' && typeof name.flags === 'string') {
639
1275
  serialized.name = { $regex: name.source, $flags: name.flags };
640
1276
  }
641
- return new Locator("role", role, JSON.stringify(serialized));
1277
+ return new Locator("role", role, JSON.stringify(serialized), this.#pageId);
642
1278
  }
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); },
1279
+ return new Locator("role", role, null, this.#pageId);
1280
+ }
1281
+ getByText(text) { return new Locator("text", text, null, this.#pageId); }
1282
+ getByLabel(label) { return new Locator("label", label, null, this.#pageId); }
1283
+ getByPlaceholder(p) { return new Locator("placeholder", p, null, this.#pageId); }
1284
+ getByTestId(id) { return new Locator("testId", id, null, this.#pageId); }
1285
+ getByAltText(alt) { return new Locator("altText", alt, null, this.#pageId); }
1286
+ getByTitle(title) { return new Locator("title", title, null, this.#pageId); }
1287
+ frameLocator(selector) {
1288
+ const pageId = this.#pageId;
1289
+ return {
1290
+ locator(innerSelector) { return new Locator("frame", JSON.stringify([["css", selector, null], ["css", innerSelector, null]]), null, pageId); },
1291
+ getByRole(role, options) { return new Locator("frame", JSON.stringify([["css", selector, null], ["role", role, options ? JSON.stringify(options) : null]]), null, pageId); },
1292
+ getByText(text) { return new Locator("frame", JSON.stringify([["css", selector, null], ["text", text, null]]), null, pageId); },
1293
+ getByLabel(label) { return new Locator("frame", JSON.stringify([["css", selector, null], ["label", label, null]]), null, pageId); },
1294
+ getByPlaceholder(placeholder) { return new Locator("frame", JSON.stringify([["css", selector, null], ["placeholder", placeholder, null]]), null, pageId); },
1295
+ getByTestId(testId) { return new Locator("frame", JSON.stringify([["css", selector, null], ["testId", testId, null]]), null, pageId); },
1296
+ getByAltText(alt) { return new Locator("frame", JSON.stringify([["css", selector, null], ["altText", alt, null]]), null, pageId); },
1297
+ getByTitle(title) { return new Locator("frame", JSON.stringify([["css", selector, null], ["title", title, null]]), null, pageId); },
1298
+ };
1299
+ }
649
1300
  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
- },
1301
+ await __pw_invoke("goBack", [options?.waitUntil || null], { pageId: this.#pageId });
1302
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1303
+ if (resolvedUrl) this.#currentUrl = resolvedUrl;
1304
+ }
654
1305
  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
- },
1306
+ await __pw_invoke("goForward", [options?.waitUntil || null], { pageId: this.#pageId });
1307
+ const resolvedUrl = await __pw_invoke("url", [], { pageId: this.#pageId });
1308
+ if (resolvedUrl) this.#currentUrl = resolvedUrl;
1309
+ }
659
1310
  async waitForURL(url, options) {
660
- return __pw_invoke("waitForURL", [url, options?.timeout || null, options?.waitUntil || null]);
661
- },
1311
+ let serializedUrl = url;
1312
+ if (url && typeof url === 'object' && typeof url.source === 'string' && typeof url.flags === 'string') {
1313
+ serializedUrl = { $regex: url.source, $flags: url.flags };
1314
+ }
1315
+ return __pw_invoke("waitForURL", [serializedUrl, options?.timeout || null, options?.waitUntil || null], { pageId: this.#pageId });
1316
+ }
662
1317
  context() {
1318
+ const contextId = this.#contextId;
1319
+ return new IsolateContext(contextId);
1320
+ }
1321
+ async click(selector) { return this.locator(selector).click(); }
1322
+ async fill(selector, value) { return this.locator(selector).fill(value); }
1323
+ async textContent(selector) { return this.locator(selector).textContent(); }
1324
+ async innerText(selector) { return this.locator(selector).innerText(); }
1325
+ async innerHTML(selector) { return this.locator(selector).innerHTML(); }
1326
+ async getAttribute(selector, name) { return this.locator(selector).getAttribute(name); }
1327
+ async inputValue(selector) { return this.locator(selector).inputValue(); }
1328
+ async isVisible(selector) { return this.locator(selector).isVisible(); }
1329
+ async isEnabled(selector) { return this.locator(selector).isEnabled(); }
1330
+ async isChecked(selector) { return this.locator(selector).isChecked(); }
1331
+ async isHidden(selector) { return this.locator(selector).isHidden(); }
1332
+ async isDisabled(selector) { return this.locator(selector).isDisabled(); }
1333
+ async screenshot(options) { return __pw_invoke("screenshot", [options || {}], { pageId: this.#pageId }); }
1334
+ async setViewportSize(size) { return __pw_invoke("setViewportSize", [size], { pageId: this.#pageId }); }
1335
+ async viewportSize() { return __pw_invoke("viewportSize", [], { pageId: this.#pageId }); }
1336
+ async emulateMedia(options) { return __pw_invoke("emulateMedia", [options], { pageId: this.#pageId }); }
1337
+ async setExtraHTTPHeaders(headers) { return __pw_invoke("setExtraHTTPHeaders", [headers], { pageId: this.#pageId }); }
1338
+ async bringToFront() { return __pw_invoke("bringToFront", [], { pageId: this.#pageId }); }
1339
+ async close() { return __pw_invoke("close", [], { pageId: this.#pageId }); }
1340
+ async isClosed() { return __pw_invoke("isClosed", [], { pageId: this.#pageId }); }
1341
+ async pdf(options) { return __pw_invoke("pdf", [options || {}], { pageId: this.#pageId }); }
1342
+ async pause() { return __pw_invoke("pause", [], { pageId: this.#pageId }); }
1343
+ async frames() { return __pw_invoke("frames", [], { pageId: this.#pageId }); }
1344
+ async mainFrame() { return __pw_invoke("mainFrame", [], { pageId: this.#pageId }); }
1345
+ get keyboard() {
1346
+ const pageId = this.#pageId;
663
1347
  return {
664
- async clearCookies() {
665
- return __pw_invoke("clearCookies", []);
666
- }
1348
+ async type(text, options) { return __pw_invoke("keyboardType", [text, options], { pageId }); },
1349
+ async press(key, options) { return __pw_invoke("keyboardPress", [key, options], { pageId }); },
1350
+ async down(key) { return __pw_invoke("keyboardDown", [key], { pageId }); },
1351
+ async up(key) { return __pw_invoke("keyboardUp", [key], { pageId }); },
1352
+ async insertText(text) { return __pw_invoke("keyboardInsertText", [text], { pageId }); }
667
1353
  };
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]);
674
- 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,
681
- };
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
- },
1354
+ }
1355
+ get mouse() {
1356
+ const pageId = this.#pageId;
1357
+ return {
1358
+ async move(x, y, options) { return __pw_invoke("mouseMove", [x, y, options], { pageId }); },
1359
+ async click(x, y, options) { return __pw_invoke("mouseClick", [x, y, options], { pageId }); },
1360
+ async down(options) { return __pw_invoke("mouseDown", [options], { pageId }); },
1361
+ async up(options) { return __pw_invoke("mouseUp", [options], { pageId }); },
1362
+ async wheel(deltaX, deltaY) { return __pw_invoke("mouseWheel", [deltaX, deltaY], { pageId }); }
1363
+ };
1364
+ }
1365
+ get request() {
1366
+ const pageId = this.#pageId;
1367
+ return {
1368
+ async fetch(url, options) {
1369
+ const result = await __pw_invoke("request", [url, options?.method || "GET", options?.data, options?.headers], { pageId });
1370
+ return {
1371
+ status: () => result.status,
1372
+ ok: () => result.ok,
1373
+ headers: () => result.headers,
1374
+ json: async () => result.json,
1375
+ text: async () => result.text,
1376
+ body: async () => result.body,
1377
+ };
1378
+ },
1379
+ async get(url, options) { return this.fetch(url, { ...options, method: "GET" }); },
1380
+ async post(url, options) { return this.fetch(url, { ...options, method: "POST" }); },
1381
+ async put(url, options) { return this.fetch(url, { ...options, method: "PUT" }); },
1382
+ async delete(url, options) { return this.fetch(url, { ...options, method: "DELETE" }); },
1383
+ };
1384
+ }
1385
+ }
1386
+ globalThis.IsolatePage = IsolatePage;
1387
+
1388
+ // IsolateContext class - represents a browser context with a specific contextId
1389
+ class IsolateContext {
1390
+ #contextId;
1391
+ constructor(contextId) { this.#contextId = contextId; }
1392
+ get __contextId() { return this.#contextId; }
1393
+
1394
+ async newPage() {
1395
+ const result = await __pw_invoke("newPage", [], { contextId: this.#contextId });
1396
+ return new IsolatePage(result.pageId, this.#contextId);
1397
+ }
1398
+ async close() { return __pw_invoke("closeContext", [], { contextId: this.#contextId }); }
1399
+ async clearCookies() { return __pw_invoke("clearCookies", [], { contextId: this.#contextId }); }
1400
+ async addCookies(cookies) { return __pw_invoke("addCookies", [cookies], { contextId: this.#contextId }); }
1401
+ async cookies(urls) { return __pw_invoke("cookies", [urls], { contextId: this.#contextId }); }
1402
+ }
1403
+ globalThis.IsolateContext = IsolateContext;
1404
+
1405
+ // browser global - for creating new contexts
1406
+ globalThis.browser = {
1407
+ async newContext(options) {
1408
+ const result = await __pw_invoke("newContext", [options || null]);
1409
+ return new IsolateContext(result.contextId);
1410
+ }
696
1411
  };
1412
+
1413
+ // context global - represents the default context
1414
+ globalThis.context = new IsolateContext("ctx_0");
1415
+
1416
+ // page global - represents the default page
1417
+ globalThis.page = new IsolatePage("page_0", "ctx_0");
697
1418
  })();
698
1419
  `);
699
1420
  context.evalSync(`
700
1421
  (function() {
1422
+ // Helper to serialize options including RegExp
1423
+ function serializeOptions(options) {
1424
+ if (!options) return null;
1425
+ const serialized = { ...options };
1426
+ if (options.name && typeof options.name === 'object' && typeof options.name.source === 'string' && typeof options.name.flags === 'string') {
1427
+ serialized.name = { $regex: options.name.source, $flags: options.name.flags };
1428
+ }
1429
+ return JSON.stringify(serialized);
1430
+ }
1431
+
701
1432
  class Locator {
702
- #type; #value; #options;
703
- constructor(type, value, options) {
1433
+ #type; #value; #options; #pageId;
1434
+ constructor(type, value, options, pageId) {
704
1435
  this.#type = type;
705
1436
  this.#value = value;
706
1437
  this.#options = options;
1438
+ this.#pageId = pageId || "page_0";
707
1439
  }
708
1440
 
709
1441
  _getInfo() { return [this.#type, this.#value, this.#options]; }
1442
+ _getPageId() { return this.#pageId; }
1443
+
1444
+ // Helper to create a chained locator
1445
+ _chain(childType, childValue, childOptions) {
1446
+ const parentInfo = this._getInfo();
1447
+ const childInfo = [childType, childValue, childOptions];
1448
+ return new Locator("chained", JSON.stringify([parentInfo, childInfo]), null, this.#pageId);
1449
+ }
710
1450
 
711
1451
  async click() {
712
- return __pw_invoke("locatorAction", [...this._getInfo(), "click", null]);
1452
+ return __pw_invoke("locatorAction", [...this._getInfo(), "click", null], { pageId: this.#pageId });
713
1453
  }
714
1454
  async dblclick() {
715
- return __pw_invoke("locatorAction", [...this._getInfo(), "dblclick", null]);
1455
+ return __pw_invoke("locatorAction", [...this._getInfo(), "dblclick", null], { pageId: this.#pageId });
716
1456
  }
717
1457
  async fill(text) {
718
- return __pw_invoke("locatorAction", [...this._getInfo(), "fill", text]);
1458
+ return __pw_invoke("locatorAction", [...this._getInfo(), "fill", text], { pageId: this.#pageId });
719
1459
  }
720
1460
  async type(text) {
721
- return __pw_invoke("locatorAction", [...this._getInfo(), "type", text]);
1461
+ return __pw_invoke("locatorAction", [...this._getInfo(), "type", text], { pageId: this.#pageId });
722
1462
  }
723
1463
  async check() {
724
- return __pw_invoke("locatorAction", [...this._getInfo(), "check", null]);
1464
+ return __pw_invoke("locatorAction", [...this._getInfo(), "check", null], { pageId: this.#pageId });
725
1465
  }
726
1466
  async uncheck() {
727
- return __pw_invoke("locatorAction", [...this._getInfo(), "uncheck", null]);
1467
+ return __pw_invoke("locatorAction", [...this._getInfo(), "uncheck", null], { pageId: this.#pageId });
728
1468
  }
729
1469
  async selectOption(value) {
730
- return __pw_invoke("locatorAction", [...this._getInfo(), "selectOption", value]);
1470
+ return __pw_invoke("locatorAction", [...this._getInfo(), "selectOption", value], { pageId: this.#pageId });
731
1471
  }
732
1472
  async clear() {
733
- return __pw_invoke("locatorAction", [...this._getInfo(), "clear", null]);
1473
+ return __pw_invoke("locatorAction", [...this._getInfo(), "clear", null], { pageId: this.#pageId });
734
1474
  }
735
1475
  async press(key) {
736
- return __pw_invoke("locatorAction", [...this._getInfo(), "press", key]);
1476
+ return __pw_invoke("locatorAction", [...this._getInfo(), "press", key], { pageId: this.#pageId });
737
1477
  }
738
1478
  async hover() {
739
- return __pw_invoke("locatorAction", [...this._getInfo(), "hover", null]);
1479
+ return __pw_invoke("locatorAction", [...this._getInfo(), "hover", null], { pageId: this.#pageId });
740
1480
  }
741
1481
  async focus() {
742
- return __pw_invoke("locatorAction", [...this._getInfo(), "focus", null]);
1482
+ return __pw_invoke("locatorAction", [...this._getInfo(), "focus", null], { pageId: this.#pageId });
743
1483
  }
744
1484
  async textContent() {
745
- return __pw_invoke("locatorAction", [...this._getInfo(), "getText", null]);
1485
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getText", null], { pageId: this.#pageId });
746
1486
  }
747
1487
  async inputValue() {
748
- return __pw_invoke("locatorAction", [...this._getInfo(), "getValue", null]);
1488
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getValue", null], { pageId: this.#pageId });
749
1489
  }
750
1490
  async isVisible() {
751
- return __pw_invoke("locatorAction", [...this._getInfo(), "isVisible", null]);
1491
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isVisible", null], { pageId: this.#pageId });
752
1492
  }
753
1493
  async isEnabled() {
754
- return __pw_invoke("locatorAction", [...this._getInfo(), "isEnabled", null]);
1494
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isEnabled", null], { pageId: this.#pageId });
755
1495
  }
756
1496
  async isChecked() {
757
- return __pw_invoke("locatorAction", [...this._getInfo(), "isChecked", null]);
1497
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isChecked", null], { pageId: this.#pageId });
758
1498
  }
759
1499
  async count() {
760
- return __pw_invoke("locatorAction", [...this._getInfo(), "count", null]);
1500
+ return __pw_invoke("locatorAction", [...this._getInfo(), "count", null], { pageId: this.#pageId });
761
1501
  }
762
1502
  async getAttribute(name) {
763
- return __pw_invoke("locatorAction", [...this._getInfo(), "getAttribute", name]);
1503
+ return __pw_invoke("locatorAction", [...this._getInfo(), "getAttribute", name], { pageId: this.#pageId });
764
1504
  }
765
1505
  async isDisabled() {
766
- return __pw_invoke("locatorAction", [...this._getInfo(), "isDisabled", null]);
1506
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isDisabled", null], { pageId: this.#pageId });
767
1507
  }
768
1508
  async isHidden() {
769
- return __pw_invoke("locatorAction", [...this._getInfo(), "isHidden", null]);
1509
+ return __pw_invoke("locatorAction", [...this._getInfo(), "isHidden", null], { pageId: this.#pageId });
770
1510
  }
771
1511
  async innerHTML() {
772
- return __pw_invoke("locatorAction", [...this._getInfo(), "innerHTML", null]);
1512
+ return __pw_invoke("locatorAction", [...this._getInfo(), "innerHTML", null], { pageId: this.#pageId });
773
1513
  }
774
1514
  async innerText() {
775
- return __pw_invoke("locatorAction", [...this._getInfo(), "innerText", null]);
1515
+ return __pw_invoke("locatorAction", [...this._getInfo(), "innerText", null], { pageId: this.#pageId });
776
1516
  }
777
1517
  async allTextContents() {
778
- return __pw_invoke("locatorAction", [...this._getInfo(), "allTextContents", null]);
1518
+ return __pw_invoke("locatorAction", [...this._getInfo(), "allTextContents", null], { pageId: this.#pageId });
779
1519
  }
780
1520
  async allInnerTexts() {
781
- return __pw_invoke("locatorAction", [...this._getInfo(), "allInnerTexts", null]);
1521
+ return __pw_invoke("locatorAction", [...this._getInfo(), "allInnerTexts", null], { pageId: this.#pageId });
782
1522
  }
783
1523
  async waitFor(options) {
784
- return __pw_invoke("locatorAction", [...this._getInfo(), "waitFor", options || {}]);
1524
+ return __pw_invoke("locatorAction", [...this._getInfo(), "waitFor", options || {}], { pageId: this.#pageId });
785
1525
  }
786
1526
  async boundingBox() {
787
- return __pw_invoke("locatorAction", [...this._getInfo(), "boundingBox", null]);
1527
+ return __pw_invoke("locatorAction", [...this._getInfo(), "boundingBox", null], { pageId: this.#pageId });
788
1528
  }
789
- locator(selector) {
790
- const parentSelector = this.#type === 'css' ? this.#value : null;
791
- if (parentSelector) {
792
- return new Locator("css", parentSelector + " " + selector, this.#options);
1529
+ async setInputFiles(files) {
1530
+ // Serialize files - if they have buffers, convert to base64
1531
+ let serializedFiles = files;
1532
+ if (Array.isArray(files) && files.length > 0 && typeof files[0] === 'object' && files[0].buffer) {
1533
+ serializedFiles = files.map(f => ({
1534
+ name: f.name,
1535
+ mimeType: f.mimeType,
1536
+ buffer: typeof f.buffer === 'string' ? f.buffer : btoa(String.fromCharCode(...new Uint8Array(f.buffer)))
1537
+ }));
793
1538
  }
794
- // For non-css locators, use css with the combined approach
795
- return new Locator("css", selector, this.#options);
1539
+ return __pw_invoke("locatorAction", [...this._getInfo(), "setInputFiles", serializedFiles], { pageId: this.#pageId });
1540
+ }
1541
+ async screenshot(options) {
1542
+ const base64 = await __pw_invoke("locatorAction", [...this._getInfo(), "screenshot", options || {}], { pageId: this.#pageId });
1543
+ return base64;
1544
+ }
1545
+ async dragTo(target) {
1546
+ const targetInfo = target._getInfo();
1547
+ return __pw_invoke("locatorAction", [...this._getInfo(), "dragTo", targetInfo], { pageId: this.#pageId });
1548
+ }
1549
+ async scrollIntoViewIfNeeded() {
1550
+ return __pw_invoke("locatorAction", [...this._getInfo(), "scrollIntoViewIfNeeded", null], { pageId: this.#pageId });
1551
+ }
1552
+ async highlight() {
1553
+ return __pw_invoke("locatorAction", [...this._getInfo(), "highlight", null], { pageId: this.#pageId });
1554
+ }
1555
+ async evaluate(fn, arg) {
1556
+ const fnString = typeof fn === 'function' ? fn.toString() : fn;
1557
+ return __pw_invoke("locatorAction", [...this._getInfo(), "evaluate", [fnString, arg]], { pageId: this.#pageId });
1558
+ }
1559
+ async evaluateAll(fn, arg) {
1560
+ const fnString = typeof fn === 'function' ? fn.toString() : fn;
1561
+ return __pw_invoke("locatorAction", [...this._getInfo(), "evaluateAll", [fnString, arg]], { pageId: this.#pageId });
1562
+ }
1563
+ locator(selector) {
1564
+ return this._chain("css", selector, null);
1565
+ }
1566
+ // Chaining: getBy* methods within a locator
1567
+ getByRole(role, options) {
1568
+ return this._chain("role", role, serializeOptions(options));
1569
+ }
1570
+ getByText(text) {
1571
+ return this._chain("text", text, null);
1572
+ }
1573
+ getByLabel(label) {
1574
+ return this._chain("label", label, null);
1575
+ }
1576
+ getByPlaceholder(placeholder) {
1577
+ return this._chain("placeholder", placeholder, null);
1578
+ }
1579
+ getByTestId(testId) {
1580
+ return this._chain("testId", testId, null);
1581
+ }
1582
+ getByAltText(altText) {
1583
+ return this._chain("altText", altText, null);
1584
+ }
1585
+ getByTitle(title) {
1586
+ return this._chain("title", title, null);
796
1587
  }
797
1588
  async all() {
798
1589
  const n = await this.count();
@@ -804,7 +1595,7 @@ async function setupPlaywright(context, options) {
804
1595
  }
805
1596
  nth(index) {
806
1597
  const existingOpts = this.#options ? JSON.parse(this.#options) : {};
807
- return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, nth: index }));
1598
+ return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, nth: index }), this.#pageId);
808
1599
  }
809
1600
  first() {
810
1601
  return this.nth(0);
@@ -824,13 +1615,28 @@ async function setupPlaywright(context, options) {
824
1615
  if (hasNotText && typeof hasNotText === 'object' && typeof hasNotText.source === 'string' && typeof hasNotText.flags === 'string') {
825
1616
  serializedFilter.hasNotText = { $regex: hasNotText.source, $flags: hasNotText.flags };
826
1617
  }
827
- return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, filter: serializedFilter }));
1618
+ // Serialize has/hasNot locators using duck-typing
1619
+ const has = options.has;
1620
+ if (has && typeof has === 'object' && typeof has._getInfo === 'function') {
1621
+ serializedFilter.has = { $locator: has._getInfo() };
1622
+ }
1623
+ const hasNot = options.hasNot;
1624
+ if (hasNot && typeof hasNot === 'object' && typeof hasNot._getInfo === 'function') {
1625
+ serializedFilter.hasNot = { $locator: hasNot._getInfo() };
1626
+ }
1627
+ return new Locator(this.#type, this.#value, JSON.stringify({ ...existingOpts, filter: serializedFilter }), this.#pageId);
828
1628
  }
829
1629
  or(other) {
830
1630
  // Create a composite locator that matches either this or other
831
1631
  const thisInfo = this._getInfo();
832
1632
  const otherInfo = other._getInfo();
833
- return new Locator("or", JSON.stringify([thisInfo, otherInfo]), null);
1633
+ return new Locator("or", JSON.stringify([thisInfo, otherInfo]), null, this.#pageId);
1634
+ }
1635
+ and(other) {
1636
+ // Create a composite locator that matches both this and other
1637
+ const thisInfo = this._getInfo();
1638
+ const otherInfo = other._getInfo();
1639
+ return new Locator("and", JSON.stringify([thisInfo, otherInfo]), null, this.#pageId);
834
1640
  }
835
1641
  }
836
1642
  globalThis.Locator = Locator;
@@ -841,84 +1647,157 @@ async function setupPlaywright(context, options) {
841
1647
  // Helper to create locator matchers
842
1648
  function createLocatorMatchers(locator, baseMatchers) {
843
1649
  const info = locator._getInfo();
1650
+ const pageId = locator._getPageId ? locator._getPageId() : "page_0";
1651
+
1652
+ // Helper for serializing regex values
1653
+ function serializeExpected(expected) {
1654
+ if (expected instanceof RegExp) {
1655
+ return { $regex: expected.source, $flags: expected.flags };
1656
+ }
1657
+ return expected;
1658
+ }
844
1659
 
845
1660
  const locatorMatchers = {
846
1661
  async toBeVisible(options) {
847
- return __pw_invoke("expectLocator", [...info, "toBeVisible", null, false, options?.timeout]);
1662
+ return __pw_invoke("expectLocator", [...info, "toBeVisible", null, false, options?.timeout], { pageId });
848
1663
  },
849
1664
  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]);
1665
+ return __pw_invoke("expectLocator", [...info, "toContainText", serializeExpected(expected), false, options?.timeout], { pageId });
852
1666
  },
853
1667
  async toHaveValue(expected, options) {
854
- return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, false, options?.timeout]);
1668
+ return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, false, options?.timeout], { pageId });
855
1669
  },
856
1670
  async toBeEnabled(options) {
857
- return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, false, options?.timeout]);
1671
+ return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, false, options?.timeout], { pageId });
858
1672
  },
859
1673
  async toBeChecked(options) {
860
- return __pw_invoke("expectLocator", [...info, "toBeChecked", null, false, options?.timeout]);
1674
+ return __pw_invoke("expectLocator", [...info, "toBeChecked", null, false, options?.timeout], { pageId });
861
1675
  },
862
1676
  async toHaveAttribute(name, value, options) {
863
- return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value }, false, options?.timeout]);
1677
+ return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value: serializeExpected(value) }, false, options?.timeout], { pageId });
864
1678
  },
865
1679
  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]);
1680
+ return __pw_invoke("expectLocator", [...info, "toHaveText", serializeExpected(expected), false, options?.timeout], { pageId });
868
1681
  },
869
1682
  async toHaveCount(count, options) {
870
- return __pw_invoke("expectLocator", [...info, "toHaveCount", count, false, options?.timeout]);
1683
+ return __pw_invoke("expectLocator", [...info, "toHaveCount", count, false, options?.timeout], { pageId });
871
1684
  },
872
1685
  async toBeHidden(options) {
873
- return __pw_invoke("expectLocator", [...info, "toBeHidden", null, false, options?.timeout]);
1686
+ return __pw_invoke("expectLocator", [...info, "toBeHidden", null, false, options?.timeout], { pageId });
874
1687
  },
875
1688
  async toBeDisabled(options) {
876
- return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, false, options?.timeout]);
1689
+ return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, false, options?.timeout], { pageId });
877
1690
  },
878
1691
  async toBeFocused(options) {
879
- return __pw_invoke("expectLocator", [...info, "toBeFocused", null, false, options?.timeout]);
1692
+ return __pw_invoke("expectLocator", [...info, "toBeFocused", null, false, options?.timeout], { pageId });
880
1693
  },
881
1694
  async toBeEmpty(options) {
882
- return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, false, options?.timeout]);
1695
+ return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, false, options?.timeout], { pageId });
1696
+ },
1697
+ // New matchers
1698
+ async toBeAttached(options) {
1699
+ return __pw_invoke("expectLocator", [...info, "toBeAttached", null, false, options?.timeout], { pageId });
1700
+ },
1701
+ async toBeEditable(options) {
1702
+ return __pw_invoke("expectLocator", [...info, "toBeEditable", null, false, options?.timeout], { pageId });
1703
+ },
1704
+ async toHaveClass(expected, options) {
1705
+ return __pw_invoke("expectLocator", [...info, "toHaveClass", serializeExpected(expected), false, options?.timeout], { pageId });
1706
+ },
1707
+ async toContainClass(expected, options) {
1708
+ return __pw_invoke("expectLocator", [...info, "toContainClass", expected, false, options?.timeout], { pageId });
1709
+ },
1710
+ async toHaveId(expected, options) {
1711
+ return __pw_invoke("expectLocator", [...info, "toHaveId", expected, false, options?.timeout], { pageId });
1712
+ },
1713
+ async toBeInViewport(options) {
1714
+ return __pw_invoke("expectLocator", [...info, "toBeInViewport", null, false, options?.timeout], { pageId });
1715
+ },
1716
+ async toHaveCSS(name, value, options) {
1717
+ return __pw_invoke("expectLocator", [...info, "toHaveCSS", { name, value: serializeExpected(value) }, false, options?.timeout], { pageId });
1718
+ },
1719
+ async toHaveJSProperty(name, value, options) {
1720
+ return __pw_invoke("expectLocator", [...info, "toHaveJSProperty", { name, value }, false, options?.timeout], { pageId });
1721
+ },
1722
+ async toHaveAccessibleName(expected, options) {
1723
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleName", serializeExpected(expected), false, options?.timeout], { pageId });
1724
+ },
1725
+ async toHaveAccessibleDescription(expected, options) {
1726
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleDescription", serializeExpected(expected), false, options?.timeout], { pageId });
1727
+ },
1728
+ async toHaveRole(expected, options) {
1729
+ return __pw_invoke("expectLocator", [...info, "toHaveRole", expected, false, options?.timeout], { pageId });
883
1730
  },
884
1731
  not: {
885
1732
  async toBeVisible(options) {
886
- return __pw_invoke("expectLocator", [...info, "toBeVisible", null, true, options?.timeout]);
1733
+ return __pw_invoke("expectLocator", [...info, "toBeVisible", null, true, options?.timeout], { pageId });
887
1734
  },
888
1735
  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]);
1736
+ return __pw_invoke("expectLocator", [...info, "toContainText", serializeExpected(expected), true, options?.timeout], { pageId });
891
1737
  },
892
1738
  async toHaveValue(expected, options) {
893
- return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, true, options?.timeout]);
1739
+ return __pw_invoke("expectLocator", [...info, "toHaveValue", expected, true, options?.timeout], { pageId });
894
1740
  },
895
1741
  async toBeEnabled(options) {
896
- return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, true, options?.timeout]);
1742
+ return __pw_invoke("expectLocator", [...info, "toBeEnabled", null, true, options?.timeout], { pageId });
897
1743
  },
898
1744
  async toBeChecked(options) {
899
- return __pw_invoke("expectLocator", [...info, "toBeChecked", null, true, options?.timeout]);
1745
+ return __pw_invoke("expectLocator", [...info, "toBeChecked", null, true, options?.timeout], { pageId });
900
1746
  },
901
1747
  async toHaveAttribute(name, value, options) {
902
- return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value }, true, options?.timeout]);
1748
+ return __pw_invoke("expectLocator", [...info, "toHaveAttribute", { name, value: serializeExpected(value) }, true, options?.timeout], { pageId });
903
1749
  },
904
1750
  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]);
1751
+ return __pw_invoke("expectLocator", [...info, "toHaveText", serializeExpected(expected), true, options?.timeout], { pageId });
907
1752
  },
908
1753
  async toHaveCount(count, options) {
909
- return __pw_invoke("expectLocator", [...info, "toHaveCount", count, true, options?.timeout]);
1754
+ return __pw_invoke("expectLocator", [...info, "toHaveCount", count, true, options?.timeout], { pageId });
910
1755
  },
911
1756
  async toBeHidden(options) {
912
- return __pw_invoke("expectLocator", [...info, "toBeHidden", null, true, options?.timeout]);
1757
+ return __pw_invoke("expectLocator", [...info, "toBeHidden", null, true, options?.timeout], { pageId });
913
1758
  },
914
1759
  async toBeDisabled(options) {
915
- return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, true, options?.timeout]);
1760
+ return __pw_invoke("expectLocator", [...info, "toBeDisabled", null, true, options?.timeout], { pageId });
916
1761
  },
917
1762
  async toBeFocused(options) {
918
- return __pw_invoke("expectLocator", [...info, "toBeFocused", null, true, options?.timeout]);
1763
+ return __pw_invoke("expectLocator", [...info, "toBeFocused", null, true, options?.timeout], { pageId });
919
1764
  },
920
1765
  async toBeEmpty(options) {
921
- return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, true, options?.timeout]);
1766
+ return __pw_invoke("expectLocator", [...info, "toBeEmpty", null, true, options?.timeout], { pageId });
1767
+ },
1768
+ // New negated matchers
1769
+ async toBeAttached(options) {
1770
+ return __pw_invoke("expectLocator", [...info, "toBeAttached", null, true, options?.timeout], { pageId });
1771
+ },
1772
+ async toBeEditable(options) {
1773
+ return __pw_invoke("expectLocator", [...info, "toBeEditable", null, true, options?.timeout], { pageId });
1774
+ },
1775
+ async toHaveClass(expected, options) {
1776
+ return __pw_invoke("expectLocator", [...info, "toHaveClass", serializeExpected(expected), true, options?.timeout], { pageId });
1777
+ },
1778
+ async toContainClass(expected, options) {
1779
+ return __pw_invoke("expectLocator", [...info, "toContainClass", expected, true, options?.timeout], { pageId });
1780
+ },
1781
+ async toHaveId(expected, options) {
1782
+ return __pw_invoke("expectLocator", [...info, "toHaveId", expected, true, options?.timeout], { pageId });
1783
+ },
1784
+ async toBeInViewport(options) {
1785
+ return __pw_invoke("expectLocator", [...info, "toBeInViewport", null, true, options?.timeout], { pageId });
1786
+ },
1787
+ async toHaveCSS(name, value, options) {
1788
+ return __pw_invoke("expectLocator", [...info, "toHaveCSS", { name, value: serializeExpected(value) }, true, options?.timeout], { pageId });
1789
+ },
1790
+ async toHaveJSProperty(name, value, options) {
1791
+ return __pw_invoke("expectLocator", [...info, "toHaveJSProperty", { name, value }, true, options?.timeout], { pageId });
1792
+ },
1793
+ async toHaveAccessibleName(expected, options) {
1794
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleName", serializeExpected(expected), true, options?.timeout], { pageId });
1795
+ },
1796
+ async toHaveAccessibleDescription(expected, options) {
1797
+ return __pw_invoke("expectLocator", [...info, "toHaveAccessibleDescription", serializeExpected(expected), true, options?.timeout], { pageId });
1798
+ },
1799
+ async toHaveRole(expected, options) {
1800
+ return __pw_invoke("expectLocator", [...info, "toHaveRole", expected, true, options?.timeout], { pageId });
922
1801
  },
923
1802
  }
924
1803
  };
@@ -934,6 +1813,44 @@ async function setupPlaywright(context, options) {
934
1813
  return locatorMatchers;
935
1814
  }
936
1815
 
1816
+ // Helper to create page matchers
1817
+ function createPageMatchers(page, baseMatchers) {
1818
+ const pageId = page.__pageId || "page_0";
1819
+
1820
+ function serializeExpected(expected) {
1821
+ if (expected instanceof RegExp) {
1822
+ return { $regex: expected.source, $flags: expected.flags };
1823
+ }
1824
+ return expected;
1825
+ }
1826
+
1827
+ const pageMatchers = {
1828
+ async toHaveURL(expected, options) {
1829
+ return __pw_invoke("expectPage", ["toHaveURL", serializeExpected(expected), false, options?.timeout], { pageId });
1830
+ },
1831
+ async toHaveTitle(expected, options) {
1832
+ return __pw_invoke("expectPage", ["toHaveTitle", serializeExpected(expected), false, options?.timeout], { pageId });
1833
+ },
1834
+ not: {
1835
+ async toHaveURL(expected, options) {
1836
+ return __pw_invoke("expectPage", ["toHaveURL", serializeExpected(expected), true, options?.timeout], { pageId });
1837
+ },
1838
+ async toHaveTitle(expected, options) {
1839
+ return __pw_invoke("expectPage", ["toHaveTitle", serializeExpected(expected), true, options?.timeout], { pageId });
1840
+ },
1841
+ }
1842
+ };
1843
+
1844
+ if (baseMatchers) {
1845
+ return {
1846
+ ...baseMatchers,
1847
+ ...pageMatchers,
1848
+ not: { ...baseMatchers.not, ...pageMatchers.not }
1849
+ };
1850
+ }
1851
+ return pageMatchers;
1852
+ }
1853
+
937
1854
  // Only extend expect if test-environment already defined it
938
1855
  if (typeof globalThis.expect === 'function') {
939
1856
  const originalExpect = globalThis.expect;
@@ -943,6 +1860,10 @@ async function setupPlaywright(context, options) {
943
1860
  if (actual && actual.constructor && actual.constructor.name === 'Locator') {
944
1861
  return createLocatorMatchers(actual, baseMatchers);
945
1862
  }
1863
+ // If actual is the page object (IsolatePage), add page-specific matchers
1864
+ if (actual && actual.__isPage === true) {
1865
+ return createPageMatchers(actual, baseMatchers);
1866
+ }
946
1867
  return baseMatchers;
947
1868
  };
948
1869
  }
@@ -977,4 +1898,4 @@ async function setupPlaywright(context, options) {
977
1898
  };
978
1899
  }
979
1900
 
980
- //# debugId=6C45722AECDACEA764756E2164756E21
1901
+ //# debugId=EC7671328EAA005364756E2164756E21