@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.
@@ -29,9 +29,12 @@ var __export = (target, all) => {
29
29
  // packages/playwright/src/client.ts
30
30
  var exports_client = {};
31
31
  __export(exports_client, {
32
+ getDefaultPlaywrightHandlerMetadata: () => getDefaultPlaywrightHandlerMetadata,
33
+ defaultPlaywrightHandler: () => defaultPlaywrightHandler,
32
34
  createPlaywrightHandler: () => createPlaywrightHandler
33
35
  });
34
36
  module.exports = __toCommonJS(exports_client);
37
+ var import_types = require("./types.cjs");
35
38
  function getLocator(page, selectorType, selectorValue, optionsJson) {
36
39
  const options = optionsJson ? JSON.parse(optionsJson) : undefined;
37
40
  const nthIndex = options?.nth;
@@ -70,6 +73,146 @@ function getLocator(page, selectorType, selectorValue, optionsJson) {
70
73
  locator = first.or(second);
71
74
  break;
72
75
  }
76
+ case "and": {
77
+ const [firstInfo, secondInfo] = JSON.parse(selectorValue);
78
+ const first = getLocator(page, firstInfo[0], firstInfo[1], firstInfo[2]);
79
+ const second = getLocator(page, secondInfo[0], secondInfo[1], secondInfo[2]);
80
+ locator = first.and(second);
81
+ break;
82
+ }
83
+ case "chained": {
84
+ const [parentInfo, childInfo] = JSON.parse(selectorValue);
85
+ const parent = getLocator(page, parentInfo[0], parentInfo[1], parentInfo[2]);
86
+ const childType = childInfo[0];
87
+ const childValue = childInfo[1];
88
+ const childOptionsJson = childInfo[2];
89
+ const childOptions = childOptionsJson ? JSON.parse(childOptionsJson) : undefined;
90
+ switch (childType) {
91
+ case "css":
92
+ locator = parent.locator(childValue);
93
+ break;
94
+ case "role": {
95
+ const roleOpts = childOptions ? { ...childOptions } : undefined;
96
+ if (roleOpts) {
97
+ delete roleOpts.nth;
98
+ delete roleOpts.filter;
99
+ if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
100
+ roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
101
+ }
102
+ }
103
+ locator = parent.getByRole(childValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
104
+ break;
105
+ }
106
+ case "text":
107
+ locator = parent.getByText(childValue);
108
+ break;
109
+ case "label":
110
+ locator = parent.getByLabel(childValue);
111
+ break;
112
+ case "placeholder":
113
+ locator = parent.getByPlaceholder(childValue);
114
+ break;
115
+ case "testId":
116
+ locator = parent.getByTestId(childValue);
117
+ break;
118
+ case "altText":
119
+ locator = parent.getByAltText(childValue);
120
+ break;
121
+ case "title":
122
+ locator = parent.getByTitle(childValue);
123
+ break;
124
+ default:
125
+ locator = parent.locator(childValue);
126
+ }
127
+ if (childOptions?.nth !== undefined) {
128
+ locator = locator.nth(childOptions.nth);
129
+ }
130
+ if (childOptions?.filter) {
131
+ const filterOpts = { ...childOptions.filter };
132
+ if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
133
+ filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
134
+ }
135
+ if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
136
+ filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
137
+ }
138
+ if (filterOpts.has && typeof filterOpts.has === "object" && filterOpts.has.$locator) {
139
+ const [type, value, opts] = filterOpts.has.$locator;
140
+ filterOpts.has = getLocator(page, type, value, opts);
141
+ }
142
+ if (filterOpts.hasNot && typeof filterOpts.hasNot === "object" && filterOpts.hasNot.$locator) {
143
+ const [type, value, opts] = filterOpts.hasNot.$locator;
144
+ filterOpts.hasNot = getLocator(page, type, value, opts);
145
+ }
146
+ locator = locator.filter(filterOpts);
147
+ }
148
+ break;
149
+ }
150
+ case "altText":
151
+ locator = page.getByAltText(selectorValue);
152
+ break;
153
+ case "title":
154
+ locator = page.getByTitle(selectorValue);
155
+ break;
156
+ case "frame": {
157
+ const [frameSelectorInfo, innerLocatorInfo] = JSON.parse(selectorValue);
158
+ const frameSelector = frameSelectorInfo[1];
159
+ const frame = page.frameLocator(frameSelector);
160
+ const innerType = innerLocatorInfo[0];
161
+ const innerValue = innerLocatorInfo[1];
162
+ const innerOptionsJson = innerLocatorInfo[2];
163
+ const innerOptions = innerOptionsJson ? JSON.parse(innerOptionsJson) : undefined;
164
+ switch (innerType) {
165
+ case "css":
166
+ locator = frame.locator(innerValue);
167
+ break;
168
+ case "role": {
169
+ const roleOpts = innerOptions ? { ...innerOptions } : undefined;
170
+ if (roleOpts) {
171
+ delete roleOpts.nth;
172
+ delete roleOpts.filter;
173
+ if (roleOpts.name && typeof roleOpts.name === "object" && roleOpts.name.$regex) {
174
+ roleOpts.name = new RegExp(roleOpts.name.$regex, roleOpts.name.$flags);
175
+ }
176
+ }
177
+ locator = frame.getByRole(innerValue, roleOpts && Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
178
+ break;
179
+ }
180
+ case "text":
181
+ locator = frame.getByText(innerValue);
182
+ break;
183
+ case "label":
184
+ locator = frame.getByLabel(innerValue);
185
+ break;
186
+ case "placeholder":
187
+ locator = frame.getByPlaceholder(innerValue);
188
+ break;
189
+ case "testId":
190
+ locator = frame.getByTestId(innerValue);
191
+ break;
192
+ case "altText":
193
+ locator = frame.getByAltText(innerValue);
194
+ break;
195
+ case "title":
196
+ locator = frame.getByTitle(innerValue);
197
+ break;
198
+ default:
199
+ locator = frame.locator(innerValue);
200
+ }
201
+ if (innerOptions?.nth !== undefined) {
202
+ locator = locator.nth(innerOptions.nth);
203
+ }
204
+ if (innerOptions?.filter) {
205
+ const filterOpts = { ...innerOptions.filter };
206
+ if (filterOpts.hasText && typeof filterOpts.hasText === "object" && filterOpts.hasText.$regex) {
207
+ filterOpts.hasText = new RegExp(filterOpts.hasText.$regex, filterOpts.hasText.$flags);
208
+ }
209
+ if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
210
+ filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
211
+ }
212
+ locator = locator.filter(filterOpts);
213
+ }
214
+ break;
215
+ }
73
216
  default:
74
217
  locator = page.locator(selectorValue);
75
218
  }
@@ -84,11 +227,19 @@ function getLocator(page, selectorType, selectorValue, optionsJson) {
84
227
  if (filterOpts.hasNotText && typeof filterOpts.hasNotText === "object" && filterOpts.hasNotText.$regex) {
85
228
  filterOpts.hasNotText = new RegExp(filterOpts.hasNotText.$regex, filterOpts.hasNotText.$flags);
86
229
  }
230
+ if (filterOpts.has && typeof filterOpts.has === "object" && filterOpts.has.$locator) {
231
+ const [type, value, opts] = filterOpts.has.$locator;
232
+ filterOpts.has = getLocator(page, type, value, opts);
233
+ }
234
+ if (filterOpts.hasNot && typeof filterOpts.hasNot === "object" && filterOpts.hasNot.$locator) {
235
+ const [type, value, opts] = filterOpts.hasNot.$locator;
236
+ filterOpts.hasNot = getLocator(page, type, value, opts);
237
+ }
87
238
  locator = locator.filter(filterOpts);
88
239
  }
89
240
  return locator;
90
241
  }
91
- async function executeLocatorAction(locator, action, actionArg, timeout) {
242
+ async function executeLocatorAction(locator, action, actionArg, timeout, fileIO) {
92
243
  switch (action) {
93
244
  case "click":
94
245
  await locator.click({ timeout });
@@ -156,6 +307,73 @@ async function executeLocatorAction(locator, action, actionArg, timeout) {
156
307
  }
157
308
  case "boundingBox":
158
309
  return await locator.boundingBox({ timeout });
310
+ case "setInputFiles": {
311
+ const files = actionArg;
312
+ if (Array.isArray(files) && files.length === 0) {
313
+ await locator.setInputFiles([], { timeout });
314
+ return null;
315
+ }
316
+ if (Array.isArray(files) && files.length > 0 && typeof files[0] === "object" && "buffer" in files[0]) {
317
+ const fileBuffers2 = files.map((f) => ({
318
+ name: f.name,
319
+ mimeType: f.mimeType,
320
+ buffer: Buffer.from(f.buffer, "base64")
321
+ }));
322
+ await locator.setInputFiles(fileBuffers2, { timeout });
323
+ return null;
324
+ }
325
+ const filePaths = Array.isArray(files) ? files : [files];
326
+ if (!fileIO?.readFile) {
327
+ throw new Error("setInputFiles() with file paths requires a readFile callback to be provided. " + "Either provide a readFile callback in defaultPlaywrightHandler options, or pass file data directly " + "as { name, mimeType, buffer } objects.");
328
+ }
329
+ const fileBuffers = await Promise.all(filePaths.map(async (filePath) => {
330
+ const fileData = await fileIO.readFile(filePath);
331
+ return {
332
+ name: fileData.name,
333
+ mimeType: fileData.mimeType,
334
+ buffer: fileData.buffer
335
+ };
336
+ }));
337
+ await locator.setInputFiles(fileBuffers, { timeout });
338
+ return null;
339
+ }
340
+ case "screenshot": {
341
+ const opts = actionArg;
342
+ const buffer = await locator.screenshot({
343
+ timeout,
344
+ type: opts?.type,
345
+ quality: opts?.quality
346
+ });
347
+ if (opts?.path) {
348
+ if (!fileIO?.writeFile) {
349
+ 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.");
350
+ }
351
+ await fileIO.writeFile(opts.path, buffer);
352
+ }
353
+ return buffer.toString("base64");
354
+ }
355
+ case "dragTo": {
356
+ const targetInfo = actionArg;
357
+ const targetLocator = getLocator(locator.page(), targetInfo[0], targetInfo[1], targetInfo[2]);
358
+ await locator.dragTo(targetLocator, { timeout });
359
+ return null;
360
+ }
361
+ case "scrollIntoViewIfNeeded":
362
+ await locator.scrollIntoViewIfNeeded({ timeout });
363
+ return null;
364
+ case "highlight":
365
+ await locator.highlight();
366
+ return null;
367
+ case "evaluate": {
368
+ const [fnString, arg] = actionArg;
369
+ const fn = new Function("return (" + fnString + ")")();
370
+ return await locator.evaluate(fn, arg);
371
+ }
372
+ case "evaluateAll": {
373
+ const [fnString, arg] = actionArg;
374
+ const fn = new Function("return (" + fnString + ")")();
375
+ return await locator.evaluateAll(fn, arg);
376
+ }
159
377
  default:
160
378
  throw new Error(`Unknown action: ${action}`);
161
379
  }
@@ -175,13 +393,22 @@ async function executeExpectAssertion(locator, matcher, expected, negated, timeo
175
393
  }
176
394
  case "toContainText": {
177
395
  const text = await locator.textContent({ timeout });
178
- const matches = text?.includes(String(expected)) ?? false;
396
+ let matches;
397
+ let expectedDisplay;
398
+ if (expected && typeof expected === "object" && expected.$regex) {
399
+ const regex = new RegExp(expected.$regex, expected.$flags);
400
+ matches = regex.test(text ?? "");
401
+ expectedDisplay = String(regex);
402
+ } else {
403
+ matches = text?.includes(String(expected)) ?? false;
404
+ expectedDisplay = String(expected);
405
+ }
179
406
  if (negated) {
180
407
  if (matches)
181
- throw new Error(`Expected text to not contain "${expected}", but got "${text}"`);
408
+ throw new Error(`Expected text to not contain ${expectedDisplay}, but got "${text}"`);
182
409
  } else {
183
410
  if (!matches)
184
- throw new Error(`Expected text to contain "${expected}", but got "${text}"`);
411
+ throw new Error(`Expected text to contain ${expectedDisplay}, but got "${text}"`);
185
412
  }
186
413
  break;
187
414
  }
@@ -219,66 +446,466 @@ async function executeExpectAssertion(locator, matcher, expected, negated, timeo
219
446
  }
220
447
  break;
221
448
  }
449
+ case "toHaveAttribute": {
450
+ const { name, value } = expected;
451
+ const actual = await locator.getAttribute(name, { timeout });
452
+ if (value instanceof RegExp || value && typeof value === "object" && value.$regex) {
453
+ const regex = value.$regex ? new RegExp(value.$regex, value.$flags) : value;
454
+ const matches = regex.test(actual ?? "");
455
+ if (negated) {
456
+ if (matches)
457
+ throw new Error(`Expected attribute "${name}" to not match ${regex}, but got "${actual}"`);
458
+ } else {
459
+ if (!matches)
460
+ throw new Error(`Expected attribute "${name}" to match ${regex}, but got "${actual}"`);
461
+ }
462
+ } else {
463
+ const matches = actual === String(value);
464
+ if (negated) {
465
+ if (matches)
466
+ throw new Error(`Expected attribute "${name}" to not be "${value}", but it was`);
467
+ } else {
468
+ if (!matches)
469
+ throw new Error(`Expected attribute "${name}" to be "${value}", but got "${actual}"`);
470
+ }
471
+ }
472
+ break;
473
+ }
474
+ case "toHaveText": {
475
+ const text = await locator.textContent({ timeout }) ?? "";
476
+ let matches;
477
+ let expectedDisplay;
478
+ if (expected && typeof expected === "object" && expected.$regex) {
479
+ const regex = new RegExp(expected.$regex, expected.$flags);
480
+ matches = regex.test(text);
481
+ expectedDisplay = String(regex);
482
+ } else {
483
+ matches = text === String(expected);
484
+ expectedDisplay = JSON.stringify(expected);
485
+ }
486
+ if (negated) {
487
+ if (matches)
488
+ throw new Error(`Expected text to not be ${expectedDisplay}, but got "${text}"`);
489
+ } else {
490
+ if (!matches)
491
+ throw new Error(`Expected text to be ${expectedDisplay}, but got "${text}"`);
492
+ }
493
+ break;
494
+ }
495
+ case "toHaveCount": {
496
+ const count = await locator.count();
497
+ const expectedCount = Number(expected);
498
+ if (negated) {
499
+ if (count === expectedCount)
500
+ throw new Error(`Expected count to not be ${expectedCount}, but it was`);
501
+ } else {
502
+ if (count !== expectedCount)
503
+ throw new Error(`Expected count to be ${expectedCount}, but got ${count}`);
504
+ }
505
+ break;
506
+ }
507
+ case "toBeHidden": {
508
+ const isHidden = await locator.isHidden();
509
+ if (negated) {
510
+ if (isHidden)
511
+ throw new Error("Expected element to not be hidden, but it was hidden");
512
+ } else {
513
+ if (!isHidden)
514
+ throw new Error("Expected element to be hidden, but it was not");
515
+ }
516
+ break;
517
+ }
518
+ case "toBeDisabled": {
519
+ const isDisabled = await locator.isDisabled();
520
+ if (negated) {
521
+ if (isDisabled)
522
+ throw new Error("Expected element to not be disabled, but it was disabled");
523
+ } else {
524
+ if (!isDisabled)
525
+ throw new Error("Expected element to be disabled, but it was not");
526
+ }
527
+ break;
528
+ }
529
+ case "toBeFocused": {
530
+ const isFocused = await locator.evaluate((el) => document.activeElement === el).catch(() => false);
531
+ if (negated) {
532
+ if (isFocused)
533
+ throw new Error("Expected element to not be focused, but it was focused");
534
+ } else {
535
+ if (!isFocused)
536
+ throw new Error("Expected element to be focused, but it was not");
537
+ }
538
+ break;
539
+ }
540
+ case "toBeEmpty": {
541
+ const text = await locator.textContent({ timeout });
542
+ const value = await locator.inputValue({ timeout }).catch(() => null);
543
+ const isEmpty = value !== null ? value === "" : (text ?? "") === "";
544
+ if (negated) {
545
+ if (isEmpty)
546
+ throw new Error("Expected element to not be empty, but it was");
547
+ } else {
548
+ if (!isEmpty)
549
+ throw new Error("Expected element to be empty, but it was not");
550
+ }
551
+ break;
552
+ }
553
+ case "toBeAttached": {
554
+ const count = await locator.count();
555
+ const isAttached = count > 0;
556
+ if (negated) {
557
+ if (isAttached)
558
+ throw new Error("Expected element to not be attached to DOM, but it was");
559
+ } else {
560
+ if (!isAttached)
561
+ throw new Error("Expected element to be attached to DOM, but it was not");
562
+ }
563
+ break;
564
+ }
565
+ case "toBeEditable": {
566
+ const isEditable = await locator.isEditable({ timeout });
567
+ if (negated) {
568
+ if (isEditable)
569
+ throw new Error("Expected element to not be editable, but it was");
570
+ } else {
571
+ if (!isEditable)
572
+ throw new Error("Expected element to be editable, but it was not");
573
+ }
574
+ break;
575
+ }
576
+ case "toHaveClass": {
577
+ const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
578
+ const classes = classAttr.split(/\s+/).filter(Boolean);
579
+ let matches;
580
+ let expectedDisplay;
581
+ if (expected && typeof expected === "object" && expected.$regex) {
582
+ const regex = new RegExp(expected.$regex, expected.$flags);
583
+ matches = regex.test(classAttr);
584
+ expectedDisplay = String(regex);
585
+ } else if (Array.isArray(expected)) {
586
+ matches = expected.every((c) => classes.includes(c));
587
+ expectedDisplay = JSON.stringify(expected);
588
+ } else {
589
+ matches = classAttr === String(expected) || classes.includes(String(expected));
590
+ expectedDisplay = String(expected);
591
+ }
592
+ if (negated) {
593
+ if (matches)
594
+ throw new Error(`Expected class to not match ${expectedDisplay}, but got "${classAttr}"`);
595
+ } else {
596
+ if (!matches)
597
+ throw new Error(`Expected class to match ${expectedDisplay}, but got "${classAttr}"`);
598
+ }
599
+ break;
600
+ }
601
+ case "toContainClass": {
602
+ const classAttr = await locator.getAttribute("class", { timeout }) ?? "";
603
+ const classes = classAttr.split(/\s+/).filter(Boolean);
604
+ const expectedClass = String(expected);
605
+ const hasClass = classes.includes(expectedClass);
606
+ if (negated) {
607
+ if (hasClass)
608
+ throw new Error(`Expected element to not contain class "${expectedClass}", but it does`);
609
+ } else {
610
+ if (!hasClass)
611
+ throw new Error(`Expected element to contain class "${expectedClass}", but classes are "${classAttr}"`);
612
+ }
613
+ break;
614
+ }
615
+ case "toHaveId": {
616
+ const id = await locator.getAttribute("id", { timeout });
617
+ const matches = id === String(expected);
618
+ if (negated) {
619
+ if (matches)
620
+ throw new Error(`Expected id to not be "${expected}", but it was`);
621
+ } else {
622
+ if (!matches)
623
+ throw new Error(`Expected id to be "${expected}", but got "${id}"`);
624
+ }
625
+ break;
626
+ }
627
+ case "toBeInViewport": {
628
+ const isInViewport = await locator.evaluate((el) => {
629
+ const rect = el.getBoundingClientRect();
630
+ return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
631
+ });
632
+ if (negated) {
633
+ if (isInViewport)
634
+ throw new Error("Expected element to not be in viewport, but it was");
635
+ } else {
636
+ if (!isInViewport)
637
+ throw new Error("Expected element to be in viewport, but it was not");
638
+ }
639
+ break;
640
+ }
641
+ case "toHaveCSS": {
642
+ const { name, value } = expected;
643
+ const actual = await locator.evaluate((el, propName) => {
644
+ return getComputedStyle(el).getPropertyValue(propName);
645
+ }, name);
646
+ let matches;
647
+ if (value && typeof value === "object" && value.$regex) {
648
+ const regex = new RegExp(value.$regex, value.$flags);
649
+ matches = regex.test(actual);
650
+ } else {
651
+ matches = actual === String(value);
652
+ }
653
+ if (negated) {
654
+ if (matches)
655
+ throw new Error(`Expected CSS "${name}" to not be "${value}", but it was`);
656
+ } else {
657
+ if (!matches)
658
+ throw new Error(`Expected CSS "${name}" to be "${value}", but got "${actual}"`);
659
+ }
660
+ break;
661
+ }
662
+ case "toHaveJSProperty": {
663
+ const { name, value } = expected;
664
+ const actual = await locator.evaluate((el, propName) => {
665
+ return el[propName];
666
+ }, name);
667
+ const matches = JSON.stringify(actual) === JSON.stringify(value);
668
+ if (negated) {
669
+ if (matches)
670
+ throw new Error(`Expected JS property "${name}" to not be ${JSON.stringify(value)}, but it was`);
671
+ } else {
672
+ if (!matches)
673
+ throw new Error(`Expected JS property "${name}" to be ${JSON.stringify(value)}, but got ${JSON.stringify(actual)}`);
674
+ }
675
+ break;
676
+ }
677
+ case "toHaveAccessibleName": {
678
+ const accessibleName = await locator.evaluate((el) => {
679
+ return el.getAttribute("aria-label") || el.getAttribute("aria-labelledby") || el.innerText || "";
680
+ });
681
+ let matches;
682
+ if (expected && typeof expected === "object" && expected.$regex) {
683
+ const regex = new RegExp(expected.$regex, expected.$flags);
684
+ matches = regex.test(accessibleName);
685
+ } else {
686
+ matches = accessibleName === String(expected);
687
+ }
688
+ if (negated) {
689
+ if (matches)
690
+ throw new Error(`Expected accessible name to not be "${expected}", but it was`);
691
+ } else {
692
+ if (!matches)
693
+ throw new Error(`Expected accessible name to be "${expected}", but got "${accessibleName}"`);
694
+ }
695
+ break;
696
+ }
697
+ case "toHaveAccessibleDescription": {
698
+ const accessibleDesc = await locator.evaluate((el) => {
699
+ const describedby = el.getAttribute("aria-describedby");
700
+ if (describedby) {
701
+ const descEl = document.getElementById(describedby);
702
+ return descEl?.textContent || "";
703
+ }
704
+ return el.getAttribute("aria-description") || "";
705
+ });
706
+ let matches;
707
+ if (expected && typeof expected === "object" && expected.$regex) {
708
+ const regex = new RegExp(expected.$regex, expected.$flags);
709
+ matches = regex.test(accessibleDesc);
710
+ } else {
711
+ matches = accessibleDesc === String(expected);
712
+ }
713
+ if (negated) {
714
+ if (matches)
715
+ throw new Error(`Expected accessible description to not be "${expected}", but it was`);
716
+ } else {
717
+ if (!matches)
718
+ throw new Error(`Expected accessible description to be "${expected}", but got "${accessibleDesc}"`);
719
+ }
720
+ break;
721
+ }
722
+ case "toHaveRole": {
723
+ const role = await locator.evaluate((el) => {
724
+ return el.getAttribute("role") || el.tagName.toLowerCase();
725
+ });
726
+ const matches = role === String(expected);
727
+ if (negated) {
728
+ if (matches)
729
+ throw new Error(`Expected role to not be "${expected}", but it was`);
730
+ } else {
731
+ if (!matches)
732
+ throw new Error(`Expected role to be "${expected}", but got "${role}"`);
733
+ }
734
+ break;
735
+ }
222
736
  default:
223
737
  throw new Error(`Unknown matcher: ${matcher}`);
224
738
  }
225
739
  }
740
+ async function executePageExpectAssertion(page, matcher, expected, negated, timeout) {
741
+ let expectedValue = expected;
742
+ if (expected && typeof expected === "object" && expected.$regex) {
743
+ expectedValue = new RegExp(expected.$regex, expected.$flags);
744
+ }
745
+ switch (matcher) {
746
+ case "toHaveURL": {
747
+ const expectedUrl = expectedValue;
748
+ const startTime = Date.now();
749
+ let lastUrl = "";
750
+ while (Date.now() - startTime < timeout) {
751
+ lastUrl = page.url();
752
+ const matches = expectedUrl instanceof RegExp ? expectedUrl.test(lastUrl) : lastUrl === expectedUrl;
753
+ if (negated ? !matches : matches)
754
+ return;
755
+ await new Promise((r) => setTimeout(r, 100));
756
+ }
757
+ if (negated) {
758
+ throw new Error(`Expected URL to not match "${expectedUrl}", but got "${lastUrl}"`);
759
+ } else {
760
+ throw new Error(`Expected URL to be "${expectedUrl}", but got "${lastUrl}"`);
761
+ }
762
+ }
763
+ case "toHaveTitle": {
764
+ const expectedTitle = expectedValue;
765
+ const startTime = Date.now();
766
+ let lastTitle = "";
767
+ while (Date.now() - startTime < timeout) {
768
+ lastTitle = await page.title();
769
+ const matches = expectedTitle instanceof RegExp ? expectedTitle.test(lastTitle) : lastTitle === expectedTitle;
770
+ if (negated ? !matches : matches)
771
+ return;
772
+ await new Promise((r) => setTimeout(r, 100));
773
+ }
774
+ if (negated) {
775
+ throw new Error(`Expected title to not match "${expectedTitle}", but got "${lastTitle}"`);
776
+ } else {
777
+ throw new Error(`Expected title to be "${expectedTitle}", but got "${lastTitle}"`);
778
+ }
779
+ }
780
+ default:
781
+ throw new Error(`Unknown page matcher: ${matcher}`);
782
+ }
783
+ }
226
784
  function createPlaywrightHandler(page, options) {
227
785
  const timeout = options?.timeout ?? 30000;
786
+ const fileIO = {
787
+ readFile: options?.readFile,
788
+ writeFile: options?.writeFile
789
+ };
790
+ const registry = {
791
+ pages: new Map([["page_0", page]]),
792
+ contexts: new Map([["ctx_0", page.context()]]),
793
+ nextPageId: 1,
794
+ nextContextId: 1
795
+ };
228
796
  return async (op) => {
229
797
  try {
798
+ switch (op.type) {
799
+ case "newContext": {
800
+ if (!options?.createContext) {
801
+ return { ok: false, error: { name: "Error", message: "createContext callback not provided. Configure createContext in playwright options to enable browser.newContext()." } };
802
+ }
803
+ const [contextOptions] = op.args;
804
+ const newContext = await options.createContext(contextOptions);
805
+ const contextId2 = `ctx_${registry.nextContextId++}`;
806
+ registry.contexts.set(contextId2, newContext);
807
+ return { ok: true, value: { contextId: contextId2 } };
808
+ }
809
+ case "newPage": {
810
+ if (!options?.createPage) {
811
+ return { ok: false, error: { name: "Error", message: "createPage callback not provided. Configure createPage in playwright options to enable context.newPage()." } };
812
+ }
813
+ const contextId2 = op.contextId ?? "ctx_0";
814
+ const targetContext2 = registry.contexts.get(contextId2);
815
+ if (!targetContext2) {
816
+ return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
817
+ }
818
+ const newPage = await options.createPage(targetContext2);
819
+ const pageId2 = `page_${registry.nextPageId++}`;
820
+ registry.pages.set(pageId2, newPage);
821
+ return { ok: true, value: { pageId: pageId2 } };
822
+ }
823
+ case "closeContext": {
824
+ const contextId2 = op.contextId ?? "ctx_0";
825
+ const context = registry.contexts.get(contextId2);
826
+ if (!context) {
827
+ return { ok: false, error: { name: "Error", message: `Context ${contextId2} not found` } };
828
+ }
829
+ await context.close();
830
+ registry.contexts.delete(contextId2);
831
+ for (const [pid, p] of registry.pages) {
832
+ if (p.context() === context) {
833
+ registry.pages.delete(pid);
834
+ }
835
+ }
836
+ return { ok: true };
837
+ }
838
+ }
839
+ const pageId = op.pageId ?? "page_0";
840
+ const targetPage = registry.pages.get(pageId);
841
+ if (!targetPage) {
842
+ return { ok: false, error: { name: "Error", message: `Page ${pageId} not found` } };
843
+ }
844
+ const contextId = op.contextId ?? "ctx_0";
845
+ const targetContext = registry.contexts.get(contextId);
230
846
  switch (op.type) {
231
847
  case "goto": {
232
848
  const [url, waitUntil] = op.args;
233
- await page.goto(url, {
849
+ await targetPage.goto(url, {
234
850
  timeout,
235
851
  waitUntil: waitUntil ?? "load"
236
852
  });
237
853
  return { ok: true };
238
854
  }
239
855
  case "reload":
240
- await page.reload({ timeout });
856
+ await targetPage.reload({ timeout });
241
857
  return { ok: true };
242
858
  case "url":
243
- return { ok: true, value: page.url() };
859
+ return { ok: true, value: targetPage.url() };
244
860
  case "title":
245
- return { ok: true, value: await page.title() };
861
+ return { ok: true, value: await targetPage.title() };
246
862
  case "content":
247
- return { ok: true, value: await page.content() };
863
+ return { ok: true, value: await targetPage.content() };
248
864
  case "waitForSelector": {
249
865
  const [selector, optionsJson] = op.args;
250
866
  const opts = optionsJson ? JSON.parse(optionsJson) : {};
251
- await page.waitForSelector(selector, { timeout, ...opts });
867
+ await targetPage.waitForSelector(selector, { timeout, ...opts });
252
868
  return { ok: true };
253
869
  }
254
870
  case "waitForTimeout": {
255
871
  const [ms] = op.args;
256
- await page.waitForTimeout(ms);
872
+ await targetPage.waitForTimeout(ms);
257
873
  return { ok: true };
258
874
  }
259
875
  case "waitForLoadState": {
260
876
  const [state] = op.args;
261
- await page.waitForLoadState(state ?? "load", { timeout });
877
+ await targetPage.waitForLoadState(state ?? "load", { timeout });
262
878
  return { ok: true };
263
879
  }
264
880
  case "evaluate": {
265
- const [script] = op.args;
266
- const result = await page.evaluate(script);
881
+ const [script, arg] = op.args;
882
+ if (op.args.length > 1) {
883
+ const fn = new Function("return (" + script + ")")();
884
+ const result2 = await targetPage.evaluate(fn, arg);
885
+ return { ok: true, value: result2 };
886
+ }
887
+ const result = await targetPage.evaluate(script);
267
888
  return { ok: true, value: result };
268
889
  }
269
890
  case "locatorAction": {
270
891
  const [selectorType, selectorValue, roleOptions, action, actionArg] = op.args;
271
- const locator = getLocator(page, selectorType, selectorValue, roleOptions);
272
- const result = await executeLocatorAction(locator, action, actionArg, timeout);
892
+ const locator = getLocator(targetPage, selectorType, selectorValue, roleOptions);
893
+ const result = await executeLocatorAction(locator, action, actionArg, timeout, fileIO);
273
894
  return { ok: true, value: result };
274
895
  }
275
896
  case "expectLocator": {
276
897
  const [selectorType, selectorValue, roleOptions, matcher, expected, negated, customTimeout] = op.args;
277
- const locator = getLocator(page, selectorType, selectorValue, roleOptions);
898
+ const locator = getLocator(targetPage, selectorType, selectorValue, roleOptions);
278
899
  const effectiveTimeout = customTimeout ?? timeout;
279
900
  await executeExpectAssertion(locator, matcher, expected, negated ?? false, effectiveTimeout);
280
901
  return { ok: true };
281
902
  }
903
+ case "expectPage": {
904
+ const [matcher, expected, negated, customTimeout] = op.args;
905
+ const effectiveTimeout = customTimeout ?? timeout;
906
+ await executePageExpectAssertion(targetPage, matcher, expected, negated ?? false, effectiveTimeout);
907
+ return { ok: true };
908
+ }
282
909
  case "request": {
283
910
  const [url, method, data, headers] = op.args;
284
911
  const requestOptions = {
@@ -290,7 +917,7 @@ function createPlaywrightHandler(page, options) {
290
917
  if (data !== undefined && data !== null) {
291
918
  requestOptions.data = data;
292
919
  }
293
- const response = await page.request.fetch(url, {
920
+ const response = await targetPage.request.fetch(url, {
294
921
  method,
295
922
  ...requestOptions
296
923
  });
@@ -313,7 +940,7 @@ function createPlaywrightHandler(page, options) {
313
940
  }
314
941
  case "goBack": {
315
942
  const [waitUntil] = op.args;
316
- await page.goBack({
943
+ await targetPage.goBack({
317
944
  timeout,
318
945
  waitUntil: waitUntil ?? "load"
319
946
  });
@@ -321,22 +948,164 @@ function createPlaywrightHandler(page, options) {
321
948
  }
322
949
  case "goForward": {
323
950
  const [waitUntil] = op.args;
324
- await page.goForward({
951
+ await targetPage.goForward({
325
952
  timeout,
326
953
  waitUntil: waitUntil ?? "load"
327
954
  });
328
955
  return { ok: true };
329
956
  }
330
957
  case "waitForURL": {
331
- const [url, customTimeout, waitUntil] = op.args;
332
- await page.waitForURL(url, {
958
+ const [urlArg, customTimeout, waitUntil] = op.args;
959
+ const url = urlArg && typeof urlArg === "object" && "$regex" in urlArg ? new RegExp(urlArg.$regex, urlArg.$flags) : urlArg;
960
+ await targetPage.waitForURL(url, {
333
961
  timeout: customTimeout ?? timeout,
334
962
  waitUntil: waitUntil ?? undefined
335
963
  });
336
964
  return { ok: true };
337
965
  }
338
966
  case "clearCookies": {
339
- await page.context().clearCookies();
967
+ const ctx = targetContext ?? targetPage.context();
968
+ await ctx.clearCookies();
969
+ return { ok: true };
970
+ }
971
+ case "screenshot": {
972
+ const [screenshotOptions] = op.args;
973
+ const buffer = await targetPage.screenshot({
974
+ type: screenshotOptions?.type,
975
+ quality: screenshotOptions?.quality,
976
+ fullPage: screenshotOptions?.fullPage,
977
+ clip: screenshotOptions?.clip
978
+ });
979
+ if (screenshotOptions?.path) {
980
+ if (!fileIO.writeFile) {
981
+ 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.");
982
+ }
983
+ await fileIO.writeFile(screenshotOptions.path, buffer);
984
+ }
985
+ return { ok: true, value: buffer.toString("base64") };
986
+ }
987
+ case "setViewportSize": {
988
+ const [size] = op.args;
989
+ await targetPage.setViewportSize(size);
990
+ return { ok: true };
991
+ }
992
+ case "viewportSize": {
993
+ return { ok: true, value: targetPage.viewportSize() };
994
+ }
995
+ case "keyboardType": {
996
+ const [text, typeOptions] = op.args;
997
+ await targetPage.keyboard.type(text, typeOptions);
998
+ return { ok: true };
999
+ }
1000
+ case "keyboardPress": {
1001
+ const [key, pressOptions] = op.args;
1002
+ await targetPage.keyboard.press(key, pressOptions);
1003
+ return { ok: true };
1004
+ }
1005
+ case "keyboardDown": {
1006
+ const [key] = op.args;
1007
+ await targetPage.keyboard.down(key);
1008
+ return { ok: true };
1009
+ }
1010
+ case "keyboardUp": {
1011
+ const [key] = op.args;
1012
+ await targetPage.keyboard.up(key);
1013
+ return { ok: true };
1014
+ }
1015
+ case "keyboardInsertText": {
1016
+ const [text] = op.args;
1017
+ await targetPage.keyboard.insertText(text);
1018
+ return { ok: true };
1019
+ }
1020
+ case "mouseMove": {
1021
+ const [x, y, moveOptions] = op.args;
1022
+ await targetPage.mouse.move(x, y, moveOptions);
1023
+ return { ok: true };
1024
+ }
1025
+ case "mouseClick": {
1026
+ const [x, y, clickOptions] = op.args;
1027
+ await targetPage.mouse.click(x, y, clickOptions);
1028
+ return { ok: true };
1029
+ }
1030
+ case "mouseDown": {
1031
+ const [downOptions] = op.args;
1032
+ if (downOptions) {
1033
+ await targetPage.mouse.down(downOptions);
1034
+ } else {
1035
+ await targetPage.mouse.down();
1036
+ }
1037
+ return { ok: true };
1038
+ }
1039
+ case "mouseUp": {
1040
+ const [upOptions] = op.args;
1041
+ if (upOptions) {
1042
+ await targetPage.mouse.up(upOptions);
1043
+ } else {
1044
+ await targetPage.mouse.up();
1045
+ }
1046
+ return { ok: true };
1047
+ }
1048
+ case "mouseWheel": {
1049
+ const [deltaX, deltaY] = op.args;
1050
+ await targetPage.mouse.wheel(deltaX, deltaY);
1051
+ return { ok: true };
1052
+ }
1053
+ case "frames": {
1054
+ const frames = targetPage.frames();
1055
+ return { ok: true, value: frames.map((f) => ({ name: f.name(), url: f.url() })) };
1056
+ }
1057
+ case "mainFrame": {
1058
+ const mainFrame = targetPage.mainFrame();
1059
+ return { ok: true, value: { name: mainFrame.name(), url: mainFrame.url() } };
1060
+ }
1061
+ case "bringToFront": {
1062
+ await targetPage.bringToFront();
1063
+ return { ok: true };
1064
+ }
1065
+ case "close": {
1066
+ await targetPage.close();
1067
+ registry.pages.delete(pageId);
1068
+ return { ok: true };
1069
+ }
1070
+ case "isClosed": {
1071
+ return { ok: true, value: targetPage.isClosed() };
1072
+ }
1073
+ case "pdf": {
1074
+ const [pdfOptions] = op.args;
1075
+ const { path: pdfPath, ...restPdfOptions } = pdfOptions ?? {};
1076
+ const buffer = await targetPage.pdf(restPdfOptions);
1077
+ if (pdfPath) {
1078
+ if (!fileIO.writeFile) {
1079
+ 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.");
1080
+ }
1081
+ await fileIO.writeFile(pdfPath, buffer);
1082
+ }
1083
+ return { ok: true, value: buffer.toString("base64") };
1084
+ }
1085
+ case "emulateMedia": {
1086
+ const [mediaOptions] = op.args;
1087
+ await targetPage.emulateMedia(mediaOptions);
1088
+ return { ok: true };
1089
+ }
1090
+ case "addCookies": {
1091
+ const [cookies] = op.args;
1092
+ const ctx = targetContext ?? targetPage.context();
1093
+ await ctx.addCookies(cookies);
1094
+ return { ok: true };
1095
+ }
1096
+ case "cookies": {
1097
+ const [urls] = op.args;
1098
+ const ctx = targetContext ?? targetPage.context();
1099
+ const cookies = await ctx.cookies(urls);
1100
+ return { ok: true, value: cookies };
1101
+ }
1102
+ case "setExtraHTTPHeaders": {
1103
+ const [headers] = op.args;
1104
+ await targetPage.setExtraHTTPHeaders(headers);
1105
+ return { ok: true };
1106
+ }
1107
+ case "pause": {
1108
+ await targetPage.pause();
340
1109
  return { ok: true };
341
1110
  }
342
1111
  default:
@@ -348,5 +1117,13 @@ function createPlaywrightHandler(page, options) {
348
1117
  }
349
1118
  };
350
1119
  }
1120
+ function defaultPlaywrightHandler(page, options) {
1121
+ const handler = createPlaywrightHandler(page, options);
1122
+ handler[import_types.DEFAULT_PLAYWRIGHT_HANDLER_META] = { page, options };
1123
+ return handler;
1124
+ }
1125
+ function getDefaultPlaywrightHandlerMetadata(handler) {
1126
+ return handler[import_types.DEFAULT_PLAYWRIGHT_HANDLER_META];
1127
+ }
351
1128
 
352
- //# debugId=2CFAC2782F92890264756E2164756E21
1129
+ //# debugId=F37479A1CB5EC1B964756E2164756E21