@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 +1 -1
- package/dist/index.mjs +1 -1
- package/dist/reporter.cjs +1 -1
- package/dist/reporter.mjs +1 -1
- package/dist/{upload-jup_jLlm.mjs → upload-BsCzkoup.mjs} +163 -17
- package/dist/upload-BsCzkoup.mjs.map +1 -0
- package/dist/{upload-D40MqSQa.cjs → upload-_UWss__c.cjs} +163 -17
- package/dist/upload-_UWss__c.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/upload-D40MqSQa.cjs.map +0 -1
- package/dist/upload-jup_jLlm.mjs.map +0 -1
|
@@ -70,7 +70,7 @@ function snapshot(value) {
|
|
|
70
70
|
}
|
|
71
71
|
//#endregion
|
|
72
72
|
//#region package.json
|
|
73
|
-
var version = "2.
|
|
73
|
+
var version = "2.7.0";
|
|
74
74
|
//#endregion
|
|
75
75
|
//#region src/actions.ts
|
|
76
76
|
const BINDING = "__ralphAction";
|
|
@@ -551,30 +551,57 @@ async function waitForAnimations(page, timeoutMs) {
|
|
|
551
551
|
//#region src/image_responses.ts
|
|
552
552
|
const IMAGE_HASH_TIMEOUT_MS = 3e4;
|
|
553
553
|
const hashesByPage = /* @__PURE__ */ new WeakMap();
|
|
554
|
+
/**
|
|
555
|
+
* The latest hash per url across all of a context's pages. The HTTP cache is
|
|
556
|
+
* shared by the context, so this is the copy a 304 tells any of them to reuse.
|
|
557
|
+
*/
|
|
558
|
+
const hashesByContext = /* @__PURE__ */ new WeakMap();
|
|
554
559
|
function sha256(data) {
|
|
555
560
|
return (0, node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
556
561
|
}
|
|
557
562
|
/**
|
|
558
|
-
* Hashes every image the
|
|
559
|
-
* bytes the page showed instead of fetching them a second time. A
|
|
560
|
-
* of a page's images comes as a burst, which hosts such as
|
|
563
|
+
* Hashes every image the context's pages load, as they load, so a snapshot
|
|
564
|
+
* hashes the bytes the page showed instead of fetching them a second time. A
|
|
565
|
+
* second fetch of a page's images comes as a burst, which hosts such as
|
|
561
566
|
* upload.wikimedia.org answer with 429 for a share of them, and the image then
|
|
562
567
|
* reads as replaced. Only the hash is kept, not the body. Entries persist
|
|
563
568
|
* across navigations until another request for the same image replaces them
|
|
564
569
|
* or the page closes.
|
|
570
|
+
*
|
|
571
|
+
* Watched on the context rather than each page: Playwright reports a popup
|
|
572
|
+
* only once its first navigation has committed, and the images that document
|
|
573
|
+
* requests early are gone by the time a listener can be added to the page.
|
|
565
574
|
*/
|
|
566
|
-
function watchImageResponses(
|
|
567
|
-
if (
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (request.resourceType() !== "image") return;
|
|
575
|
+
function watchImageResponses(context) {
|
|
576
|
+
if (hashesByContext.has(context)) return;
|
|
577
|
+
const latest = /* @__PURE__ */ new Map();
|
|
578
|
+
hashesByContext.set(context, latest);
|
|
579
|
+
context.on("request", (request) => {
|
|
580
|
+
if (request.resourceType() !== "image" || request.serviceWorker()) return;
|
|
581
|
+
const hashes = pageHashes(request.frame().page());
|
|
582
|
+
const cached = latest.get(request.url());
|
|
572
583
|
const hash = request.response().then(async (response) => {
|
|
584
|
+
if (response?.status() === 304) return cached;
|
|
573
585
|
return response?.ok() ? sha256(await response.body()) : void 0;
|
|
574
586
|
}).catch(() => void 0);
|
|
575
|
-
for (
|
|
587
|
+
for (const url of redirectChain(request)) {
|
|
588
|
+
hashes.set(url, hash);
|
|
589
|
+
latest.set(url, hash);
|
|
590
|
+
}
|
|
576
591
|
});
|
|
592
|
+
}
|
|
593
|
+
function pageHashes(page) {
|
|
594
|
+
const existing = hashesByPage.get(page);
|
|
595
|
+
if (existing) return existing;
|
|
596
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
597
|
+
hashesByPage.set(page, hashes);
|
|
577
598
|
page.once("close", () => hashesByPage.delete(page));
|
|
599
|
+
return hashes;
|
|
600
|
+
}
|
|
601
|
+
function redirectChain(request) {
|
|
602
|
+
const urls = [];
|
|
603
|
+
for (let redirected = request; redirected !== null; redirected = redirected.redirectedFrom()) urls.push(redirected.url());
|
|
604
|
+
return urls;
|
|
578
605
|
}
|
|
579
606
|
/**
|
|
580
607
|
* The hash from the latest observed request for this URL, across navigations.
|
|
@@ -654,8 +681,10 @@ async function captureAriaSnapshot(page, { viewport, scroll }) {
|
|
|
654
681
|
boxes: true,
|
|
655
682
|
depth: MAX_ARIA_DEPTH
|
|
656
683
|
});
|
|
657
|
-
const
|
|
658
|
-
|
|
684
|
+
const placed = await Promise.all(snapshot.map((node) => toDocument(page, node, scroll)));
|
|
685
|
+
const clips = await readClips(page, placed);
|
|
686
|
+
const visible = placed.flatMap((node) => {
|
|
687
|
+
const kept = keepShown(node, viewport.width, clips, void 0);
|
|
659
688
|
return kept === void 0 ? [] : [kept];
|
|
660
689
|
});
|
|
661
690
|
const described = await Promise.all(visible.map((node) => describe(page, node, {}, false)));
|
|
@@ -719,12 +748,129 @@ function isReachable(box, viewportWidth) {
|
|
|
719
748
|
function isShown(node, viewportWidth) {
|
|
720
749
|
return node.ref !== void 0 && isReachable(node.box, viewportWidth);
|
|
721
750
|
}
|
|
722
|
-
function
|
|
751
|
+
function edgesOf(box) {
|
|
752
|
+
return {
|
|
753
|
+
left: box.x,
|
|
754
|
+
top: box.y,
|
|
755
|
+
right: box.x + box.width,
|
|
756
|
+
bottom: box.y + box.height
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function intersect(a, b) {
|
|
760
|
+
if (a === void 0) return b;
|
|
761
|
+
return {
|
|
762
|
+
left: Math.max(a.left, b.left),
|
|
763
|
+
top: Math.max(a.top, b.top),
|
|
764
|
+
right: Math.min(a.right, b.right),
|
|
765
|
+
bottom: Math.min(a.bottom, b.bottom)
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
const ROUNDING_SLACK = 1;
|
|
769
|
+
function sticksOut(inner, outer) {
|
|
770
|
+
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;
|
|
771
|
+
}
|
|
772
|
+
function overlaps(a, b) {
|
|
773
|
+
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;
|
|
774
|
+
}
|
|
775
|
+
function isOutside(box, clip) {
|
|
776
|
+
return box.x >= clip.right || box.x + box.width <= clip.left || box.y >= clip.bottom || box.y + box.height <= clip.top;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* The area each element is clipped to by its ancestors' `overflow`, in
|
|
780
|
+
* document space, by ref.
|
|
781
|
+
*
|
|
782
|
+
* The boxes are not clipped (the rows of a 448px scroll box run thousands of
|
|
783
|
+
* pixels down the page), and reading an element's ancestors is a page round
|
|
784
|
+
* trip. Only an element that may overflow one is read: a root, one sticking
|
|
785
|
+
* out of its parent, or one running into its next sibling. The clipping
|
|
786
|
+
* ancestor itself is often missing from the tree, since Playwright unwraps a
|
|
787
|
+
* nameless wrapper with one child. Every other element takes its parent's
|
|
788
|
+
* clip.
|
|
789
|
+
*/
|
|
790
|
+
async function readClips(page, nodes) {
|
|
791
|
+
const candidates = [];
|
|
792
|
+
const visit = (node, parent, next, frame) => {
|
|
793
|
+
if (node.box !== void 0 && (parent === void 0 || sticksOut(node.box, parent) || next?.box !== void 0 && overlaps(node.box, next.box))) candidates.push({
|
|
794
|
+
node,
|
|
795
|
+
frame
|
|
796
|
+
});
|
|
797
|
+
const childFrame = node.role === "iframe" && node.box !== void 0 ? intersect(frame, edgesOf(node.box)) : frame;
|
|
798
|
+
const elements = (node.children ?? []).filter((child) => {
|
|
799
|
+
return typeof child !== "string";
|
|
800
|
+
});
|
|
801
|
+
elements.forEach((child, index) => {
|
|
802
|
+
visit(child, node.box, elements[index + 1], childFrame);
|
|
803
|
+
});
|
|
804
|
+
};
|
|
805
|
+
nodes.forEach((node, index) => {
|
|
806
|
+
visit(node, void 0, nodes[index + 1], void 0);
|
|
807
|
+
});
|
|
808
|
+
const clips = await Promise.all(candidates.map(async ({ node, frame }) => {
|
|
809
|
+
const clip = await readClip(page, node);
|
|
810
|
+
return clip === void 0 || node.ref === void 0 ? [] : [[node.ref, intersect(frame, clip)]];
|
|
811
|
+
}));
|
|
812
|
+
return new Map(clips.flat());
|
|
813
|
+
}
|
|
814
|
+
async function readClip(page, node) {
|
|
815
|
+
if (node.box === void 0 || node.ref === void 0) return;
|
|
816
|
+
let read;
|
|
817
|
+
try {
|
|
818
|
+
read = await page.locator(`aria-ref=${node.ref}`).evaluate((element) => {
|
|
819
|
+
const clip = {
|
|
820
|
+
left: -Infinity,
|
|
821
|
+
top: -Infinity,
|
|
822
|
+
right: Infinity,
|
|
823
|
+
bottom: Infinity
|
|
824
|
+
};
|
|
825
|
+
let position = getComputedStyle(element).position;
|
|
826
|
+
for (let ancestor = element.parentElement; ancestor !== null && ancestor !== document.body && ancestor !== document.documentElement; ancestor = ancestor.parentElement) {
|
|
827
|
+
if (position === "fixed") break;
|
|
828
|
+
const style = getComputedStyle(ancestor);
|
|
829
|
+
if (position === "absolute" && style.position === "static" && style.transform === "none") continue;
|
|
830
|
+
position = style.position;
|
|
831
|
+
const clipsX = style.overflowX !== "visible";
|
|
832
|
+
const clipsY = style.overflowY !== "visible";
|
|
833
|
+
if (!clipsX && !clipsY || style.display === "inline") continue;
|
|
834
|
+
const rect = ancestor.getBoundingClientRect();
|
|
835
|
+
const left = rect.left + ancestor.clientLeft;
|
|
836
|
+
const top = rect.top + ancestor.clientTop;
|
|
837
|
+
if (clipsX) {
|
|
838
|
+
clip.left = Math.max(clip.left, left);
|
|
839
|
+
clip.right = Math.min(clip.right, left + ancestor.clientWidth);
|
|
840
|
+
}
|
|
841
|
+
if (clipsY) {
|
|
842
|
+
clip.top = Math.max(clip.top, top);
|
|
843
|
+
clip.bottom = Math.min(clip.bottom, top + ancestor.clientHeight);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const own = element.getBoundingClientRect();
|
|
847
|
+
return {
|
|
848
|
+
x: own.x,
|
|
849
|
+
y: own.y,
|
|
850
|
+
clip
|
|
851
|
+
};
|
|
852
|
+
}, void 0, { timeout: ELEMENT_LOOKUP_TIMEOUT_MS });
|
|
853
|
+
} catch {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const dx = node.box.x - read.x;
|
|
857
|
+
const dy = node.box.y - read.y;
|
|
858
|
+
return {
|
|
859
|
+
left: read.clip.left + dx,
|
|
860
|
+
top: read.clip.top + dy,
|
|
861
|
+
right: read.clip.right + dx,
|
|
862
|
+
bottom: read.clip.bottom + dy
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
function keepShown(node, viewportWidth, clips, inherited) {
|
|
723
866
|
if (node.role === "iframe" && node.box !== void 0 && (node.box.width < MIN_IFRAME_SIZE || node.box.height < MIN_IFRAME_SIZE)) return;
|
|
867
|
+
const clip = (node.ref === void 0 ? void 0 : clips.get(node.ref)) ?? inherited;
|
|
868
|
+
if (clip !== void 0 && node.box !== void 0 && isOutside(node.box, clip)) return;
|
|
724
869
|
const shown = isShown(node, viewportWidth);
|
|
870
|
+
const childClip = node.role === "iframe" && node.box !== void 0 ? intersect(clip, edgesOf(node.box)) : clip;
|
|
725
871
|
const children = (node.children ?? []).flatMap((child) => {
|
|
726
872
|
if (typeof child === "string") return shown ? [child] : [];
|
|
727
|
-
const kept = keepShown(child, viewportWidth);
|
|
873
|
+
const kept = keepShown(child, viewportWidth, clips, childClip);
|
|
728
874
|
return kept === void 0 ? [] : [kept];
|
|
729
875
|
});
|
|
730
876
|
if (!shown && children.length === 0) return;
|
|
@@ -1305,6 +1451,7 @@ var Recorder = class {
|
|
|
1305
1451
|
freezer
|
|
1306
1452
|
});
|
|
1307
1453
|
this.actions.observe(context);
|
|
1454
|
+
watchImageResponses(context);
|
|
1308
1455
|
context.on("page", onPage);
|
|
1309
1456
|
for (const page of context.pages()) onPage(page);
|
|
1310
1457
|
return ready;
|
|
@@ -1405,7 +1552,6 @@ var Recorder = class {
|
|
|
1405
1552
|
}
|
|
1406
1553
|
}).catch(() => void 0);
|
|
1407
1554
|
page.on("framenavigated", state.onNavigation);
|
|
1408
|
-
watchImageResponses(page);
|
|
1409
1555
|
this.urlChanged(state);
|
|
1410
1556
|
}
|
|
1411
1557
|
urlChanged(state) {
|
|
@@ -2039,4 +2185,4 @@ Object.defineProperty(exports, "visitAllLinks", {
|
|
|
2039
2185
|
}
|
|
2040
2186
|
});
|
|
2041
2187
|
|
|
2042
|
-
//# sourceMappingURL=upload-
|
|
2188
|
+
//# sourceMappingURL=upload-_UWss__c.cjs.map
|