@codepress/interactions-playwright 1.0.0 → 1.1.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 CHANGED
@@ -41,6 +41,7 @@ Options (all optional):
41
41
  | `routeSegments` | `[]` | literal path segments kept in the default route template (others become `:id`) |
42
42
  | `maxElements` | 50000 | element limit |
43
43
  | `theme` | the page's `prefers-color-scheme` | `"light"`, `"dark"` or `"unknown"` to record, for apps with their own theme toggle (Playwright's `colorScheme` / `page.emulateMedia` set the default) |
44
+ | `testId` | `defaultTestIdFilter` | `(value, element) => string \| null`: which `data-interaction-id` / `data-testid` values to record, as the browser SDK's [`testId` option](https://github.com/quantfive/codepress-interactions/blob/main/packages/browser/README.md#test-ids-and-privacy). It runs in the page, so it must be self-contained, like a `page.evaluate` callback; a name the page does not have fails the capture. A name the page does have, such as a test variable called `status`, or any other runtime error in the hook, drops the ID with no error, so check the snapshot's IDs when you write a hook. Write out the rule your app uses so fingerprints match production, calling `globalThis.__codepressInteractions.defaultTestIdFilter(value)` where it calls the default. `(value) => value` keeps every ID |
44
45
  | `ignoreFingerprints` | `[]` | fingerprints to leave out (known findings accepted for now) |
45
46
  | `events` | none | protocol v2 events to run the click and stability checks over too |
46
47
  | `checks` and thresholds | see [detect options](https://github.com/quantfive/codepress-interactions/blob/main/packages/browser/README.md#ui-checks) | |
package/dist/index.cjs CHANGED
@@ -42,12 +42,40 @@ async function captureSnapshot(page, options = {}) {
42
42
  routeSegments: options.routeSegments,
43
43
  maxElements: options.maxElements,
44
44
  theme: options.theme
45
- }
45
+ },
46
+ testId: typeof options.testId === "function" ? String(options.testId) : null
46
47
  };
47
- const body = await page.evaluate(async ({ script, options: options2 }) => {
48
+ const body = await page.evaluate(async ({ script, options: options2, testId }) => {
48
49
  const scope = globalThis;
49
50
  if (!scope.__codepressInteractions) (0, eval)(script);
50
- return scope.__codepressInteractions.walk(options2);
51
+ if (testId === null) return scope.__codepressInteractions.walk(options2);
52
+ let filter;
53
+ try {
54
+ filter = (0, eval)(`(${testId})`);
55
+ } catch {
56
+ try {
57
+ filter = Object.values((0, eval)(`({${testId}})`))[0];
58
+ } catch {
59
+ }
60
+ }
61
+ if (typeof filter !== "function")
62
+ throw new Error("testId must be a self-contained function: bound and native functions cannot run in the page");
63
+ const hook = filter;
64
+ let missing = null;
65
+ const body2 = await scope.__codepressInteractions.walk({
66
+ ...options2,
67
+ testId: (value, element) => {
68
+ try {
69
+ return hook(value, element);
70
+ } catch (error) {
71
+ if (error instanceof ReferenceError) missing ??= error.message;
72
+ throw error;
73
+ }
74
+ }
75
+ });
76
+ if (missing !== null)
77
+ throw new Error(`testId runs in the page and must be self-contained: ${missing}`);
78
+ return body2;
51
79
  }, args);
52
80
  const sha256 = (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(body)).digest("hex");
53
81
  return { body, sha256 };
package/dist/index.d.cts CHANGED
@@ -15,6 +15,15 @@ interface CaptureOptions {
15
15
  maxElements?: number;
16
16
  /** Theme to record when the app has its own toggle. Default: the page's prefers-color-scheme (`colorScheme` in the Playwright config, or `page.emulateMedia`). */
17
17
  theme?: "light" | "dark" | "unknown";
18
+ /**
19
+ * Test ID filter, as the browser SDK's `testId` option: return the value to
20
+ * keep it, `null` to drop it. Replaces the default (`defaultTestIdFilter`).
21
+ * It runs in the page, so like a `page.evaluate` callback it must be
22
+ * self-contained: no variables from the test file, no imports. Using one
23
+ * fails the capture. To extend the default, call
24
+ * `globalThis.__codepressInteractions.defaultTestIdFilter(value)`.
25
+ */
26
+ testId?: (value: string, element: Element) => string | null;
18
27
  }
19
28
  interface Snapshot {
20
29
  body: SnapshotBody;
package/dist/index.d.ts CHANGED
@@ -15,6 +15,15 @@ interface CaptureOptions {
15
15
  maxElements?: number;
16
16
  /** Theme to record when the app has its own toggle. Default: the page's prefers-color-scheme (`colorScheme` in the Playwright config, or `page.emulateMedia`). */
17
17
  theme?: "light" | "dark" | "unknown";
18
+ /**
19
+ * Test ID filter, as the browser SDK's `testId` option: return the value to
20
+ * keep it, `null` to drop it. Replaces the default (`defaultTestIdFilter`).
21
+ * It runs in the page, so like a `page.evaluate` callback it must be
22
+ * self-contained: no variables from the test file, no imports. Using one
23
+ * fails the capture. To extend the default, call
24
+ * `globalThis.__codepressInteractions.defaultTestIdFilter(value)`.
25
+ */
26
+ testId?: (value: string, element: Element) => string | null;
18
27
  }
19
28
  interface Snapshot {
20
29
  body: SnapshotBody;
package/dist/index.js CHANGED
@@ -19,12 +19,40 @@ async function captureSnapshot(page, options = {}) {
19
19
  routeSegments: options.routeSegments,
20
20
  maxElements: options.maxElements,
21
21
  theme: options.theme
22
- }
22
+ },
23
+ testId: typeof options.testId === "function" ? String(options.testId) : null
23
24
  };
24
- const body = await page.evaluate(async ({ script, options: options2 }) => {
25
+ const body = await page.evaluate(async ({ script, options: options2, testId }) => {
25
26
  const scope = globalThis;
26
27
  if (!scope.__codepressInteractions) (0, eval)(script);
27
- return scope.__codepressInteractions.walk(options2);
28
+ if (testId === null) return scope.__codepressInteractions.walk(options2);
29
+ let filter;
30
+ try {
31
+ filter = (0, eval)(`(${testId})`);
32
+ } catch {
33
+ try {
34
+ filter = Object.values((0, eval)(`({${testId}})`))[0];
35
+ } catch {
36
+ }
37
+ }
38
+ if (typeof filter !== "function")
39
+ throw new Error("testId must be a self-contained function: bound and native functions cannot run in the page");
40
+ const hook = filter;
41
+ let missing = null;
42
+ const body2 = await scope.__codepressInteractions.walk({
43
+ ...options2,
44
+ testId: (value, element) => {
45
+ try {
46
+ return hook(value, element);
47
+ } catch (error) {
48
+ if (error instanceof ReferenceError) missing ??= error.message;
49
+ throw error;
50
+ }
51
+ }
52
+ });
53
+ if (missing !== null)
54
+ throw new Error(`testId runs in the page and must be self-contained: ${missing}`);
55
+ return body2;
28
56
  }, args);
29
57
  const sha256 = createHash("sha256").update(JSON.stringify(body)).digest("hex");
30
58
  return { body, sha256 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codepress/interactions-playwright",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Playwright helpers that run the CodePress Interactions UI checks in tests: capture a snapshot, list findings, assert none",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "README.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@codepress/interactions-browser": "^1.0.0"
27
+ "@codepress/interactions-browser": "^1.1.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@playwright/test": ">=1.40"