@ralphralphai/playwright 2.6.0 → 2.8.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/README.md +19 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +1 -1
- package/dist/reporter.cjs +1 -1
- package/dist/reporter.mjs +1 -1
- package/dist/{upload-CGIMWRQM.mjs → upload-CcdDM33Z.mjs} +93 -38
- package/dist/upload-CcdDM33Z.mjs.map +1 -0
- package/dist/{upload-H-EycbCA.cjs → upload-KEwXpYxG.cjs} +93 -38
- package/dist/upload-KEwXpYxG.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/upload-CGIMWRQM.mjs.map +0 -1
- package/dist/upload-H-EycbCA.cjs.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.8.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.
|
|
@@ -648,7 +675,7 @@ const MIN_IFRAME_SIZE = 5;
|
|
|
648
675
|
* screenshot just showed: sticky headers, lazy loading, scroll-triggered
|
|
649
676
|
* animation.
|
|
650
677
|
*/
|
|
651
|
-
async function captureAriaSnapshot(page, { viewport, scroll }) {
|
|
678
|
+
async function captureAriaSnapshot(page, { viewport, scroll }, ignore = []) {
|
|
652
679
|
const snapshot = await page.locator("body").ariaSnapshotJSON({
|
|
653
680
|
mode: "ai",
|
|
654
681
|
boxes: true,
|
|
@@ -660,7 +687,10 @@ async function captureAriaSnapshot(page, { viewport, scroll }) {
|
|
|
660
687
|
const kept = keepShown(node, viewport.width, clips, void 0);
|
|
661
688
|
return kept === void 0 ? [] : [kept];
|
|
662
689
|
});
|
|
663
|
-
const
|
|
690
|
+
const ignoreSelector = ignore.length === 0 ? void 0 : ignore.join(", ");
|
|
691
|
+
const facts = /* @__PURE__ */ new Map();
|
|
692
|
+
const normalized = (await Promise.all(visible.map((node) => readTree(page, node, ignoreSelector, facts)))).filter((node) => node !== void 0);
|
|
693
|
+
const described = await Promise.all(normalized.map((node) => describe(page, node, {}, false, facts)));
|
|
664
694
|
const json = Buffer.from(JSON.stringify(fillMissingRefs(described)));
|
|
665
695
|
return json.byteLength > 4194304 ? void 0 : json;
|
|
666
696
|
}
|
|
@@ -847,12 +877,14 @@ function keepShown(node, viewportWidth, clips, inherited) {
|
|
|
847
877
|
return kept === void 0 ? [] : [kept];
|
|
848
878
|
});
|
|
849
879
|
if (!shown && children.length === 0) return;
|
|
850
|
-
|
|
851
|
-
if (node.role === "generic" && !node.name && (node.children?.length ?? 0) > 1 && children.length === 1 && typeof only !== "string" && only?.ref !== void 0) return only;
|
|
852
|
-
return node.children === void 0 ? node : {
|
|
880
|
+
return loneChild(node, children) ?? (node.children === void 0 ? node : {
|
|
853
881
|
...node,
|
|
854
882
|
children
|
|
855
|
-
};
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
function loneChild(node, children) {
|
|
886
|
+
const [only] = children;
|
|
887
|
+
if (node.role === "generic" && !node.name && (node.children?.length ?? 0) > 1 && children.length === 1 && typeof only !== "string" && only?.ref !== void 0) return only;
|
|
856
888
|
}
|
|
857
889
|
function fillMissingRefs(nodes) {
|
|
858
890
|
let generated = 0;
|
|
@@ -871,34 +903,40 @@ function fillMissingRefs(nodes) {
|
|
|
871
903
|
};
|
|
872
904
|
return nodes.map(fill);
|
|
873
905
|
}
|
|
874
|
-
async function readElement(page, ref, isImage) {
|
|
906
|
+
async function readElement(page, ref, isImage, ignoreSelector) {
|
|
875
907
|
try {
|
|
876
|
-
return await page.locator(`aria-ref=${ref}`).evaluate((element, { properties, isImage, dynamicAttribute }) => {
|
|
908
|
+
return await page.locator(`aria-ref=${ref}`).evaluate((element, { properties, isImage, dynamicAttribute, ignoreSelector }) => {
|
|
877
909
|
const computed = getComputedStyle(element);
|
|
878
910
|
const typed = "computedStyleMap" in element ? element.computedStyleMap() : void 0;
|
|
879
911
|
const style = Object.fromEntries(properties.map((property) => [property, property.startsWith("margin-") && typed?.get(property)?.toString() === "auto" ? "auto" : computed.getPropertyValue(property)]));
|
|
880
912
|
const dynamic = element.closest(`[${dynamicAttribute}]`) !== null;
|
|
881
|
-
|
|
913
|
+
let ignored = false;
|
|
914
|
+
for (let at = element; ignoreSelector !== void 0 && at !== null && !ignored;) {
|
|
915
|
+
try {
|
|
916
|
+
ignored = at.matches(ignoreSelector);
|
|
917
|
+
} catch {
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
const root = at.getRootNode();
|
|
921
|
+
at = at.assignedSlot ?? at.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
|
|
922
|
+
}
|
|
923
|
+
const facts = {
|
|
882
924
|
style,
|
|
883
|
-
dynamic
|
|
925
|
+
dynamic,
|
|
926
|
+
ignored
|
|
884
927
|
};
|
|
928
|
+
if (!isImage) return facts;
|
|
885
929
|
if (element instanceof HTMLImageElement) return {
|
|
886
|
-
|
|
887
|
-
dynamic,
|
|
930
|
+
...facts,
|
|
888
931
|
image: { src: element.currentSrc || element.src }
|
|
889
932
|
};
|
|
890
933
|
if (element instanceof SVGElement) return {
|
|
891
|
-
|
|
892
|
-
dynamic,
|
|
934
|
+
...facts,
|
|
893
935
|
image: { markup: element.outerHTML }
|
|
894
936
|
};
|
|
895
937
|
const backgroundUrl = /url\("(.+)"\)/.exec(computed.backgroundImage)?.[1];
|
|
896
|
-
return backgroundUrl === void 0 ? {
|
|
897
|
-
|
|
898
|
-
dynamic
|
|
899
|
-
} : {
|
|
900
|
-
style,
|
|
901
|
-
dynamic,
|
|
938
|
+
return backgroundUrl === void 0 ? facts : {
|
|
939
|
+
...facts,
|
|
902
940
|
image: { src: new URL(backgroundUrl, document.baseURI).href }
|
|
903
941
|
};
|
|
904
942
|
}, {
|
|
@@ -908,7 +946,8 @@ async function readElement(page, ref, isImage) {
|
|
|
908
946
|
...isImage ? Object.keys(IMAGE_PROPERTIES) : []
|
|
909
947
|
],
|
|
910
948
|
isImage,
|
|
911
|
-
dynamicAttribute: DYNAMIC_ATTRIBUTE
|
|
949
|
+
dynamicAttribute: DYNAMIC_ATTRIBUTE,
|
|
950
|
+
ignoreSelector
|
|
912
951
|
}, { timeout: ELEMENT_LOOKUP_TIMEOUT_MS });
|
|
913
952
|
} catch {
|
|
914
953
|
return;
|
|
@@ -930,13 +969,29 @@ function distinctStyle(style, inherited) {
|
|
|
930
969
|
});
|
|
931
970
|
return distinct.length === 0 ? void 0 : Object.fromEntries(distinct);
|
|
932
971
|
}
|
|
933
|
-
async function
|
|
972
|
+
async function readTree(page, child, ignoreSelector, factsByRef) {
|
|
973
|
+
if (typeof child === "string") return child;
|
|
974
|
+
const facts = child.ref === void 0 ? void 0 : await readElement(page, child.ref, child.role === "img", ignoreSelector);
|
|
975
|
+
if (facts?.ignored) return;
|
|
976
|
+
if (child.ref !== void 0 && facts !== void 0) factsByRef.set(child.ref, facts);
|
|
977
|
+
const children = child.children === void 0 ? void 0 : (await Promise.all(child.children.map((grandchild) => readTree(page, grandchild, ignoreSelector, factsByRef)))).filter((grandchild) => grandchild !== void 0);
|
|
978
|
+
if (children !== void 0) {
|
|
979
|
+
if (children.length === 0 && (child.ref === void 0 || child.role === "generic" && !child.name && (child.children?.length ?? 0) > 0)) return;
|
|
980
|
+
const only = loneChild(child, children);
|
|
981
|
+
if (only !== void 0) return only;
|
|
982
|
+
}
|
|
983
|
+
return children === void 0 ? child : {
|
|
984
|
+
...child,
|
|
985
|
+
children
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
async function describe(page, child, inherited, insideDynamic, factsByRef) {
|
|
934
989
|
if (typeof child === "string") return child;
|
|
935
|
-
const facts = child.ref === void 0 ? void 0 :
|
|
990
|
+
const facts = child.ref === void 0 ? void 0 : factsByRef.get(child.ref);
|
|
936
991
|
const style = facts === void 0 ? void 0 : distinctStyle(facts.style, inherited);
|
|
937
992
|
const imageHash = facts?.image === void 0 ? void 0 : await hashImage(page, facts.image);
|
|
938
993
|
const dynamic = facts?.dynamic === true && !insideDynamic;
|
|
939
|
-
const children = child.children === void 0 ? void 0 : await Promise.all(child.children.map((grandchild) => describe(page, grandchild, facts?.style ?? inherited, insideDynamic || dynamic)));
|
|
994
|
+
const children = child.children === void 0 ? void 0 : await Promise.all(child.children.map((grandchild) => describe(page, grandchild, facts?.style ?? inherited, insideDynamic || dynamic, factsByRef)));
|
|
940
995
|
return {
|
|
941
996
|
...child,
|
|
942
997
|
...style && { style },
|
|
@@ -1424,6 +1479,7 @@ var Recorder = class {
|
|
|
1424
1479
|
freezer
|
|
1425
1480
|
});
|
|
1426
1481
|
this.actions.observe(context);
|
|
1482
|
+
watchImageResponses(context);
|
|
1427
1483
|
context.on("page", onPage);
|
|
1428
1484
|
for (const page of context.pages()) onPage(page);
|
|
1429
1485
|
return ready;
|
|
@@ -1524,7 +1580,6 @@ var Recorder = class {
|
|
|
1524
1580
|
}
|
|
1525
1581
|
}).catch(() => void 0);
|
|
1526
1582
|
page.on("framenavigated", state.onNavigation);
|
|
1527
|
-
watchImageResponses(page);
|
|
1528
1583
|
this.urlChanged(state);
|
|
1529
1584
|
}
|
|
1530
1585
|
urlChanged(state) {
|
|
@@ -1617,7 +1672,7 @@ var Recorder = class {
|
|
|
1617
1672
|
before,
|
|
1618
1673
|
image,
|
|
1619
1674
|
after,
|
|
1620
|
-
snapshot: await Promise.race([captureAriaSnapshot(page, after).catch(() => void 0), (0, node_timers_promises.setTimeout)(Math.max(0, deadline.endsAt - Date.now()), void 0, { ref: false })])
|
|
1675
|
+
snapshot: await Promise.race([captureAriaSnapshot(page, after, this.options.ignoreInDiff).catch(() => void 0), (0, node_timers_promises.setTimeout)(Math.max(0, deadline.endsAt - Date.now()), void 0, { ref: false })])
|
|
1621
1676
|
};
|
|
1622
1677
|
};
|
|
1623
1678
|
const { before, image, after, snapshot } = freezer ? await freezer.capture(page, deadline.signal, capture, deadline.endsAt) : await capture();
|
|
@@ -2158,4 +2213,4 @@ Object.defineProperty(exports, "visitAllLinks", {
|
|
|
2158
2213
|
}
|
|
2159
2214
|
});
|
|
2160
2215
|
|
|
2161
|
-
//# sourceMappingURL=upload-
|
|
2216
|
+
//# sourceMappingURL=upload-KEwXpYxG.cjs.map
|