@ralphralphai/playwright 2.5.0 → 2.7.0

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.
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_upload = require("./upload-D40MqSQa.cjs");
2
+ const require_upload = require("./upload-_UWss__c.cjs");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_upload.__toESM(node_path, 1);
5
5
  let _playwright_test = require("@playwright/test");
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as resolveMode, a as prepareArtifactUpload, f as Recorder, g as loadConfigSync, h as loadConfig, i as createRawGraphBundle, l as uploadRawGraph, p as visitAllLinks, u as MANIFEST_ATTACHMENT } from "./upload-jup_jLlm.mjs";
1
+ import { _ as resolveMode, a as prepareArtifactUpload, f as Recorder, g as loadConfigSync, h as loadConfig, i as createRawGraphBundle, l as uploadRawGraph, p as visitAllLinks, u as MANIFEST_ATTACHMENT } from "./upload-BsCzkoup.mjs";
2
2
  import path from "node:path";
3
3
  import { expect, mergeTests, test as test$1 } from "@playwright/test";
4
4
  //#region src/screens.ts
package/dist/reporter.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_upload = require("./upload-D40MqSQa.cjs");
1
+ const require_upload = require("./upload-_UWss__c.cjs");
2
2
  let node_path = require("node:path");
3
3
  node_path = require_upload.__toESM(node_path, 1);
4
4
  let node_fs_promises = require("node:fs/promises");
package/dist/reporter.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as resolveMode, a as prepareArtifactUpload, c as resultLink, d as REPORTER_ENV, l as uploadRawGraph, m as version, n as bundleImagePath, o as registerDeployment, r as bundleSnapshotPath, s as resolveUploadOptions, t as RAW_GRAPH_BUNDLE_INDEX, u as MANIFEST_ATTACHMENT } from "./upload-jup_jLlm.mjs";
1
+ import { _ as resolveMode, a as prepareArtifactUpload, c as resultLink, d as REPORTER_ENV, l as uploadRawGraph, m as version, n as bundleImagePath, o as registerDeployment, r as bundleSnapshotPath, s as resolveUploadOptions, t as RAW_GRAPH_BUNDLE_INDEX, u as MANIFEST_ATTACHMENT } from "./upload-BsCzkoup.mjs";
2
2
  import path from "node:path";
3
3
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
4
  import { randomUUID } from "node:crypto";
@@ -45,7 +45,7 @@ function snapshot(value) {
45
45
  }
46
46
  //#endregion
47
47
  //#region package.json
48
- var version = "2.5.0";
48
+ var version = "2.7.0";
49
49
  //#endregion
50
50
  //#region src/actions.ts
51
51
  const BINDING = "__ralphAction";
@@ -526,30 +526,57 @@ async function waitForAnimations(page, timeoutMs) {
526
526
  //#region src/image_responses.ts
527
527
  const IMAGE_HASH_TIMEOUT_MS = 3e4;
528
528
  const hashesByPage = /* @__PURE__ */ new WeakMap();
529
+ /**
530
+ * The latest hash per url across all of a context's pages. The HTTP cache is
531
+ * shared by the context, so this is the copy a 304 tells any of them to reuse.
532
+ */
533
+ const hashesByContext = /* @__PURE__ */ new WeakMap();
529
534
  function sha256(data) {
530
535
  return createHash("sha256").update(data).digest("hex");
531
536
  }
532
537
  /**
533
- * Hashes every image the page loads, as it loads, so a snapshot hashes the
534
- * bytes the page showed instead of fetching them a second time. A second fetch
535
- * of a page's images comes as a burst, which hosts such as
538
+ * Hashes every image the context's pages load, as they load, so a snapshot
539
+ * hashes the bytes the page showed instead of fetching them a second time. A
540
+ * second fetch of a page's images comes as a burst, which hosts such as
536
541
  * upload.wikimedia.org answer with 429 for a share of them, and the image then
537
542
  * reads as replaced. Only the hash is kept, not the body. Entries persist
538
543
  * across navigations until another request for the same image replaces them
539
544
  * or the page closes.
545
+ *
546
+ * Watched on the context rather than each page: Playwright reports a popup
547
+ * only once its first navigation has committed, and the images that document
548
+ * requests early are gone by the time a listener can be added to the page.
540
549
  */
541
- function watchImageResponses(page) {
542
- if (hashesByPage.has(page)) return;
543
- const hashes = /* @__PURE__ */ new Map();
544
- hashesByPage.set(page, hashes);
545
- page.on("request", (request) => {
546
- if (request.resourceType() !== "image") return;
550
+ function watchImageResponses(context) {
551
+ if (hashesByContext.has(context)) return;
552
+ const latest = /* @__PURE__ */ new Map();
553
+ hashesByContext.set(context, latest);
554
+ context.on("request", (request) => {
555
+ if (request.resourceType() !== "image" || request.serviceWorker()) return;
556
+ const hashes = pageHashes(request.frame().page());
557
+ const cached = latest.get(request.url());
547
558
  const hash = request.response().then(async (response) => {
559
+ if (response?.status() === 304) return cached;
548
560
  return response?.ok() ? sha256(await response.body()) : void 0;
549
561
  }).catch(() => void 0);
550
- for (let redirected = request; redirected !== null; redirected = redirected.redirectedFrom()) hashes.set(redirected.url(), hash);
562
+ for (const url of redirectChain(request)) {
563
+ hashes.set(url, hash);
564
+ latest.set(url, hash);
565
+ }
551
566
  });
567
+ }
568
+ function pageHashes(page) {
569
+ const existing = hashesByPage.get(page);
570
+ if (existing) return existing;
571
+ const hashes = /* @__PURE__ */ new Map();
572
+ hashesByPage.set(page, hashes);
552
573
  page.once("close", () => hashesByPage.delete(page));
574
+ return hashes;
575
+ }
576
+ function redirectChain(request) {
577
+ const urls = [];
578
+ for (let redirected = request; redirected !== null; redirected = redirected.redirectedFrom()) urls.push(redirected.url());
579
+ return urls;
553
580
  }
554
581
  /**
555
582
  * The hash from the latest observed request for this URL, across navigations.
@@ -629,8 +656,10 @@ async function captureAriaSnapshot(page, { viewport, scroll }) {
629
656
  boxes: true,
630
657
  depth: MAX_ARIA_DEPTH
631
658
  });
632
- const visible = (await Promise.all(snapshot.map((node) => toDocument(page, node, scroll)))).flatMap((node) => {
633
- const kept = keepShown(node, viewport.width);
659
+ const placed = await Promise.all(snapshot.map((node) => toDocument(page, node, scroll)));
660
+ const clips = await readClips(page, placed);
661
+ const visible = placed.flatMap((node) => {
662
+ const kept = keepShown(node, viewport.width, clips, void 0);
634
663
  return kept === void 0 ? [] : [kept];
635
664
  });
636
665
  const described = await Promise.all(visible.map((node) => describe(page, node, {}, false)));
@@ -694,12 +723,129 @@ function isReachable(box, viewportWidth) {
694
723
  function isShown(node, viewportWidth) {
695
724
  return node.ref !== void 0 && isReachable(node.box, viewportWidth);
696
725
  }
697
- function keepShown(node, viewportWidth) {
726
+ function edgesOf(box) {
727
+ return {
728
+ left: box.x,
729
+ top: box.y,
730
+ right: box.x + box.width,
731
+ bottom: box.y + box.height
732
+ };
733
+ }
734
+ function intersect(a, b) {
735
+ if (a === void 0) return b;
736
+ return {
737
+ left: Math.max(a.left, b.left),
738
+ top: Math.max(a.top, b.top),
739
+ right: Math.min(a.right, b.right),
740
+ bottom: Math.min(a.bottom, b.bottom)
741
+ };
742
+ }
743
+ const ROUNDING_SLACK = 1;
744
+ function sticksOut(inner, outer) {
745
+ return inner.x < outer.x - ROUNDING_SLACK || inner.y < outer.y - ROUNDING_SLACK || inner.x + inner.width > outer.x + outer.width + ROUNDING_SLACK || inner.y + inner.height > outer.y + outer.height + ROUNDING_SLACK;
746
+ }
747
+ function overlaps(a, b) {
748
+ return Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x) > ROUNDING_SLACK && Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y) > ROUNDING_SLACK;
749
+ }
750
+ function isOutside(box, clip) {
751
+ return box.x >= clip.right || box.x + box.width <= clip.left || box.y >= clip.bottom || box.y + box.height <= clip.top;
752
+ }
753
+ /**
754
+ * The area each element is clipped to by its ancestors' `overflow`, in
755
+ * document space, by ref.
756
+ *
757
+ * The boxes are not clipped (the rows of a 448px scroll box run thousands of
758
+ * pixels down the page), and reading an element's ancestors is a page round
759
+ * trip. Only an element that may overflow one is read: a root, one sticking
760
+ * out of its parent, or one running into its next sibling. The clipping
761
+ * ancestor itself is often missing from the tree, since Playwright unwraps a
762
+ * nameless wrapper with one child. Every other element takes its parent's
763
+ * clip.
764
+ */
765
+ async function readClips(page, nodes) {
766
+ const candidates = [];
767
+ const visit = (node, parent, next, frame) => {
768
+ if (node.box !== void 0 && (parent === void 0 || sticksOut(node.box, parent) || next?.box !== void 0 && overlaps(node.box, next.box))) candidates.push({
769
+ node,
770
+ frame
771
+ });
772
+ const childFrame = node.role === "iframe" && node.box !== void 0 ? intersect(frame, edgesOf(node.box)) : frame;
773
+ const elements = (node.children ?? []).filter((child) => {
774
+ return typeof child !== "string";
775
+ });
776
+ elements.forEach((child, index) => {
777
+ visit(child, node.box, elements[index + 1], childFrame);
778
+ });
779
+ };
780
+ nodes.forEach((node, index) => {
781
+ visit(node, void 0, nodes[index + 1], void 0);
782
+ });
783
+ const clips = await Promise.all(candidates.map(async ({ node, frame }) => {
784
+ const clip = await readClip(page, node);
785
+ return clip === void 0 || node.ref === void 0 ? [] : [[node.ref, intersect(frame, clip)]];
786
+ }));
787
+ return new Map(clips.flat());
788
+ }
789
+ async function readClip(page, node) {
790
+ if (node.box === void 0 || node.ref === void 0) return;
791
+ let read;
792
+ try {
793
+ read = await page.locator(`aria-ref=${node.ref}`).evaluate((element) => {
794
+ const clip = {
795
+ left: -Infinity,
796
+ top: -Infinity,
797
+ right: Infinity,
798
+ bottom: Infinity
799
+ };
800
+ let position = getComputedStyle(element).position;
801
+ for (let ancestor = element.parentElement; ancestor !== null && ancestor !== document.body && ancestor !== document.documentElement; ancestor = ancestor.parentElement) {
802
+ if (position === "fixed") break;
803
+ const style = getComputedStyle(ancestor);
804
+ if (position === "absolute" && style.position === "static" && style.transform === "none") continue;
805
+ position = style.position;
806
+ const clipsX = style.overflowX !== "visible";
807
+ const clipsY = style.overflowY !== "visible";
808
+ if (!clipsX && !clipsY || style.display === "inline") continue;
809
+ const rect = ancestor.getBoundingClientRect();
810
+ const left = rect.left + ancestor.clientLeft;
811
+ const top = rect.top + ancestor.clientTop;
812
+ if (clipsX) {
813
+ clip.left = Math.max(clip.left, left);
814
+ clip.right = Math.min(clip.right, left + ancestor.clientWidth);
815
+ }
816
+ if (clipsY) {
817
+ clip.top = Math.max(clip.top, top);
818
+ clip.bottom = Math.min(clip.bottom, top + ancestor.clientHeight);
819
+ }
820
+ }
821
+ const own = element.getBoundingClientRect();
822
+ return {
823
+ x: own.x,
824
+ y: own.y,
825
+ clip
826
+ };
827
+ }, void 0, { timeout: ELEMENT_LOOKUP_TIMEOUT_MS });
828
+ } catch {
829
+ return;
830
+ }
831
+ const dx = node.box.x - read.x;
832
+ const dy = node.box.y - read.y;
833
+ return {
834
+ left: read.clip.left + dx,
835
+ top: read.clip.top + dy,
836
+ right: read.clip.right + dx,
837
+ bottom: read.clip.bottom + dy
838
+ };
839
+ }
840
+ function keepShown(node, viewportWidth, clips, inherited) {
698
841
  if (node.role === "iframe" && node.box !== void 0 && (node.box.width < MIN_IFRAME_SIZE || node.box.height < MIN_IFRAME_SIZE)) return;
842
+ const clip = (node.ref === void 0 ? void 0 : clips.get(node.ref)) ?? inherited;
843
+ if (clip !== void 0 && node.box !== void 0 && isOutside(node.box, clip)) return;
699
844
  const shown = isShown(node, viewportWidth);
845
+ const childClip = node.role === "iframe" && node.box !== void 0 ? intersect(clip, edgesOf(node.box)) : clip;
700
846
  const children = (node.children ?? []).flatMap((child) => {
701
847
  if (typeof child === "string") return shown ? [child] : [];
702
- const kept = keepShown(child, viewportWidth);
848
+ const kept = keepShown(child, viewportWidth, clips, childClip);
703
849
  return kept === void 0 ? [] : [kept];
704
850
  });
705
851
  if (!shown && children.length === 0) return;
@@ -1280,6 +1426,7 @@ var Recorder = class {
1280
1426
  freezer
1281
1427
  });
1282
1428
  this.actions.observe(context);
1429
+ watchImageResponses(context);
1283
1430
  context.on("page", onPage);
1284
1431
  for (const page of context.pages()) onPage(page);
1285
1432
  return ready;
@@ -1380,7 +1527,6 @@ var Recorder = class {
1380
1527
  }
1381
1528
  }).catch(() => void 0);
1382
1529
  page.on("framenavigated", state.onNavigation);
1383
- watchImageResponses(page);
1384
1530
  this.urlChanged(state);
1385
1531
  }
1386
1532
  urlChanged(state) {
@@ -1907,4 +2053,4 @@ async function responseDetail(response) {
1907
2053
  //#endregion
1908
2054
  export { resolveMode as _, prepareArtifactUpload as a, resultLink as c, REPORTER_ENV as d, Recorder as f, loadConfigSync as g, loadConfig as h, createRawGraphBundle as i, uploadRawGraph as l, version as m, bundleImagePath as n, registerDeployment as o, visitAllLinks as p, bundleSnapshotPath as r, resolveUploadOptions as s, RAW_GRAPH_BUNDLE_INDEX as t, MANIFEST_ATTACHMENT as u };
1909
2055
 
1910
- //# sourceMappingURL=upload-jup_jLlm.mjs.map
2056
+ //# sourceMappingURL=upload-BsCzkoup.mjs.map