@empiricalrun/playwright-utils 0.63.0 → 0.63.2

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/generated/api-worker-openapi.d.ts +8176 -7526
  3. package/dist/generated/api-worker-openapi.d.ts.map +1 -1
  4. package/dist/overlay-tests/cache.spec.js +31 -7
  5. package/dist/overlay-tests/click.spec.js +2 -7
  6. package/dist/overlay-tests/dismiss-mechanics.spec.d.ts +2 -0
  7. package/dist/overlay-tests/dismiss-mechanics.spec.d.ts.map +1 -0
  8. package/dist/overlay-tests/dismiss-mechanics.spec.js +59 -0
  9. package/dist/overlay-tests/dropdown.spec.d.ts +2 -0
  10. package/dist/overlay-tests/dropdown.spec.d.ts.map +1 -0
  11. package/dist/overlay-tests/dropdown.spec.js +53 -0
  12. package/dist/overlay-tests/fixtures.d.ts +3 -1
  13. package/dist/overlay-tests/fixtures.d.ts.map +1 -1
  14. package/dist/overlay-tests/fixtures.js +15 -0
  15. package/dist/overlay-tests/typesafe-hit-check.spec.d.ts +2 -0
  16. package/dist/overlay-tests/typesafe-hit-check.spec.d.ts.map +1 -0
  17. package/dist/overlay-tests/typesafe-hit-check.spec.js +57 -0
  18. package/dist/overlay-tests/typesafe.spec.d.ts +2 -0
  19. package/dist/overlay-tests/typesafe.spec.d.ts.map +1 -0
  20. package/dist/overlay-tests/typesafe.spec.js +86 -0
  21. package/dist/test/scripts/pw-locator-patch/actions/click.d.ts.map +1 -1
  22. package/dist/test/scripts/pw-locator-patch/actions/click.js +8 -0
  23. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/cache.d.ts +1 -1
  24. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/cache.d.ts.map +1 -1
  25. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/cache.js +9 -6
  26. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/index.d.ts +3 -0
  27. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/index.d.ts.map +1 -1
  28. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/index.js +13 -3
  29. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/typesafe.d.ts +38 -0
  30. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/typesafe.d.ts.map +1 -0
  31. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/typesafe.js +159 -0
  32. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/utils.d.ts +3 -0
  33. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/utils.d.ts.map +1 -1
  34. package/dist/test/scripts/pw-locator-patch/dismiss-overlays/utils.js +11 -0
  35. package/package.json +6 -9
  36. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dismissWithTypeSafe = dismissWithTypeSafe;
4
+ exports.checkHit = checkHit;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const SYSTEM_ONE_URL = "https://api.typesafe.ai/v1/systemone";
7
+ const MODEL = "jev-preview";
8
+ const MAX_STEPS = 4;
9
+ const MIN_POPUP_PROBABILITY = 0.5;
10
+ const MIN_BOX_CONFIDENCE = 0.3;
11
+ async function dismissWithTypeSafe(page, overlay) {
12
+ const steps = [];
13
+ const stop = (stopReason) => ({
14
+ dismissed: stopReason === "popup_gone" && steps.length > 1,
15
+ stopReason,
16
+ steps,
17
+ });
18
+ let previousScreenshot;
19
+ for (let i = 0; i < MAX_STEPS; i++) {
20
+ const viewport = page.viewportSize();
21
+ if (!viewport) {
22
+ throw new Error("Viewport size is required for TypeSafe dismissal");
23
+ }
24
+ const screenshot = await page.screenshot({ type: "png" });
25
+ if (previousScreenshot?.equals(screenshot)) {
26
+ return stop("no_visual_change");
27
+ }
28
+ previousScreenshot = screenshot;
29
+ const startedAt = Date.now();
30
+ const response = await askSystemOne(screenshot, overlay.text);
31
+ const { popup_visible, dismiss_control } = response.answers;
32
+ const step = {
33
+ popupProbability: popup_visible.noul,
34
+ box: dismiss_control.box,
35
+ boxConfidence: dismiss_control.confidence,
36
+ clicked: undefined,
37
+ hitCheck: undefined,
38
+ latencyMs: Date.now() - startedAt,
39
+ inputTokens: response.usage.input_tokens,
40
+ outputTokens: response.usage.output_tokens,
41
+ };
42
+ steps.push(step);
43
+ if (popup_visible.noul < MIN_POPUP_PROBABILITY) {
44
+ return stop("popup_gone");
45
+ }
46
+ if (dismiss_control.confidence < MIN_BOX_CONFIDENCE) {
47
+ return stop("low_box_confidence");
48
+ }
49
+ const { box } = dismiss_control;
50
+ const point = {
51
+ x: Math.round(((box.left + box.right) / 2) * viewport.width),
52
+ y: Math.round(((box.top + box.bottom) / 2) * viewport.height),
53
+ };
54
+ step.hitCheck = await checkHit(overlay.target, point);
55
+ if (step.hitCheck !== "ok") {
56
+ return stop(step.hitCheck);
57
+ }
58
+ step.clicked = point;
59
+ await page.mouse.click(point.x, point.y);
60
+ await page.waitForTimeout(300);
61
+ }
62
+ return stop("max_steps");
63
+ }
64
+ function checkHit(target, point) {
65
+ return target.evaluate((targetEl, { x, y }) => {
66
+ const deepHit = (px, py) => {
67
+ let el = document.elementFromPoint(px, py);
68
+ while (el?.shadowRoot) {
69
+ const inner = el.shadowRoot.elementFromPoint(px, py);
70
+ if (!inner || inner === el) {
71
+ break;
72
+ }
73
+ el = inner;
74
+ }
75
+ return el;
76
+ };
77
+ const parentOf = (el) => el.parentNode instanceof ShadowRoot
78
+ ? el.parentNode.host
79
+ : el.parentElement;
80
+ const isAncestorOrSelf = (ancestor, el) => {
81
+ for (let n = el; n; n = parentOf(n)) {
82
+ if (n === ancestor) {
83
+ return true;
84
+ }
85
+ }
86
+ return false;
87
+ };
88
+ const rect = targetEl.getBoundingClientRect();
89
+ const blocker = deepHit(rect.left + rect.width / 2, rect.top + rect.height / 2);
90
+ if (!blocker || isAncestorOrSelf(targetEl, blocker)) {
91
+ return "no_blocker";
92
+ }
93
+ let overlayRoot = blocker;
94
+ for (let p = parentOf(overlayRoot); p && !isAncestorOrSelf(p, targetEl); p = parentOf(p)) {
95
+ overlayRoot = p;
96
+ }
97
+ const hit = deepHit(x, y);
98
+ if (!hit || !isAncestorOrSelf(overlayRoot, hit)) {
99
+ return "outside_overlay";
100
+ }
101
+ const interactive = 'button, a[href], input, select, label, summary, iframe, [role=button], [role=link], [role=checkbox], [role=menuitem], [role=tab], [onclick], [tabindex]:not([tabindex="-1"])';
102
+ for (let n = hit; n; n = parentOf(n)) {
103
+ if (n.matches(interactive) || getComputedStyle(n).cursor === "pointer") {
104
+ return "ok";
105
+ }
106
+ if (n === overlayRoot) {
107
+ break;
108
+ }
109
+ }
110
+ return "not_clickable";
111
+ }, point);
112
+ }
113
+ async function askSystemOne(screenshot, overlayText) {
114
+ const apiKey = process.env.TYPESAFE_API_KEY;
115
+ if (!apiKey) {
116
+ throw new Error("TYPESAFE_API_KEY is not set");
117
+ }
118
+ const reference = (0, node_crypto_1.randomUUID)();
119
+ const popup = overlayText
120
+ ? `the popup that contains the text "${overlayText.trim().slice(0, 300)}"`
121
+ : "the popup";
122
+ const body = {
123
+ model: MODEL,
124
+ state: {
125
+ screenshot: reference,
126
+ context: "A browser test was blocked because a popup (modal, toast, survey, banner or product tour) covers the element it wants to click.",
127
+ },
128
+ assets: {
129
+ [reference]: {
130
+ type: "image",
131
+ format: "png",
132
+ data_base64: screenshot.toString("base64"),
133
+ },
134
+ },
135
+ questions: {
136
+ popup_visible: {
137
+ type: "noul",
138
+ instructions: `Is ${popup} still visible in \`screenshot\`?`,
139
+ },
140
+ dismiss_control: {
141
+ type: "box",
142
+ instructions: `the single control in \`screenshot\` to click next to dismiss ${popup}: a close (×) button, or a Close, Skip, No thanks, Got it, Accept, Agree or Continue button. Prefer the option that accepts or continues over Decline or Cancel. If a required checkbox must be ticked first, the checkbox. Never the page's own controls behind the popup.`,
143
+ },
144
+ },
145
+ };
146
+ const response = await fetch(SYSTEM_ONE_URL, {
147
+ method: "POST",
148
+ headers: {
149
+ Authorization: `Bearer ${apiKey}`,
150
+ "Content-Type": "application/json",
151
+ "TypeSafe-Danger-Zone-Images": "true",
152
+ },
153
+ body: JSON.stringify(body),
154
+ });
155
+ if (!response.ok) {
156
+ throw new Error(`TypeSafe ${response.status}: ${await response.text()}`);
157
+ }
158
+ return (await response.json());
159
+ }
@@ -2,6 +2,9 @@ import type { Page } from "@playwright/test";
2
2
  import type { TestFn } from "../../types";
3
3
  import type { OverlayElement } from "./types";
4
4
  export declare function overlayDescription(element: OverlayElement | undefined): string | undefined;
5
+ export declare const DISMISS_OVERLAY_STEP = "Dismiss overlay";
6
+ export declare const DISMISSAL_AGENT_STEP = "Run overlay dismissal agent";
7
+ export declare function dismissOverlayStepTitle(element: OverlayElement | undefined): string;
5
8
  export declare function isProductFruitsOverlay(element: OverlayElement | undefined): boolean | undefined;
6
9
  export declare function textContent(pageRef: Page, element: OverlayElement | undefined): Promise<string | undefined>;
7
10
  export declare function annotateForReport(testFn: TestFn, description: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,sBASrE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,uBAKzE;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,IAAI,EACb,OAAO,EAAE,cAAc,GAAG,SAAS,+BAgDpC;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QAMpE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,uDAiBrD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,sBASrE;AAED,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AACtD,eAAO,MAAM,oBAAoB,gCAAgC,CAAC;AAElE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,UAO1E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,uBAKzE;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,IAAI,EACb,OAAO,EAAE,cAAc,GAAG,SAAS,+BAgDpC;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QAMpE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,uDAiBrD"}
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DISMISSAL_AGENT_STEP = exports.DISMISS_OVERLAY_STEP = void 0;
3
4
  exports.overlayDescription = overlayDescription;
5
+ exports.dismissOverlayStepTitle = dismissOverlayStepTitle;
4
6
  exports.isProductFruitsOverlay = isProductFruitsOverlay;
5
7
  exports.textContent = textContent;
6
8
  exports.annotateForReport = annotateForReport;
@@ -17,6 +19,15 @@ function overlayDescription(element) {
17
19
  }
18
20
  }
19
21
  }
22
+ exports.DISMISS_OVERLAY_STEP = "Dismiss overlay";
23
+ exports.DISMISSAL_AGENT_STEP = "Run overlay dismissal agent";
24
+ function dismissOverlayStepTitle(element) {
25
+ const root = element?.parent ?? element?.interceptor ?? "";
26
+ const name = /aria-label="([^"]+)"/.exec(root)?.[1] ??
27
+ /role="([^"]+)"/.exec(root)?.[1] ??
28
+ /<([a-z][a-z0-9-]*)/i.exec(root)?.[1];
29
+ return name ? `${exports.DISMISS_OVERLAY_STEP} "${name}"` : exports.DISMISS_OVERLAY_STEP;
30
+ }
20
31
  function isProductFruitsOverlay(element) {
21
32
  return (element?.parent?.includes("productfruits--container") ||
22
33
  element?.interceptor.includes("productfruits--container"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.63.0",
3
+ "version": "0.63.2",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -37,7 +37,6 @@
37
37
  "@playwright/test": "1.63.0",
38
38
  "@types/async-retry": "^1.4.8",
39
39
  "@types/authenticator": "^1.1.4",
40
- "@types/babel__code-frame": "^7.0.6",
41
40
  "@types/console-log-level": "^1.4.5",
42
41
  "@types/mime": "3.0.0",
43
42
  "@types/node": "^22",
@@ -45,22 +44,20 @@
45
44
  "openapi-typescript": "7.6.1",
46
45
  "playwright-core": "1.63.0",
47
46
  "serve-handler": "^6.1.6",
48
- "@empiricalrun/shared-types": "0.21.0"
47
+ "@empiricalrun/shared-types": "0.21.2"
49
48
  },
50
49
  "dependencies": {
51
- "@babel/code-frame": "^7.24.7",
52
50
  "async-retry": "^1.3.3",
53
51
  "authenticator": "^1.1.5",
54
52
  "console-log-level": "^1.4.1",
55
53
  "mime": "3.0.0",
56
- "openapi-fetch": "0.14.1",
57
54
  "openai": "^7.19.0",
55
+ "openapi-fetch": "0.14.1",
58
56
  "puppeteer-extra-plugin-recaptcha": "^3.6.8",
59
- "rimraf": "^6.0.1",
60
57
  "ts-morph": "^23.0.0",
61
- "@empiricalrun/cua": "^0.5.2",
62
- "@empiricalrun/llm": "^0.28.2",
63
- "@empiricalrun/r2-uploader": "^0.11.3",
58
+ "@empiricalrun/cua": "^0.5.4",
59
+ "@empiricalrun/llm": "^0.28.3",
60
+ "@empiricalrun/r2-uploader": "^0.11.4",
64
61
  "@empiricalrun/reporter": "^0.30.3"
65
62
  },
66
63
  "scripts": {
@@ -1 +1 @@
1
- {"version":"7.0.0-dev.20260623.1","root":["./src/api-worker-client.ts","./src/email.ts","./src/inbox-client.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/config/index.ts","./src/config/proxy.ts","./src/config/devices/types.ts","./src/generated/api-worker-openapi.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/overlay-tests/visual.spec.ts","./src/reporter/attachment-cleanup-test-reporter.ts","./src/reporter/attachment-cleanup.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/harness.ts","./src/reporter/ibr-utils.ts","./src/reporter/incremental-blob-reporter.ts","./src/reporter/lifecycle-events.ts","./src/reporter/local-test.ts","./src/reporter/parity-utils.ts","./src/reporter/reporter-state.ts","./src/reporter/test-case-sync-reporter.ts","./src/reporter/test-case-sync.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/coverage.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/expect/index.ts","./src/test/expect/types.ts","./src/test/expect/visual.ts","./src/test/expect/webhook.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/actions/click.ts","./src/test/scripts/pw-locator-patch/actions/hover.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"]}
1
+ {"version":"7.0.0-dev.20260623.1","root":["./src/api-worker-client.ts","./src/email.ts","./src/inbox-client.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/config/index.ts","./src/config/proxy.ts","./src/config/devices/types.ts","./src/generated/api-worker-openapi.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/dismiss-mechanics.spec.ts","./src/overlay-tests/dropdown.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/overlay-tests/typesafe-hit-check.spec.ts","./src/overlay-tests/typesafe.spec.ts","./src/overlay-tests/visual.spec.ts","./src/reporter/attachment-cleanup-test-reporter.ts","./src/reporter/attachment-cleanup.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/harness.ts","./src/reporter/ibr-utils.ts","./src/reporter/incremental-blob-reporter.ts","./src/reporter/lifecycle-events.ts","./src/reporter/local-test.ts","./src/reporter/parity-utils.ts","./src/reporter/reporter-state.ts","./src/reporter/test-case-sync-reporter.ts","./src/reporter/test-case-sync.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/coverage.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/expect/index.ts","./src/test/expect/types.ts","./src/test/expect/visual.ts","./src/test/expect/webhook.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/actions/click.ts","./src/test/scripts/pw-locator-patch/actions/hover.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/typesafe.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"]}