diff --git a/src/StackAlchemist.Web/e2e/smoke/advanced-draft-persistence.spec.ts b/src/StackAlchemist.Web/e2e/smoke/advanced-draft-persistence.spec.ts index 07e6966..027b3ee 100644 --- a/src/StackAlchemist.Web/e2e/smoke/advanced-draft-persistence.spec.ts +++ b/src/StackAlchemist.Web/e2e/smoke/advanced-draft-persistence.spec.ts @@ -2,34 +2,38 @@ import { expect, test } from "@playwright/test"; test.describe("Smoke: Advanced Draft Persistence", () => { test.beforeEach(async ({ page }) => { - // Drafts from prior specs/sessions must not leak in. - await page.addInitScript(() => window.localStorage.clear()); + // Drafts from prior specs/sessions must not leak in. Clear storage ONCE — + // NOT via page.addInitScript(), which re-runs on every navigation including + // the page.reload() these tests depend on, wiping the draft mid-test. + await page.goto("/advanced"); + await page.evaluate(() => window.localStorage.clear()); }); test("wizard edits survive a reload and show the restore notice", async ({ page }) => { await page.goto("/advanced?step=1"); - const entityName = page.getByDisplayValue("Product"); + const entityName = page.getByPlaceholder("EntityName"); + await expect(entityName).toHaveValue("Product"); await entityName.fill("Subscription"); // Outlive the 800ms persist debounce before reloading. await page.waitForTimeout(1200); await page.reload(); - await expect(page.getByDisplayValue("Subscription")).toBeVisible(); + await expect(page.getByPlaceholder("EntityName")).toHaveValue("Subscription"); await expect(page.getByTestId("advanced-draft-restored")).toBeVisible(); }); test("start fresh discards the draft", async ({ page }) => { await page.goto("/advanced?step=1"); - await page.getByDisplayValue("Product").fill("Subscription"); + await page.getByPlaceholder("EntityName").fill("Subscription"); await page.waitForTimeout(1200); await page.reload(); await expect(page.getByTestId("advanced-draft-restored")).toBeVisible(); await page.getByRole("button", { name: "start fresh" }).click(); - await expect(page.getByDisplayValue("Product")).toBeVisible(); + await expect(page.getByPlaceholder("EntityName")).toHaveValue("Product"); await page.reload(); await expect(page.getByTestId("advanced-draft-restored")).not.toBeVisible(); }); diff --git a/src/StackAlchemist.Web/e2e/smoke/personalization-modal-keyboard.spec.ts b/src/StackAlchemist.Web/e2e/smoke/personalization-modal-keyboard.spec.ts index d5def60..b8a9bf8 100644 --- a/src/StackAlchemist.Web/e2e/smoke/personalization-modal-keyboard.spec.ts +++ b/src/StackAlchemist.Web/e2e/smoke/personalization-modal-keyboard.spec.ts @@ -19,12 +19,17 @@ test.describe("Smoke: Personalization modal keyboard a11y", () => { await page.getByTestId("advanced-personalize-button").click(); await expect(page.getByRole("dialog")).toBeVisible(); - // Tab through all focusable elements — focus must not escape to the page behind. + // Tab through focusable elements — focus must stay trapped inside the dialog, + // not escape to the page behind. Assert containment directly: comparing the + // focused element's innerText (CSS-uppercased) against the dialog's textContent + // (raw, mixed-case) was brittle and never matched — the original bug. for (let i = 0; i < 10; i++) { await page.keyboard.press("Tab"); - const focused = page.locator(":focus"); - const dialogHandle = page.getByRole("dialog"); - await expect(dialogHandle).toContainText(await focused.innerText().catch(() => "")); + const trapped = await page.evaluate(() => { + const dialog = document.querySelector('[role="dialog"]'); + return !!dialog && dialog.contains(document.activeElement); + }); + expect(trapped, `focus escaped the dialog after Tab #${i + 1}`).toBe(true); } // Confirm: after Escape, some element on the page (opener button) receives focus.