@ralphralphai/playwright 2.7.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-BsCzkoup.mjs → upload-CcdDM33Z.mjs} +55 -27
- package/dist/upload-CcdDM33Z.mjs.map +1 -0
- package/dist/{upload-_UWss__c.cjs → upload-KEwXpYxG.cjs} +55 -27
- package/dist/upload-KEwXpYxG.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/upload-BsCzkoup.mjs.map +0 -1
- package/dist/upload-_UWss__c.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -254,6 +254,24 @@ Pass an `edgeLabel` to describe the step in your own words, e.g.
|
|
|
254
254
|
`ralph.recordNode(page, { name: 'Done', edgeLabel: 'Sign in' })`. A later call
|
|
255
255
|
without one keeps it.
|
|
256
256
|
|
|
257
|
+
### Excluding elements from snapshots
|
|
258
|
+
|
|
259
|
+
For parts of the page that change on their own, such as ads, chat widgets or
|
|
260
|
+
live counters, list CSS selectors in `ignoreInDiff`:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
ralphOptions: {
|
|
264
|
+
ignoreInDiff: ['.ad-slot', '#intercom-container', 'live-clock'],
|
|
265
|
+
},
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Matched elements and their descendants, including content in open shadow roots
|
|
269
|
+
and slots, are left out of the ARIA snapshot used for diffing. They still appear
|
|
270
|
+
in the screenshot, and the space they take up still moves the elements around
|
|
271
|
+
them. Text that sits inline in a larger element, like a bare `<span>` in a
|
|
272
|
+
paragraph, belongs to that element's text and is not removed. Wrap it in an
|
|
273
|
+
element with a role, or match the whole paragraph.
|
|
274
|
+
|
|
257
275
|
### When recording fails
|
|
258
276
|
|
|
259
277
|
- A failed `recordNode` call rejects.
|
|
@@ -414,6 +432,7 @@ export default defineConfig<RalphFixtures>({
|
|
|
414
432
|
| `ready` | | `(page, signal) => Promise<void>`. Waits for your app before an automatic recording. Honor `signal`. |
|
|
415
433
|
| `reduceMotion` | `false` | Emulates `prefers-reduced-motion: reduce` and waits for finite animations before recording. |
|
|
416
434
|
| `freezeDuringCapture` | `false` | Finishes finite browser animations, allows JS-driven DOM changes to settle within a bounded wait, then freezes timers and animations across capture. |
|
|
435
|
+
| `ignoreInDiff` | | CSS selectors for subtrees to omit from the ARIA snapshot. See [Excluding elements from snapshots](#excluding-elements-from-snapshots). |
|
|
417
436
|
| `strictNodeTags` | `false` | Fails the test when the app reports a tag outside what `ralph.setNodeConditions` declared. |
|
|
418
437
|
| `ignoreUntaggedCalls` | `false` | Makes `recordNode`, `setNodeConditions`, and `observe` no-ops in untagged tests instead of throwing. |
|
|
419
438
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_upload = require("./upload-
|
|
2
|
+
const require_upload = require("./upload-KEwXpYxG.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.d.mts
CHANGED
|
@@ -18,6 +18,11 @@ type RalphOptions = {
|
|
|
18
18
|
*/
|
|
19
19
|
freezeDuringCapture?: boolean;
|
|
20
20
|
recordTimeoutMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* CSS selectors for subtrees the diff ignores, such as an ad slot or a chat
|
|
23
|
+
* widget. They are left out of the aria snapshot but stay in the screenshot.
|
|
24
|
+
*/
|
|
25
|
+
ignoreInDiff?: string[];
|
|
21
26
|
ready?: (page: Page, signal: AbortSignal) => Promise<void>;
|
|
22
27
|
/**
|
|
23
28
|
* Fail the test when the app reports a tag outside the set the 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-
|
|
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-CcdDM33Z.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
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-
|
|
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-CcdDM33Z.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.
|
|
48
|
+
var version = "2.8.0";
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region src/actions.ts
|
|
51
51
|
const BINDING = "__ralphAction";
|
|
@@ -650,7 +650,7 @@ const MIN_IFRAME_SIZE = 5;
|
|
|
650
650
|
* screenshot just showed: sticky headers, lazy loading, scroll-triggered
|
|
651
651
|
* animation.
|
|
652
652
|
*/
|
|
653
|
-
async function captureAriaSnapshot(page, { viewport, scroll }) {
|
|
653
|
+
async function captureAriaSnapshot(page, { viewport, scroll }, ignore = []) {
|
|
654
654
|
const snapshot = await page.locator("body").ariaSnapshotJSON({
|
|
655
655
|
mode: "ai",
|
|
656
656
|
boxes: true,
|
|
@@ -662,7 +662,10 @@ async function captureAriaSnapshot(page, { viewport, scroll }) {
|
|
|
662
662
|
const kept = keepShown(node, viewport.width, clips, void 0);
|
|
663
663
|
return kept === void 0 ? [] : [kept];
|
|
664
664
|
});
|
|
665
|
-
const
|
|
665
|
+
const ignoreSelector = ignore.length === 0 ? void 0 : ignore.join(", ");
|
|
666
|
+
const facts = /* @__PURE__ */ new Map();
|
|
667
|
+
const normalized = (await Promise.all(visible.map((node) => readTree(page, node, ignoreSelector, facts)))).filter((node) => node !== void 0);
|
|
668
|
+
const described = await Promise.all(normalized.map((node) => describe(page, node, {}, false, facts)));
|
|
666
669
|
const json = Buffer.from(JSON.stringify(fillMissingRefs(described)));
|
|
667
670
|
return json.byteLength > 4194304 ? void 0 : json;
|
|
668
671
|
}
|
|
@@ -849,12 +852,14 @@ function keepShown(node, viewportWidth, clips, inherited) {
|
|
|
849
852
|
return kept === void 0 ? [] : [kept];
|
|
850
853
|
});
|
|
851
854
|
if (!shown && children.length === 0) return;
|
|
852
|
-
|
|
853
|
-
if (node.role === "generic" && !node.name && (node.children?.length ?? 0) > 1 && children.length === 1 && typeof only !== "string" && only?.ref !== void 0) return only;
|
|
854
|
-
return node.children === void 0 ? node : {
|
|
855
|
+
return loneChild(node, children) ?? (node.children === void 0 ? node : {
|
|
855
856
|
...node,
|
|
856
857
|
children
|
|
857
|
-
};
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
function loneChild(node, children) {
|
|
861
|
+
const [only] = children;
|
|
862
|
+
if (node.role === "generic" && !node.name && (node.children?.length ?? 0) > 1 && children.length === 1 && typeof only !== "string" && only?.ref !== void 0) return only;
|
|
858
863
|
}
|
|
859
864
|
function fillMissingRefs(nodes) {
|
|
860
865
|
let generated = 0;
|
|
@@ -873,34 +878,40 @@ function fillMissingRefs(nodes) {
|
|
|
873
878
|
};
|
|
874
879
|
return nodes.map(fill);
|
|
875
880
|
}
|
|
876
|
-
async function readElement(page, ref, isImage) {
|
|
881
|
+
async function readElement(page, ref, isImage, ignoreSelector) {
|
|
877
882
|
try {
|
|
878
|
-
return await page.locator(`aria-ref=${ref}`).evaluate((element, { properties, isImage, dynamicAttribute }) => {
|
|
883
|
+
return await page.locator(`aria-ref=${ref}`).evaluate((element, { properties, isImage, dynamicAttribute, ignoreSelector }) => {
|
|
879
884
|
const computed = getComputedStyle(element);
|
|
880
885
|
const typed = "computedStyleMap" in element ? element.computedStyleMap() : void 0;
|
|
881
886
|
const style = Object.fromEntries(properties.map((property) => [property, property.startsWith("margin-") && typed?.get(property)?.toString() === "auto" ? "auto" : computed.getPropertyValue(property)]));
|
|
882
887
|
const dynamic = element.closest(`[${dynamicAttribute}]`) !== null;
|
|
883
|
-
|
|
888
|
+
let ignored = false;
|
|
889
|
+
for (let at = element; ignoreSelector !== void 0 && at !== null && !ignored;) {
|
|
890
|
+
try {
|
|
891
|
+
ignored = at.matches(ignoreSelector);
|
|
892
|
+
} catch {
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
const root = at.getRootNode();
|
|
896
|
+
at = at.assignedSlot ?? at.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
|
|
897
|
+
}
|
|
898
|
+
const facts = {
|
|
884
899
|
style,
|
|
885
|
-
dynamic
|
|
900
|
+
dynamic,
|
|
901
|
+
ignored
|
|
886
902
|
};
|
|
903
|
+
if (!isImage) return facts;
|
|
887
904
|
if (element instanceof HTMLImageElement) return {
|
|
888
|
-
|
|
889
|
-
dynamic,
|
|
905
|
+
...facts,
|
|
890
906
|
image: { src: element.currentSrc || element.src }
|
|
891
907
|
};
|
|
892
908
|
if (element instanceof SVGElement) return {
|
|
893
|
-
|
|
894
|
-
dynamic,
|
|
909
|
+
...facts,
|
|
895
910
|
image: { markup: element.outerHTML }
|
|
896
911
|
};
|
|
897
912
|
const backgroundUrl = /url\("(.+)"\)/.exec(computed.backgroundImage)?.[1];
|
|
898
|
-
return backgroundUrl === void 0 ? {
|
|
899
|
-
|
|
900
|
-
dynamic
|
|
901
|
-
} : {
|
|
902
|
-
style,
|
|
903
|
-
dynamic,
|
|
913
|
+
return backgroundUrl === void 0 ? facts : {
|
|
914
|
+
...facts,
|
|
904
915
|
image: { src: new URL(backgroundUrl, document.baseURI).href }
|
|
905
916
|
};
|
|
906
917
|
}, {
|
|
@@ -910,7 +921,8 @@ async function readElement(page, ref, isImage) {
|
|
|
910
921
|
...isImage ? Object.keys(IMAGE_PROPERTIES) : []
|
|
911
922
|
],
|
|
912
923
|
isImage,
|
|
913
|
-
dynamicAttribute: DYNAMIC_ATTRIBUTE
|
|
924
|
+
dynamicAttribute: DYNAMIC_ATTRIBUTE,
|
|
925
|
+
ignoreSelector
|
|
914
926
|
}, { timeout: ELEMENT_LOOKUP_TIMEOUT_MS });
|
|
915
927
|
} catch {
|
|
916
928
|
return;
|
|
@@ -932,13 +944,29 @@ function distinctStyle(style, inherited) {
|
|
|
932
944
|
});
|
|
933
945
|
return distinct.length === 0 ? void 0 : Object.fromEntries(distinct);
|
|
934
946
|
}
|
|
935
|
-
async function
|
|
947
|
+
async function readTree(page, child, ignoreSelector, factsByRef) {
|
|
948
|
+
if (typeof child === "string") return child;
|
|
949
|
+
const facts = child.ref === void 0 ? void 0 : await readElement(page, child.ref, child.role === "img", ignoreSelector);
|
|
950
|
+
if (facts?.ignored) return;
|
|
951
|
+
if (child.ref !== void 0 && facts !== void 0) factsByRef.set(child.ref, facts);
|
|
952
|
+
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);
|
|
953
|
+
if (children !== void 0) {
|
|
954
|
+
if (children.length === 0 && (child.ref === void 0 || child.role === "generic" && !child.name && (child.children?.length ?? 0) > 0)) return;
|
|
955
|
+
const only = loneChild(child, children);
|
|
956
|
+
if (only !== void 0) return only;
|
|
957
|
+
}
|
|
958
|
+
return children === void 0 ? child : {
|
|
959
|
+
...child,
|
|
960
|
+
children
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
async function describe(page, child, inherited, insideDynamic, factsByRef) {
|
|
936
964
|
if (typeof child === "string") return child;
|
|
937
|
-
const facts = child.ref === void 0 ? void 0 :
|
|
965
|
+
const facts = child.ref === void 0 ? void 0 : factsByRef.get(child.ref);
|
|
938
966
|
const style = facts === void 0 ? void 0 : distinctStyle(facts.style, inherited);
|
|
939
967
|
const imageHash = facts?.image === void 0 ? void 0 : await hashImage(page, facts.image);
|
|
940
968
|
const dynamic = facts?.dynamic === true && !insideDynamic;
|
|
941
|
-
const children = child.children === void 0 ? void 0 : await Promise.all(child.children.map((grandchild) => describe(page, grandchild, facts?.style ?? inherited, insideDynamic || dynamic)));
|
|
969
|
+
const children = child.children === void 0 ? void 0 : await Promise.all(child.children.map((grandchild) => describe(page, grandchild, facts?.style ?? inherited, insideDynamic || dynamic, factsByRef)));
|
|
942
970
|
return {
|
|
943
971
|
...child,
|
|
944
972
|
...style && { style },
|
|
@@ -1619,7 +1647,7 @@ var Recorder = class {
|
|
|
1619
1647
|
before,
|
|
1620
1648
|
image,
|
|
1621
1649
|
after,
|
|
1622
|
-
snapshot: await Promise.race([captureAriaSnapshot(page, after).catch(() => void 0), setTimeout$1(Math.max(0, deadline.endsAt - Date.now()), void 0, { ref: false })])
|
|
1650
|
+
snapshot: await Promise.race([captureAriaSnapshot(page, after, this.options.ignoreInDiff).catch(() => void 0), setTimeout$1(Math.max(0, deadline.endsAt - Date.now()), void 0, { ref: false })])
|
|
1623
1651
|
};
|
|
1624
1652
|
};
|
|
1625
1653
|
const { before, image, after, snapshot } = freezer ? await freezer.capture(page, deadline.signal, capture, deadline.endsAt) : await capture();
|
|
@@ -2053,4 +2081,4 @@ async function responseDetail(response) {
|
|
|
2053
2081
|
//#endregion
|
|
2054
2082
|
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 };
|
|
2055
2083
|
|
|
2056
|
-
//# sourceMappingURL=upload-
|
|
2084
|
+
//# sourceMappingURL=upload-CcdDM33Z.mjs.map
|