@empiricalrun/playwright-utils 0.62.1 → 0.63.1
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/CHANGELOG.md +26 -0
- package/dist/api-worker-client.d.ts +0 -13
- package/dist/api-worker-client.d.ts.map +1 -1
- package/dist/api-worker-client.js +0 -15
- package/dist/auth/google.d.ts.map +1 -1
- package/dist/auth/google.js +0 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +0 -9
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +0 -8
- package/dist/generated/api-worker-openapi.d.ts +3031 -1420
- package/dist/generated/api-worker-openapi.d.ts.map +1 -1
- package/dist/overlay-tests/cache.spec.js +31 -17
- package/dist/overlay-tests/click.spec.js +2 -25
- package/dist/overlay-tests/dismiss-mechanics.spec.d.ts +2 -0
- package/dist/overlay-tests/dismiss-mechanics.spec.d.ts.map +1 -0
- package/dist/overlay-tests/dismiss-mechanics.spec.js +59 -0
- package/dist/overlay-tests/dropdown.spec.d.ts +2 -0
- package/dist/overlay-tests/dropdown.spec.d.ts.map +1 -0
- package/dist/overlay-tests/dropdown.spec.js +53 -0
- package/dist/overlay-tests/fixtures.d.ts +3 -1
- package/dist/overlay-tests/fixtures.d.ts.map +1 -1
- package/dist/overlay-tests/fixtures.js +15 -0
- package/dist/overlay-tests/typesafe-hit-check.spec.d.ts +2 -0
- package/dist/overlay-tests/typesafe-hit-check.spec.d.ts.map +1 -0
- package/dist/overlay-tests/typesafe-hit-check.spec.js +57 -0
- package/dist/overlay-tests/typesafe.spec.d.ts +2 -0
- package/dist/overlay-tests/typesafe.spec.d.ts.map +1 -0
- package/dist/overlay-tests/typesafe.spec.js +86 -0
- package/dist/playwright-extensions.d.ts.map +1 -1
- package/dist/playwright-extensions.js +0 -1
- package/dist/reporter/attachment-cleanup-test-reporter.d.ts +0 -6
- package/dist/reporter/attachment-cleanup-test-reporter.d.ts.map +1 -1
- package/dist/reporter/attachment-cleanup-test-reporter.js +0 -6
- package/dist/reporter/attachment-cleanup.d.ts +0 -33
- package/dist/reporter/attachment-cleanup.d.ts.map +1 -1
- package/dist/reporter/attachment-cleanup.js +2 -51
- package/dist/reporter/empirical-reporter.d.ts.map +1 -1
- package/dist/reporter/empirical-reporter.js +0 -9
- package/dist/reporter/harness.d.ts +0 -62
- package/dist/reporter/harness.d.ts.map +1 -1
- package/dist/reporter/harness.js +1 -53
- package/dist/reporter/ibr-utils.d.ts +0 -5
- package/dist/reporter/ibr-utils.d.ts.map +1 -1
- package/dist/reporter/ibr-utils.js +0 -5
- package/dist/reporter/incremental-blob-reporter.d.ts +0 -17
- package/dist/reporter/incremental-blob-reporter.d.ts.map +1 -1
- package/dist/reporter/incremental-blob-reporter.js +5 -35
- package/dist/reporter/lifecycle-events.d.ts.map +1 -1
- package/dist/reporter/lifecycle-events.js +0 -9
- package/dist/reporter/parity-utils.d.ts +0 -30
- package/dist/reporter/parity-utils.d.ts.map +1 -1
- package/dist/reporter/parity-utils.js +4 -28
- package/dist/reporter/reporter-state.d.ts +0 -12
- package/dist/reporter/reporter-state.d.ts.map +1 -1
- package/dist/reporter/reporter-state.js +0 -12
- package/dist/reporter/test-case-sync.d.ts.map +1 -1
- package/dist/reporter/test-case-sync.js +0 -12
- package/dist/reporter/uploader.d.ts.map +1 -1
- package/dist/reporter/uploader.js +0 -6
- package/dist/reporter/util.d.ts +0 -33
- package/dist/reporter/util.d.ts.map +1 -1
- package/dist/reporter/util.js +0 -37
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +1 -10
- package/dist/test/coverage.d.ts.map +1 -1
- package/dist/test/coverage.js +1 -3
- package/dist/test/expect/visual.d.ts.map +1 -1
- package/dist/test/expect/visual.js +1 -9
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +4 -21
- package/dist/test/scripts/agent-capabilities.js +2 -2
- package/dist/test/scripts/index.d.ts +2 -2
- package/dist/test/scripts/index.d.ts.map +1 -1
- package/dist/test/scripts/index.js +3 -16
- package/dist/test/scripts/pw-locator-patch/actions/click.d.ts.map +1 -0
- package/dist/test/scripts/pw-locator-patch/{highlight → actions}/click.js +8 -6
- package/dist/test/scripts/pw-locator-patch/actions/hover.d.ts.map +1 -0
- package/dist/test/scripts/pw-locator-patch/{highlight → actions}/hover.js +0 -3
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/cache.d.ts +1 -1
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/cache.d.ts.map +1 -1
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/cache.js +9 -13
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/index.d.ts +3 -0
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/index.d.ts.map +1 -1
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/index.js +14 -12
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/prompt.d.ts.map +1 -1
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/prompt.js +0 -1
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/typesafe.d.ts +38 -0
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/typesafe.d.ts.map +1 -0
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/typesafe.js +159 -0
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/utils.d.ts +3 -0
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/utils.d.ts.map +1 -1
- package/dist/test/scripts/pw-locator-patch/dismiss-overlays/utils.js +13 -5
- package/dist/test/scripts/pw-locator-patch/utils/index.d.ts +0 -1
- package/dist/test/scripts/pw-locator-patch/utils/index.d.ts.map +1 -1
- package/dist/test/scripts/pw-locator-patch/utils/index.js +0 -18
- package/dist/test/types.d.ts +2 -5
- package/dist/test/types.d.ts.map +1 -1
- package/dist/test/video-labels.d.ts.map +1 -1
- package/dist/test/video-labels.js +0 -1
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +0 -3
- package/docs/fixtures.md +4 -5
- package/package.json +6 -6
- package/playwright.config.ts +0 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/upstream-oracle/ibr-adapter.js +0 -23
- package/upstream-oracle/run.mjs +1 -69
- package/dist/test/scripts/locator-highlights.d.ts +0 -4
- package/dist/test/scripts/locator-highlights.d.ts.map +0 -1
- package/dist/test/scripts/locator-highlights.js +0 -28
- package/dist/test/scripts/pw-locator-patch/highlight/click.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/expect.d.ts +0 -5
- package/dist/test/scripts/pw-locator-patch/highlight/expect.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/expect.js +0 -23
- package/dist/test/scripts/pw-locator-patch/highlight/hover.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/inner-text.d.ts +0 -6
- package/dist/test/scripts/pw-locator-patch/highlight/inner-text.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/inner-text.js +0 -30
- package/dist/test/scripts/pw-locator-patch/highlight/input-value.d.ts +0 -6
- package/dist/test/scripts/pw-locator-patch/highlight/input-value.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/input-value.js +0 -30
- package/dist/test/scripts/pw-locator-patch/highlight/is-checked.d.ts +0 -6
- package/dist/test/scripts/pw-locator-patch/highlight/is-checked.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/is-checked.js +0 -30
- package/dist/test/scripts/pw-locator-patch/highlight/is-disabled.d.ts +0 -6
- package/dist/test/scripts/pw-locator-patch/highlight/is-disabled.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/is-disabled.js +0 -30
- package/dist/test/scripts/pw-locator-patch/highlight/is-editable.d.ts +0 -6
- package/dist/test/scripts/pw-locator-patch/highlight/is-editable.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/is-editable.js +0 -29
- package/dist/test/scripts/pw-locator-patch/highlight/text-content.d.ts +0 -6
- package/dist/test/scripts/pw-locator-patch/highlight/text-content.d.ts.map +0 -1
- package/dist/test/scripts/pw-locator-patch/highlight/text-content.js +0 -30
- /package/dist/test/scripts/pw-locator-patch/{highlight → actions}/click.d.ts +0 -0
- /package/dist/test/scripts/pw-locator-patch/{highlight → actions}/hover.d.ts +0 -0
|
@@ -13,12 +13,11 @@ const useCache = (data) => {
|
|
|
13
13
|
version: "2025-03-06",
|
|
14
14
|
data,
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
const dir = path_1.default.dirname(cache_1.CACHE_FILE);
|
|
16
|
+
const dir = path_1.default.dirname((0, cache_1.cacheFile)());
|
|
18
17
|
if (!fs_1.default.existsSync(dir)) {
|
|
19
18
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
20
19
|
}
|
|
21
|
-
fs_1.default.writeFileSync(cache_1.
|
|
20
|
+
fs_1.default.writeFileSync((0, cache_1.cacheFile)(), JSON.stringify(cacheValue, null, 2), "utf8");
|
|
22
21
|
};
|
|
23
22
|
// Unsets env vars for the duration of a test and puts them back exactly as they
|
|
24
23
|
// were — assigning `undefined` to process.env would store the string
|
|
@@ -42,14 +41,7 @@ function clearEnv(names) {
|
|
|
42
41
|
fixtures_1.test.beforeEach(async ({ page }) => {
|
|
43
42
|
(0, test_1.injectLocatorHighlightScripts)(page, fixtures_1.test);
|
|
44
43
|
});
|
|
45
|
-
fixtures_1.test.afterEach(() => {
|
|
46
|
-
// Delete overlay cache
|
|
47
|
-
if (fs_1.default.existsSync(cache_1.CACHE_FILE)) {
|
|
48
|
-
fs_1.default.unlinkSync(cache_1.CACHE_FILE);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
44
|
(0, fixtures_1.test)("overlay dismiss should not trigger recursively when executing from cache", async ({ page, server, }) => {
|
|
52
|
-
// Set up cache with a bad value: click on "Target" to dimiss the glass pane
|
|
53
45
|
useCache([
|
|
54
46
|
{
|
|
55
47
|
element: { dom: '<div class="glass-pane">', textContent: "" },
|
|
@@ -58,15 +50,8 @@ fixtures_1.test.afterEach(() => {
|
|
|
58
50
|
]);
|
|
59
51
|
await page.goto(`${server.baseURL}/glass-pane.html`);
|
|
60
52
|
let error;
|
|
61
|
-
// The only two routes the browser agent has to a model: the project API key
|
|
62
|
-
// (api-worker's proxy) and a sandbox's OPENAI_BASE_URL. Clearing both makes
|
|
63
|
-
// llmProxyOpenAIConfig throw before any request — which matters here more
|
|
64
|
-
// than elsewhere, because this is the recursive case and every extra
|
|
65
|
-
// dismissal would spawn another agent and another set of screenshots.
|
|
66
53
|
const restoreEnv = clearEnv(["EMPIRICALRUN_API_KEY", "OPENAI_BASE_URL"]);
|
|
67
54
|
try {
|
|
68
|
-
// Target element click will trigger the cache - which will invoke another
|
|
69
|
-
// click to the same element (recursive overlay dismissal)
|
|
70
55
|
await page.getByRole("button", { name: "Target" }).click();
|
|
71
56
|
}
|
|
72
57
|
catch (e) {
|
|
@@ -78,3 +63,32 @@ fixtures_1.test.afterEach(() => {
|
|
|
78
63
|
(0, fixtures_1.expect)(error).toBeDefined();
|
|
79
64
|
(0, fixtures_1.expect)(error.message).toContain('<div class="glass-pane"></div> intercepts pointer events');
|
|
80
65
|
});
|
|
66
|
+
for (const clickMode of ["mouse", "dispatch"]) {
|
|
67
|
+
(0, fixtures_1.test)(`cached dismissal in ${clickMode} mode keeps the test's listbox open`, async ({ page, server, }) => {
|
|
68
|
+
fixtures_1.test.fail(clickMode === "mouse", "A mouse click replayed on the toast is a pointerdown outside the listbox, which closes it");
|
|
69
|
+
await page.goto(`${server.baseURL}/dropdown-toast.html`);
|
|
70
|
+
await page.getByRole("combobox", { name: "Usage month" }).click();
|
|
71
|
+
const toast = page.getByRole("status", { name: "Notifications" });
|
|
72
|
+
await (0, fixtures_1.expect)(toast).toBeVisible();
|
|
73
|
+
const toastText = await toast.evaluate((el) => el.textContent?.trim().replace(/\s+/g, " ") ?? "");
|
|
74
|
+
useCache([
|
|
75
|
+
{
|
|
76
|
+
element: {
|
|
77
|
+
dom: '<section id="toast" role="status" aria-label="Notifications">',
|
|
78
|
+
textContent: toastText,
|
|
79
|
+
},
|
|
80
|
+
code: `await page.getByRole('button', { name: 'Later' }).click();\n`,
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
const restoreEnv = clearEnv(["EMPIRICALRUN_API_KEY", "OPENAI_BASE_URL"]);
|
|
84
|
+
process.env.EMPIRICALRUN_OVERLAY_CLICK_MODE = clickMode;
|
|
85
|
+
try {
|
|
86
|
+
await page.getByRole("option", { name: "September" }).click();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
delete process.env.EMPIRICALRUN_OVERLAY_CLICK_MODE;
|
|
90
|
+
restoreEnv();
|
|
91
|
+
}
|
|
92
|
+
await (0, fixtures_1.expect)(page.locator("#status")).toHaveText("Showing usage for September (1280 credits)");
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -10,14 +10,8 @@ const fixtures_1 = require("./fixtures");
|
|
|
10
10
|
fixtures_1.test.beforeEach(async ({ page }) => {
|
|
11
11
|
(0, test_1.injectLocatorHighlightScripts)(page, fixtures_1.test);
|
|
12
12
|
});
|
|
13
|
-
fixtures_1.test.afterAll(async () => {
|
|
14
|
-
// Delete overlay cache
|
|
15
|
-
if (fs_1.default.existsSync(cache_1.CACHE_FILE)) {
|
|
16
|
-
fs_1.default.unlinkSync(cache_1.CACHE_FILE);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
13
|
function getCacheDataWithDom({ dom }) {
|
|
20
|
-
const cacheData = JSON.parse(fs_1.default.readFileSync(cache_1.
|
|
14
|
+
const cacheData = JSON.parse(fs_1.default.readFileSync((0, cache_1.cacheFile)(), "utf8"));
|
|
21
15
|
return cacheData.data.find(({ element }) => {
|
|
22
16
|
if (dom) {
|
|
23
17
|
return element.dom.includes(dom);
|
|
@@ -26,7 +20,7 @@ function getCacheDataWithDom({ dom }) {
|
|
|
26
20
|
});
|
|
27
21
|
}
|
|
28
22
|
function getCacheDataWithTextContent({ textContent }) {
|
|
29
|
-
const cacheData = JSON.parse(fs_1.default.readFileSync(cache_1.
|
|
23
|
+
const cacheData = JSON.parse(fs_1.default.readFileSync((0, cache_1.cacheFile)(), "utf8"));
|
|
30
24
|
return cacheData.data.find(({ element }) => {
|
|
31
25
|
if (textContent) {
|
|
32
26
|
return element.textContent.includes(textContent);
|
|
@@ -36,28 +30,20 @@ function getCacheDataWithTextContent({ textContent }) {
|
|
|
36
30
|
}
|
|
37
31
|
(0, fixtures_1.test)("should dismiss survicate for hover", async ({ page, server }) => {
|
|
38
32
|
await page.goto(`${server.baseURL}/survey.html`);
|
|
39
|
-
// Assert that Survicate and button loads
|
|
40
33
|
await (0, fixtures_1.expect)(page.getByText("Start survey")).toBeVisible();
|
|
41
34
|
const targetButton = page.getByRole("button", { name: "Target" });
|
|
42
35
|
await (0, fixtures_1.expect)(targetButton).toBeVisible();
|
|
43
|
-
// Get initial z-index
|
|
44
36
|
const initialZIndex = await targetButton.evaluate((el) => window.getComputedStyle(el).getPropertyValue("z-index"));
|
|
45
|
-
// Do the hover, which should pass after survey overlay is dismissed
|
|
46
37
|
await targetButton.hover();
|
|
47
|
-
// Assert z-index hasn't changed
|
|
48
38
|
const finalZIndex = await targetButton.evaluate((el) => window.getComputedStyle(el).getPropertyValue("z-index"));
|
|
49
39
|
(0, fixtures_1.expect)(finalZIndex).toBe(initialZIndex);
|
|
50
40
|
});
|
|
51
|
-
// Skipped since it's flaky
|
|
52
41
|
fixtures_1.test.skip("should dismiss two-step overlay for click", async ({ page, server, }) => {
|
|
53
42
|
await page.goto(`${server.baseURL}/tos.html`);
|
|
54
|
-
// Assert that button and overlay load
|
|
55
43
|
await (0, fixtures_1.expect)(page.getByRole("button", { name: "Target" })).toBeVisible();
|
|
56
44
|
await (0, fixtures_1.expect)(page.getByRole("heading", { name: "Terms of Service" })).toBeVisible();
|
|
57
|
-
// Do the click, which should pass
|
|
58
45
|
await page.getByRole("button", { name: "Target" }).click();
|
|
59
46
|
await (0, fixtures_1.expect)(page.getByRole("heading", { name: "Terms of Service" })).not.toBeVisible();
|
|
60
|
-
// Assert cache is correct
|
|
61
47
|
const withDom = getCacheDataWithDom({
|
|
62
48
|
dom: '<div class="terms-text">',
|
|
63
49
|
});
|
|
@@ -75,7 +61,6 @@ fixtures_1.test.skip("should dismiss two-step overlay for click", async ({ page,
|
|
|
75
61
|
await page.goto(`${server.baseURL}/overlay-form.html`);
|
|
76
62
|
await page.getByRole("button", { name: "Target" }).click();
|
|
77
63
|
await (0, fixtures_1.expect)(page.getByText("Target was clicked")).toBeVisible();
|
|
78
|
-
// Assert cache is correct
|
|
79
64
|
const withDom = getCacheDataWithDom({
|
|
80
65
|
dom: 'id="hearAboutUs"',
|
|
81
66
|
});
|
|
@@ -92,16 +77,12 @@ fixtures_1.test.skip("should dismiss two-step overlay for click", async ({ page,
|
|
|
92
77
|
});
|
|
93
78
|
(0, fixtures_1.test)("should choose and dismiss the correct overlay", async ({ page, server, }) => {
|
|
94
79
|
await page.goto(`${server.baseURL}/choose-an-overlay.html`);
|
|
95
|
-
// Assert that button and overlays load
|
|
96
80
|
await (0, fixtures_1.expect)(page.getByRole("button", { name: "Target" })).toBeVisible();
|
|
97
81
|
await (0, fixtures_1.expect)(page.getByText("This is a toast message")).toBeVisible();
|
|
98
82
|
await (0, fixtures_1.expect)(page.getByRole("button", { name: "Close" })).toBeVisible();
|
|
99
|
-
// Do the click, which should pass
|
|
100
83
|
await page.getByRole("button", { name: "Target" }).click();
|
|
101
|
-
// Assert correct overlay was dismissed
|
|
102
84
|
await (0, fixtures_1.expect)(page.getByText("This is a toast message")).not.toBeVisible();
|
|
103
85
|
await (0, fixtures_1.expect)(page.getByRole("button", { name: "Close" })).toBeVisible();
|
|
104
|
-
// Assert cache is correct
|
|
105
86
|
const withDom = getCacheDataWithDom({
|
|
106
87
|
dom: '<div id="toast" class="z-[100]">',
|
|
107
88
|
});
|
|
@@ -115,19 +96,15 @@ fixtures_1.test.skip("should dismiss two-step overlay for click", async ({ page,
|
|
|
115
96
|
(0, fixtures_1.expect)(code).toBeDefined();
|
|
116
97
|
(0, fixtures_1.expect)(code).toContain("await page.getByRole('button', { name: '×' }).click()");
|
|
117
98
|
});
|
|
118
|
-
// Test is flaky - skipping it
|
|
119
99
|
fixtures_1.test.skip("should fallback when the overlay cannot be dismissed", async ({ page, server, }) => {
|
|
120
100
|
await page.goto(`${server.baseURL}/no-overlay.html`);
|
|
121
101
|
let error;
|
|
122
102
|
try {
|
|
123
|
-
// Click on element that is hidden below another button, which is not
|
|
124
|
-
// dismissible
|
|
125
103
|
await page.getByRole("button", { name: "Bottom Button" }).click();
|
|
126
104
|
}
|
|
127
105
|
catch (e) {
|
|
128
106
|
error = e;
|
|
129
107
|
}
|
|
130
|
-
// TODO: add better assertions with result from master agent
|
|
131
108
|
console.log(fixtures_1.test.info().annotations);
|
|
132
109
|
(0, fixtures_1.expect)(error).toBeDefined();
|
|
133
110
|
(0, fixtures_1.expect)(error.message).toContain('<button type="button" class="top-button">Top Button</button> intercepts pointer events');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dismiss-mechanics.spec.d.ts","sourceRoot":"","sources":["../../src/overlay-tests/dismiss-mechanics.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fixtures_1 = require("./fixtures");
|
|
4
|
+
const dismissals = {
|
|
5
|
+
"native click": (page) => page.getByRole("button", { name: "Later" }).click(),
|
|
6
|
+
"dispatched click": (page) => page.getByRole("button", { name: "Later" }).dispatchEvent("click"),
|
|
7
|
+
"Escape key": (page) => page.keyboard.press("Escape"),
|
|
8
|
+
};
|
|
9
|
+
const keepsListboxOpen = {
|
|
10
|
+
pointerdown: {
|
|
11
|
+
"native click": false,
|
|
12
|
+
"dispatched click": true,
|
|
13
|
+
"Escape key": false,
|
|
14
|
+
},
|
|
15
|
+
focusout: {
|
|
16
|
+
"native click": false,
|
|
17
|
+
"dispatched click": true,
|
|
18
|
+
"Escape key": false,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
for (const [closeOn, expectations] of Object.entries(keepsListboxOpen)) {
|
|
22
|
+
for (const [name, keepsOpen] of Object.entries(expectations)) {
|
|
23
|
+
(0, fixtures_1.test)(`${name} on a toast ${keepsOpen ? "keeps" : "closes"} a listbox that closes on ${closeOn}`, async ({ page, server, }) => {
|
|
24
|
+
await page.goto(`${server.baseURL}/dropdown-toast.html?close=${closeOn}`);
|
|
25
|
+
await page.getByRole("combobox", { name: "Usage month" }).click();
|
|
26
|
+
const listbox = page.getByRole("listbox", { name: "Months" });
|
|
27
|
+
await (0, fixtures_1.expect)(listbox).toBeVisible();
|
|
28
|
+
await (0, fixtures_1.expect)(page.getByText("Install our CLI for your coding agents")).toBeVisible();
|
|
29
|
+
await dismissals[name](page);
|
|
30
|
+
await (0, fixtures_1.expect)(page.getByText("Install our CLI for your coding agents")).toBeVisible({ visible: name === "Escape key" });
|
|
31
|
+
await (0, fixtures_1.expect)(listbox).toBeVisible({ visible: keepsOpen });
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
(0, fixtures_1.test)("dispatched click lets the test finish selecting a month", async ({ page, server, }) => {
|
|
36
|
+
await page.goto(`${server.baseURL}/dropdown-toast.html?close=pointerdown`);
|
|
37
|
+
await page.getByRole("combobox", { name: "Usage month" }).click();
|
|
38
|
+
await (0, fixtures_1.expect)(page.getByText("Install our CLI for your coding agents")).toBeVisible();
|
|
39
|
+
await page.getByRole("button", { name: "Later" }).dispatchEvent("click");
|
|
40
|
+
const response = page.waitForResponse(/usage\.json/);
|
|
41
|
+
await page.getByRole("option", { name: "September" }).click();
|
|
42
|
+
await response;
|
|
43
|
+
await (0, fixtures_1.expect)(page.locator("#status")).toHaveText("Showing usage for September (1280 credits)");
|
|
44
|
+
});
|
|
45
|
+
(0, fixtures_1.test)("Escape on an open date picker throws away the picked date", async ({ page, server, }) => {
|
|
46
|
+
await page.goto(`${server.baseURL}/datepicker.html`);
|
|
47
|
+
await page.getByPlaceholder("Pick a date").click();
|
|
48
|
+
await page.getByRole("grid").getByRole("button", { name: "15" }).click();
|
|
49
|
+
await page.keyboard.press("Escape");
|
|
50
|
+
await page.getByRole("button", { name: "Save" }).click();
|
|
51
|
+
await (0, fixtures_1.expect)(page.locator("#status")).toHaveText("Saved without a date");
|
|
52
|
+
});
|
|
53
|
+
(0, fixtures_1.test)("Escape on open search results clears the query", async ({ page, server, }) => {
|
|
54
|
+
await page.goto(`${server.baseURL}/search-results.html`);
|
|
55
|
+
await page.getByRole("searchbox", { name: "Search assets" }).fill("orders");
|
|
56
|
+
await (0, fixtures_1.expect)(page.getByText("Showing 74 results")).toBeVisible();
|
|
57
|
+
await page.keyboard.press("Escape");
|
|
58
|
+
await (0, fixtures_1.expect)(page.getByRole("searchbox", { name: "Search assets" })).toHaveValue("");
|
|
59
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown.spec.d.ts","sourceRoot":"","sources":["../../src/overlay-tests/dropdown.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("../test");
|
|
4
|
+
const fixtures_1 = require("./fixtures");
|
|
5
|
+
fixtures_1.test.beforeEach(async ({ page }) => {
|
|
6
|
+
(0, test_1.injectLocatorHighlightScripts)(page, fixtures_1.test);
|
|
7
|
+
});
|
|
8
|
+
async function openMonthsUnderToast(page) {
|
|
9
|
+
await page.getByRole("combobox", { name: "Usage month" }).click();
|
|
10
|
+
await (0, fixtures_1.expect)(page.getByText("Install our CLI for your coding agents")).toBeVisible();
|
|
11
|
+
}
|
|
12
|
+
for (const clickMode of ["mouse", "dispatch"]) {
|
|
13
|
+
fixtures_1.test.describe(`overlay click mode: ${clickMode}`, () => {
|
|
14
|
+
fixtures_1.test.beforeEach(() => {
|
|
15
|
+
process.env.EMPIRICALRUN_OVERLAY_CLICK_MODE = clickMode;
|
|
16
|
+
});
|
|
17
|
+
fixtures_1.test.afterEach(() => {
|
|
18
|
+
delete process.env.EMPIRICALRUN_OVERLAY_CLICK_MODE;
|
|
19
|
+
});
|
|
20
|
+
(0, fixtures_1.test)("selects a month through a toast covering the open listbox", async ({ page, server, }) => {
|
|
21
|
+
fixtures_1.test.fail(clickMode === "mouse", "A mouse click on the toast is a pointerdown outside the listbox, which closes it before the retried click");
|
|
22
|
+
await page.goto(`${server.baseURL}/dropdown-toast.html`);
|
|
23
|
+
await openMonthsUnderToast(page);
|
|
24
|
+
await page.getByRole("option", { name: "September" }).click();
|
|
25
|
+
await (0, fixtures_1.expect)(page.locator("#status")).toHaveText("Showing usage for September (1280 credits)");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
(0, fixtures_1.test)("usage response wait survives a toast covering the listbox", async ({ page, server, }) => {
|
|
30
|
+
fixtures_1.test.fail(true, "waitForResponse times out while the click waits out its actionability timeout, before any dismissal runs");
|
|
31
|
+
await page.goto(`${server.baseURL}/dropdown-toast.html`);
|
|
32
|
+
await openMonthsUnderToast(page);
|
|
33
|
+
await Promise.all([
|
|
34
|
+
page.waitForResponse(/usage\.json/),
|
|
35
|
+
page.getByRole("option", { name: "September" }).click(),
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
(0, fixtures_1.test)("saves with the picked date when the open date picker covers Save", async ({ page, server, }) => {
|
|
39
|
+
fixtures_1.test.skip(true, "Not deterministic: the agent keeps the picked date in some runs and loses it in others");
|
|
40
|
+
await page.goto(`${server.baseURL}/datepicker.html`);
|
|
41
|
+
await page.getByPlaceholder("Pick a date").click();
|
|
42
|
+
await page.getByRole("grid").getByRole("button", { name: "15" }).click();
|
|
43
|
+
await page.getByRole("button", { name: "Save" }).click();
|
|
44
|
+
await (0, fixtures_1.expect)(page.locator("#status")).toHaveText("Saved for 2026-09-15");
|
|
45
|
+
});
|
|
46
|
+
(0, fixtures_1.test)("keeps the search query when open results cover a filter", async ({ page, server, }) => {
|
|
47
|
+
fixtures_1.test.skip(true, "Not deterministic: the agent keeps the query in some runs and clears it in others");
|
|
48
|
+
await page.goto(`${server.baseURL}/search-results.html`);
|
|
49
|
+
await page.getByRole("searchbox", { name: "Search assets" }).fill("orders");
|
|
50
|
+
await (0, fixtures_1.expect)(page.getByText("Showing 74 results")).toBeVisible();
|
|
51
|
+
await page.getByRole("button", { name: "Verified" }).click();
|
|
52
|
+
await (0, fixtures_1.expect)(page.locator("#status")).toHaveText('Query "orders", verified only: true');
|
|
53
|
+
});
|
|
@@ -3,7 +3,9 @@ type ServerFixture = {
|
|
|
3
3
|
baseURL: string;
|
|
4
4
|
};
|
|
5
5
|
export declare const expect: import("@playwright/test").Expect<{}>;
|
|
6
|
-
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions
|
|
6
|
+
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
7
|
+
overlayCacheFile: string;
|
|
8
|
+
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & {
|
|
7
9
|
server: ServerFixture;
|
|
8
10
|
}>;
|
|
9
11
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/overlay-tests/fixtures.ts"],"names":[],"mappings":"AAKA,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,MAAM,uCAAc,CAAC;AAClC,eAAO,MAAM,IAAI;
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/overlay-tests/fixtures.ts"],"names":[],"mappings":"AAKA,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,MAAM,uCAAc,CAAC;AAClC,eAAO,MAAM,IAAI;sBACK,MAAM;;YAChB,aAAa;EAsCvB,CAAC"}
|
|
@@ -10,6 +10,21 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const serve_handler_1 = __importDefault(require("serve-handler"));
|
|
11
11
|
exports.expect = test_1.test.expect;
|
|
12
12
|
exports.test = test_1.test.extend({
|
|
13
|
+
overlayCacheFile: [
|
|
14
|
+
async ({}, use, testInfo) => {
|
|
15
|
+
const previous = process.env.EMPIRICALRUN_OVERLAY_CACHE_FILE;
|
|
16
|
+
const file = testInfo.outputPath("overlay-cache.json");
|
|
17
|
+
process.env.EMPIRICALRUN_OVERLAY_CACHE_FILE = file;
|
|
18
|
+
await use(file);
|
|
19
|
+
if (previous === undefined) {
|
|
20
|
+
delete process.env.EMPIRICALRUN_OVERLAY_CACHE_FILE;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
process.env.EMPIRICALRUN_OVERLAY_CACHE_FILE = previous;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{ auto: true },
|
|
27
|
+
],
|
|
13
28
|
server: [
|
|
14
29
|
async ({}, use, workerInfo) => {
|
|
15
30
|
const port = 1234 + workerInfo.workerIndex;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typesafe-hit-check.spec.d.ts","sourceRoot":"","sources":["../../src/overlay-tests/typesafe-hit-check.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const typesafe_1 = require("../test/scripts/pw-locator-patch/dismiss-overlays/typesafe");
|
|
4
|
+
const fixtures_1 = require("./fixtures");
|
|
5
|
+
async function centerOf(locator) {
|
|
6
|
+
const box = await locator.boundingBox();
|
|
7
|
+
if (!box) {
|
|
8
|
+
throw new Error("Element has no bounding box");
|
|
9
|
+
}
|
|
10
|
+
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
11
|
+
}
|
|
12
|
+
(0, fixtures_1.test)("allows the toast close button", async ({ page, server }) => {
|
|
13
|
+
await page.goto(`${server.baseURL}/choose-an-overlay.html`);
|
|
14
|
+
const target = await page
|
|
15
|
+
.getByRole("button", { name: "Target" })
|
|
16
|
+
.elementHandle();
|
|
17
|
+
const point = await centerOf(page.locator("#toastCloseBtn"));
|
|
18
|
+
(0, fixtures_1.expect)(await (0, typesafe_1.checkHit)(target, point)).toBe("ok");
|
|
19
|
+
});
|
|
20
|
+
(0, fixtures_1.test)("refuses the dialog behind a toast that blocks the target", async ({ page, server, }) => {
|
|
21
|
+
await page.goto(`${server.baseURL}/choose-an-overlay.html`);
|
|
22
|
+
const target = await page
|
|
23
|
+
.getByRole("button", { name: "Target" })
|
|
24
|
+
.elementHandle();
|
|
25
|
+
const point = await centerOf(page.getByRole("button", { name: "Noted" }));
|
|
26
|
+
(0, fixtures_1.expect)(await (0, typesafe_1.checkHit)(target, point)).toBe("outside_overlay");
|
|
27
|
+
});
|
|
28
|
+
(0, fixtures_1.test)("refuses a page control outside the blocking toast", async ({ page, server, }) => {
|
|
29
|
+
await page.goto(`${server.baseURL}/toast-without-close.html`);
|
|
30
|
+
const target = await page
|
|
31
|
+
.getByRole("button", { name: "Target" })
|
|
32
|
+
.elementHandle();
|
|
33
|
+
const point = await centerOf(page.getByRole("button", { name: "Close project" }));
|
|
34
|
+
(0, fixtures_1.expect)(await (0, typesafe_1.checkHit)(target, point)).toBe("outside_overlay");
|
|
35
|
+
});
|
|
36
|
+
(0, fixtures_1.test)("refuses text inside the blocking toast", async ({ page, server }) => {
|
|
37
|
+
await page.goto(`${server.baseURL}/toast-without-close.html`);
|
|
38
|
+
const target = await page
|
|
39
|
+
.getByRole("button", { name: "Target" })
|
|
40
|
+
.elementHandle();
|
|
41
|
+
const point = await centerOf(page.locator("#toast"));
|
|
42
|
+
(0, fixtures_1.expect)(await (0, typesafe_1.checkHit)(target, point)).toBe("not_clickable");
|
|
43
|
+
});
|
|
44
|
+
(0, fixtures_1.test)("refuses the target under a glass pane", async ({ page, server }) => {
|
|
45
|
+
await page.goto(`${server.baseURL}/glass-pane.html`);
|
|
46
|
+
const target = await page
|
|
47
|
+
.getByRole("button", { name: "Target" })
|
|
48
|
+
.elementHandle();
|
|
49
|
+
(0, fixtures_1.expect)(await (0, typesafe_1.checkHit)(target, await centerOf(page.getByRole("button", { name: "Target" })))).toBe("not_clickable");
|
|
50
|
+
});
|
|
51
|
+
(0, fixtures_1.test)("reports no blocker when the target is uncovered", async ({ page, server, }) => {
|
|
52
|
+
await page.goto(`${server.baseURL}/toast-without-close.html`);
|
|
53
|
+
const target = await page
|
|
54
|
+
.getByRole("button", { name: "Close project" })
|
|
55
|
+
.elementHandle();
|
|
56
|
+
(0, fixtures_1.expect)(await (0, typesafe_1.checkHit)(target, await centerOf(page.getByRole("button", { name: "Close project" })))).toBe("no_blocker");
|
|
57
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typesafe.spec.d.ts","sourceRoot":"","sources":["../../src/overlay-tests/typesafe.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const dismiss_overlays_1 = require("../test/scripts/pw-locator-patch/dismiss-overlays");
|
|
9
|
+
const typesafe_1 = require("../test/scripts/pw-locator-patch/dismiss-overlays/typesafe");
|
|
10
|
+
const utils_1 = require("../test/scripts/pw-locator-patch/dismiss-overlays/utils");
|
|
11
|
+
const fixtures_1 = require("./fixtures");
|
|
12
|
+
fixtures_1.test.skip(!process.env.TYPESAFE_API_KEY, "TYPESAFE_API_KEY is not set");
|
|
13
|
+
const RESULTS_FILE = node_path_1.default.join(process.cwd(), "test-results", "typesafe-overlay-results.jsonl");
|
|
14
|
+
const DISMISS_ATTEMPTS = 3;
|
|
15
|
+
async function actWithTypeSafeDismissal(page, target, action) {
|
|
16
|
+
const results = [];
|
|
17
|
+
for (let attempt = 0;; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
await target[action]({ timeout: 3_000 });
|
|
20
|
+
return results;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (attempt === DISMISS_ATTEMPTS || !(0, dismiss_overlays_1.isErrorSupported)(error.message)) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
const element = (0, dismiss_overlays_1.extractInterceptingElement)(error.message);
|
|
27
|
+
const text = await (0, utils_1.textContent)(page, element);
|
|
28
|
+
const result = await (0, typesafe_1.dismissWithTypeSafe)(page, {
|
|
29
|
+
text,
|
|
30
|
+
target: await target.elementHandle(),
|
|
31
|
+
});
|
|
32
|
+
results.push(result);
|
|
33
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(RESULTS_FILE), { recursive: true });
|
|
34
|
+
node_fs_1.default.appendFileSync(RESULTS_FILE, JSON.stringify({
|
|
35
|
+
test: fixtures_1.test.info().title,
|
|
36
|
+
attempt,
|
|
37
|
+
overlayText: text,
|
|
38
|
+
...result,
|
|
39
|
+
}) + "\n");
|
|
40
|
+
if (!result.dismissed) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
(0, fixtures_1.test)("dismisses the toast but not the dialog", async ({ page, server }) => {
|
|
47
|
+
await page.goto(`${server.baseURL}/choose-an-overlay.html`);
|
|
48
|
+
await actWithTypeSafeDismissal(page, page.getByRole("button", { name: "Target" }), "click");
|
|
49
|
+
await (0, fixtures_1.expect)(page.getByText("This is a toast message")).not.toBeVisible();
|
|
50
|
+
await (0, fixtures_1.expect)(page.getByRole("heading", { name: "Center Overlay" })).toBeVisible();
|
|
51
|
+
});
|
|
52
|
+
(0, fixtures_1.test)("ticks the checkbox and accepts terms", async ({ page, server }) => {
|
|
53
|
+
await page.goto(`${server.baseURL}/tos.html`);
|
|
54
|
+
page.on("dialog", (dialog) => dialog.dismiss());
|
|
55
|
+
await actWithTypeSafeDismissal(page, page.getByRole("button", { name: "Target" }), "click");
|
|
56
|
+
await (0, fixtures_1.expect)(page.getByRole("heading", { name: "Terms of Service" })).not.toBeVisible();
|
|
57
|
+
});
|
|
58
|
+
(0, fixtures_1.test)("dismisses a product tour and terms in sequence", async ({ page, server, }) => {
|
|
59
|
+
await page.goto(`${server.baseURL}/loop-to-dismiss.html`);
|
|
60
|
+
await actWithTypeSafeDismissal(page, page.getByRole("button", { name: "Target" }), "click");
|
|
61
|
+
await (0, fixtures_1.expect)(page.getByText("Clicked!")).toBeVisible();
|
|
62
|
+
});
|
|
63
|
+
(0, fixtures_1.test)("dismisses a Survicate survey for hover", async ({ page, server }) => {
|
|
64
|
+
await page.goto(`${server.baseURL}/survey.html`);
|
|
65
|
+
await (0, fixtures_1.expect)(page.getByText("Start survey")).toBeVisible({ timeout: 15_000 });
|
|
66
|
+
await actWithTypeSafeDismissal(page, page.getByRole("button", { name: "Click target" }), "hover");
|
|
67
|
+
});
|
|
68
|
+
(0, fixtures_1.test)("cannot fill a form overlay", async ({ page, server }) => {
|
|
69
|
+
await page.goto(`${server.baseURL}/overlay-form.html`);
|
|
70
|
+
await (0, fixtures_1.expect)(actWithTypeSafeDismissal(page, page.getByRole("button", { name: "Target" }), "click")).rejects.toThrow();
|
|
71
|
+
});
|
|
72
|
+
(0, fixtures_1.test)("does not click a page control when the toast has no close button", async ({ page, server, }) => {
|
|
73
|
+
await page.goto(`${server.baseURL}/toast-without-close.html`);
|
|
74
|
+
await (0, fixtures_1.expect)(actWithTypeSafeDismissal(page, page.getByRole("button", { name: "Target" }), "click")).rejects.toThrow();
|
|
75
|
+
await (0, fixtures_1.expect)(page.getByText("Project closed")).not.toBeVisible();
|
|
76
|
+
});
|
|
77
|
+
(0, fixtures_1.test)("does not click anything for an invisible glass pane", async ({ page, server, }) => {
|
|
78
|
+
await page.goto(`${server.baseURL}/glass-pane.html`);
|
|
79
|
+
let dialogOpened = false;
|
|
80
|
+
page.on("dialog", (dialog) => {
|
|
81
|
+
dialogOpened = true;
|
|
82
|
+
return dialog.dismiss();
|
|
83
|
+
});
|
|
84
|
+
await (0, fixtures_1.expect)(actWithTypeSafeDismissal(page, page.getByRole("button", { name: "Target" }), "click")).rejects.toThrow();
|
|
85
|
+
(0, fixtures_1.expect)(dialogOpened).toBe(false);
|
|
86
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-extensions.d.ts","sourceRoot":"","sources":["../src/playwright-extensions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"playwright-extensions.d.ts","sourceRoot":"","sources":["../src/playwright-extensions.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7C,CAAC;AAEF,OAAO,QAAQ,iBAAiB,CAAC;IAC/B,UAAU,OAAQ,SAAQ,eAAe;KAAG;CAC7C;AAED,OAAO,QAAQ,kBAAkB,CAAC;IAChC,UAAU,OAAQ,SAAQ,eAAe;KAAG;CAC7C"}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test-only reporter used by attachment-cleanup integration tests.
|
|
3
|
-
*
|
|
4
|
-
* Records a timestamped event log of register/markUploaded/eviction events
|
|
5
|
-
* so tests can make precise assertions about ordering and timing.
|
|
6
|
-
*/
|
|
7
1
|
import type { FullResult, Reporter, TestCase, TestResult } from "@playwright/test/reporter";
|
|
8
2
|
declare class AttachmentCleanupTestReporter implements Reporter {
|
|
9
3
|
private _timeline;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-cleanup-test-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/attachment-cleanup-test-reporter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"attachment-cleanup-test-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/attachment-cleanup-test-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AA0CnC,cAAM,6BAA8B,YAAW,QAAQ;IACrD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAkC;IAEhD,cAQC;IAED,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,QAyB5C;IAEK,KAAK,CAAC,OAAO,EAAE,UAAU,iBAyB9B;CACF;eAEc,6BAA6B"}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Test-only reporter used by attachment-cleanup integration tests.
|
|
4
|
-
*
|
|
5
|
-
* Records a timestamped event log of register/markUploaded/eviction events
|
|
6
|
-
* so tests can make precise assertions about ordering and timing.
|
|
7
|
-
*/
|
|
8
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
4
|
};
|
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tracks test attachments and evicts the oldest uploaded ones from disk
|
|
3
|
-
* when total size exceeds the configured limit.
|
|
4
|
-
*
|
|
5
|
-
* Two-phase tracking:
|
|
6
|
-
* 1. register() — called immediately when an attachment is created.
|
|
7
|
-
* Adds the file to the total size accounting so the LRU knows about
|
|
8
|
-
* all disk usage, even while uploads are still queued.
|
|
9
|
-
* 2. markUploaded() — called after the file is uploaded to R2.
|
|
10
|
-
* Marks the file as safe to evict (delete from disk).
|
|
11
|
-
*
|
|
12
|
-
* Eviction only deletes files that have been uploaded. This ensures we
|
|
13
|
-
* never delete a file before its upload completes.
|
|
14
|
-
*
|
|
15
|
-
* Why this exists:
|
|
16
|
-
* Test attachments (videos, traces, screenshots) accumulate on disk during a
|
|
17
|
-
* test run. On Fargate workers with 50 GB ephemeral storage, large test suites
|
|
18
|
-
* can exhaust disk space. Since attachments are already uploaded to R2, we can
|
|
19
|
-
* safely delete them from disk — but we can't delete them immediately because
|
|
20
|
-
* Playwright's blob reporter reads them during onEnd to embed as resources/.
|
|
21
|
-
*
|
|
22
|
-
* The blob reporter's statSync guard (Playwright 1.57+) gracefully skips
|
|
23
|
-
* missing files, and patchBlobZip replaces resources/ with
|
|
24
|
-
* _empirical_urls.json pointing to R2 URLs anyway.
|
|
25
|
-
*/
|
|
26
1
|
export declare class AttachmentCleanup {
|
|
27
2
|
private _pending;
|
|
28
3
|
private _uploaded;
|
|
@@ -36,17 +11,9 @@ export declare class AttachmentCleanup {
|
|
|
36
11
|
private _diskLogTick;
|
|
37
12
|
private _onEvict;
|
|
38
13
|
constructor(limitBytes?: number);
|
|
39
|
-
/**
|
|
40
|
-
* Phase 1: Register an attachment immediately when it's created.
|
|
41
|
-
* Adds to total size accounting and attempts eviction of already-uploaded files.
|
|
42
|
-
*/
|
|
43
14
|
register(attachment: {
|
|
44
15
|
path?: string | undefined;
|
|
45
16
|
}): void;
|
|
46
|
-
/**
|
|
47
|
-
* Phase 2: Mark an attachment as uploaded (safe to evict).
|
|
48
|
-
* Moves from pending to the uploaded queue and attempts eviction.
|
|
49
|
-
*/
|
|
50
17
|
markUploaded(attachmentPath: string): void;
|
|
51
18
|
private _evict;
|
|
52
19
|
private _logDiskStatus;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-cleanup.d.ts","sourceRoot":"","sources":["../../src/reporter/attachment-cleanup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attachment-cleanup.d.ts","sourceRoot":"","sources":["../../src/reporter/attachment-cleanup.ts"],"names":[],"mappings":"AA6DA,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAA+C;IACpE,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAyC;IAEzD,YAAY,UAAU,GAAE,MAAmC,EAM1D;IAED,QAAQ,CAAC,UAAU,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,QAejD;IAED,YAAY,CAAC,cAAc,EAAE,MAAM,QASlC;IAED,OAAO,CAAC,MAAM;IA4Bd,OAAO,CAAC,cAAc;IAItB,UAAU,SAWT;IAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAE3C;IAED,IAAI,KAAK;QAEL,eAAe;QACf,aAAa;QACb,YAAY;QACZ,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,SAAS;QACT,UAAU;MAEb;CACF"}
|