diff --git a/package-lock.json b/package-lock.json index d78ed4be..19e17a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "plmail-e2e", "devDependencies": { - "@playwright/test": "~1.61.1", + "@playwright/test": "^1.62.1", "@types/node": "^26.1.2", "typescript": "^7.0.2" }, @@ -15,19 +15,19 @@ } }, "node_modules/@playwright/test": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", - "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.61.1" + "playwright": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@types/node": { @@ -396,35 +396,35 @@ } }, "node_modules/playwright": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", - "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.61.1" + "playwright-core": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", - "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/typescript": { diff --git a/package.json b/package.json index 99a834fe..bb05793d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "node": ">=18" }, "devDependencies": { - "@playwright/test": "~1.61.1", + "@playwright/test": "^1.62.1", "@types/node": "^26.1.2", "typescript": "^7.0.2" } diff --git a/tests/e2e/appearance/list-display.spec.ts b/tests/e2e/appearance/list-display.spec.ts index 27a98271..9377f660 100644 --- a/tests/e2e/appearance/list-display.spec.ts +++ b/tests/e2e/appearance/list-display.spec.ts @@ -55,15 +55,32 @@ const toggle = (page: Page, field: string, on = true) => const setToggle = async (page: Page, field: string, on: boolean) => { const segment = toggle(page, field, on); - if (false === (await segment.isChecked())) { - // sr-only radio: the visible segment is its sibling label text, so the - // input is checked directly, the way the row-select checkbox is. - await segment.check({ force: true }); - // The panel debounces its POST; wait for the write, not for a timeout. - await page.waitForResponse( - (r) => r.url().includes("/settings/appearance") && r.request().method() === "POST", - ); + if (true === (await segment.isChecked())) { + return; } + + // The panel debounces its POST; wait for the write, and arm the wait before + // the click so a fast answer cannot be missed. + const written = page.waitForResponse( + (r) => r.url().includes("/settings/appearance") && r.request().method() === "POST", + ); + + // Click the LABEL, which is both what a person clicks and the only thing + // that reliably works. The radio is `peer sr-only` — a 1px clipped box — + // and a forced click on one of those lands without changing its state: + // Playwright says "clicking the checkbox did not change its state", which + // is the same failure label-bulk-toggle.spec.ts documents and works around. + // It used to be intermittent and became reliable, which is how it surfaced + // as eight failures at once rather than one every third run. + // + // `pick()` above has always clicked the label for exactly this reason; this + // helper simply never caught up with it. + await page + .locator(`${PANEL} label:has(input[data-toggles="${field}"][value="${on ? "1" : "0"}"])`) + .click(); + + await expect(segment).toBeChecked(); + await written; }; test.describe("appearance — mail list display", () => {