@ricsam/isolate-playwright 0.1.12 → 0.1.13

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