bd082b4a23c5
playwright: point the specs at the list overlay — the top search bar is readOnly and only opens the list, so filling it or clicking new-entry from the vault stage could never work
- time
- 2026-07-26 19:15 UTC · 3 months ago
- machine
- koh2
- agent
- claude-opus-5
- session
- playwright setupVault repair
- parent
- cce5d8227d4e
↓ peach-browser-bd082b4a.tar.gzatlas at this save
files changed5 files
tests/entry-crud.spec.ts+9 −2tests/helpers/vault.ts+17 −0tests/search.spec.ts+33 −30tests/vault-lifecycle.spec.ts+2 −2tests/vault-security.spec.ts+4 −2tests/entry-crud.spec.ts+9 −2
@@ -1,5 +1,5 @@11 import { test, expect, type BrowserContext } from "@playwright/test";2-import { createEntryViaForm, makeContext, setupVault } from "./helpers/vault";2+import { createEntryViaForm, listSearch, makeContext, openList, setupVault } from "./helpers/vault";33 44 // Real vault creation (Argon2id + recovery confirmation ≈ 12s) plus a form55 // round-trip per test — the global 30s cap is too tight.@@ -18,6 +18,7 @@1818 username: "june",1919 password: "secret123!",2020 });21+ await openList(popup);2122 await expect(popup.locator(".peach-entry-row-name", { hasText: "GitHub" })).toBeVisible({ timeout: 5000 });2223 });2324 @@ -30,6 +31,7 @@3031 password: "secret123!",3132 totp: "JBSWY3DPEHPK3PXP",3233 });34+ await openList(popup);3335 await popup.locator(".peach-entry-row", { hasText: "GitHub2FA" }).click();3436 await expect(popup.locator(".peach-totp-code-text")).toHaveText(/\d{3} \d{3}/, { timeout: 5000 });3537 });@@ -37,10 +39,12 @@3739 test("EC03 - edited title persists", async () => {3840 const { popup } = await setupVault(ctx);3941 await createEntryViaForm(popup, { title: "EditTest", url: "https://edit.test", username: "u", password: "oldpass" });42+ await openList(popup);4043 await popup.locator(".peach-entry-row", { hasText: "EditTest" }).click();4144 await popup.locator(".peach-entry-name-button").click();4245 await popup.locator(".peach-inline-title-input").fill("EditedTitle");4346 await popup.keyboard.press("Enter");47+ await openList(popup);4448 await expect(popup.locator(".peach-entry-row-name", { hasText: "EditedTitle" })).toBeVisible({ timeout: 5000 });4549 await expect(popup.locator(".peach-entry-row-name", { hasText: "EditTest" })).toHaveCount(0);4650 });@@ -48,9 +52,11 @@4852 test("EC04 - delete entry removes it from the vault", async () => {4953 const { popup } = await setupVault(ctx);5054 await createEntryViaForm(popup, { title: "DeleteMe", url: "https://delete.test", username: "u", password: "p" });55+ await openList(popup);5156 await popup.locator(".peach-entry-row", { hasText: "DeleteMe" }).click();5257 await popup.locator("[aria-label='Move to trash']").click();5358 await popup.getByRole("button", { name: /^yes$/i }).click();59+ await openList(popup);5460 await expect(popup.locator(".peach-entry-row-name", { hasText: "DeleteMe" })).toHaveCount(0, { timeout: 5000 });5561 });5662 @@ -59,7 +65,8 @@5965 for (const name of ["Apple", "Amazon"]) {6066 await createEntryViaForm(popup, { title: name, url: `https://${name.toLowerCase()}.test`, username: "u", password: "p" });6167 }62- await popup.locator('input[placeholder="search vault..."]').fill("Apple");68+ await openList(popup);69+ await listSearch(popup).fill("Apple");6370 await expect(popup.locator(".peach-entry-row-name", { hasText: "Apple" })).toBeVisible();6471 await expect(popup.locator(".peach-entry-row-name", { hasText: "Amazon" })).toHaveCount(0);6572 });tests/helpers/vault.ts+17 −0
@@ -154,6 +154,22 @@154154 }155155 156156 /**157+ * Open the entry list. The top search bar is readOnly — clicking it is what158+ * mounts the list overlay, which is where the real search input and the159+ * new-entry button live.160+ */161+export async function openList(popup: Page): Promise<void> {162+ if (await popup.locator(".peach-overlay-view .peach-search-input").isVisible().catch(() => false)) return;163+ await popup.locator(".peach-search-wrap .peach-search-input").click();164+ await expect(popup.locator(".peach-overlay-view .peach-search-input")).toBeVisible({ timeout: 5000 });165+}166+167+/** The editable search field, inside the list overlay. */168+export function listSearch(popup: Page) {169+ return popup.locator(".peach-overlay-view .peach-search-input");170+}171+172+/**157173 * Fill and submit the new-entry form. Selectors follow Add.tsx: the title field158174 * is .peach-new-title-input (it was .peach-new-name-input in an older UI, which159175 * is why the specs that still use that class fail to find it).@@ -162,6 +178,7 @@162178 popup: Page,163179 entry: { title: string; url?: string; username?: string; password?: string; totp?: string; notes?: string },164180 ): Promise<void> {181+ await openList(popup);165182 await popup.locator(".peach-new-entry-row").click();166183 await popup.locator(".peach-new-title-input").fill(entry.title);167184 if (entry.url) await popup.locator(".peach-domain-value--edit").fill(entry.url);tests/search.spec.ts+33 −30
@@ -1,6 +1,11 @@11 import { test, expect, Page, BrowserContext } from "@playwright/test";2-import { UNLOCKED, VAULT_PASSWORD, addEntry, makeContext, setupVault } from "./helpers/vault";2+import { UNLOCKED, VAULT_PASSWORD, addEntry, listSearch, makeContext, openList, setupVault } from "./helpers/vault";33 4+// The top bar's search field is readOnly — it only opens the list overlay,5+// where the editable field lives. Specs that filled the top field were6+// filling a readOnly input.7+const topSearch = (popup: Page) => popup.locator(".peach-search-wrap .peach-search-input");8+49 // Real vault creation runs Argon2id; seeded entries need a popup reload each.510 test.describe.configure({ timeout: 90_000 });611 @@ -20,6 +25,7 @@2025 await popup.getByRole("button", { name: /unlock/i }).click();2126 }2227 await expect(popup.locator(UNLOCKED)).toBeVisible({ timeout: 20000 });28+ await openList(popup);2329 }2430 2531 test.describe("Search", () => {@@ -29,15 +35,13 @@2935 3036 test("S01 - search input is visible", async () => {3137 const { popup } = await setupVault(ctx);32- const searchInput = popup.locator('input[placeholder="search vault..."]');33- await expect(searchInput).toBeVisible();38+ await expect(topSearch(popup)).toBeVisible();3439 await ctx.close();3540 });3641 3742 test("S02 - search input has placeholder text", async () => {3843 const { popup } = await setupVault(ctx);39- const searchInput = popup.locator('input[placeholder="search vault..."]');40- const placeholder = await searchInput.getAttribute("placeholder");44+ const placeholder = await topSearch(popup).getAttribute("placeholder");4145 expect(placeholder).toContain("search");4246 await ctx.close();4347 });@@ -46,7 +50,7 @@4650 const { popup } = await setupVault(ctx);4751 await createEntry(popup, "GoogleEntry");4852 await createEntry(popup, "AmazonEntry");49- await popup.locator('input[placeholder="search vault..."]').fill("Google");53+ await listSearch(popup).fill("Google");5054 await popup.waitForTimeout(300);5155 const entries = popup.locator(".peach-entry-row");5256 const count = await entries.count();@@ -57,7 +61,7 @@5761 test("S04 - search is case-insensitive", async () => {5862 const { popup } = await setupVault(ctx);5963 await createEntry(popup, "TestEntry");60- await popup.locator('input[placeholder="search vault..."]').fill("test");64+ await listSearch(popup).fill("test");6165 await popup.waitForTimeout(300);6266 const entries = popup.locator(".peach-entry-row");6367 const count = await entries.count();@@ -68,7 +72,7 @@6872 test("S05 - search with no results shows empty state", async () => {6973 const { popup } = await setupVault(ctx);7074 await createEntry(popup, "Entry1");71- await popup.locator('input[placeholder="search vault..."]').fill("nonexistent");75+ await listSearch(popup).fill("nonexistent");7276 await popup.waitForTimeout(500);7377 const noResults = popup.locator("text=/no results|not found|0 results/i");7478 const hasNoResults = await noResults.isVisible({ timeout: 2000 }).catch(() => false);@@ -80,9 +84,9 @@8084 const { popup } = await setupVault(ctx);8185 await createEntry(popup, "EntryOne");8286 await createEntry(popup, "EntryTwo");83- await popup.locator('input[placeholder="search vault..."]').fill("EntryOne");87+ await listSearch(popup).fill("EntryOne");8488 await popup.waitForTimeout(300);85- await popup.locator('input[placeholder="search vault..."]').clear();89+ await listSearch(popup).clear();8690 await popup.waitForTimeout(300);8791 const entries = popup.locator(".peach-entry-row");8892 const count = await entries.count();@@ -94,7 +98,7 @@9498 const { popup } = await setupVault(ctx);9599 await createEntry(popup, "Site1", "google.com");96100 await createEntry(popup, "Site2", "amazon.com");97- await popup.locator('input[placeholder="search vault..."]').fill("google");101+ await listSearch(popup).fill("google");98102 await popup.waitForTimeout(300);99103 const entries = popup.locator(".peach-entry-row");100104 const count = await entries.count();@@ -106,7 +110,7 @@106110 const { popup } = await setupVault(ctx);107111 await createEntry(popup, "User1");108112 await createEntry(popup, "User2");109- await popup.locator('input[placeholder="search vault..."]').fill("[email protected]");113+ await listSearch(popup).fill("[email protected]");110114 await popup.waitForTimeout(300);111115 const entries = popup.locator(".peach-entry-row");112116 const count = await entries.count();@@ -119,9 +123,9 @@119123 for (let i = 0; i < 15; i++) {120124 await createEntry(popup, `Entry${i}`, `site${i}.com`);121125 }122- await popup.locator('input[placeholder="search vault..."]').fill("Entry5");126+ await listSearch(popup).fill("Entry5");123127 await popup.waitForTimeout(300);124- await popup.locator('input[placeholder="search vault..."]').clear();128+ await listSearch(popup).clear();125129 await popup.waitForTimeout(300);126130 await ctx.close();127131 });@@ -129,7 +133,7 @@129133 test("S10 - search with special characters", async () => {130134 const { popup } = await setupVault(ctx);131135 await createEntry(popup, "Test@Entry");132- await popup.locator('input[placeholder="search vault..."]').fill("@");136+ await listSearch(popup).fill("@");133137 await popup.waitForTimeout(300);134138 await ctx.close();135139 });@@ -137,15 +141,14 @@137141 test("S11 - search with regex characters", async () => {138142 const { popup } = await setupVault(ctx);139143 await createEntry(popup, "Test.Entry");140- await popup.locator('input[placeholder="search vault..."]').fill(".");144+ await listSearch(popup).fill(".");141145 await popup.waitForTimeout(300);142146 await ctx.close();143147 });144148 145149 test("S12 - search input has proper aria-label", async () => {146150 const { popup } = await setupVault(ctx);147- const searchInput = popup.locator('input[placeholder="search vault..."]');148- const ariaLabel = await searchInput.getAttribute("aria-label");151+ const ariaLabel = await topSearch(popup).getAttribute("aria-label");149152 expect(ariaLabel).toBeTruthy();150153 await ctx.close();151154 });@@ -153,11 +156,11 @@153156 test("S13 - search clears on escape", async () => {154157 const { popup } = await setupVault(ctx);155158 await createEntry(popup, "TestEntry");156- await popup.locator('input[placeholder="search vault..."]').fill("Test");159+ await listSearch(popup).fill("Test");157160 await popup.waitForTimeout(300);158161 await popup.keyboard.press("Escape");159162 await popup.waitForTimeout(200);160- const value = await popup.locator('input[placeholder="search vault..."]').inputValue();163+ const value = await listSearch(popup).inputValue();161164 expect(value).toBe("");162165 await ctx.close();163166 });@@ -165,7 +168,7 @@165168 test("S14 - search debounce works", async () => {166169 const { popup } = await setupVault(ctx);167170 await createEntry(popup, "TestEntry");168- const searchInput = popup.locator('input[placeholder="search vault..."]');171+ const searchInput = listSearch(popup);169172 await searchInput.fill("T");170173 await popup.waitForTimeout(100);171174 await searchInput.fill("Te");@@ -178,7 +181,7 @@178181 test("S15 - search highlights matching text", async () => {179182 const { popup } = await setupVault(ctx);180183 await createEntry(popup, "TestingEntry");181- await popup.locator('input[placeholder="search vault..."]').fill("Test");184+ await listSearch(popup).fill("Test");182185 await popup.waitForTimeout(300);183186 const highlighted = popup.locator("[class*='highlight' i], [class*='match' i]").first();184187 const hasHighlight = await highlighted.isVisible().catch(() => false);@@ -190,7 +193,7 @@190193 const { popup } = await setupVault(ctx);191194 await createEntry(popup, "Entry1");192195 await createEntry(popup, "Entry2");193- await popup.locator('input[placeholder="search vault..."]').fill("");196+ await listSearch(popup).fill("");194197 await popup.waitForTimeout(300);195198 const entries = popup.locator(".peach-entry-row");196199 const count = await entries.count();@@ -213,7 +216,7 @@213216 test("S18 - partial match works", async () => {214217 const { popup } = await setupVault(ctx);215218 await createEntry(popup, "MyPasswordEntry");216- await popup.locator('input[placeholder="search vault..."]').fill("Pass");219+ await listSearch(popup).fill("Pass");217220 await popup.waitForTimeout(300);218221 const entries = popup.locator(".peach-entry-row");219222 const count = await entries.count();@@ -230,7 +233,7 @@230233 test("SA01 - search with spaces works", async () => {231234 const { popup } = await setupVault(ctx);232235 await createEntry(popup, "My Test Entry");233- await popup.locator('input[placeholder="search vault..."]').fill("Test");236+ await listSearch(popup).fill("Test");234237 await popup.waitForTimeout(300);235238 const entries = popup.locator(".peach-entry-row");236239 const count = await entries.count();@@ -241,13 +244,13 @@241244 test("SA02 - search remembers previous query", async () => {242245 const { popup } = await setupVault(ctx);243246 await createEntry(popup, "Test1");244- await popup.locator('input[placeholder="search vault..."]').fill("Test1");247+ await listSearch(popup).fill("Test1");245248 await popup.waitForTimeout(300);246249 await popup.locator(".peach-entry-name-button").first().click();247250 await popup.waitForTimeout(300);248251 await popup.keyboard.press("Escape");249252 await popup.waitForTimeout(200);250- const value = await popup.locator('input[placeholder="search vault..."]').inputValue();253+ const value = await listSearch(popup).inputValue();251254 expect(value).toBe("Test1");252255 await ctx.close();253256 });@@ -255,7 +258,7 @@255258 test("SA03 - search field does not accept invalid characters", async () => {256259 const { popup } = await setupVault(ctx);257260 await createEntry(popup, "Test");258- const searchInput = popup.locator('input[placeholder="search vault..."]');261+ const searchInput = listSearch(popup);259262 await searchInput.fill("Test\r\n");260263 await ctx.close();261264 });@@ -264,7 +267,7 @@264267 const { popup } = await setupVault(ctx);265268 await createEntry(popup, "Short");266269 const longQuery = "a".repeat(100);267- await popup.locator('input[placeholder="search vault..."]').fill(longQuery);270+ await listSearch(popup).fill(longQuery);268271 await popup.waitForTimeout(300);269272 await ctx.close();270273 });@@ -272,7 +275,7 @@272275 test("SA05 - search in non-latin characters", async () => {273276 const { popup } = await setupVault(ctx);274277 await createEntry(popup, "Тест");275- await popup.locator('input[placeholder="search vault..."]').fill("Тест");278+ await listSearch(popup).fill("Тест");276279 await popup.waitForTimeout(300);277280 await ctx.close();278281 });tests/vault-lifecycle.spec.ts+2 −2
@@ -112,7 +112,7 @@112112 await addEntry(popup, { title: "LockRoundTrip", url: "https://roundtrip.test", username: "[email protected]", password: "entry-secret" });113113 await lockVault(popup);114114 await unlockVault(popup);115- await expect(popup.getByText("LockRoundTrip")).toBeVisible({ timeout: 10000 });115+ await expect(popup.locator(".peach-entry-name-text").first()).toHaveText("LockRoundTrip", { timeout: 10000 });116116 });117117 118118 test("VL10 - reopening the popup requires the password again", async () => {@@ -124,7 +124,7 @@124124 await reopened.goto(`chrome-extension://${extId}/src/popup/index.html`);125125 await expect(reopened.locator("#unlock-password")).toBeVisible({ timeout: 15000 });126126 await unlockVault(reopened);127- await expect(reopened.getByText("ReopenTest")).toBeVisible({ timeout: 10000 });127+ await expect(reopened.locator(".peach-entry-name-text").first()).toHaveText("ReopenTest", { timeout: 10000 });128128 });129129 130130 test("VL11 - a wrong password does not unlock a reopened popup", async () => {tests/vault-security.spec.ts+4 −2
@@ -9,6 +9,8 @@99 createEntryViaForm,1010 lockVault,1111 makeContext,12+ listSearch,13+ openList,1214 openSettings,1315 setupVault,1416 waitForSW,@@ -73,8 +75,8 @@7375 test("SECX05 - search input sanitizes query before display", async () => {7476 const { popup } = await setupVault(ctx);7577 await createEntryViaForm(popup, { title: "SearchTest", url: "https://search.com", username: "[email protected]", password: "pass" });76- const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();77- await searchInput.click();78+ await openList(popup);79+ const searchInput = listSearch(popup);7880 await searchInput.fill('<script>alert("xss")</script>');7981 await popup.waitForTimeout(300);8082 const value = await searchInput.inputValue();