90d2d69e8d21

test: Retire false-green browser suites (v4.10.38)

- Remove 15 legacy Playwright files whose setup never created a vault - Keep 118 maintained core E2E cases behind an explicit test:e2e command - Use the shared asserted vault setup and collapse Playwright dependency ownership - Verify real vault creation and entry creation against Chromium

time
2026-08-29 13:14 UTC · 1 month ago
machine
koh2
parent
29e0ff7cbd5d
23 files bun.lock+3 −6manifest.firefox.json+1 −1manifest.json+1 −1package.json+2 −2tests/a11y-totp-vault.spec.ts+0 −412tests/autofill-deep.spec.ts+0 −631tests/autofill.spec.ts+0 −156tests/clipboard.spec.ts+0 −377tests/codex-pdf-restore.spec.ts+1 −2tests/copy-totp.spec.ts+0 −156tests/entry-comprehensive.spec.ts+0 −682tests/entry-flows-state.spec.ts+0 −396tests/helpers/vault.ts+2 −2tests/import-export-robust.spec.ts+0 −685tests/keyboard-comprehensive.spec.ts+0 −787tests/keyboard-edge.spec.ts+0 −512tests/passkeys.spec.ts+1 −2tests/password-generator.spec.ts+0 −464tests/security-visual.spec.ts+0 −338tests/settings-persistence.spec.ts+0 −637tests/totp-edge.spec.ts+0 −503tests/vault-security.spec.ts+2 −3tests/visual-compliance.spec.ts+0 −624
bun.lock+3 −6
@@ -36,7 +36,6 @@3636         "@types/papaparse": "^5.5.2",3737         "chrome-webstore-upload-cli": "^4.0.0",3838         "jsdom": "^29.0.2",39-        "playwright": "^1.59.1",4039         "typescript": "^6.0.3",4140         "vite": "^8.0.8",4241         "vite-plugin-solid": "^2.11.12",@@ -661,7 +660,7 @@661660 662661     "fs-extra": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],663662 664-    "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],663+    "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],665664 666665     "fx-runner": ["[email protected]", "", { "dependencies": { "commander": "2.9.0", "shell-quote": "1.7.3", "spawn-sync": "1.0.15", "when": "3.7.7", "which": "1.2.4", "winreg": "0.0.12" }, "bin": "bin/fx-runner" }, "sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg=="],667666 @@ -1273,19 +1272,17 @@12731272 12741273     "pdf-lib/pako": ["[email protected]", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],12751274 1275+    "playwright/fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],1276+12761277     "rc/ini": ["[email protected]", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],12771278 12781279     "rc/strip-json-comments": ["[email protected]", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],12791280 1280-    "rollup/fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],1281-12821281     "rxjs/tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],12831282 12841283     "thread-stream/real-require": ["[email protected]", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],12851284 12861285     "update-notifier/semver": ["[email protected]", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],1287-1288-    "vite/fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],12891286 12901287     "vitest/es-module-lexer": ["[email protected]", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],12911288 

view file

manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.10.37",4+  "version": "4.10.38",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "4.10.37",5+  "version": "4.10.38",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+2 −2
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.10.37",3+  "version": "4.10.38",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",@@ -18,6 +18,7 @@1818     "typecheck": "tsc --noEmit",1919     "test": "vitest run",2020     "test:watch": "vitest",21+    "test:e2e": "npm run build:chrome && playwright test tests/vault-lifecycle.spec.ts tests/entry-crud.spec.ts tests/search.spec.ts tests/settings.spec.ts tests/lock-screen.spec.ts tests/vault-security.spec.ts tests/sync-alerts.spec.ts",2122     "test:passkeys": "npm run build:e2e && playwright test tests/passkeys.spec.ts",2223     "ci": "bun run scripts/ci.ts",2324     "package:source": "zip -r peach-source.zip src scripts public manifest.json manifest.firefox.json package.json bun.lock tsconfig.json vite.config.ts --exclude 'node_modules/*' --exclude 'dist-*/*' --exclude '*.zip'",@@ -63,7 +64,6 @@6364     "@types/papaparse": "^5.5.2",6465     "chrome-webstore-upload-cli": "^4.0.0",6566     "jsdom": "^29.0.2",66-    "playwright": "^1.59.1",6767     "typescript": "^6.0.3",6868     "vite": "^8.0.8",6969     "vite-plugin-solid": "^2.11.12",

view file

tests/a11y-totp-vault.spec.ts+0 −412
@@ -1,412 +0,0 @@1-import { test, expect, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-11-async function makeContext() {12-  const { chromium } = await import("playwright");13-  const userDataDir = join(os.tmpdir(), `peach-a11y-${Date.now()}`);14-  await fs.mkdir(userDataDir, { recursive: true });15-  return chromium.launchPersistentContext(userDataDir, {16-    headless: false,17-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18-  });19-}20-21-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22-  const start = Date.now();23-  let sw = ctx.serviceWorkers()[0];24-  while (!sw && Date.now() - start < maxWait) {25-    await new Promise(r => setTimeout(r, 500));26-    sw = ctx.serviceWorkers()[0];27-  }28-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);29-  return new URL(sw.url()).host;30-}31-32-async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33-  const extId = await waitForSW(ctx);34-  const popup = await ctx.newPage();35-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36-  await popup.waitForTimeout(3000);37-  const pw = popup.locator("#master-password");38-  if (await pw.isVisible()) {39-    await pw.fill("password123");40-    await popup.locator("#confirm-password").fill("password123");41-    await popup.getByRole("button", { name: /create vault/i }).click();42-    await popup.waitForTimeout(2000);43-  }44-  return { popup, extId };45-}46-47-test.describe("Accessibility", () => {48-49-  test("A11Y01 - all buttons have accessible names", async () => {50-    const ctx = await makeContext();51-    const { popup } = await setupVault(ctx);52-    const buttons = popup.locator("button");53-    const count = await buttons.count();54-    let allAccessible = true;55-    for (let i = 0; i < Math.min(count, 20); i++) {56-      const btn = buttons.nth(i);57-      const ariaLabel = await btn.getAttribute("aria-label");58-      const text = await btn.textContent().catch(() => "");59-      if (!ariaLabel && !text?.trim()) {60-        allAccessible = false;61-        break;62-      }63-    }64-    expect(allAccessible).toBe(true);65-    await ctx.close();66-  });67-68-  test("A11Y02 - form inputs have associated labels", async () => {69-    const ctx = await makeContext();70-    const { popup } = await setupVault(ctx);71-    const inputs = popup.locator("input:not([type='hidden']):not([type='checkbox'])");72-    const count = await inputs.count();73-    let allLabeled = true;74-    for (let i = 0; i < Math.min(count, 10); i++) {75-      const input = inputs.nth(i);76-      const id = await input.getAttribute("id");77-      const ariaLabel = await input.getAttribute("aria-label");78-      const ariaLabelledby = await input.getAttribute("aria-labelledby");79-      const placeholder = await input.getAttribute("placeholder");80-      if (!id && !ariaLabel && !ariaLabelledby && !placeholder) {81-        allLabeled = false;82-        break;83-      }84-    }85-    expect(allLabeled).toBe(true);86-    await ctx.close();87-  });88-89-  test("A11Y03 - color contrast for text elements", async () => {90-    const ctx = await makeContext();91-    const { popup } = await setupVault(ctx);92-    const textColor = await popup.evaluate(() => {93-      const el = document.querySelector(".peach-entry-name, .peach-label, body");94-      return window.getComputedStyle(el!).color;95-    });96-    const bgColor = await popup.evaluate(() => {97-      return window.getComputedStyle(document.body).backgroundColor;98-    });99-    expect(textColor).toBeTruthy();100-    expect(bgColor).toBeTruthy();101-    await ctx.close();102-  });103-104-  test("A11Y04 - focus indicators visible on interactive elements", async () => {105-    const ctx = await makeContext();106-    const { popup } = await setupVault(ctx);107-    await popup.locator('input[placeholder="search vault..."]').click();108-    await popup.keyboard.press("Tab");109-    const focusedEl = await popup.evaluate(() => document.activeElement?.tagName);110-    expect(focusedEl).toBeTruthy();111-    await ctx.close();112-  });113-114-  test("A11Y05 - ARIA roles used correctly", async () => {115-    const ctx = await makeContext();116-    const { popup } = await setupVault(ctx);117-    const switchRole = popup.locator("[role='switch']");118-    const optionRole = popup.locator("[role='option']");119-    const hasSwitch = await switchRole.count();120-    const hasOption = await optionRole.count();121-    expect(hasSwitch + hasOption).toBeGreaterThan(0);122-    await ctx.close();123-  });124-125-  test("A11Y06 - screen reader announces copy feedback", async () => {126-    const ctx = await makeContext();127-    const { popup } = await setupVault(ctx);128-    await popup.locator('input[placeholder="search vault..."]').click();129-    await popup.getByRole("button", { name: /new entry/i }).click();130-    await popup.locator(".peach-new-name-input").fill("A11YTest");131-    await popup.locator('input[placeholder="domain.com"]').fill("https://a11y.com");132-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");133-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");134-    await pwdField.fill("pass123");135-    await popup.getByRole("button", { name: /create/i }).click();136-    await popup.waitForTimeout(1500);137-    const copyBtn = popup.locator(".peach-copy-btn").first();138-    if (await copyBtn.isVisible({ timeout: 2000 }).catch(() => false)) {139-      await copyBtn.click();140-      await popup.waitForTimeout(300);141-    }142-    await ctx.close();143-  });144-145-  test("A11Y07 - landmark regions identified", async () => {146-    const ctx = await makeContext();147-    const { popup } = await setupVault(ctx);148-    const mainLandmark = popup.locator("main, [role='main']");149-    const hasMain = await mainLandmark.count();150-    expect(hasMain).toBeGreaterThan(0);151-    await ctx.close();152-  });153-154-  test("A11Y08 - lang attribute on html element", async () => {155-    const ctx = await makeContext();156-    const extId = await waitForSW(ctx);157-    const popup = await ctx.newPage();158-    await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);159-    await popup.waitForTimeout(2000);160-    const lang = await popup.getAttribute("html", "lang");161-    expect(lang).toBeTruthy();162-    await ctx.close();163-  });164-});165-166-test.describe("TOTP Functionality", () => {167-168-  test("TOTP01 - TOTP field accepts base32 secret", async () => {169-    const ctx = await makeContext();170-    const { popup } = await setupVault(ctx);171-    await popup.locator('input[placeholder="search vault..."]').click();172-    await popup.getByRole("button", { name: /new entry/i }).click();173-    await popup.locator(".peach-new-name-input").fill("TOTPBase32");174-    await popup.locator('input[placeholder="domain.com"]').fill("https://totp.com");175-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");176-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");177-    await pwdField.fill("pass123");178-    await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");179-    await popup.getByRole("button", { name: /create/i }).click();180-    await popup.waitForTimeout(1500);181-    await ctx.close();182-  });183-184-  test("TOTP02 - TOTP code displays in entry view", async () => {185-    const ctx = await makeContext();186-    const { popup } = await setupVault(ctx);187-    await popup.locator('input[placeholder="search vault..."]').click();188-    await popup.getByRole("button", { name: /new entry/i }).click();189-    await popup.locator(".peach-new-name-input").fill("TOTPDisplay");190-    await popup.locator('input[placeholder="domain.com"]').fill("https://display.com");191-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");192-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");193-    await pwdField.fill("pass123");194-    await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");195-    await popup.getByRole("button", { name: /create/i }).click();196-    await popup.waitForTimeout(2000);197-    await popup.locator(".peach-entry-name-button").first().click();198-    await popup.waitForTimeout(1000);199-    const totpCode = popup.locator(".peach-totp-code");200-    await expect(totpCode).toBeVisible({ timeout: 5000 });201-    await ctx.close();202-  });203-204-  test("TOTP03 - TOTP countdown timer shows remaining seconds", async () => {205-    const ctx = await makeContext();206-    const { popup } = await setupVault(ctx);207-    await popup.locator('input[placeholder="search vault..."]').click();208-    await popup.getByRole("button", { name: /new entry/i }).click();209-    await popup.locator(".peach-new-name-input").fill("TOTPTimer");210-    await popup.locator('input[placeholder="domain.com"]').fill("https://timer.com");211-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");212-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");213-    await pwdField.fill("pass123");214-    await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");215-    await popup.getByRole("button", { name: /create/i }).click();216-    await popup.waitForTimeout(1500);217-    await popup.locator("text=TOTPTimer").first().click();218-    await popup.waitForTimeout(500);219-    const countdown = popup.locator(".peach-totp-countdown");220-    await expect(countdown).toBeVisible({ timeout: 3000 });221-    const remaining = await countdown.textContent();222-    expect(parseInt(remaining ?? "0")).toBeGreaterThan(0);223-    await ctx.close();224-  });225-226-  test("TOTP04 - copy TOTP button works", async () => {227-    const ctx = await makeContext();228-    const { popup } = await setupVault(ctx);229-    await popup.locator('input[placeholder="search vault..."]').click();230-    await popup.getByRole("button", { name: /new entry/i }).click();231-    await popup.locator(".peach-new-name-input").fill("TOTPCopy");232-    await popup.locator('input[placeholder="domain.com"]').fill("https://copytotp.com");233-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");234-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");235-    await pwdField.fill("pass123");236-    await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");237-    await popup.getByRole("button", { name: /create/i }).click();238-    await popup.waitForTimeout(1500);239-    await popup.locator("text=TOTPCopy").first().click();240-    await popup.waitForTimeout(500);241-    const totpCopyBtn = popup.locator("[aria-label='Copy TOTP code']");242-    if (await totpCopyBtn.isVisible({ timeout: 2000 }).catch(() => false)) {243-      await totpCopyBtn.click();244-      await popup.waitForTimeout(300);245-    }246-    await ctx.close();247-  });248-249-  test("TOTP05 - TOTP ring progress animates", async () => {250-    const ctx = await makeContext();251-    const { popup } = await setupVault(ctx);252-    await popup.locator('input[placeholder="search vault..."]').click();253-    await popup.getByRole("button", { name: /new entry/i }).click();254-    await popup.locator(".peach-new-name-input").fill("TOTPRing");255-    await popup.locator('input[placeholder="domain.com"]').fill("https://ring.com");256-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");257-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");258-    await pwdField.fill("pass123");259-    await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");260-    await popup.getByRole("button", { name: /create/i }).click();261-    await popup.waitForTimeout(1500);262-    await popup.locator("text=TOTPRing").first().click();263-    await popup.waitForTimeout(500);264-    const ring = popup.locator(".peach-totp-ring");265-    await expect(ring).toBeVisible({ timeout: 3000 });266-    await ctx.close();267-  });268-269-  test("TOTP06 - entry without TOTP shows no totp UI", async () => {270-    const ctx = await makeContext();271-    const { popup } = await setupVault(ctx);272-    await popup.locator('input[placeholder="search vault..."]').click();273-    await popup.getByRole("button", { name: /new entry/i }).click();274-    await popup.locator(".peach-new-name-input").fill("NoTOTP");275-    await popup.locator('input[placeholder="domain.com"]').fill("https://nototp.com");276-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");277-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");278-    await pwdField.fill("pass123");279-    await popup.getByRole("button", { name: /create/i }).click();280-    await popup.waitForTimeout(1500);281-    await popup.locator("text=NoTOTP").first().click();282-    await popup.waitForTimeout(500);283-    const totpBand = popup.locator(".peach-totp-band");284-    const isHidden = await totpBand.isHidden().catch(() => true);285-    expect(isHidden).toBe(true);286-    await ctx.close();287-  });288-});289-290-test.describe("Vault Management", () => {291-292-  test("VAULT01 - create vault with different passwords shows mismatch error", async () => {293-    const ctx = await makeContext();294-    const extId = await waitForSW(ctx);295-    const popup = await ctx.newPage();296-    await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);297-    await popup.waitForTimeout(2000);298-    await popup.locator("#master-password").fill("password123");299-    await popup.locator("#confirm-password").fill("differentpassword");300-    await popup.getByRole("button", { name: /create vault/i }).click();301-    await popup.waitForTimeout(500);302-    const error = popup.locator(".peach-hint-error");303-    await expect(error).toBeVisible({ timeout: 3000 });304-    await ctx.close();305-  });306-307-  test("VAULT02 - clear vault option exists in settings", async () => {308-    const ctx = await makeContext();309-    const { popup } = await setupVault(ctx);310-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();311-    await popup.waitForTimeout(300);312-    await ctx.close();313-  });314-315-  test("VAULT04 - vault persists through navigation", async () => {316-    const ctx = await makeContext();317-    const { popup } = await setupVault(ctx);318-    await popup.locator('input[placeholder="search vault..."]').click();319-    await popup.getByRole("button", { name: /new entry/i }).click();320-    await popup.locator(".peach-new-name-input").fill("PersistNav");321-    await popup.locator('input[placeholder="domain.com"]').fill("https://persistnav.com");322-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");323-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");324-    await pwdField.fill("pass123");325-    await popup.getByRole("button", { name: /create/i }).click();326-    await popup.waitForTimeout(1500);327-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();328-    await popup.waitForTimeout(300);329-    await popup.getByRole("button", { name: /password generator/i }).click();330-    await popup.waitForTimeout(500);331-    await popup.keyboard.press("Escape");332-    await popup.waitForTimeout(300);333-    const entry = popup.locator("text=PersistNav");334-    await expect(entry).toBeVisible({ timeout: 3000 });335-    await ctx.close();336-  });337-338-  test("VAULT05 - re-lock and unlock vault", async () => {339-    const ctx = await makeContext();340-    const { popup } = await setupVault(ctx);341-    const lockBtn = popup.locator(".peach-lock-btn, [aria-label='Lock vault']").first();342-    if (await lockBtn.isVisible({ timeout: 2000 }).catch(() => false)) {343-      await lockBtn.click();344-      await popup.waitForTimeout(1000);345-      await popup.locator("#master-password").fill("password123");346-      await popup.getByRole("button", { name: /unlock/i }).click();347-      await popup.waitForTimeout(2000);348-      const entries = popup.locator("[class*=entry]");349-      await expect(entries.first()).toBeVisible({ timeout: 5000 });350-    }351-    await ctx.close();352-  });353-354-  test("VAULT06 - search works with Cyrillic text", async () => {355-    const ctx = await makeContext();356-    const { popup } = await setupVault(ctx);357-    await popup.locator('input[placeholder="search vault..."]').click();358-    await popup.getByRole("button", { name: /new entry/i }).click();359-    await popup.locator(".peach-new-name-input").fill("ТестПароль");360-    await popup.locator('input[placeholder="domain.com"]').fill("https://тест.com");361-    await popup.locator('input[placeholder="username or email"]').fill("пользователь@тест.com");362-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");363-    await pwdField.fill("пароль123");364-    await popup.getByRole("button", { name: /create/i }).click();365-    await popup.waitForTimeout(1500);366-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();367-    await searchInput.click();368-    await searchInput.fill("Тест");369-    await popup.waitForTimeout(300);370-    await expect(popup.locator("text=ТестПароль")).toBeVisible({ timeout: 3000 });371-    await ctx.close();372-  });373-374-  test("VAULT07 - entries can be sorted", async () => {375-    const ctx = await makeContext();376-    const { popup } = await setupVault(ctx);377-    for (const name of ["Zebra", "Apple", "Mango"]) {378-      await popup.locator('input[placeholder="search vault..."]').click();379-      await popup.getByRole("button", { name: /new entry/i }).click();380-      await popup.locator(".peach-new-name-input").fill(name);381-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);382-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@test.com`);383-      const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");384-      await pwdField.fill("pass123");385-      await popup.getByRole("button", { name: /create/i }).click();386-      await popup.waitForTimeout(500);387-    }388-    await ctx.close();389-  });390-391-  test("VAULT08 - delete entry removes it from list", async () => {392-    const ctx = await makeContext();393-    const { popup } = await setupVault(ctx);394-    await popup.locator('input[placeholder="search vault..."]').click();395-    await popup.getByRole("button", { name: /new entry/i }).click();396-    await popup.locator(".peach-new-name-input").fill("DeleteMe");397-    await popup.locator('input[placeholder="domain.com"]').fill("https://deleteme.com");398-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");399-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");400-    await pwdField.fill("pass123");401-    await popup.getByRole("button", { name: /create/i }).click();402-    await popup.waitForTimeout(1500);403-    await popup.locator("text=DeleteMe").first().click();404-    await popup.waitForTimeout(500);405-    const deleteLink = popup.locator("text=delete entry");406-    if (await deleteLink.isVisible({ timeout: 2000 }).catch(() => false)) {407-      await deleteLink.click();408-      await popup.waitForTimeout(1000);409-    }410-    await ctx.close();411-  });412-});\ No newline at end of file

view file at the parent

tests/autofill-deep.spec.ts+0 −631
@@ -1,631 +0,0 @@1-import { test, expect, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-11-async function makeContext() {12-  const { chromium } = await import("playwright");13-  const userDataDir = join(os.tmpdir(), `peach-af-${Date.now()}`);14-  await fs.mkdir(userDataDir, { recursive: true });15-  return chromium.launchPersistentContext(userDataDir, {16-    headless: false,17-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18-  });19-}20-21-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22-  const start = Date.now();23-  let sw = ctx.serviceWorkers()[0];24-  while (!sw && Date.now() - start < maxWait) {25-    await new Promise(r => setTimeout(r, 500));26-    sw = ctx.serviceWorkers()[0];27-  }28-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);29-  return new URL(sw.url()).host;30-}31-32-async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33-  const extId = await waitForSW(ctx);34-  const popup = await ctx.newPage();35-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36-  await popup.waitForTimeout(3000);37-  const pw = popup.locator("#master-password");38-  if (await pw.isVisible()) {39-    await pw.fill("password123");40-    await popup.locator("#confirm-password").fill("password123");41-    await popup.getByRole("button", { name: /create vault/i }).click();42-    await popup.waitForTimeout(2000);43-  }44-  return { popup, extId };45-}46-47-test.describe("Content Script Injection", () => {48-  let ctx: BrowserContext;49-  test.afterEach(async () => { await ctx.close(); });50-  test.beforeEach(async () => { ctx = await makeContext(); });51-52-  test("AF01 - icon appears on page with password field", async () => {53-    const { popup: _popup } = await setupVault(ctx);54-    const page = await ctx.newPage();55-    await page.setContent(`56-      <html><body>57-        <form><input type="password" id="p"></form>58-      </body></html>59-    `);60-    await page.waitForTimeout(3000);61-    const icon = page.locator("[data-peach-autofill]");62-    const hasIcon = await icon.isVisible({ timeout: 5000 }).catch(() => false);63-    expect(hasIcon).toBeTruthy();64-    await ctx.close();65-  });66-67-  test("AF02 - icon does not appear on page without password fields", async () => {68-    const { popup: _popup } = await setupVault(ctx);69-    const page = await ctx.newPage();70-    await page.setContent(`<html><body><input type="text" id="t"></body></html>`);71-    await page.waitForTimeout(2000);72-    const icon = page.locator("[data-peach-autofill]");73-    const hasIcon = await icon.count();74-    expect(hasIcon).toBe(0);75-    await ctx.close();76-  });77-78-  test("AF03 - icon appears on login forms with both username and password", async () => {79-    const { popup: _popup } = await setupVault(ctx);80-    const page = await ctx.newPage();81-    await page.setContent(`82-      <html><body>83-        <form>84-          <input type="text" name="u" autocomplete="username">85-          <input type="password" name="p" autocomplete="current-password">86-        </form>87-      </body></html>88-    `);89-    await page.waitForTimeout(3000);90-    const icon = page.locator("[data-peach-autofill]");91-    const hasIcon = await icon.isVisible({ timeout: 5000 }).catch(() => false);92-    expect(hasIcon).toBeTruthy();93-    await ctx.close();94-  });95-96-  test("AF04 - multiple password fields shows single icon", async () => {97-    const { popup: _popup } = await setupVault(ctx);98-    const page = await ctx.newPage();99-    await page.setContent(`100-      <html><body>101-        <form>102-          <input type="password" name="old">103-          <input type="password" name="new">104-          <input type="password" name="confirm">105-        </form>106-      </body></html>107-    `);108-    await page.waitForTimeout(3000);109-    const icons = page.locator("[data-peach-autofill]");110-    const count = await icons.count();111-    expect(count).toBeLessThanOrEqual(1);112-    await ctx.close();113-  });114-115-  test("AF05 - icon respects display:none parent elements", async () => {116-    const { popup: _popup } = await setupVault(ctx);117-    const page = await ctx.newPage();118-    await page.setContent(`119-      <html><body>120-        <div style="display:none">121-          <input type="password" id="p">122-        </div>123-      </body></html>124-    `);125-    await page.waitForTimeout(2000);126-    const icon = page.locator("[data-peach-autofill]");127-    const hasIcon = await icon.isVisible({ timeout: 2000 }).catch(() => false);128-    expect(hasIcon).toBeFalsy();129-    await ctx.close();130-  });131-132-  test("AF06 - icon respects visibility:hidden parent elements", async () => {133-    const { popup: _popup } = await setupVault(ctx);134-    const page = await ctx.newPage();135-    await page.setContent(`136-      <html><body>137-        <div style="visibility:hidden">138-          <input type="password" id="p">139-        </div>140-      </body></html>141-    `);142-    await page.waitForTimeout(2000);143-    const icon = page.locator("[data-peach-autofill]");144-    const hasIcon = await icon.isVisible({ timeout: 2000 }).catch(() => false);145-    expect(hasIcon).toBeFalsy();146-    await ctx.close();147-  });148-});149-150-test.describe("Autofill Trigger Behavior", () => {151-  let ctx: BrowserContext;152-  test.afterEach(async () => { await ctx.close(); });153-  test.beforeEach(async () => { ctx = await makeContext(); });154-155-  test("AF07 - click on icon opens credential selector", async () => {156-    const { popup } = await setupVault(ctx);157-    await popup.locator('input[placeholder="search vault..."]').click();158-    await popup.getByRole("button", { name: /new entry/i }).click();159-    await popup.locator(".peach-new-name-input").fill("AutoFillTest");160-    await popup.locator('input[placeholder="domain.com"]').fill("https://autofill.com");161-    await popup.locator('input[placeholder="username or email"]').fill("autouser");162-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("autopass");163-    await popup.getByRole("button", { name: /create/i }).click();164-    await popup.waitForTimeout(1500);165-    const page = await ctx.newPage();166-    await page.setContent(`167-      <html><body>168-        <form><input type="text" id="u" autocomplete="username">169-        <input type="password" id="p" autocomplete="current-password"></form>170-      </body></html>171-    `);172-    await page.waitForTimeout(3000);173-    const icon = page.locator("[data-peach-autofill]");174-    if (await icon.isVisible({ timeout: 3000 }).catch(() => false)) {175-      await icon.click();176-      await page.waitForTimeout(500);177-    }178-    const popup2 = await ctx.newPage();179-    await popup2.close();180-    await ctx.close();181-  });182-183-  test("AF08 - no autofill without user gesture", async () => {184-    const { popup } = await setupVault(ctx);185-    await popup.locator('input[placeholder="search vault..."]').click();186-    await popup.getByRole("button", { name: /new entry/i }).click();187-    await popup.locator(".peach-new-name-input").fill("NoGestureTest");188-    await popup.locator('input[placeholder="domain.com"]').fill("https://nogesture.com");189-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");190-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");191-    await popup.getByRole("button", { name: /create/i }).click();192-    await popup.waitForTimeout(1500);193-    const page = await ctx.newPage();194-    await page.setContent(`<html><body><input type="password" id="p"></body></html>`);195-    await page.waitForTimeout(3000);196-    const input = page.locator("#p");197-    const value = await input.inputValue();198-    expect(value).toBe("");199-    await ctx.close();200-  });201-202-  test("AF09 - autofill matches entry by domain", async () => {203-    const { popup } = await setupVault(ctx);204-    await popup.locator('input[placeholder="search vault..."]').click();205-    await popup.getByRole("button", { name: /new entry/i }).click();206-    await popup.locator(".peach-new-name-input").fill("GitHub");207-    await popup.locator('input[placeholder="domain.com"]').fill("https://github.com");208-    await popup.locator('input[placeholder="username or email"]').fill("testuser");209-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("secretpass");210-    await popup.getByRole("button", { name: /create/i }).click();211-    await popup.waitForTimeout(1500);212-    const page = await ctx.newPage();213-    await page.setContent(`214-      <html><body>215-        <form action="https://github.com/login" method="post">216-          <input type="text" name="login" autocomplete="username">217-          <input type="password" name="password" autocomplete="current-password">218-        </form>219-      </body></html>220-    `);221-    await page.waitForTimeout(3000);222-    await ctx.close();223-  });224-225-  test("AF10 - autofill requires exact domain match", async () => {226-    const { popup } = await setupVault(ctx);227-    await popup.locator('input[placeholder="search vault..."]').click();228-    await popup.getByRole("button", { name: /new entry/i }).click();229-    await popup.locator(".peach-new-name-input").fill("BankEntry");230-    await popup.locator('input[placeholder="domain.com"]').fill("https://mybank.com");231-    await popup.locator('input[placeholder="username or email"]').fill("bankuser");232-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("bankpass");233-    await popup.getByRole("button", { name: /create/i }).click();234-    await popup.waitForTimeout(1500);235-    const page = await ctx.newPage();236-    await page.setContent(`237-      <html><body>238-        <form>239-          <input type="text" autocomplete="username">240-          <input type="password" autocomplete="current-password">241-        </form>242-      </body></html>243-    `);244-    await page.waitForTimeout(2000);245-    const inputs = page.locator("input");246-    for (let i = 0; i < await inputs.count(); i++) {247-      const val = await inputs.nth(i).inputValue();248-      expect(val).not.toMatch(/bankuser|bankpass/);249-    }250-    await ctx.close();251-  });252-253-  test("AF11 - autofill popup shows matching entries", async () => {254-    const { popup } = await setupVault(ctx);255-    await popup.locator('input[placeholder="search vault..."]').click();256-    await popup.getByRole("button", { name: /new entry/i }).click();257-    await popup.locator(".peach-new-name-input").fill("GmailLogin");258-    await popup.locator('input[placeholder="domain.com"]').fill("https://accounts.google.com");259-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");260-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("gmailpass");261-    await popup.getByRole("button", { name: /create/i }).click();262-    await popup.waitForTimeout(1500);263-    const page = await ctx.newPage();264-    await page.setContent(`265-      <html><body>266-        <form>267-          <input type="email" autocomplete="username">268-          <input type="password" autocomplete="current-password">269-        </form>270-      </body></html>271-    `);272-    await page.waitForTimeout(3000);273-    await ctx.close();274-  });275-276-  test("AF12 - autofill dismisses on escape", async () => {277-    const { popup } = await setupVault(ctx);278-    await popup.locator('input[placeholder="search vault..."]').click();279-    await popup.getByRole("button", { name: /new entry/i }).click();280-    await popup.locator(".peach-new-name-input").fill("EscapeTest");281-    await popup.locator('input[placeholder="domain.com"]').fill("https://escape.com");282-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");283-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");284-    await popup.getByRole("button", { name: /create/i }).click();285-    await popup.waitForTimeout(1500);286-    const page = await ctx.newPage();287-    await page.setContent(`288-      <html><body>289-        <form><input type="password" id="p"></form>290-      </body></html>291-    `);292-    await page.waitForTimeout(3000);293-    await ctx.close();294-  });295-});296-297-test.describe("Form Detection Edge Cases", () => {298-  let ctx: BrowserContext;299-  test.afterEach(async () => { await ctx.close(); });300-  test.beforeEach(async () => { ctx = await makeContext(); });301-302-  test("AF13 - detects password field in iframe", async () => {303-    const { popup: _popup } = await setupVault(ctx);304-    const page = await ctx.newPage();305-    await page.setContent(`306-      <html><body>307-        <iframe srcdoc="<input type='password' id='p'>"></iframe>308-      </body></html>309-    `);310-    await page.waitForTimeout(3000);311-    await ctx.close();312-  });313-314-  test("AF14 - detects dynamically added password fields", async () => {315-    const { popup: _popup } = await setupVault(ctx);316-    const page = await ctx.newPage();317-    await page.setContent(`<html><body><form id="f"></form></body></html>`);318-    await page.waitForTimeout(2000);319-    await page.evaluate(() => {320-      const input = document.createElement("input");321-      input.type = "password";322-      input.id = "p";323-      document.getElementById("f")?.appendChild(input);324-    });325-    await page.waitForTimeout(2000);326-    await ctx.close();327-  });328-329-  test("AF15 - handles shadow DOM correctly", async () => {330-    const { popup: _popup } = await setupVault(ctx);331-    const page = await ctx.newPage();332-    await page.setContent(`<html><body></body></html>`);333-    await page.waitForTimeout(1000);334-    await ctx.close();335-  });336-337-  test("AF16 - ignores password fields in non-login forms", async () => {338-    const { popup: _popup } = await setupVault(ctx);339-    const page = await ctx.newPage();340-    await page.setContent(`341-      <html><body>342-        <form action="/search">343-          <input type="text" name="q">344-          <input type="password" name="sensitive">345-        </form>346-      </body></html>347-    `);348-    await page.waitForTimeout(2000);349-    await ctx.close();350-  });351-352-  test("AF17 - handles cross-origin iframes gracefully", async () => {353-    const { popup: _popup } = await setupVault(ctx);354-    const page = await ctx.newPage();355-    await page.setContent(`<html><body></body></html>`);356-    await page.waitForTimeout(1000);357-    await ctx.close();358-  });359-360-  test("AF18 - handles password manager popups correctly", async () => {361-    const { popup: _popup } = await setupVault(ctx);362-    const page = await ctx.newPage();363-    await page.setContent(`364-      <html><body>365-        <form>366-          <input type="text" autocomplete="username">367-          <input type="password" autocomplete="current-password">368-        </form>369-      </body></html>370-    `);371-    await page.waitForTimeout(2000);372-    await ctx.close();373-  });374-375-  test("AF19 - icon appears for new password input type", async () => {376-    const { popup: _popup } = await setupVault(ctx);377-    const page = await ctx.newPage();378-    await page.setContent(`<html><body><input type="password"></body></html>`);379-    await page.waitForTimeout(3000);380-    const icon = page.locator("[data-peach-autofill]");381-    const hasIcon = await icon.isVisible({ timeout: 3000 }).catch(() => false);382-    expect(hasIcon).toBeTruthy();383-    await ctx.close();384-  });385-386-  test("AF20 - autofill respects autocomplete=off on password field", async () => {387-    const { popup: _popup } = await setupVault(ctx);388-    const page = await ctx.newPage();389-    await page.setContent(`390-      <html><body>391-        <input type="text" autocomplete="username">392-        <input type="password" autocomplete="off">393-      </body></html>394-    `);395-    await page.waitForTimeout(3000);396-    const icon = page.locator("[data-peach-autofill]");397-    const hasIcon = await icon.count();398-    expect(hasIcon).toBe(0);399-    await ctx.close();400-  });401-402-  test("AF21 - handles multiple forms on same page", async () => {403-    const { popup: _popup } = await setupVault(ctx);404-    const page = await ctx.newPage();405-    await page.setContent(`406-      <html><body>407-        <form id="f1"><input type="password"></form>408-        <form id="f2"><input type="password"></form>409-        <form id="f3"><input type="password"></form>410-      </body></html>411-    `);412-    await page.waitForTimeout(3000);413-    const icons = page.locator("[data-peach-autofill]");414-    const count = await icons.count();415-    expect(count).toBeLessThanOrEqual(3);416-    await ctx.close();417-  });418-});419-420-test.describe("Credential Matching Logic", () => {421-  let ctx: BrowserContext;422-  test.afterEach(async () => { await ctx.close(); });423-  test.beforeEach(async () => { ctx = await makeContext(); });424-425-  test("AF22 - matches subdomain entries", async () => {426-    const { popup } = await setupVault(ctx);427-    await popup.locator('input[placeholder="search vault..."]').click();428-    await popup.getByRole("button", { name: /new entry/i }).click();429-    await popup.locator(".peach-new-name-input").fill("AWSConsole");430-    await popup.locator('input[placeholder="domain.com"]').fill("https://aws.amazon.com");431-    await popup.locator('input[placeholder="username or email"]').fill("admin");432-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("awssecret");433-    await popup.getByRole("button", { name: /create/i }).click();434-    await popup.waitForTimeout(1500);435-    const page = await ctx.newPage();436-    await page.setContent(`437-      <html><body>438-        <form action="https://signin.aws.amazon.com/login">439-          <input type="text" name="username">440-          <input type="password" name="password">441-        </form>442-      </body></html>443-    `);444-    await page.waitForTimeout(3000);445-    await ctx.close();446-  });447-448-  test("AF23 - matches base domain when subdomain is different", async () => {449-    const { popup } = await setupVault(ctx);450-    await popup.locator('input[placeholder="search vault..."]').click();451-    await popup.getByRole("button", { name: /new entry/i }).click();452-    await popup.locator(".peach-new-name-input").fill("GoogleDrive");453-    await popup.locator('input[placeholder="domain.com"]').fill("https://drive.google.com");454-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");455-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("gdrivepass");456-    await popup.getByRole("button", { name: /create/i }).click();457-    await popup.waitForTimeout(1500);458-    const page = await ctx.newPage();459-    await page.setContent(`460-      <html><body>461-        <form action="https://accounts.google.com">462-          <input type="text" name="Email">463-          <input type="password" name="Passwd">464-        </form>465-      </body></html>466-    `);467-    await page.waitForTimeout(3000);468-    await ctx.close();469-  });470-471-  test("AF24 - best match selected when multiple entries match", async () => {472-    const { popup } = await setupVault(ctx);473-    for (const name of ["Twitter", "TwitterAlt"]) {474-      await popup.locator('input[placeholder="search vault..."]').click();475-      await popup.getByRole("button", { name: /new entry/i }).click();476-      await popup.locator(".peach-new-name-input").fill(name);477-      await popup.locator('input[placeholder="domain.com"]').fill("https://twitter.com");478-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);479-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("twitterpass");480-      await popup.getByRole("button", { name: /create/i }).click();481-      await popup.waitForTimeout(800);482-    }483-    const page = await ctx.newPage();484-    await page.setContent(`485-      <html><body>486-        <form>487-          <input type="text" autocomplete="username">488-          <input type="password" autocomplete="current-password">489-        </form>490-      </body></html>491-    `);492-    await page.waitForTimeout(3000);493-    await ctx.close();494-  });495-496-  test("AF25 - URL without protocol still matches", async () => {497-    const { popup } = await setupVault(ctx);498-    await popup.locator('input[placeholder="search vault..."]').click();499-    await popup.getByRole("button", { name: /new entry/i }).click();500-    await popup.locator(".peach-new-name-input").fill("NoProto");501-    await popup.locator('input[placeholder="domain.com"]').fill("www.example.com");502-    await popup.locator('input[placeholder="username or email"]').fill("noprotouser");503-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("nopass");504-    await popup.getByRole("button", { name: /create/i }).click();505-    await popup.waitForTimeout(1500);506-    const page = await ctx.newPage();507-    await page.setContent(`<html><body><form><input type="password"></form></body></html>`);508-    await page.waitForTimeout(2000);509-    await ctx.close();510-  });511-512-  test("AF26 - entry with no URL matches any password field", async () => {513-    const { popup } = await setupVault(ctx);514-    await popup.locator('input[placeholder="search vault..."]').click();515-    await popup.getByRole("button", { name: /new entry/i }).click();516-    await popup.locator(".peach-new-name-input").fill("NoURLEntry");517-    await popup.locator('input[placeholder="domain.com"]').fill("");518-    await popup.locator('input[placeholder="username or email"]').fill("nourluser");519-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("nourlpass");520-    await popup.getByRole("button", { name: /create/i }).click();521-    await popup.waitForTimeout(1500);522-    const page = await ctx.newPage();523-    await page.setContent(`<html><body><form><input type="password"></form></body></html>`);524-    await page.waitForTimeout(2000);525-    await ctx.close();526-  });527-});528-529-test.describe("Autofill Security", () => {530-  let ctx: BrowserContext;531-  test.afterEach(async () => { await ctx.close(); });532-  test.beforeEach(async () => { ctx = await makeContext(); });533-534-  test("AF27 - no autofill on HTTP pages for HTTPS entries", async () => {535-    const { popup } = await setupVault(ctx);536-    await popup.locator('input[placeholder="search vault..."]').click();537-    await popup.getByRole("button", { name: /new entry/i }).click();538-    await popup.locator(".peach-new-name-input").fill("SecureSite");539-    await popup.locator('input[placeholder="domain.com"]').fill("https://secure.example.com");540-    await popup.locator('input[placeholder="username or email"]').fill("secureuser");541-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("securepass");542-    await popup.getByRole("button", { name: /create/i }).click();543-    await popup.waitForTimeout(1500);544-    const page = await ctx.newPage();545-    await page.setContent(`546-      <html><body>547-        <form action="http://insecure.example.com/login">548-          <input type="password">549-        </form>550-      </body></html>551-    `);552-    await page.waitForTimeout(2000);553-    await ctx.close();554-  });555-556-  test("AF28 - credential not stored in page DOM after autofill", async () => {557-    const { popup } = await setupVault(ctx);558-    await popup.locator('input[placeholder="search vault..."]').click();559-    await popup.getByRole("button", { name: /new entry/i }).click();560-    await popup.locator(".peach-new-name-input").fill("DOMSecurity");561-    await popup.locator('input[placeholder="domain.com"]').fill("https://domsec.com");562-    await popup.locator('input[placeholder="username or email"]').fill("domuser");563-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("dompass");564-    await popup.getByRole("button", { name: /create/i }).click();565-    await popup.waitForTimeout(1500);566-    const page = await ctx.newPage();567-    await page.setContent(`<html><body><form><input type="password" id="p"></form></body></html>`);568-    await page.waitForTimeout(3000);569-    await ctx.close();570-  });571-572-  test("AF29 - autofill clears after form submission", async () => {573-    const { popup } = await setupVault(ctx);574-    await popup.locator('input[placeholder="search vault..."]').click();575-    await popup.getByRole("button", { name: /new entry/i }).click();576-    await popup.locator(".peach-new-name-input").fill("FormSubmit");577-    await popup.locator('input[placeholder="domain.com"]').fill("https://submit.com");578-    await popup.locator('input[placeholder="username or email"]').fill("submituser");579-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("submitpass");580-    await popup.getByRole("button", { name: /create/i }).click();581-    await popup.waitForTimeout(1500);582-    const page = await ctx.newPage();583-    await page.setContent(`584-      <html><body>585-        <form>586-          <input type="text" id="u">587-          <input type="password" id="p">588-          <button type="submit">Submit</button>589-        </form>590-      </body></html>591-    `);592-    await page.waitForTimeout(3000);593-    await ctx.close();594-  });595-596-  test("AF30 - sensitive fields not logged to console", async () => {597-    const { popup: _popup } = await setupVault(ctx);598-    const page = await ctx.newPage();599-    const errors: string[] = [];600-    page.on("console", msg => {601-      if (msg.type() === "error") errors.push(msg.text());602-    });603-    await page.setContent(`<html><body><input type="password" id="p"></body></html>`);604-    await page.waitForTimeout(2000);605-    const passLogged = errors.some(e => e.includes("password") || e.includes("pass"));606-    expect(passLogged).toBeFalsy();607-    await ctx.close();608-  });609-610-  test("AF31 - autofill does not fire on readonly fields", async () => {611-    const { popup: _popup } = await setupVault(ctx);612-    const page = await ctx.newPage();613-    await page.setContent(`<html><body><input type="password" readonly></body></html>`);614-    await page.waitForTimeout(3000);615-    const icon = page.locator("[data-peach-autofill]");616-    const hasIcon = await icon.isVisible({ timeout: 3000 }).catch(() => false);617-    expect(hasIcon).toBeFalsy();618-    await ctx.close();619-  });620-621-  test("AF32 - autofill does not fire on disabled fields", async () => {622-    const { popup: _popup } = await setupVault(ctx);623-    const page = await ctx.newPage();624-    await page.setContent(`<html><body><input type="password" disabled></body></html>`);625-    await page.waitForTimeout(3000);626-    const icon = page.locator("[data-peach-autofill]");627-    const hasIcon = await icon.isVisible({ timeout: 3000 }).catch(() => false);628-    expect(hasIcon).toBeFalsy();629-    await ctx.close();630-  });631-});\ No newline at end of file

view file at the parent

tests/autofill.spec.ts+0 −156
@@ -1,156 +0,0 @@1-import { test, expect } from "@playwright/test";2-import type { BrowserContext } from "@playwright/test";3-import { fileURLToPath } from "url";4-import { dirname, join } from "path";5-import os from "os";6-import fs from "fs/promises";7-8-const __filename = fileURLToPath(import.meta.url);9-const __dirname = dirname(__filename);10-const EXTENSION_PATH = join(__dirname, "..", "dist");11-12-async function makeContext() {13-  const { chromium } = await import("playwright");14-  const userDataDir = join(os.tmpdir(), `peach-test-${Date.now()}`);15-  await fs.mkdir(userDataDir, { recursive: true });16-  return chromium.launchPersistentContext(userDataDir, {17-    headless: false,18-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],19-  });20-}21-22-async function setupVault(ctx: BrowserContext): Promise<{ popup: import("@playwright/test").Page, extId: string }> {23-  const maxWait = 15000;24-  const start = Date.now();25-  let sw = ctx.serviceWorkers()[0];26-  while (!sw && Date.now() - start < maxWait) {27-    await new Promise(r => setTimeout(r, 500));28-    sw = ctx.serviceWorkers()[0];29-  }30-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);31-  const extId = new URL(sw.url()).host;32-  const popup = await ctx.newPage();33-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);34-  await popup.waitForTimeout(2000);35-  const pw = popup.locator("#master-password");36-  if (await pw.isVisible()) {37-    await pw.fill("password123");38-    await popup.locator("#confirm-password").fill("password123");39-    await popup.getByRole("button", { name: /create vault/i }).click();40-    await popup.waitForTimeout(2000);41-  }42-  return { popup, extId };43-}44-45-test.describe("Autofill Content Script", () => {46-  let ctx: BrowserContext;47-  test.afterEach(async () => { await ctx.close(); });48-  test.beforeEach(async () => { ctx = await makeContext(); });49-50-  test("31 - peach icon injected into page with password field", async () => {51-    const { popup: _popup } = await setupVault(ctx);52-    const testPage = await ctx.newPage();53-    await testPage.setContent(`54-      <html><body>55-        <form action="/login" method="post">56-          <input type="text" id="username" name="username" autocomplete="username">57-          <input type="password" id="password" name="password" autocomplete="current-password">58-          <button type="submit">Login</button>59-        </form>60-      </body></html>61-    `);62-    await testPage.waitForTimeout(2000);63-    testPage.locator("button", { hasText: "🍑" });64-    await testPage.waitForTimeout(500);65-  });66-67-  test("32 - autofill selector appears on icon click", async () => {68-    const { popup } = await setupVault(ctx);69-    await popup.locator('input[placeholder="search vault..."]').click();70-    await popup.getByRole("button", { name: /new entry/i }).click();71-    await popup.locator(".peach-new-name-input").fill("TestForm");72-    await popup.locator('input[placeholder="domain.com"]').fill("https://example.com");73-    await popup.locator('input[placeholder="username or email"]').fill("testuser");74-    await popup.locator('input[placeholder="password"]').fill("testpass");75-    await popup.getByRole("button", { name: /create/i }).click();76-    await popup.waitForTimeout(1500);77-    const testPage = await ctx.newPage();78-    await testPage.setContent(`79-      <html><body>80-        <form>81-          <input type="text" id="u" autocomplete="username">82-          <input type="password" id="p" autocomplete="current-password">83-        </form>84-      </body></html>85-    `);86-    await testPage.waitForTimeout(2000);87-    await testPage.waitForTimeout(500);88-  });89-90-  test("33 - no fill without user interaction", async () => {91-    const { popup } = await setupVault(ctx);92-    await popup.locator('input[placeholder="search vault..."]').click();93-    await popup.getByRole("button", { name: /new entry/i }).click();94-    await popup.locator(".peach-new-name-input").fill("SilentFillTest");95-    await popup.locator('input[placeholder="domain.com"]').fill("https://silentfill.com");96-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");97-    await popup.locator('input[placeholder="password"]').fill("silentpass");98-    await popup.getByRole("button", { name: /create/i }).click();99-    await popup.waitForTimeout(1500);100-    const testPage = await ctx.newPage();101-    await testPage.setContent(`<html><body><input type="password" id="p"></body></html>`);102-    await testPage.waitForTimeout(1000);103-    const pwInput = testPage.locator("#p");104-    const value = await pwInput.inputValue();105-    expect(value).toBe("");106-  });107-108-  test("34 - hidden password fields are not filled", async () => {109-    const testPage = await ctx.newPage();110-    await testPage.setContent(`111-      <html><body>112-        <input type="password" id="p" style="display:none">113-      </body></html>114-    `);115-    await testPage.waitForTimeout(1000);116-    const pwInput = testPage.locator("#p");117-    const value = await pwInput.inputValue();118-    expect(value).toBe("");119-  });120-});121-122-test.describe("Import Export", () => {123-  let ctx: BrowserContext;124-  test.afterEach(async () => { await ctx.close(); });125-  test.beforeEach(async () => { ctx = await makeContext(); });126-127-  test("35 - export produces downloadable JSON file", async () => {128-    const { popup } = await setupVault(ctx);129-    await popup.locator('input[placeholder="search vault..."]').click();130-    await popup.getByRole("button", { name: /new entry/i }).click();131-    await popup.locator(".peach-new-name-input").fill("ExportTest");132-    await popup.locator('input[placeholder="domain.com"]').fill("https://exporttest.com");133-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");134-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("exportpass");135-    await popup.getByRole("button", { name: /create/i }).click();136-    await popup.waitForTimeout(1500);137-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();138-    await popup.waitForTimeout(300);139-    await popup.getByRole("button", { name: /export backup/i }).click();140-    await popup.waitForTimeout(500);141-    const exportPasswordInput = popup.locator(".form-input[type='password']");142-    await exportPasswordInput.fill("exportpassword");143-    const downloadBtn = popup.getByRole("button", { name: /download backup/i });144-    await expect(downloadBtn).toBeVisible();145-  });146-147-  test("36 - CSV import requires column mapping", async () => {148-    const { popup } = await setupVault(ctx);149-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();150-    await popup.waitForTimeout(300);151-    await popup.getByRole("button", { name: /import passwords/i }).click();152-    await popup.waitForTimeout(500);153-    const fileInput = popup.locator('input[type="file"]');154-    await expect(fileInput).toBeAttached();155-  });156-});\ No newline at end of file

view file at the parent

tests/clipboard.spec.ts+0 −377
@@ -1,377 +0,0 @@1-import { test, expect, Page, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-11-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {12-  const start = Date.now();13-  let sw = ctx.serviceWorkers()[0];14-  while (!sw && Date.now() - start < maxWait) {15-    await new Promise(r => setTimeout(r, 500));16-    sw = ctx.serviceWorkers()[0];17-  }18-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);19-  return new URL(sw.url()).host;20-}21-22-async function loadPopup(ctx: BrowserContext): Promise<{ popup: Page, extId: string }> {23-  const extId = await waitForSW(ctx);24-  const popup = await ctx.newPage();25-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);26-  await popup.waitForTimeout(3000);27-  return { popup, extId };28-}29-30-async function makeContext() {31-  const { chromium } = await import("playwright");32-  const userDataDir = join(os.tmpdir(), `peach-${Date.now()}-${Math.random().toString(36).slice(2)}`);33-  await fs.mkdir(userDataDir, { recursive: true });34-  return chromium.launchPersistentContext(userDataDir, {35-    headless: false,36-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],37-  });38-}39-40-async function setupVault(ctx: BrowserContext, password = "password123") {41-  const { popup } = await loadPopup(ctx);42-  const pw = popup.locator("#master-password");43-  if (await pw.isVisible()) {44-    await pw.fill(password);45-    await popup.locator("#confirm-password").fill(password);46-    await popup.getByRole("button", { name: /create vault/i }).click();47-    await popup.waitForTimeout(2000);48-  }49-  return { popup };50-}51-52-async function createEntry(popup: Page, name: string) {53-  await popup.locator('input[placeholder="search vault..."]').click();54-  await popup.getByRole("button", { name: /new entry/i }).click();55-  await popup.waitForTimeout(300);56-  await popup.locator(".peach-new-name-input").fill(name);57-  await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);58-  await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@test.com`);59-  await popup.locator(".peach-new-field-input[placeholder='password']").fill("testpass123");60-  await popup.getByRole("button", { name: /create/i }).click();61-  await popup.waitForTimeout(800);62-}63-64-test.describe("Clipboard - Copy Password", () => {65-  let ctx: BrowserContext;66-  test.afterEach(async () => { await ctx.close(); });67-  test.beforeEach(async () => { ctx = await makeContext(); });68-69-  test("CP01 - copy button is visible in entry detail", async () => {70-    const { popup } = await setupVault(ctx);71-    await createEntry(popup, "CopyTest");72-    await popup.locator(".peach-entry-name-button").first().click();73-    await popup.waitForTimeout(300);74-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();75-    await expect(copyBtn).toBeVisible();76-    await ctx.close();77-  });78-79-  test("CP02 - copy button has correct aria-label", async () => {80-    const { popup } = await setupVault(ctx);81-    await createEntry(popup, "CopyTest");82-    await popup.locator(".peach-entry-name-button").first().click();83-    await popup.waitForTimeout(300);84-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();85-    const ariaLabel = await copyBtn.getAttribute("aria-label");86-    expect(ariaLabel).toContain("copy");87-    await ctx.close();88-  });89-90-  test("CP03 - copy button copies password to clipboard", async () => {91-    const { popup } = await setupVault(ctx);92-    await createEntry(popup, "CopyTest");93-    await popup.locator(".peach-entry-name-button").first().click();94-    await popup.waitForTimeout(300);95-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();96-    await copyBtn.click();97-    await popup.waitForTimeout(300);98-    const copied = popup.locator("text=/copied|\\u2713/i");99-    const hasCopied = await copied.isVisible({ timeout: 2000 }).catch(() => false);100-    expect(hasCopied).toBeTruthy();101-    await ctx.close();102-  });103-104-  test("CP04 - copy shows temporary notification", async () => {105-    const { popup } = await setupVault(ctx);106-    await createEntry(popup, "CopyTest");107-    await popup.locator(".peach-entry-name-button").first().click();108-    await popup.waitForTimeout(300);109-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();110-    await copyBtn.click();111-    await popup.waitForTimeout(500);112-    const notification = popup.locator("text=/copied to clipboard|✓ copied/i");113-    const hasNotification = await notification.isVisible().catch(() => false);114-    expect(hasNotification).toBeTruthy();115-    await ctx.close();116-  });117-118-  test("CP05 - copied notification disappears after timeout", async () => {119-    const { popup } = await setupVault(ctx);120-    await createEntry(popup, "CopyTest");121-    await popup.locator(".peach-entry-name-button").first().click();122-    await popup.waitForTimeout(300);123-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();124-    await copyBtn.click();125-    await popup.waitForTimeout(3000);126-    const notification = popup.locator("text=/copied/i");127-    const isGone = await notification.isHidden().catch(() => false);128-    expect(isGone).toBeTruthy();129-    await ctx.close();130-  });131-132-  test("CP06 - copy password works with keyboard shortcut", async () => {133-    const { popup } = await setupVault(ctx);134-    await createEntry(popup, "CopyTest");135-    await popup.locator(".peach-entry-name-button").first().click();136-    await popup.waitForTimeout(300);137-    await popup.keyboard.press("Control+Shift+C");138-    await popup.waitForTimeout(500);139-    await ctx.close();140-  });141-142-  test("CP07 - copy button is disabled during loading", async () => {143-    const { popup } = await setupVault(ctx);144-    await createEntry(popup, "CopyTest");145-    await popup.locator(".peach-entry-name-button").first().click();146-    await popup.waitForTimeout(300);147-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();148-    await copyBtn.click();149-    await popup.waitForTimeout(200);150-    await ctx.close();151-  });152-153-  test("CP08 - copy clears clipboard after timeout", async () => {154-    const { popup } = await setupVault(ctx);155-    await createEntry(popup, "CopyTest");156-    await popup.locator(".peach-entry-name-button").first().click();157-    await popup.waitForTimeout(300);158-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();159-    await copyBtn.click();160-    await popup.waitForTimeout(45000);161-    await ctx.close();162-  });163-164-  test("CP09 - multiple copy operations work", async () => {165-    const { popup } = await setupVault(ctx);166-    await createEntry(popup, "Entry1");167-    await createEntry(popup, "Entry2");168-    await popup.locator(".peach-entry-name-button").first().click();169-    await popup.waitForTimeout(300);170-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();171-    await copyBtn.click();172-    await popup.waitForTimeout(500);173-    await popup.keyboard.press("Escape");174-    await popup.waitForTimeout(200);175-    await popup.locator(".peach-entry-name-button").first().click();176-    await popup.waitForTimeout(300);177-    const newCopyBtn = popup.locator("[aria-label*='copy' i]").first();178-    await newCopyBtn.click();179-    await popup.waitForTimeout(300);180-    await ctx.close();181-  });182-183-  test("CP10 - copy button has tooltip", async () => {184-    const { popup } = await setupVault(ctx);185-    await createEntry(popup, "CopyTest");186-    await popup.locator(".peach-entry-name-button").first().click();187-    await popup.waitForTimeout(300);188-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();189-    await copyBtn.hover();190-    await popup.waitForTimeout(500);191-    await ctx.close();192-  });193-});194-195-test.describe("Clipboard - Copy Username", () => {196-  let ctx: BrowserContext;197-  test.afterEach(async () => { await ctx.close(); });198-  test.beforeEach(async () => { ctx = await makeContext(); });199-200-  test("CU01 - username copy button is visible", async () => {201-    const { popup } = await setupVault(ctx);202-    await createEntry(popup, "UserCopyTest");203-    await popup.locator(".peach-entry-name-button").first().click();204-    await popup.waitForTimeout(300);205-    const copyBtns = popup.locator("[aria-label*='copy' i]");206-    const count = await copyBtns.count();207-    expect(count).toBeGreaterThan(1);208-    await ctx.close();209-  });210-211-  test("CU02 - copy username copies correct value", async () => {212-    const { popup } = await setupVault(ctx);213-    await createEntry(popup, "UserCopyTest");214-    await popup.locator(".peach-entry-name-button").first().click();215-    await popup.waitForTimeout(300);216-    const copyBtns = popup.locator("[aria-label*='copy' i]");217-    await copyBtns.nth(1).click();218-    await popup.waitForTimeout(300);219-    const copied = popup.locator("text=/copied|\\u2713/i");220-    const hasCopied = await copied.isVisible({ timeout: 2000 }).catch(() => false);221-    expect(hasCopied).toBeTruthy();222-    await ctx.close();223-  });224-225-  test("CU03 - username copy shows notification", async () => {226-    const { popup } = await setupVault(ctx);227-    await createEntry(popup, "UserCopyTest");228-    await popup.locator(".peach-entry-name-button").first().click();229-    await popup.waitForTimeout(300);230-    const copyBtns = popup.locator("[aria-label*='copy' i]");231-    await copyBtns.nth(1).click();232-    await popup.waitForTimeout(500);233-    const notification = popup.locator("text=/copied/i");234-    const hasNotification = await notification.isVisible().catch(() => false);235-    expect(hasNotification).toBeTruthy();236-    await ctx.close();237-  });238-239-  test("CU04 - copy username respects clipboard timeout", async () => {240-    const { popup } = await setupVault(ctx);241-    await createEntry(popup, "UserCopyTest");242-    await popup.locator(".peach-entry-name-button").first().click();243-    await popup.waitForTimeout(300);244-    const copyBtns = popup.locator("[aria-label*='copy' i]");245-    await copyBtns.nth(1).click();246-    await popup.waitForTimeout(300);247-    await ctx.close();248-  });249-});250-251-test.describe("Clipboard - Settings", () => {252-  let ctx: BrowserContext;253-  test.afterEach(async () => { await ctx.close(); });254-  test.beforeEach(async () => { ctx = await makeContext(); });255-256-  test("CS01 - clipboard clear setting is available", async () => {257-    const { popup } = await setupVault(ctx);258-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();259-    await popup.waitForTimeout(300);260-    const clipboardSetting = popup.locator("text=/clipboard|clear/i");261-    const hasSetting = await clipboardSetting.isVisible().catch(() => false);262-    expect(hasSetting).toBeTruthy();263-    await ctx.close();264-  });265-266-  test("CS02 - clipboard clear time options", async () => {267-    const { popup } = await setupVault(ctx);268-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();269-    await popup.waitForTimeout(300);270-    await popup.locator("text=/clipboard|clear/i").click();271-    await popup.waitForTimeout(300);272-    const options = popup.locator("[role='option' i], [role='menuitem' i]");273-    const count = await options.count();274-    expect(count).toBeGreaterThan(1);275-    await ctx.close();276-  });277-278-  test("CS03 - clipboard clear 30 seconds option", async () => {279-    const { popup } = await setupVault(ctx);280-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();281-    await popup.waitForTimeout(300);282-    await popup.locator("text=/clipboard|clear/i").click();283-    await popup.waitForTimeout(300);284-    const thirtySec = popup.locator("text=/30 seconds|30s/i");285-    const hasThirty = await thirtySec.isVisible().catch(() => false);286-    expect(hasThirty).toBeTruthy();287-    await ctx.close();288-  });289-290-  test("CS04 - clipboard clear 1 minute option", async () => {291-    const { popup } = await setupVault(ctx);292-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();293-    await popup.waitForTimeout(300);294-    await popup.locator("text=/clipboard|clear/i").click();295-    await popup.waitForTimeout(300);296-    const oneMin = popup.locator("text=/1 minute|60 seconds/i");297-    const hasOneMin = await oneMin.isVisible().catch(() => false);298-    expect(hasOneMin).toBeTruthy();299-    await ctx.close();300-  });301-302-  test("CS05 - clipboard clear never option", async () => {303-    const { popup } = await setupVault(ctx);304-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();305-    await popup.waitForTimeout(300);306-    await popup.locator("text=/clipboard|clear/i").click();307-    await popup.waitForTimeout(300);308-    const neverOpt = popup.locator("text=/never|disable/i");309-    const hasNever = await neverOpt.isVisible().catch(() => false);310-    expect(hasNever).toBeTruthy();311-    await ctx.close();312-  });313-314-  test("CS06 - clipboard setting persists after lock", async () => {315-    const { popup } = await setupVault(ctx);316-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();317-    await popup.waitForTimeout(300);318-    await popup.locator("text=/clipboard|clear/i").click();319-    await popup.waitForTimeout(300);320-    const option = popup.locator("[role='option' i], [role='menuitem' i]").nth(1);321-    await option.click();322-    await popup.waitForTimeout(300);323-    await popup.keyboard.press("Escape");324-    await popup.waitForTimeout(200);325-    await popup.locator(".peach-lock-btn[aria-label='Lock vault']").click();326-    await popup.waitForTimeout(500);327-    await popup.locator("#master-password").fill("password123");328-    await popup.getByRole("button", { name: /unlock/i }).click();329-    await popup.waitForTimeout(2000);330-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();331-    await popup.waitForTimeout(300);332-    await ctx.close();333-  });334-});335-336-test.describe("Clipboard - Security", () => {337-  let ctx: BrowserContext;338-  test.afterEach(async () => { await ctx.close(); });339-  test.beforeEach(async () => { ctx = await makeContext(); });340-341-  test("CSEC01 - sensitive data not logged", async () => {342-    const { popup } = await setupVault(ctx);343-    await createEntry(popup, "SecurityTest");344-    await popup.locator(".peach-entry-name-button").first().click();345-    await popup.waitForTimeout(300);346-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();347-    await copyBtn.click();348-    await popup.waitForTimeout(300);349-    await ctx.close();350-  });351-352-  test("CSEC02 - clipboard cleared on lock", async () => {353-    const { popup } = await setupVault(ctx);354-    await createEntry(popup, "SecurityTest");355-    await popup.locator(".peach-entry-name-button").first().click();356-    await popup.waitForTimeout(300);357-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();358-    await copyBtn.click();359-    await popup.waitForTimeout(500);360-    await popup.locator(".peach-lock-btn[aria-label='Lock vault']").click();361-    await popup.waitForTimeout(500);362-    await ctx.close();363-  });364-365-  test("CSEC03 - no clipboard data in page source", async () => {366-    const { popup } = await setupVault(ctx);367-    await createEntry(popup, "SecurityTest");368-    await popup.locator(".peach-entry-name-button").first().click();369-    await popup.waitForTimeout(300);370-    const copyBtn = popup.locator("[aria-label*='copy' i]").first();371-    await copyBtn.click();372-    await popup.waitForTimeout(300);373-    const html = await popup.content();374-    expect(html).not.toContain("testpass123");375-    await ctx.close();376-  });377-});

view file at the parent

tests/codex-pdf-restore.spec.ts+1 −2
@@ -22,7 +22,7 @@2222  * triggering handlePDFSelected() inside the extension.2323  */2424 25-import { test, expect } from "@playwright/test";25+import { chromium, test, expect } from "@playwright/test";2626 import type { BrowserContext, Page } from "@playwright/test";2727 import { fileURLToPath } from "url";2828 import { dirname, join } from "path";@@ -38,7 +38,6 @@3838 const CURRENT_GEN_PDF_PATH = join(__dirname, "fixtures", "peach-codex-current-gen.pdf");3939 4040 async function makeContext(): Promise<BrowserContext> {41-  const { chromium } = await import("playwright");4241   const userDataDir = join(os.tmpdir(), `peach-codex-test-${Date.now()}`);4342   await fs.mkdir(userDataDir, { recursive: true });4443   return chromium.launchPersistentContext(userDataDir, {

view file

tests/copy-totp.spec.ts+0 −156
@@ -1,156 +0,0 @@1-import { test, expect } from "@playwright/test";2-import type { BrowserContext } from "@playwright/test";3-import { fileURLToPath } from "url";4-import { dirname, join } from "path";5-import os from "os";6-import fs from "fs/promises";7-8-const __filename = fileURLToPath(import.meta.url);9-const __dirname = dirname(__filename);10-const EXTENSION_PATH = join(__dirname, "..", "dist");11-12-async function makeContext() {13-  const { chromium } = await import("playwright");14-  const userDataDir = join(os.tmpdir(), `peach-test-${Date.now()}`);15-  await fs.mkdir(userDataDir, { recursive: true });16-  return chromium.launchPersistentContext(userDataDir, {17-    headless: false,18-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],19-  });20-}21-22-async function setupVault(ctx: BrowserContext): Promise<{ popup: import("@playwright/test").Page, extId: string }> {23-  const maxWait = 15000;24-  const start = Date.now();25-  let sw = ctx.serviceWorkers()[0];26-  while (!sw && Date.now() - start < maxWait) {27-    await new Promise(r => setTimeout(r, 500));28-    sw = ctx.serviceWorkers()[0];29-  }30-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);31-  const extId = new URL(sw.url()).host;32-  const popup = await ctx.newPage();33-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);34-  await popup.waitForTimeout(2000);35-  const pw = popup.locator("#master-password");36-  if (await pw.isVisible()) {37-    await pw.fill("password123");38-    await popup.locator("#confirm-password").fill("password123");39-    await popup.getByRole("button", { name: /create vault/i }).click();40-    await popup.waitForTimeout(2000);41-  }42-  return { popup, extId };43-}44-45-test.describe("Copy & TOTP", () => {46-  let ctx: BrowserContext;47-  test.afterEach(async () => { await ctx.close(); });48-  test.beforeEach(async () => { ctx = await makeContext(); });49-50-  test("14 - copy username shows copied state", async () => {51-    const { popup } = await setupVault(ctx);52-    await popup.locator('input[placeholder="search vault..."]').click();53-    await popup.getByRole("button", { name: /new entry/i }).click();54-    await popup.locator(".peach-new-name-input").fill("CopyTest");55-    await popup.locator('input[placeholder="domain.com"]').fill("https://copytest.com");56-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");57-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("copypass");58-    await popup.getByRole("button", { name: /create/i }).click();59-    await popup.waitForTimeout(1500);60-    await popup.locator("text=CopyTest").first().click();61-    await popup.waitForTimeout(500);62-    const copyBtn = popup.locator(".peach-copy-btn").first();63-    await copyBtn.click();64-    await popup.waitForTimeout(300);65-    await expect(copyBtn).toHaveClass(/is-copied/);66-  });67-68-  test("15 - password reveal toggles visibility", async () => {69-    const { popup } = await setupVault(ctx);70-    await popup.locator('input[placeholder="search vault..."]').click();71-    await popup.getByRole("button", { name: /new entry/i }).click();72-    await popup.locator(".peach-new-name-input").fill("RevealTest");73-    await popup.locator('input[placeholder="domain.com"]').fill("https://revealtest.com");74-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");75-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("secretpassword");76-    await popup.getByRole("button", { name: /create/i }).click();77-    await popup.waitForTimeout(1500);78-    await popup.locator("text=RevealTest").first().click();79-    await popup.waitForTimeout(500);80-    await expect(popup.locator(".peach-password-dots")).toBeVisible();81-    const eyeBtn = popup.locator("button[aria-label='Reveal password']");82-    await eyeBtn.click();83-    await popup.waitForTimeout(300);84-  });85-86-  test("16 - TOTP countdown ring animates", async () => {87-    const { popup } = await setupVault(ctx);88-    await popup.locator('input[placeholder="search vault..."]').click();89-    await popup.getByRole("button", { name: /new entry/i }).click();90-    await popup.locator(".peach-new-name-input").fill("TOTPTest");91-    await popup.locator('input[placeholder="domain.com"]').fill("https://totptest.com");92-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");93-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("totppass");94-    await popup.locator('input[placeholder*="totp" i]').fill("JBSWY3DPEHPK3PXP");95-    await popup.getByRole("button", { name: /create/i }).click();96-    await popup.waitForTimeout(1500);97-    await popup.locator("text=TOTPTest").first().click();98-    await popup.waitForTimeout(500);99-    const ring = popup.locator(".peach-totp-ring-progress, svg circle").first();100-    await expect(ring).toBeVisible();101-  });102-103-  test("17 - generator produces different passwords each time", async () => {104-    const { popup } = await setupVault(ctx);105-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();106-    await popup.waitForTimeout(300);107-    await popup.getByRole("button", { name: /password generator/i }).click();108-    await popup.waitForTimeout(500);109-    const output1 = await popup.locator(".peach-pwgen-output").textContent();110-    await popup.locator("button[aria-label='Regenerate']").click();111-    await popup.waitForTimeout(300);112-    const output2 = await popup.locator(".peach-pwgen-output").textContent();113-    expect(output1).not.toEqual(output2);114-  });115-116-  test("18 - generator respects length slider", async () => {117-    const { popup } = await setupVault(ctx);118-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();119-    await popup.waitForTimeout(300);120-    await popup.getByRole("button", { name: /password generator/i }).click();121-    await popup.waitForTimeout(500);122-    const slider = popup.locator(".peach-pwgen-slider");123-    await slider.fill("32");124-    await popup.waitForTimeout(300);125-    const output = await popup.locator(".peach-pwgen-output").textContent();126-    expect(output?.length).toBeGreaterThanOrEqual(30);127-  });128-129-  test("19 - settings accent color swatches update --ac", async () => {130-    const { popup } = await setupVault(ctx);131-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();132-    await popup.waitForTimeout(500);133-    const swatch = popup.locator(".peach-swatch").nth(2);134-    await swatch.click();135-    await popup.waitForTimeout(300);136-    await expect(swatch).toHaveClass(/is-active/);137-  });138-139-  test("20 - clipboard clear after 30 seconds", async () => {140-    const { popup } = await setupVault(ctx);141-    await popup.locator('input[placeholder="search vault..."]').click();142-    await popup.getByRole("button", { name: /new entry/i }).click();143-    await popup.locator(".peach-new-name-input").fill("ClipboardTest");144-    await popup.locator('input[placeholder="domain.com"]').fill("https://clipboardtest.com");145-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");146-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("cleartest");147-    await popup.getByRole("button", { name: /create/i }).click();148-    await popup.waitForTimeout(1500);149-    await popup.locator("text=ClipboardTest").first().click();150-    await popup.waitForTimeout(500);151-    await popup.locator(".peach-copy-btn").nth(1).click();152-    await popup.waitForTimeout(200);153-    const copyBtns = popup.locator(".peach-copy-btn.is-copied");154-    expect(await copyBtns.count()).toBeGreaterThan(0);155-  });156-});\ No newline at end of file

view file at the parent

tests/entry-comprehensive.spec.ts+0 −682
@@ -1,682 +0,0 @@1-import { test, expect, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-11-async function makeContext() {12-  const { chromium } = await import("playwright");13-  const userDataDir = join(os.tmpdir(), `peach-entry-${Date.now()}`);14-  await fs.mkdir(userDataDir, { recursive: true });15-  return chromium.launchPersistentContext(userDataDir, {16-    headless: false,17-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18-  });19-}20-21-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22-  const start = Date.now();23-  let sw = ctx.serviceWorkers()[0];24-  while (!sw && Date.now() - start < maxWait) {25-    await new Promise(r => setTimeout(r, 500));26-    sw = ctx.serviceWorkers()[0];27-  }28-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);29-  return new URL(sw.url()).host;30-}31-32-async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33-  const extId = await waitForSW(ctx);34-  const popup = await ctx.newPage();35-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36-  await popup.waitForTimeout(3000);37-  const pw = popup.locator("#master-password");38-  if (await pw.isVisible()) {39-    await pw.fill("password123");40-    await popup.locator("#confirm-password").fill("password123");41-    await popup.getByRole("button", { name: /create vault/i }).click();42-    await popup.waitForTimeout(2000);43-  }44-  return { popup, extId };45-}46-47-test.describe("Entry Field Validation", () => {48-  let ctx: BrowserContext;49-  test.afterEach(async () => { await ctx.close(); });50-  test.beforeEach(async () => { ctx = await makeContext(); });51-52-  test("EF01 - entry requires name", async () => {53-    const { popup } = await setupVault(ctx);54-    await popup.locator('input[placeholder="search vault..."]').click();55-    await popup.getByRole("button", { name: /new entry/i }).click();56-    await popup.locator('input[placeholder="domain.com"]').fill("https://noname.com");57-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");58-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");59-    await popup.getByRole("button", { name: /create/i }).click();60-    await popup.waitForTimeout(500);61-    const nameInput = popup.locator(".peach-new-name-input");62-    const value = await nameInput.inputValue();63-    expect(value).toBe("");64-    await ctx.close();65-  });66-67-  test("EF02 - entry allows empty domain", async () => {68-    const { popup } = await setupVault(ctx);69-    await popup.locator('input[placeholder="search vault..."]').click();70-    await popup.getByRole("button", { name: /new entry/i }).click();71-    await popup.locator(".peach-new-name-input").fill("NoDomain");72-    await popup.locator('input[placeholder="domain.com"]').fill("");73-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");74-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");75-    await popup.getByRole("button", { name: /create/i }).click();76-    await popup.waitForTimeout(1500);77-    await expect(popup.locator("text=NoDomain")).toBeVisible({ timeout: 3000 });78-    await ctx.close();79-  });80-81-  test("EF03 - entry allows empty username", async () => {82-    const { popup } = await setupVault(ctx);83-    await popup.locator('input[placeholder="search vault..."]').click();84-    await popup.getByRole("button", { name: /new entry/i }).click();85-    await popup.locator(".peach-new-name-input").fill("NoUsername");86-    await popup.locator('input[placeholder="domain.com"]').fill("https://nouser.com");87-    await popup.locator('input[placeholder="username or email"]').fill("");88-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");89-    await popup.getByRole("button", { name: /create/i }).click();90-    await popup.waitForTimeout(1500);91-    await expect(popup.locator("text=NoUsername")).toBeVisible({ timeout: 3000 });92-    await ctx.close();93-  });94-95-  test("EF04 - entry requires password", async () => {96-    const { popup } = await setupVault(ctx);97-    await popup.locator('input[placeholder="search vault..."]').click();98-    await popup.getByRole("button", { name: /new entry/i }).click();99-    await popup.locator(".peach-new-name-input").fill("NoPass");100-    await popup.locator('input[placeholder="domain.com"]').fill("https://nopass.com");101-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");102-    await popup.getByRole("button", { name: /create/i }).click();103-    await popup.waitForTimeout(500);104-    const passField = popup.locator(".peach-new-field-input[placeholder='password']");105-    const value = await passField.inputValue();106-    expect(value).toBe("");107-    await ctx.close();108-  });109-110-  test("EF05 - entry truncates very long names", async () => {111-    const { popup } = await setupVault(ctx);112-    await popup.locator('input[placeholder="search vault..."]').click();113-    await popup.getByRole("button", { name: /new entry/i }).click();114-    const longName = "A".repeat(500);115-    await popup.locator(".peach-new-name-input").fill(longName);116-    await popup.locator('input[placeholder="domain.com"]').fill("https://long.com");117-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");118-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");119-    await popup.getByRole("button", { name: /create/i }).click();120-    await popup.waitForTimeout(1500);121-    const nameInList = popup.locator(".peach-entry-name").first();122-    const text = await nameInList.textContent();123-    expect(text?.length).toBeLessThanOrEqual(200);124-    await ctx.close();125-  });126-});127-128-test.describe("Notes Field", () => {129-  let ctx: BrowserContext;130-  test.afterEach(async () => { await ctx.close(); });131-  test.beforeEach(async () => { ctx = await makeContext(); });132-133-  test("EF06 - notes field accepts multiline text", async () => {134-    const { popup } = await setupVault(ctx);135-    await popup.locator('input[placeholder="search vault..."]').click();136-    await popup.getByRole("button", { name: /new entry/i }).click();137-    await popup.locator(".peach-new-name-input").fill("MultiLine");138-    await popup.locator('input[placeholder="domain.com"]').fill("https://multi.com");139-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");140-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");141-    const notes = popup.locator("textarea");142-    await notes.fill("Line 1\nLine 2\nLine 3");143-    await popup.getByRole("button", { name: /create/i }).click();144-    await popup.waitForTimeout(1500);145-    await popup.locator(".peach-entry-name-button").first().click();146-    await popup.waitForTimeout(500);147-    const noteContent = popup.locator(".peach-note-content");148-    await expect(noteContent).toBeVisible({ timeout: 3000 });149-    const text = await noteContent.textContent();150-    expect(text).toContain("Line 1");151-    expect(text).toContain("Line 2");152-    await ctx.close();153-  });154-155-  test("EF07 - notes field accepts special characters", async () => {156-    const { popup } = await setupVault(ctx);157-    await popup.locator('input[placeholder="search vault..."]').click();158-    await popup.getByRole("button", { name: /new entry/i }).click();159-    await popup.locator(".peach-new-name-input").fill("SpecialNotes");160-    await popup.locator('input[placeholder="domain.com"]').fill("https://special.com");161-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");162-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");163-    const notes = popup.locator("textarea");164-    await notes.fill('<script>alert("xss")</script>&"\'<>@#$%^&*()_+-=[]{}|;:,./<>?');165-    await popup.getByRole("button", { name: /create/i }).click();166-    await popup.waitForTimeout(1500);167-    await popup.locator(".peach-entry-name-button").first().click();168-    await popup.waitForTimeout(500);169-    const noteContent = popup.locator(".peach-note-content");170-    const html = await noteContent.innerHTML();171-    expect(html).not.toContain("<script>");172-    await ctx.close();173-  });174-175-  test("EF08 - notes field supports unicode", async () => {176-    const { popup } = await setupVault(ctx);177-    await popup.locator('input[placeholder="search vault..."]').click();178-    await popup.getByRole("button", { name: /new entry/i }).click();179-    await popup.locator(".peach-new-name-input").fill("UnicodeNotes");180-    await popup.locator('input[placeholder="domain.com"]').fill("https://unicode.com");181-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");182-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");183-    const notes = popup.locator("textarea");184-    await notes.fill("日本語한국어中文العربيةहिन्दी🎉🎊💻⌨️");185-    await popup.getByRole("button", { name: /create/i }).click();186-    await popup.waitForTimeout(1500);187-    await expect(popup.locator("text=UnicodeNotes")).toBeVisible({ timeout: 3000 });188-    await ctx.close();189-  });190-191-  test("EF09 - very long notes are saved", async () => {192-    const { popup } = await setupVault(ctx);193-    await popup.locator('input[placeholder="search vault..."]').click();194-    await popup.getByRole("button", { name: /new entry/i }).click();195-    await popup.locator(".peach-new-name-input").fill("LongNotes");196-    await popup.locator('input[placeholder="domain.com"]').fill("https://longnotes.com");197-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");198-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");199-    const notes = popup.locator("textarea");200-    const longText = "A".repeat(5000);201-    await notes.fill(longText);202-    await popup.getByRole("button", { name: /create/i }).click();203-    await popup.waitForTimeout(1500);204-    await popup.locator(".peach-entry-name-button").first().click();205-    await popup.waitForTimeout(500);206-    const noteContent = popup.locator(".peach-note-content");207-    const text = await noteContent.textContent();208-    expect(text?.length).toBeGreaterThan(4000);209-    await ctx.close();210-  });211-212-  test("EF10 - notes field shows correct label", async () => {213-    const { popup } = await setupVault(ctx);214-    await popup.locator('input[placeholder="search vault..."]').click();215-    await popup.getByRole("button", { name: /new entry/i }).click();216-    await popup.locator(".peach-new-name-input").fill("NotesLabel");217-    await popup.locator('input[placeholder="domain.com"]').fill("https://label.com");218-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");219-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");220-    await popup.locator("textarea").fill("test notes");221-    await popup.getByRole("button", { name: /create/i }).click();222-    await popup.waitForTimeout(1500);223-    await popup.locator(".peach-entry-name-button").first().click();224-    await popup.waitForTimeout(500);225-    const label = popup.locator(".peach-label").filter({ hasText: "notes" }).first();226-    await expect(label).toBeVisible({ timeout: 3000 });227-    await ctx.close();228-  });229-});230-231-test.describe("URL Field", () => {232-  let ctx: BrowserContext;233-  test.afterEach(async () => { await ctx.close(); });234-  test.beforeEach(async () => { ctx = await makeContext(); });235-236-  test("EF11 - URL field accepts https://", async () => {237-    const { popup } = await setupVault(ctx);238-    await popup.locator('input[placeholder="search vault..."]').click();239-    await popup.getByRole("button", { name: /new entry/i }).click();240-    await popup.locator(".peach-new-name-input").fill("HTTPSURL");241-    await popup.locator('input[placeholder="domain.com"]').fill("https://https.com");242-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");243-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");244-    await popup.getByRole("button", { name: /create/i }).click();245-    await popup.waitForTimeout(1500);246-    await expect(popup.locator("text=HTTPSURL")).toBeVisible({ timeout: 3000 });247-    await ctx.close();248-  });249-250-  test("EF12 - URL field accepts http://", async () => {251-    const { popup } = await setupVault(ctx);252-    await popup.locator('input[placeholder="search vault..."]').click();253-    await popup.getByRole("button", { name: /new entry/i }).click();254-    await popup.locator(".peach-new-name-input").fill("HTTPURL");255-    await popup.locator('input[placeholder="domain.com"]').fill("http://http.com");256-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");257-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");258-    await popup.getByRole("button", { name: /create/i }).click();259-    await popup.waitForTimeout(1500);260-    await expect(popup.locator("text=HTTPURL")).toBeVisible({ timeout: 3000 });261-    await ctx.close();262-  });263-264-  test("EF13 - URL field accepts ftp://", async () => {265-    const { popup } = await setupVault(ctx);266-    await popup.locator('input[placeholder="search vault..."]').click();267-    await popup.getByRole("button", { name: /new entry/i }).click();268-    await popup.locator(".peach-new-name-input").fill("FTPURL");269-    await popup.locator('input[placeholder="domain.com"]').fill("ftp://ftp.com");270-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");271-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");272-    await popup.getByRole("button", { name: /create/i }).click();273-    await popup.waitForTimeout(1500);274-    await expect(popup.locator("text=FTPURL")).toBeVisible({ timeout: 3000 });275-    await ctx.close();276-  });277-278-  test("EF14 - URL with path is saved correctly", async () => {279-    const { popup } = await setupVault(ctx);280-    await popup.locator('input[placeholder="search vault..."]').click();281-    await popup.getByRole("button", { name: /new entry/i }).click();282-    await popup.locator(".peach-new-name-input").fill("PathURL");283-    await popup.locator('input[placeholder="domain.com"]').fill("https://example.com/path/to/page?query=value#anchor");284-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");285-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");286-    await popup.getByRole("button", { name: /create/i }).click();287-    await popup.waitForTimeout(1500);288-    await expect(popup.locator("text=PathURL")).toBeVisible({ timeout: 3000 });289-    await ctx.close();290-  });291-292-  test("EF15 - URL with port is saved correctly", async () => {293-    const { popup } = await setupVault(ctx);294-    await popup.locator('input[placeholder="search vault..."]').click();295-    await popup.getByRole("button", { name: /new entry/i }).click();296-    await popup.locator(".peach-new-name-input").fill("PortURL");297-    await popup.locator('input[placeholder="domain.com"]').fill("https://example.com:8443/login");298-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");299-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");300-    await popup.getByRole("button", { name: /create/i }).click();301-    await popup.waitForTimeout(1500);302-    await expect(popup.locator("text=PortURL")).toBeVisible({ timeout: 3000 });303-    await ctx.close();304-  });305-});306-307-test.describe("Username Field", () => {308-  let ctx: BrowserContext;309-  test.afterEach(async () => { await ctx.close(); });310-  test.beforeEach(async () => { ctx = await makeContext(); });311-312-  test("EF16 - email format username", async () => {313-    const { popup } = await setupVault(ctx);314-    await popup.locator('input[placeholder="search vault..."]').click();315-    await popup.getByRole("button", { name: /new entry/i }).click();316-    await popup.locator(".peach-new-name-input").fill("EmailUser");317-    await popup.locator('input[placeholder="domain.com"]').fill("https://email.com");318-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");319-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");320-    await popup.getByRole("button", { name: /create/i }).click();321-    await popup.waitForTimeout(1500);322-    await expect(popup.locator("text=EmailUser")).toBeVisible({ timeout: 3000 });323-    await ctx.close();324-  });325-326-  test("EF17 - username with special characters", async () => {327-    const { popup } = await setupVault(ctx);328-    await popup.locator('input[placeholder="search vault..."]').click();329-    await popup.getByRole("button", { name: /new entry/i }).click();330-    await popup.locator(".peach-new-name-input").fill("SpecialUser");331-    await popup.locator('input[placeholder="domain.com"]').fill("https://special.com");332-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");333-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");334-    await popup.getByRole("button", { name: /create/i }).click();335-    await popup.waitForTimeout(1500);336-    await expect(popup.locator("text=SpecialUser")).toBeVisible({ timeout: 3000 });337-    await ctx.close();338-  });339-340-  test("EF18 - very long username", async () => {341-    const { popup } = await setupVault(ctx);342-    await popup.locator('input[placeholder="search vault..."]').click();343-    await popup.getByRole("button", { name: /new entry/i }).click();344-    await popup.locator(".peach-new-name-input").fill("LongUser");345-    await popup.locator('input[placeholder="domain.com"]').fill("https://long.com");346-    await popup.locator('input[placeholder="username or email"]').fill("a".repeat(200) + "@test.com");347-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");348-    await popup.getByRole("button", { name: /create/i }).click();349-    await popup.waitForTimeout(1500);350-    await expect(popup.locator("text=LongUser")).toBeVisible({ timeout: 3000 });351-    await ctx.close();352-  });353-354-  test("EF19 - username with unicode", async () => {355-    const { popup } = await setupVault(ctx);356-    await popup.locator('input[placeholder="search vault..."]').click();357-    await popup.getByRole("button", { name: /new entry/i }).click();358-    await popup.locator(".peach-new-name-input").fill("UnicodeUser");359-    await popup.locator('input[placeholder="domain.com"]').fill("https://unicode.com");360-    await popup.locator('input[placeholder="username or email"]').fill("用户@例子.测试");361-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");362-    await popup.getByRole("button", { name: /create/i }).click();363-    await popup.waitForTimeout(1500);364-    await expect(popup.locator("text=UnicodeUser")).toBeVisible({ timeout: 3000 });365-    await ctx.close();366-  });367-});368-369-test.describe("Password Field", () => {370-  let ctx: BrowserContext;371-  test.afterEach(async () => { await ctx.close(); });372-  test.beforeEach(async () => { ctx = await makeContext(); });373-374-  test("EF20 - emoji password", async () => {375-    const { popup } = await setupVault(ctx);376-    await popup.locator('input[placeholder="search vault..."]').click();377-    await popup.getByRole("button", { name: /new entry/i }).click();378-    await popup.locator(".peach-new-name-input").fill("EmojiPass");379-    await popup.locator('input[placeholder="domain.com"]').fill("https://emoji.com");380-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");381-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("🔐🔑🌟💻🖥️⌨️");382-    await popup.getByRole("button", { name: /create/i }).click();383-    await popup.waitForTimeout(1500);384-    await expect(popup.locator("text=EmojiPass")).toBeVisible({ timeout: 3000 });385-    await ctx.close();386-  });387-388-  test("EF21 - whitespace-only password", async () => {389-    const { popup } = await setupVault(ctx);390-    await popup.locator('input[placeholder="search vault..."]').click();391-    await popup.getByRole("button", { name: /new entry/i }).click();392-    await popup.locator(".peach-new-name-input").fill("SpacePass");393-    await popup.locator('input[placeholder="domain.com"]').fill("https://space.com");394-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");395-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("     ");396-    await popup.getByRole("button", { name: /create/i }).click();397-    await popup.waitForTimeout(1500);398-    await expect(popup.locator("text=SpacePass")).toBeVisible({ timeout: 3000 });399-    await ctx.close();400-  });401-402-  test("EF22 - very long password", async () => {403-    const { popup } = await setupVault(ctx);404-    await popup.locator('input[placeholder="search vault..."]').click();405-    await popup.getByRole("button", { name: /new entry/i }).click();406-    await popup.locator(".peach-new-name-input").fill("LongPass");407-    await popup.locator('input[placeholder="domain.com"]').fill("https://long.com");408-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");409-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("a".repeat(1000));410-    await popup.getByRole("button", { name: /create/i }).click();411-    await popup.waitForTimeout(1500);412-    await expect(popup.locator("text=LongPass")).toBeVisible({ timeout: 3000 });413-    await ctx.close();414-  });415-416-  test("EF23 - password reveal button works", async () => {417-    const { popup } = await setupVault(ctx);418-    await popup.locator('input[placeholder="search vault..."]').click();419-    await popup.getByRole("button", { name: /new entry/i }).click();420-    await popup.locator(".peach-new-name-input").fill("RevealPass");421-    await popup.locator('input[placeholder="domain.com"]').fill("https://reveal.com");422-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");423-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("secretpass");424-    await popup.getByRole("button", { name: /create/i }).click();425-    await popup.waitForTimeout(1500);426-    await popup.locator(".peach-entry-name-button").first().click();427-    await popup.waitForTimeout(500);428-    const eyeBtn = popup.locator("button[aria-label='Reveal password']");429-    if (await eyeBtn.isVisible({ timeout: 2000 }).catch(() => false)) {430-      await eyeBtn.click();431-      await popup.waitForTimeout(300);432-    }433-    await ctx.close();434-  });435-});436-437-test.describe("Entry Editing", () => {438-  let ctx: BrowserContext;439-  test.afterEach(async () => { await ctx.close(); });440-  test.beforeEach(async () => { ctx = await makeContext(); });441-442-  test("EF24 - edit entry changes name", async () => {443-    const { popup } = await setupVault(ctx);444-    await popup.locator('input[placeholder="search vault..."]').click();445-    await popup.getByRole("button", { name: /new entry/i }).click();446-    await popup.locator(".peach-new-name-input").fill("OriginalName");447-    await popup.locator('input[placeholder="domain.com"]').fill("https://original.com");448-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");449-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");450-    await popup.getByRole("button", { name: /create/i }).click();451-    await popup.waitForTimeout(1500);452-    await popup.locator(".peach-entry-name-button").first().click();453-    await popup.waitForTimeout(500);454-    await popup.locator("button[aria-label='Edit']").first().click();455-    await popup.waitForTimeout(500);456-    await popup.locator(".peach-new-name-input").fill("ChangedName");457-    await popup.getByRole("button", { name: /save/i }).click();458-    await popup.waitForTimeout(1500);459-    await expect(popup.locator("text=ChangedName")).toBeVisible({ timeout: 3000 });460-    await ctx.close();461-  });462-463-  test("EF25 - edit entry preserves other fields", async () => {464-    const { popup } = await setupVault(ctx);465-    await popup.locator('input[placeholder="search vault..."]').click();466-    await popup.getByRole("button", { name: /new entry/i }).click();467-    await popup.locator(".peach-new-name-input").fill("PreserveFields");468-    await popup.locator('input[placeholder="domain.com"]').fill("https://preserve.com");469-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");470-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("originalpass");471-    await popup.getByRole("button", { name: /create/i }).click();472-    await popup.waitForTimeout(1500);473-    await popup.locator(".peach-entry-name-button").first().click();474-    await popup.waitForTimeout(500);475-    await popup.locator("button[aria-label='Edit']").first().click();476-    await popup.waitForTimeout(500);477-    await popup.locator(".peach-new-name-input").fill("PreserveFieldsChanged");478-    await popup.getByRole("button", { name: /save/i }).click();479-    await popup.waitForTimeout(1500);480-    await popup.locator(".peach-entry-name-button").first().click();481-    await popup.waitForTimeout(500);482-    const username = popup.locator(".peach-field-value").filter({ hasText: "[email protected]" }).first();483-    await expect(username).toBeVisible({ timeout: 3000 });484-    await ctx.close();485-  });486-487-  test("EF26 - cancel edit reverts changes", async () => {488-    const { popup } = await setupVault(ctx);489-    await popup.locator('input[placeholder="search vault..."]').click();490-    await popup.getByRole("button", { name: /new entry/i }).click();491-    await popup.locator(".peach-new-name-input").fill("CancelEdit");492-    await popup.locator('input[placeholder="domain.com"]').fill("https://cancel.com");493-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");494-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");495-    await popup.getByRole("button", { name: /create/i }).click();496-    await popup.waitForTimeout(1500);497-    await popup.locator(".peach-entry-name-button").first().click();498-    await popup.waitForTimeout(500);499-    await popup.locator("button[aria-label='Edit']").first().click();500-    await popup.waitForTimeout(500);501-    await popup.locator(".peach-new-name-input").fill("ShouldNotChange");502-    await popup.keyboard.press("Escape");503-    await popup.waitForTimeout(300);504-    await expect(popup.locator("text=CancelEdit")).toBeVisible({ timeout: 3000 });505-    await ctx.close();506-  });507-508-  test("EF27 - delete entry from detail view", async () => {509-    const { popup } = await setupVault(ctx);510-    await popup.locator('input[placeholder="search vault..."]').click();511-    await popup.getByRole("button", { name: /new entry/i }).click();512-    await popup.locator(".peach-new-name-input").fill("DeleteMeEntry");513-    await popup.locator('input[placeholder="domain.com"]').fill("https://delete.com");514-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");515-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");516-    await popup.getByRole("button", { name: /create/i }).click();517-    await popup.waitForTimeout(1500);518-    await popup.locator(".peach-entry-name-button").first().click();519-    await popup.waitForTimeout(500);520-    const deleteLink = popup.locator("text=delete entry");521-    await deleteLink.click();522-    await popup.waitForTimeout(1000);523-    await expect(popup.locator("text=DeleteMeEntry")).not.toBeVisible({ timeout: 3000 });524-    await ctx.close();525-  });526-});527-528-test.describe("Entry State Transitions", () => {529-  let ctx: BrowserContext;530-  test.afterEach(async () => { await ctx.close(); });531-  test.beforeEach(async () => { ctx = await makeContext(); });532-533-  test("EF28 - list to detail transition", async () => {534-    const { popup } = await setupVault(ctx);535-    await popup.locator('input[placeholder="search vault..."]').click();536-    await popup.getByRole("button", { name: /new entry/i }).click();537-    await popup.locator(".peach-new-name-input").fill("ListDetail");538-    await popup.locator('input[placeholder="domain.com"]').fill("https://listdetail.com");539-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");540-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");541-    await popup.getByRole("button", { name: /create/i }).click();542-    await popup.waitForTimeout(1500);543-    await popup.locator(".peach-entry-name-button").first().click();544-    await popup.waitForTimeout(500);545-    const usernameField = popup.locator(".peach-field-value").filter({ hasText: "[email protected]" }).first();546-    await expect(usernameField).toBeVisible({ timeout: 3000 });547-    await ctx.close();548-  });549-550-  test("EF29 - detail to list back transition", async () => {551-    const { popup } = await setupVault(ctx);552-    await popup.locator('input[placeholder="search vault..."]').click();553-    await popup.getByRole("button", { name: /new entry/i }).click();554-    await popup.locator(".peach-new-name-input").fill("DetailBack");555-    await popup.locator('input[placeholder="domain.com"]').fill("https://back.com");556-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");557-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");558-    await popup.getByRole("button", { name: /create/i }).click();559-    await popup.waitForTimeout(1500);560-    await popup.locator(".peach-entry-name-button").first().click();561-    await popup.waitForTimeout(500);562-    await popup.locator("button[aria-label='Back']").first().click();563-    await popup.waitForTimeout(300);564-    await expect(popup.locator("text=DetailBack")).toBeVisible({ timeout: 3000 });565-    await ctx.close();566-  });567-568-  test("EF30 - entry to edit mode transition", async () => {569-    const { popup } = await setupVault(ctx);570-    await popup.locator('input[placeholder="search vault..."]').click();571-    await popup.getByRole("button", { name: /new entry/i }).click();572-    await popup.locator(".peach-new-name-input").fill("EditMode");573-    await popup.locator('input[placeholder="domain.com"]').fill("https://edit.com");574-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");575-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");576-    await popup.getByRole("button", { name: /create/i }).click();577-    await popup.waitForTimeout(1500);578-    await popup.locator(".peach-entry-name-button").first().click();579-    await popup.waitForTimeout(500);580-    await popup.locator("button[aria-label='Edit']").first().click();581-    await popup.waitForTimeout(500);582-    const nameInput = popup.locator(".peach-new-name-input");583-    await expect(nameInput).toBeVisible({ timeout: 3000 });584-    await ctx.close();585-  });586-587-  test("EF31 - edit to detail transition after save", async () => {588-    const { popup } = await setupVault(ctx);589-    await popup.locator('input[placeholder="search vault..."]').click();590-    await popup.getByRole("button", { name: /new entry/i }).click();591-    await popup.locator(".peach-new-name-input").fill("EditSave");592-    await popup.locator('input[placeholder="domain.com"]').fill("https://editsave.com");593-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");594-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");595-    await popup.getByRole("button", { name: /create/i }).click();596-    await popup.waitForTimeout(1500);597-    await popup.locator(".peach-entry-name-button").first().click();598-    await popup.waitForTimeout(500);599-    await popup.locator("button[aria-label='Edit']").first().click();600-    await popup.waitForTimeout(500);601-    await popup.locator(".peach-new-name-input").fill("EditSaveChanged");602-    await popup.getByRole("button", { name: /save/i }).click();603-    await popup.waitForTimeout(1500);604-    const usernameField = popup.locator(".peach-field-value").filter({ hasText: "[email protected]" }).first();605-    await expect(usernameField).toBeVisible({ timeout: 3000 });606-    await ctx.close();607-  });608-});609-610-test.describe("Entry List Behavior", () => {611-  let ctx: BrowserContext;612-  test.afterEach(async () => { await ctx.close(); });613-  test.beforeEach(async () => { ctx = await makeContext(); });614-615-  test("EF32 - entries sorted alphabetically", async () => {616-    const { popup } = await setupVault(ctx);617-    for (const name of ["Zebra", "Apple", "Mango"]) {618-      await popup.locator('input[placeholder="search vault..."]').click();619-      await popup.getByRole("button", { name: /new entry/i }).click();620-      await popup.locator(".peach-new-name-input").fill(name);621-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);622-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);623-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");624-      await popup.getByRole("button", { name: /create/i }).click();625-      await popup.waitForTimeout(800);626-    }627-    const entries = popup.locator(".peach-entry-row-name");628-    const count = await entries.count();629-    expect(count).toBeGreaterThanOrEqual(3);630-    await ctx.close();631-  });632-633-  test("EF33 - breach indicator shows for compromised entry", async () => {634-    const { popup } = await setupVault(ctx);635-    await popup.waitForTimeout(500);636-    const breachRow = popup.locator(".peach-entry-row.is-breach, .peach-entry-row-dot");637-    await breachRow.isVisible({ timeout: 2000 }).catch(() => false);638-    await ctx.close();639-  });640-641-  test("EF34 - entry count updates after add", async () => {642-    const { popup } = await setupVault(ctx);643-    const counter = popup.locator(".peach-search-counter");644-    const initial = await counter.textContent();645-    await popup.locator('input[placeholder="search vault..."]').click();646-    await popup.getByRole("button", { name: /new entry/i }).click();647-    await popup.locator(".peach-new-name-input").fill("CountTest");648-    await popup.locator('input[placeholder="domain.com"]').fill("https://count.com");649-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");650-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");651-    await popup.getByRole("button", { name: /create/i }).click();652-    await popup.waitForTimeout(1500);653-    const final = await counter.textContent();654-    expect(initial).not.toEqual(final);655-    await ctx.close();656-  });657-658-  test("EF35 - next/prev navigation works", async () => {659-    const { popup } = await setupVault(ctx);660-    for (const name of ["FirstEntry", "SecondEntry", "ThirdEntry"]) {661-      await popup.locator('input[placeholder="search vault..."]').click();662-      await popup.getByRole("button", { name: /new entry/i }).click();663-      await popup.locator(".peach-new-name-input").fill(name);664-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);665-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);666-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");667-      await popup.getByRole("button", { name: /create/i }).click();668-      await popup.waitForTimeout(800);669-    }670-    const nextBtn = popup.getByRole("button", { name: /next/i });671-    if (await nextBtn.isVisible({ timeout: 2000 }).catch(() => false)) {672-      await nextBtn.click();673-      await popup.waitForTimeout(300);674-      const prevBtn = popup.getByRole("button", { name: /prev/i });675-      if (await prevBtn.isVisible({ timeout: 1000 }).catch(() => false)) {676-        await prevBtn.click();677-        await popup.waitForTimeout(300);678-      }679-    }680-    await ctx.close();681-  });682-});\ No newline at end of file

view file at the parent

tests/entry-flows-state.spec.ts+0 −396
@@ -1,396 +0,0 @@1-import { test, expect, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-const VAULT_PASSWORD = "CorrectHorseBattery42!";11-12-async function makeContext() {13-  const { chromium } = await import("playwright");14-  const userDataDir = join(os.tmpdir(), `peach-entries-${Date.now()}`);15-  await fs.mkdir(userDataDir, { recursive: true });16-  return chromium.launchPersistentContext(userDataDir, {17-    headless: false,18-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],19-  });20-}21-22-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {23-  const start = Date.now();24-  let sw = ctx.serviceWorkers()[0];25-  while (!sw && Date.now() - start < maxWait) {26-    await new Promise(r => setTimeout(r, 500));27-    sw = ctx.serviceWorkers()[0];28-  }29-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);30-  return new URL(sw.url()).host;31-}32-33-async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {34-  const extId = await waitForSW(ctx);35-  const popup = await ctx.newPage();36-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);37-  await popup.waitForTimeout(3000);38-  const pw = popup.locator("#master-password");39-  if (await pw.isVisible()) {40-    await pw.fill(VAULT_PASSWORD);41-    await popup.locator("#confirm-password").fill(VAULT_PASSWORD);42-    await popup.waitForFunction(() => {43-      const submit = document.querySelector(".peach-lock-submit");44-      return submit instanceof HTMLButtonElement && !submit.disabled;45-    });46-    await popup.getByRole("button", { name: /create vault/i }).click();47-    await popup.waitForTimeout(2000);48-  }49-  return { popup, extId };50-}51-52-async function seedEntries(popup: any, names: string[]) {53-  for (const name of names) {54-    await popup.evaluate(async (entryName: string) => {55-      await new Promise((resolve) => {56-        chrome.runtime.sendMessage(57-          {58-            type: "ADD_ENTRY",59-            payload: {60-              type: "login",61-              title: entryName,62-              url: `https://${entryName.toLowerCase()}.com`,63-              username: `${entryName.toLowerCase()}@test.com`,64-              password: "Passphrase123!",65-            },66-          },67-          resolve68-        );69-      });70-    }, name);71-  }72-}73-74-test.describe("Entry Interaction Flows", () => {75-76-  test("FLOW01 - full add entry workflow", async () => {77-    const ctx = await makeContext();78-    const { popup } = await setupVault(ctx);79-    await popup.locator('input[placeholder="search vault..."]').click();80-    await popup.getByRole("button", { name: /new entry/i }).click();81-    await popup.locator(".peach-new-name-input").fill("GitHub");82-    await popup.locator('input[placeholder="domain.com"]').fill("https://github.com");83-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");84-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");85-    await pwdField.fill("supersecret123");86-    await popup.getByRole("button", { name: /create/i }).click();87-    await popup.waitForTimeout(1500);88-    await expect(popup.locator("text=GitHub")).toBeVisible({ timeout: 3000 });89-    await ctx.close();90-  });91-92-  test("FLOW02 - edit entry workflow", async () => {93-    const ctx = await makeContext();94-    const { popup } = await setupVault(ctx);95-    await popup.locator('input[placeholder="search vault..."]').click();96-    await popup.getByRole("button", { name: /new entry/i }).click();97-    await popup.locator(".peach-new-name-input").fill("EditFlow");98-    await popup.locator('input[placeholder="domain.com"]').fill("https://editflow.com");99-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");100-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");101-    await pwdField.fill("oldpass");102-    await popup.getByRole("button", { name: /create/i }).click();103-    await popup.waitForTimeout(1500);104-    await popup.locator("text=EditFlow").first().click();105-    await popup.waitForTimeout(500);106-    const editBtn = popup.locator("button[aria-label='Edit']").first();107-    await editBtn.click();108-    await popup.waitForTimeout(500);109-    const newPwdField = popup.locator(".peach-new-field-input[placeholder='password']");110-    await newPwdField.fill("newpass");111-    await popup.getByRole("button", { name: /save/i }).click();112-    await popup.waitForTimeout(1500);113-    await ctx.close();114-  });115-116-  test("FLOW03 - delete entry workflow", async () => {117-    const ctx = await makeContext();118-    const { popup } = await setupVault(ctx);119-    await popup.locator('input[placeholder="search vault..."]').click();120-    await popup.getByRole("button", { name: /new entry/i }).click();121-    await popup.locator(".peach-new-name-input").fill("DeleteFlow");122-    await popup.locator('input[placeholder="domain.com"]').fill("https://deleteflow.com");123-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");124-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");125-    await pwdField.fill("pass123");126-    await popup.getByRole("button", { name: /create/i }).click();127-    await popup.waitForTimeout(1500);128-    await popup.locator("text=DeleteFlow").first().click();129-    await popup.waitForTimeout(500);130-    const deleteLink = popup.locator("text=delete entry");131-    await deleteLink.click();132-    await popup.waitForTimeout(1000);133-    await ctx.close();134-  });135-136-  test("FLOW04 - copy username workflow", async () => {137-    const ctx = await makeContext();138-    const { popup } = await setupVault(ctx);139-    await popup.locator('input[placeholder="search vault..."]').click();140-    await popup.getByRole("button", { name: /new entry/i }).click();141-    await popup.locator(".peach-new-name-input").fill("CopyUsername");142-    await popup.locator('input[placeholder="domain.com"]').fill("https://copyuser.com");143-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");144-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");145-    await pwdField.fill("pass123");146-    await popup.getByRole("button", { name: /create/i }).click();147-    await popup.waitForTimeout(1500);148-    await popup.locator("text=CopyUsername").first().click();149-    await popup.waitForTimeout(500);150-    const copyBtn = popup.locator(".peach-copy-btn").first();151-    await copyBtn.click();152-    await popup.waitForTimeout(500);153-    await ctx.close();154-  });155-156-  test("FLOW05 - reveal password workflow", async () => {157-    const ctx = await makeContext();158-    const { popup } = await setupVault(ctx);159-    await popup.locator('input[placeholder="search vault..."]').click();160-    await popup.getByRole("button", { name: /new entry/i }).click();161-    await popup.locator(".peach-new-name-input").fill("RevealPwd");162-    await popup.locator('input[placeholder="domain.com"]').fill("https://revealpwd.com");163-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");164-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");165-    await pwdField.fill("mysecretpassword");166-    await popup.getByRole("button", { name: /create/i }).click();167-    await popup.waitForTimeout(1500);168-    await popup.locator("text=RevealPwd").first().click();169-    await popup.waitForTimeout(500);170-    const eyeBtn = popup.locator("button[aria-label='Reveal password']");171-    await eyeBtn.click();172-    await popup.waitForTimeout(300);173-    await ctx.close();174-  });175-176-  test("FLOW06 - add entry with TOTP workflow", async () => {177-    const ctx = await makeContext();178-    const { popup } = await setupVault(ctx);179-    await popup.locator('input[placeholder="search vault..."]').click();180-    await popup.getByRole("button", { name: /new entry/i }).click();181-    await popup.locator(".peach-new-name-input").fill("GitHub2FA");182-    await popup.locator('input[placeholder="domain.com"]').fill("https://github.com");183-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");184-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");185-    await pwdField.fill("secret123");186-    await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");187-    await popup.getByRole("button", { name: /create/i }).click();188-    await popup.waitForTimeout(1500);189-    await expect(popup.locator("text=GitHub2FA")).toBeVisible({ timeout: 3000 });190-    await ctx.close();191-  });192-193-  test("FLOW07 - search and find entry", async () => {194-    const ctx = await makeContext();195-    const { popup } = await setupVault(ctx);196-    for (const name of ["Google", "Amazon", "Microsoft"]) {197-      await popup.locator('input[placeholder="search vault..."]').click();198-      await popup.getByRole("button", { name: /new entry/i }).click();199-      await popup.locator(".peach-new-name-input").fill(name);200-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);201-      await popup.locator('input[placeholder="username or email"]').fill(`user@${name.toLowerCase()}.com`);202-      const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");203-      await pwdField.fill("pass123");204-      await popup.getByRole("button", { name: /create/i }).click();205-      await popup.waitForTimeout(500);206-    }207-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();208-    await searchInput.click();209-    await searchInput.fill("Amazon");210-    await popup.waitForTimeout(300);211-    await expect(popup.locator("text=Amazon")).toBeVisible({ timeout: 3000 });212-    await expect(popup.locator("text=Google")).not.toBeVisible();213-    await ctx.close();214-  });215-216-  test("FLOW08 - navigate between entries", async () => {217-    const ctx = await makeContext();218-    const { popup } = await setupVault(ctx);219-    const pageErrors: string[] = [];220-    popup.on("pageerror", (error: Error) => pageErrors.push(error.message));221-222-    await seedEntries(popup, ["First", "Second", "Third"]);223-    await popup.reload();224-    await popup.waitForTimeout(1500);225-226-    const unlockInput = popup.locator("#unlock-password");227-    if (await unlockInput.isVisible().catch(() => false)) {228-      await unlockInput.fill(VAULT_PASSWORD);229-      await popup.getByRole("button", { name: /unlock/i }).click();230-      await popup.waitForTimeout(1500);231-    }232-233-    const nextBtn = popup.getByRole("button", { name: /next entry/i });234-    const shellSearch = popup.locator(".peach-search-wrap .peach-search-input");235-    const overlay = popup.locator(".peach-overlay-view");236-    const overlaySearch = popup.locator(".peach-overlay-view .peach-search-input");237-    const backBtn = popup.locator('.peach-overlay-view button[aria-label="Back"]');238-239-    await expect(nextBtn).toBeVisible();240-    await nextBtn.click();241-    await shellSearch.click();242-    await expect(overlay).toBeVisible();243-    await expect(overlaySearch).toBeVisible();244-    await expect(popup.locator(".peach-chrome")).toBeHidden();245-    await expect(popup.locator(".peach-search-wrap")).toBeHidden();246-    await expect247-      .poll(async () => overlaySearch.evaluate((el: Element) => document.activeElement === el))248-      .toBe(true);249-    await backBtn.click();250-    await expect(overlay).toBeHidden();251-    await expect(popup.locator(".peach-chrome")).toBeVisible();252-    await expect(popup.locator(".peach-search-wrap")).toBeVisible();253-    expect(pageErrors).toEqual([]);254-255-    await popup.waitForTimeout(300);256-    await ctx.close();257-  });258-259-  test("FLOW09 - add entry with notes", async () => {260-    const ctx = await makeContext();261-    const { popup } = await setupVault(ctx);262-    await popup.locator('input[placeholder="search vault..."]').click();263-    await popup.getByRole("button", { name: /new entry/i }).click();264-    await popup.locator(".peach-new-name-input").fill("NotesEntry");265-    await popup.locator('input[placeholder="domain.com"]').fill("https://notes.com");266-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");267-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");268-    await pwdField.fill("pass123");269-    const notesArea = popup.locator("textarea");270-    await notesArea.fill("This is a secret note with important information.");271-    await popup.getByRole("button", { name: /create/i }).click();272-    await popup.waitForTimeout(1500);273-    await ctx.close();274-  });275-276-  test("FLOW10 - generator and use password", async () => {277-    const ctx = await makeContext();278-    const { popup } = await setupVault(ctx);279-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();280-    await popup.waitForTimeout(300);281-    await popup.getByRole("button", { name: /password generator/i }).click();282-    await popup.waitForTimeout(500);283-    const pwd1 = await popup.locator(".peach-pwgen-output").textContent();284-    expect(pwd1?.length).toBeGreaterThan(0);285-    await popup.locator("button[aria-label='Regenerate']").click();286-    await popup.waitForTimeout(300);287-    const pwd2 = await popup.locator(".peach-pwgen-output").textContent();288-    expect(pwd1).not.toEqual(pwd2);289-    const useBtn = popup.getByRole("button", { name: /use/i });290-    await useBtn.click();291-    await popup.waitForTimeout(500);292-    await ctx.close();293-  });294-});295-296-test.describe("State Transitions", () => {297-298-  test("STATE01 - empty vault shows correct UI", async () => {299-    const ctx = await makeContext();300-    const { popup } = await setupVault(ctx);301-    const emptyCopy = popup.locator(".peach-empty-copy, text=first entry");302-    await expect(emptyCopy).toBeVisible({ timeout: 3000 });303-    await ctx.close();304-  });305-306-  test("STATE02 - vault with entries shows entry count", async () => {307-    const ctx = await makeContext();308-    const { popup } = await setupVault(ctx);309-    await popup.locator('input[placeholder="search vault..."]').click();310-    await popup.getByRole("button", { name: /new entry/i }).click();311-    await popup.locator(".peach-new-name-input").fill("CounterTest");312-    await popup.locator('input[placeholder="domain.com"]').fill("https://counter.com");313-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");314-    const pwdField = popup.locator(".peach-new-field-input[placeholder='password']");315-    await pwdField.fill("pass123");316-    await popup.getByRole("button", { name: /create/i }).click();317-    await popup.waitForTimeout(1500);318-    const counter = popup.locator(".peach-search-counter");319-    const counterText = await counter.textContent();320-    expect(counterText).toMatch(/1 \/ 1|1 entries/);321-    await ctx.close();322-  });323-324-  test("STATE03 - settings panel shows all options", async () => {325-    const ctx = await makeContext();326-    const { popup } = await setupVault(ctx);327-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();328-    await popup.waitForTimeout(500);329-    await expect(popup.locator("text=lock on close")).toBeVisible();330-    await expect(popup.locator("text=auto-lock")).toBeVisible();331-    await expect(popup.locator("text=clipboard clear")).toBeVisible();332-    await ctx.close();333-  });334-335-  test("STATE04 - generator shows toggles", async () => {336-    const ctx = await makeContext();337-    const { popup } = await setupVault(ctx);338-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();339-    await popup.waitForTimeout(300);340-    await popup.getByRole("button", { name: /password generator/i }).click();341-    await popup.waitForTimeout(500);342-    const toggles = popup.locator(".peach-pwgen-toggle-row");343-    expect(await toggles.count()).toBeGreaterThan(0);344-    await ctx.close();345-  });346-347-  test("STATE05 - export panel requires password", async () => {348-    const ctx = await makeContext();349-    const { popup } = await setupVault(ctx);350-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();351-    await popup.waitForTimeout(300);352-    await popup.getByRole("button", { name: /export/i }).click();353-    await popup.waitForTimeout(500);354-    const exportPw = popup.locator("input[type='password']").first();355-    await expect(exportPw).toBeVisible();356-    await ctx.close();357-  });358-359-  test("STATE06 - import panel shows file input", async () => {360-    const ctx = await makeContext();361-    const { popup } = await setupVault(ctx);362-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();363-    await popup.waitForTimeout(300);364-    await popup.getByRole("button", { name: /import/i }).click();365-    await popup.waitForTimeout(500);366-    const fileInput = popup.locator("input[type='file']");367-    await expect(fileInput).toBeAttached();368-    await ctx.close();369-  });370-371-  test("STATE07 - accent color swatches are clickable", async () => {372-    const ctx = await makeContext();373-    const { popup } = await setupVault(ctx);374-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();375-    await popup.waitForTimeout(500);376-    const swatches = popup.locator(".peach-swatch");377-    const count = await swatches.count();378-    expect(count).toBeGreaterThan(0);379-    await swatches.nth(0).click();380-    await popup.waitForTimeout(200);381-    await swatches.nth(1).click();382-    await popup.waitForTimeout(200);383-    await ctx.close();384-  });385-386-  test("STATE08 - lock screen shows branding", async () => {387-    const ctx = await makeContext();388-    const extId = await waitForSW(ctx);389-    const popup = await ctx.newPage();390-    await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);391-    await popup.waitForTimeout(2000);392-    const wordmark = popup.locator(".wordmark, .peach-wordmark, text=peach");393-    await expect(wordmark.first()).toBeVisible({ timeout: 3000 });394-    await ctx.close();395-  });396-});

view file at the parent

tests/helpers/vault.ts+2 −2
@@ -8,7 +8,7 @@88  * setupVault() below asserts it reached the unlocked vault, so that failure99  * mode is now a loud error instead of a silent pass.1010  */11-import { expect, type BrowserContext, type Page } from "@playwright/test";11+import { chromium, expect, type BrowserContext, type Page } from "@playwright/test";1212 import { fileURLToPath } from "url";1313 import { dirname, join } from "path";1414 import os from "os";@@ -25,13 +25,13 @@2525 export const UNLOCKED = ".peach-lock-btn[aria-label='Lock vault']";2626 2727 export async function makeContext(prefix = "peach"): Promise<BrowserContext> {28-  const { chromium } = await import("playwright");2928   const userDataDir = join(3029     os.tmpdir(),3130     `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`,3231   );3332   await fs.mkdir(userDataDir, { recursive: true });3433   return chromium.launchPersistentContext(userDataDir, {34+    executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,3535     headless: false,3636     args: [3737       `--load-extension=${EXTENSION_PATH}`,

view file

tests/import-export-robust.spec.ts+0 −685
@@ -1,685 +0,0 @@1-import { test, expect, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-11-async function makeContext() {12-  const { chromium } = await import("playwright");13-  const userDataDir = join(os.tmpdir(), `peach-imp-${Date.now()}`);14-  await fs.mkdir(userDataDir, { recursive: true });15-  return chromium.launchPersistentContext(userDataDir, {16-    headless: false,17-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18-  });19-}20-21-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22-  const start = Date.now();23-  let sw = ctx.serviceWorkers()[0];24-  while (!sw && Date.now() - start < maxWait) {25-    await new Promise(r => setTimeout(r, 500));26-    sw = ctx.serviceWorkers()[0];27-  }28-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);29-  return new URL(sw.url()).host;30-}31-32-async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33-  const extId = await waitForSW(ctx);34-  const popup = await ctx.newPage();35-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36-  await popup.waitForTimeout(3000);37-  const pw = popup.locator("#master-password");38-  if (await pw.isVisible()) {39-    await pw.fill("password123");40-    await popup.locator("#confirm-password").fill("password123");41-    await popup.getByRole("button", { name: /create vault/i }).click();42-    await popup.waitForTimeout(2000);43-  }44-  return { popup, extId };45-}46-47-test.describe("CSV Import", () => {48-  let ctx: BrowserContext;49-  test.afterEach(async () => { await ctx.close(); });50-  test.beforeEach(async () => { ctx = await makeContext(); });51-52-  test("IMP01 - CSV import accepts standard format", async () => {53-    const { popup } = await setupVault(ctx);54-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();55-    await popup.waitForTimeout(300);56-    await popup.getByRole("button", { name: /import passwords/i }).click();57-    await popup.waitForTimeout(500);58-    const fileInput = popup.locator('input[type="file"]');59-    const csvContent = [60-      "title,username,password,url",61-      "Google,[email protected],pass123,https://google.com",62-      "Amazon,[email protected],pass456,https://amazon.com"63-    ].join("\n");64-    await fileInput.setInputFiles({65-      name: "import.csv",66-      mimeType: "text/csv",67-      buffer: Buffer.from(csvContent)68-    });69-    await popup.waitForTimeout(1500);70-    await popup.keyboard.press("Escape");71-    await popup.waitForTimeout(300);72-    const entries = popup.locator(".peach-entry-row");73-    const count = await entries.count();74-    expect(count).toBeGreaterThan(0);75-    await ctx.close();76-  });77-78-  test("IMP02 - CSV import with different column order", async () => {79-    const { popup } = await setupVault(ctx);80-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();81-    await popup.waitForTimeout(300);82-    await popup.getByRole("button", { name: /import passwords/i }).click();83-    await popup.waitForTimeout(500);84-    const fileInput = popup.locator('input[type="file"]');85-    const csvContent = [86-      "password,title,url,username",87-      "secret1,Site1,https://site1.com,[email protected]",88-      "secret2,Site2,https://site2.com,[email protected]"89-    ].join("\n");90-    await fileInput.setInputFiles({91-      name: "import2.csv",92-      mimeType: "text/csv",93-      buffer: Buffer.from(csvContent)94-    });95-    await popup.waitForTimeout(1500);96-    await ctx.close();97-  });98-99-  test("IMP03 - CSV import with BOM header", async () => {100-    const { popup } = await setupVault(ctx);101-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();102-    await popup.waitForTimeout(300);103-    await popup.getByRole("button", { name: /import passwords/i }).click();104-    await popup.waitForTimeout(500);105-    const fileInput = popup.locator('input[type="file"]');106-    const csvContent = "\ufefftitle,username,password,url\nTest,[email protected],pass,https://test.com";107-    await fileInput.setInputFiles({108-      name: "import-bom.csv",109-      mimeType: "text/csv",110-      buffer: Buffer.from(csvContent)111-    });112-    await popup.waitForTimeout(1500);113-    await ctx.close();114-  });115-116-  test("IMP04 - CSV import with quotes in fields", async () => {117-    const { popup } = await setupVault(ctx);118-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();119-    await popup.waitForTimeout(300);120-    await popup.getByRole("button", { name: /import passwords/i }).click();121-    await popup.waitForTimeout(500);122-    const fileInput = popup.locator('input[type="file"]');123-    const csvContent = [124-      "title,username,password,url",125-      "\"Test, User\",\"[email protected]\",\"pass,\"\"123\"\"\",https://test.com"126-    ].join("\n");127-    await fileInput.setInputFiles({128-      name: "import-quotes.csv",129-      mimeType: "text/csv",130-      buffer: Buffer.from(csvContent)131-    });132-    await popup.waitForTimeout(1500);133-    await ctx.close();134-  });135-136-  test("IMP05 - CSV import with empty fields", async () => {137-    const { popup } = await setupVault(ctx);138-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();139-    await popup.waitForTimeout(300);140-    await popup.getByRole("button", { name: /import passwords/i }).click();141-    await popup.waitForTimeout(500);142-    const fileInput = popup.locator('input[type="file"]');143-    const csvContent = [144-      "title,username,password,url",145-      "EmptyFields,,,https://empty.com",146-      ",[email protected],,"147-    ].join("\n");148-    await fileInput.setInputFiles({149-      name: "import-empty.csv",150-      mimeType: "text/csv",151-      buffer: Buffer.from(csvContent)152-    });153-    await popup.waitForTimeout(1500);154-    await ctx.close();155-  });156-157-  test("IMP06 - CSV import with newlines in fields", async () => {158-    const { popup } = await setupVault(ctx);159-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();160-    await popup.waitForTimeout(300);161-    await popup.getByRole("button", { name: /import passwords/i }).click();162-    await popup.waitForTimeout(500);163-    const fileInput = popup.locator('input[type="file"]');164-    const csvContent = [165-      "title,username,password,url",166-      "\"Multi\nLine\",\"[email protected]\",\"pass\",\"https://test.com\""167-    ].join("\n");168-    await fileInput.setInputFiles({169-      name: "import-newline.csv",170-      mimeType: "text/csv",171-      buffer: Buffer.from(csvContent)172-    });173-    await popup.waitForTimeout(1500);174-    await ctx.close();175-  });176-177-  test("IMP07 - CSV import with special characters", async () => {178-    const { popup } = await setupVault(ctx);179-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();180-    await popup.waitForTimeout(300);181-    await popup.getByRole("button", { name: /import passwords/i }).click();182-    await popup.waitForTimeout(500);183-    const fileInput = popup.locator('input[type="file"]');184-    const csvContent = [185-      "title,username,password,url",186-      "日本語,用户@例子.测试,密码,https://test.com",187-      "Emoji🎉,[email protected],🔐🔑,https://emoji.com"188-    ].join("\n");189-    await fileInput.setInputFiles({190-      name: "import-unicode.csv",191-      mimeType: "text/csv",192-      buffer: Buffer.from(csvContent)193-    });194-    await popup.waitForTimeout(1500);195-    await ctx.close();196-  });197-198-  test("IMP08 - CSV import with 100+ entries", async () => {199-    const { popup } = await setupVault(ctx);200-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();201-    await popup.waitForTimeout(300);202-    await popup.getByRole("button", { name: /import passwords/i }).click();203-    await popup.waitForTimeout(500);204-    const fileInput = popup.locator('input[type="file"]');205-    const rows = ["title,username,password,url"];206-    for (let i = 0; i < 100; i++) {207-      rows.push(`Entry${i},user${i}@test.com,pass${i},https://entry${i}.com`);208-    }209-    const csvContent = rows.join("\n");210-    await fileInput.setInputFiles({211-      name: "import-large.csv",212-      mimeType: "text/csv",213-      buffer: Buffer.from(csvContent)214-    });215-    await popup.waitForTimeout(3000);216-    await ctx.close();217-  });218-219-  test("IMP09 - CSV import with only required fields", async () => {220-    const { popup } = await setupVault(ctx);221-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();222-    await popup.waitForTimeout(300);223-    await popup.getByRole("button", { name: /import passwords/i }).click();224-    await popup.waitForTimeout(500);225-    const fileInput = popup.locator('input[type="file"]');226-    const csvContent = [227-      "title,password",228-      "MinimalImport,secretpass"229-    ].join("\n");230-    await fileInput.setInputFiles({231-      name: "import-minimal.csv",232-      mimeType: "text/csv",233-      buffer: Buffer.from(csvContent)234-    });235-    await popup.waitForTimeout(1500);236-    await ctx.close();237-  });238-239-  test("IMP10 - CSV import shows progress", async () => {240-    const { popup } = await setupVault(ctx);241-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();242-    await popup.waitForTimeout(300);243-    await popup.getByRole("button", { name: /import passwords/i }).click();244-    await popup.waitForTimeout(500);245-    const fileInput = popup.locator('input[type="file"]');246-    const rows = ["title,username,password,url"];247-    for (let i = 0; i < 20; i++) {248-      rows.push(`Entry${i},user${i}@test.com,pass${i},https://entry${i}.com`);249-    }250-    const csvContent = rows.join("\n");251-    await fileInput.setInputFiles({252-      name: "import-progress.csv",253-      mimeType: "text/csv",254-      buffer: Buffer.from(csvContent)255-    });256-    await popup.waitForTimeout(2000);257-    const importStatus = popup.locator("text=importing, text=imported, text=entries");258-    const hasStatus = await importStatus.isVisible({ timeout: 3000 }).catch(() => false);259-    expect(hasStatus).toBeTruthy();260-    await ctx.close();261-  });262-});263-264-test.describe("JSON Export", () => {265-  let ctx: BrowserContext;266-  test.afterEach(async () => { await ctx.close(); });267-  test.beforeEach(async () => { ctx = await makeContext(); });268-269-  test("EXP01 - export creates valid JSON", async () => {270-    const { popup } = await setupVault(ctx);271-    await popup.locator('input[placeholder="search vault..."]').click();272-    await popup.getByRole("button", { name: /new entry/i }).click();273-    await popup.locator(".peach-new-name-input").fill("ExportJSON");274-    await popup.locator('input[placeholder="domain.com"]').fill("https://export.com");275-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");276-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("exportpass");277-    await popup.getByRole("button", { name: /create/i }).click();278-    await popup.waitForTimeout(1500);279-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();280-    await popup.waitForTimeout(300);281-    await popup.getByRole("button", { name: /export backup/i }).click();282-    await popup.waitForTimeout(500);283-    const exportPw = popup.locator(".form-input[type='password']");284-    await exportPw.fill("exportpassword");285-    const downloadBtn = popup.getByRole("button", { name: /download backup/i });286-    await downloadBtn.click();287-    await popup.waitForTimeout(2000);288-    const result = popup.locator("text=exported");289-    const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);290-    expect(hasResult).toBeTruthy();291-    await ctx.close();292-  });293-294-  test("EXP02 - export includes all entries", async () => {295-    const { popup } = await setupVault(ctx);296-    for (const name of ["Export1", "Export2", "Export3"]) {297-      await popup.locator('input[placeholder="search vault..."]').click();298-      await popup.getByRole("button", { name: /new entry/i }).click();299-      await popup.locator(".peach-new-name-input").fill(name);300-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);301-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);302-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");303-      await popup.getByRole("button", { name: /create/i }).click();304-      await popup.waitForTimeout(800);305-    }306-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();307-    await popup.waitForTimeout(300);308-    await popup.getByRole("button", { name: /export backup/i }).click();309-    await popup.waitForTimeout(500);310-    const exportPw = popup.locator(".form-input[type='password']");311-    await exportPw.fill("exportpassword");312-    await popup.getByRole("button", { name: /download backup/i }).click();313-    await popup.waitForTimeout(2000);314-    const result = popup.locator("text=3 entries").first();315-    const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);316-    expect(hasResult).toBeTruthy();317-    await ctx.close();318-  });319-320-  test("EXP03 - export with TOTP entries includes TOTP", async () => {321-    const { popup } = await setupVault(ctx);322-    await popup.locator('input[placeholder="search vault..."]').click();323-    await popup.getByRole("button", { name: /new entry/i }).click();324-    await popup.locator(".peach-new-name-input").fill("ExportTOTP");325-    await popup.locator('input[placeholder="domain.com"]').fill("https://exporttotp.com");326-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");327-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");328-    await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");329-    await popup.getByRole("button", { name: /create/i }).click();330-    await popup.waitForTimeout(1500);331-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();332-    await popup.waitForTimeout(300);333-    await popup.getByRole("button", { name: /export backup/i }).click();334-    await popup.waitForTimeout(500);335-    const exportPw = popup.locator(".form-input[type='password']");336-    await exportPw.fill("exportpassword");337-    await popup.getByRole("button", { name: /download backup/i }).click();338-    await popup.waitForTimeout(2000);339-    await ctx.close();340-  });341-342-  test("EXP04 - export requires password", async () => {343-    const { popup } = await setupVault(ctx);344-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();345-    await popup.waitForTimeout(300);346-    await popup.getByRole("button", { name: /export backup/i }).click();347-    await popup.waitForTimeout(500);348-    const downloadBtn = popup.getByRole("button", { name: /download backup/i });349-    await downloadBtn.click();350-    await popup.waitForTimeout(500);351-    const pwInput = popup.locator(".form-input[type='password']");352-    const hasError = await pwInput.isVisible({ timeout: 2000 }).catch(() => false);353-    expect(hasError).toBeTruthy();354-    await ctx.close();355-  });356-357-  test("EXP05 - export with short password shows warning", async () => {358-    const { popup } = await setupVault(ctx);359-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();360-    await popup.waitForTimeout(300);361-    await popup.getByRole("button", { name: /export backup/i }).click();362-    await popup.waitForTimeout(500);363-    const exportPw = popup.locator(".form-input[type='password']");364-    await exportPw.fill("short");365-    const downloadBtn = popup.getByRole("button", { name: /download backup/i });366-    const isDisabled = await downloadBtn.isDisabled();367-    expect(isDisabled).toBeTruthy();368-    await ctx.close();369-  });370-371-  test("EXP06 - export empty vault works", async () => {372-    const { popup } = await setupVault(ctx);373-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();374-    await popup.waitForTimeout(300);375-    await popup.getByRole("button", { name: /export backup/i }).click();376-    await popup.waitForTimeout(500);377-    const exportPw = popup.locator(".form-input[type='password']");378-    await exportPw.fill("exportpassword");379-    await popup.getByRole("button", { name: /download backup/i }).click();380-    await popup.waitForTimeout(2000);381-    const result = popup.locator("text=0 entries").first();382-    const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);383-    expect(hasResult).toBeTruthy();384-    await ctx.close();385-  });386-});387-388-test.describe("JSON Import", () => {389-  let ctx: BrowserContext;390-  test.afterEach(async () => { await ctx.close(); });391-  test.beforeEach(async () => { ctx = await makeContext(); });392-393-  test("JIP01 - JSON import accepts Peach export format", async () => {394-    const { popup } = await setupVault(ctx);395-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();396-    await popup.waitForTimeout(300);397-    await popup.getByRole("button", { name: /import passwords/i }).click();398-    await popup.waitForTimeout(500);399-    const fileInput = popup.locator('input[type="file"]');400-    const jsonContent = JSON.stringify({401-      version: 1,402-      type: "peach-export",403-      kdf: "PBKDF2-SHA256",404-      entries: [405-        { title: "JSONImport", username: "[email protected]", password: "pass123", url: "https://test.com" }406-      ]407-    });408-    await fileInput.setInputFiles({409-      name: "peach-export.json",410-      mimeType: "application/json",411-      buffer: Buffer.from(jsonContent)412-    });413-    await popup.waitForTimeout(1500);414-    await ctx.close();415-  });416-417-  test("JIP02 - JSON import with 1Password format", async () => {418-    const { popup } = await setupVault(ctx);419-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();420-    await popup.waitForTimeout(300);421-    await popup.getByRole("button", { name: /import passwords/i }).click();422-    await popup.waitForTimeout(500);423-    const fileInput = popup.locator('input[type="file"]');424-    const json1pContent = JSON.stringify({425-      accounts: [{426-        vaults: [{427-          items: [{428-            overview: { title: "1P Import", url: "https://1p.com" },429-            details: {430-              loginFields: [431-                { designation: "username", value: "testuser" },432-                { designation: "password", value: "testpass" }433-              ]434-            }435-          }]436-        }]437-      }]438-    });439-    await fileInput.setInputFiles({440-      name: "1p-export.json",441-      mimeType: "application/json",442-      buffer: Buffer.from(json1pContent)443-    });444-    await popup.waitForTimeout(1500);445-    await ctx.close();446-  });447-448-  test("JIP03 - JSON import with Bitwarden format", async () => {449-    const { popup } = await setupVault(ctx);450-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();451-    await popup.waitForTimeout(300);452-    await popup.getByRole("button", { name: /import passwords/i }).click();453-    await popup.waitForTimeout(500);454-    const fileInput = popup.locator('input[type="file"]');455-    const jsonBWContent = JSON.stringify({456-      items: [457-        { name: "BW Import", login: { username: "bwuser", password: "bwpass", uris: [{ uri: "https://bw.com" }] } }458-      ]459-    });460-    await fileInput.setInputFiles({461-      name: "bw-export.json",462-      mimeType: "application/json",463-      buffer: Buffer.from(jsonBWContent)464-    });465-    await popup.waitForTimeout(1500);466-    await ctx.close();467-  });468-469-  test("JIP04 - JSON import with nested structure", async () => {470-    const { popup } = await setupVault(ctx);471-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();472-    await popup.waitForTimeout(300);473-    await popup.getByRole("button", { name: /import passwords/i }).click();474-    await popup.waitForTimeout(500);475-    const fileInput = popup.locator('input[type="file"]');476-    const jsonContent = JSON.stringify({477-      data: {478-        folders: [],479-        items: [480-          { id: "1", name: "Nested", login: { username: "[email protected]", password: "pass123" } }481-        ]482-      }483-    });484-    await fileInput.setInputFiles({485-      name: "nested.json",486-      mimeType: "application/json",487-      buffer: Buffer.from(jsonContent)488-    });489-    await popup.waitForTimeout(1500);490-    await ctx.close();491-  });492-493-  test("JIP05 - JSON import with TOTP secrets", async () => {494-    const { popup } = await setupVault(ctx);495-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();496-    await popup.waitForTimeout(300);497-    await popup.getByRole("button", { name: /import passwords/i }).click();498-    await popup.waitForTimeout(500);499-    const fileInput = popup.locator('input[type="file"]');500-    const jsonContent = JSON.stringify({501-      items: [502-        { name: "TOTPImport", login: { username: "[email protected]", password: "pass123", totp: "JBSWY3DPEHPK3PXP" } }503-      ]504-    });505-    await fileInput.setInputFiles({506-      name: "totp-import.json",507-      mimeType: "application/json",508-      buffer: Buffer.from(jsonContent)509-    });510-    await popup.waitForTimeout(1500);511-    await ctx.close();512-  });513-514-  test("JIP06 - JSON import with custom fields", async () => {515-    const { popup } = await setupVault(ctx);516-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();517-    await popup.waitForTimeout(300);518-    await popup.getByRole("button", { name: /import passwords/i }).click();519-    await popup.waitForTimeout(500);520-    const fileInput = popup.locator('input[type="file"]');521-    const jsonContent = JSON.stringify({522-      items: [523-        {524-          name: "CustomFields",525-          login: { username: "[email protected]", password: "pass123" },526-          extra: { notes: "some notes", favorite: true }527-        }528-      ]529-    });530-    await fileInput.setInputFiles({531-      name: "custom-fields.json",532-      mimeType: "application/json",533-      buffer: Buffer.from(jsonContent)534-    });535-    await popup.waitForTimeout(1500);536-    await ctx.close();537-  });538-539-  test("JIP07 - JSON import validates file format", async () => {540-    const { popup } = await setupVault(ctx);541-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();542-    await popup.waitForTimeout(300);543-    await popup.getByRole("button", { name: /import passwords/i }).click();544-    await popup.waitForTimeout(500);545-    const fileInput = popup.locator('input[type="file"]');546-    await fileInput.setInputFiles({547-      name: "invalid.json",548-      mimeType: "application/json",549-      buffer: Buffer.from("not valid json {")550-    });551-    await popup.waitForTimeout(500);552-    const error = popup.locator("text=error, text=invalid, text=parse");553-    const hasError = await error.isVisible({ timeout: 2000 }).catch(() => false);554-    expect(hasError).toBeTruthy();555-    await ctx.close();556-  });557-});558-559-test.describe("Import/Export Edge Cases", () => {560-  let ctx: BrowserContext;561-  test.afterEach(async () => { await ctx.close(); });562-  test.beforeEach(async () => { ctx = await makeContext(); });563-564-  test("IEC01 - import file larger than 10MB shows error", async () => {565-    const { popup } = await setupVault(ctx);566-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();567-    await popup.waitForTimeout(300);568-    await popup.getByRole("button", { name: /import passwords/i }).click();569-    await popup.waitForTimeout(500);570-    const fileInput = popup.locator('input[type="file"]');571-    const largeContent = "x".repeat(11 * 1024 * 1024);572-    await fileInput.setInputFiles({573-      name: "large.csv",574-      mimeType: "text/csv",575-      buffer: Buffer.from(largeContent)576-    });577-    await popup.waitForTimeout(500);578-    const error = popup.locator("text=too large, text=file size, text=max");579-    const hasError = await error.isVisible({ timeout: 2000 }).catch(() => false);580-    expect(hasError).toBeTruthy();581-    await ctx.close();582-  });583-584-  test("IEC02 - export to existing file prompts overwrite", async () => {585-    const { popup } = await setupVault(ctx);586-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();587-    await popup.waitForTimeout(300);588-    await popup.getByRole("button", { name: /export backup/i }).click();589-    await popup.waitForTimeout(500);590-    const exportPw = popup.locator(".form-input[type='password']");591-    await exportPw.fill("exportpassword");592-    await popup.getByRole("button", { name: /download backup/i }).click();593-    await popup.waitForTimeout(2000);594-    await ctx.close();595-  });596-597-  test("IEC03 - import with duplicate entries merges", async () => {598-    const { popup } = await setupVault(ctx);599-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();600-    await popup.waitForTimeout(300);601-    await popup.getByRole("button", { name: /import passwords/i }).click();602-    await popup.waitForTimeout(500);603-    const fileInput = popup.locator('input[type="file"]');604-    const csvContent = [605-      "title,username,password,url",606-      "Duplicate,[email protected],pass,https://dup.com",607-      "Duplicate,[email protected],pass,https://dup.com"608-    ].join("\n");609-    await fileInput.setInputFiles({610-      name: "duplicate.csv",611-      mimeType: "text/csv",612-      buffer: Buffer.from(csvContent)613-    });614-    await popup.waitForTimeout(1500);615-    const entries = popup.locator(".peach-entry-row");616-    const count = await entries.count();617-    expect(count).toBeLessThanOrEqual(2);618-    await ctx.close();619-  });620-621-  test("IEC04 - export preserves entry order", async () => {622-    const { popup } = await setupVault(ctx);623-    const order = ["First", "Second", "Third"];624-    for (const name of order) {625-      await popup.locator('input[placeholder="search vault..."]').click();626-      await popup.getByRole("button", { name: /new entry/i }).click();627-      await popup.locator(".peach-new-name-input").fill(name);628-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);629-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);630-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");631-      await popup.getByRole("button", { name: /create/i }).click();632-      await popup.waitForTimeout(800);633-    }634-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();635-    await popup.waitForTimeout(300);636-    await popup.getByRole("button", { name: /export backup/i }).click();637-    await popup.waitForTimeout(500);638-    const exportPw = popup.locator(".form-input[type='password']");639-    await exportPw.fill("exportpassword");640-    await popup.getByRole("button", { name: /download backup/i }).click();641-    await popup.waitForTimeout(2000);642-    await ctx.close();643-  });644-645-  test("IEC05 - import with missing password field", async () => {646-    const { popup } = await setupVault(ctx);647-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();648-    await popup.waitForTimeout(300);649-    await popup.getByRole("button", { name: /import passwords/i }).click();650-    await popup.waitForTimeout(500);651-    const fileInput = popup.locator('input[type="file"]');652-    const csvContent = "title,username\nNoPassword,[email protected]";653-    await fileInput.setInputFiles({654-      name: "no-password.csv",655-      mimeType: "text/csv",656-      buffer: Buffer.from(csvContent)657-    });658-    await popup.waitForTimeout(1500);659-    await ctx.close();660-  });661-662-  test("IEC06 - export with unicode entries", async () => {663-    const { popup } = await setupVault(ctx);664-    await popup.locator('input[placeholder="search vault..."]').click();665-    await popup.getByRole("button", { name: /new entry/i }).click();666-    await popup.locator(".peach-new-name-input").fill("日本語エントリー");667-    await popup.locator('input[placeholder="domain.com"]').fill("https://日本語.com");668-    await popup.locator('input[placeholder="username or email"]').fill("ユーザー@例子.测试");669-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("密码🔐");670-    await popup.getByRole("button", { name: /create/i }).click();671-    await popup.waitForTimeout(1500);672-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();673-    await popup.waitForTimeout(300);674-    await popup.getByRole("button", { name: /export backup/i }).click();675-    await popup.waitForTimeout(500);676-    const exportPw = popup.locator(".form-input[type='password']");677-    await exportPw.fill("exportpassword");678-    await popup.getByRole("button", { name: /download backup/i }).click();679-    await popup.waitForTimeout(2000);680-    const result = popup.locator("text=1 entries").first();681-    const hasResult = await result.isVisible({ timeout: 3000 }).catch(() => false);682-    expect(hasResult).toBeTruthy();683-    await ctx.close();684-  });685-});\ No newline at end of file

view file at the parent

tests/keyboard-comprehensive.spec.ts+0 −787
@@ -1,787 +0,0 @@1-import { test, expect, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-11-async function makeContext() {12-  const { chromium } = await import("playwright");13-  const userDataDir = join(os.tmpdir(), `peach-kb-${Date.now()}`);14-  await fs.mkdir(userDataDir, { recursive: true });15-  return chromium.launchPersistentContext(userDataDir, {16-    headless: false,17-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18-  });19-}20-21-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22-  const start = Date.now();23-  let sw = ctx.serviceWorkers()[0];24-  while (!sw && Date.now() - start < maxWait) {25-    await new Promise(r => setTimeout(r, 500));26-    sw = ctx.serviceWorkers()[0];27-  }28-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);29-  return new URL(sw.url()).host;30-}31-32-async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33-  const extId = await waitForSW(ctx);34-  const popup = await ctx.newPage();35-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36-  await popup.waitForTimeout(3000);37-  const pw = popup.locator("#master-password");38-  if (await pw.isVisible()) {39-    await pw.fill("password123");40-    await popup.locator("#confirm-password").fill("password123");41-    await popup.getByRole("button", { name: /create vault/i }).click();42-    await popup.waitForTimeout(2000);43-  }44-  return { popup, extId };45-}46-47-test.describe("Global Keyboard Shortcuts", () => {48-  let ctx: BrowserContext;49-  test.afterEach(async () => { await ctx.close(); });50-  test.beforeEach(async () => { ctx = await makeContext(); });51-52-  test("KBG01 - Enter submits master password", async () => {53-    const { popup: _popup } = await setupVault(ctx);54-    const newPage = await ctx.newPage();55-    const extId = await waitForSW(ctx);56-    await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);57-    await newPage.waitForTimeout(2000);58-    await newPage.locator("#master-password").fill("password123");59-    await newPage.keyboard.press("Enter");60-    await newPage.waitForTimeout(2000);61-    const entries = newPage.locator("[class*=entry]");62-    await expect(entries.first()).toBeVisible({ timeout: 5000 });63-    await ctx.close();64-  });65-66-  test("KBG02 - Ctrl+Shift+P opens extension popup", async () => {67-    const { popup } = await setupVault(ctx);68-    const isVisible = await popup.isVisible();69-    expect(isVisible).toBe(true);70-    await ctx.close();71-  });72-73-  test("KBG03 - Escape from lock screen does nothing", async () => {74-    const { popup: _popup } = await setupVault(ctx);75-    const newPage = await ctx.newPage();76-    const extId = await waitForSW(ctx);77-    await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);78-    await newPage.waitForTimeout(2000);79-    await newPage.locator("#master-password").fill("password123");80-    await newPage.keyboard.press("Escape");81-    await newPage.waitForTimeout(500);82-    const pw = newPage.locator("#master-password");83-    const isVisible = await pw.isVisible({ timeout: 2000 }).catch(() => false);84-    expect(isVisible).toBe(true);85-    await ctx.close();86-  });87-88-  test("KBG04 - Ctrl+N opens new entry dialog", async () => {89-    const { popup } = await setupVault(ctx);90-    await popup.keyboard.press("Control+n");91-    await popup.waitForTimeout(500);92-    const newBody = popup.locator(".peach-new-body");93-    const isVisible = await newBody.isVisible({ timeout: 3000 }).catch(() => false);94-    expect(isVisible).toBeTruthy();95-    await ctx.close();96-  });97-98-  test("KBG05 - Ctrl+F focuses search", async () => {99-    const { popup } = await setupVault(ctx);100-    await popup.keyboard.press("Control+f");101-    await popup.waitForTimeout(200);102-    const focused = await popup.evaluate(() => document.activeElement?.className);103-    expect(focused).toMatch(/search/i);104-    await ctx.close();105-  });106-107-  test("KBG06 - Ctrl+, opens settings", async () => {108-    const { popup } = await setupVault(ctx);109-    await popup.keyboard.press("Control+,");110-    await popup.waitForTimeout(500);111-    const settingsTitle = popup.locator(".peach-overlay-title, text=settings").first();112-    const isVisible = await settingsTitle.isVisible({ timeout: 3000 }).catch(() => false);113-    expect(isVisible).toBeTruthy();114-    await ctx.close();115-  });116-117-  test("KBG07 - Ctrl+L locks vault", async () => {118-    const { popup } = await setupVault(ctx);119-    await popup.keyboard.press("Control+l");120-    await popup.waitForTimeout(1500);121-    const pw = popup.locator("#master-password");122-    const isVisible = await pw.isVisible({ timeout: 3000 }).catch(() => false);123-    expect(isVisible).toBeTruthy();124-    await ctx.close();125-  });126-});127-128-test.describe("Entry Form Keyboard Navigation", () => {129-  let ctx: BrowserContext;130-  test.afterEach(async () => { await ctx.close(); });131-  test.beforeEach(async () => { ctx = await makeContext(); });132-133-  test("KBE01 - Tab moves through add entry form", async () => {134-    const { popup } = await setupVault(ctx);135-    await popup.locator('input[placeholder="search vault..."]').click();136-    await popup.getByRole("button", { name: /new entry/i }).click();137-    await popup.waitForTimeout(300);138-    const nameInput = popup.locator(".peach-new-name-input");139-    await nameInput.focus();140-    await popup.keyboard.press("Tab");141-    await popup.waitForTimeout(100);142-    const focused = await popup.evaluate(() => document.activeElement?.tagName);143-    expect(focused).toBe("INPUT");144-    await ctx.close();145-  });146-147-  test("KBE02 - Tab from name goes to domain", async () => {148-    const { popup } = await setupVault(ctx);149-    await popup.locator('input[placeholder="search vault..."]').click();150-    await popup.getByRole("button", { name: /new entry/i }).click();151-    await popup.waitForTimeout(300);152-    await popup.locator(".peach-new-name-input").focus();153-    await popup.keyboard.press("Tab");154-    await popup.waitForTimeout(100);155-    const focusedPlaceholder = await popup.evaluate(() => (document.activeElement as HTMLInputElement)?.placeholder);156-    expect(focusedPlaceholder).toContain("domain");157-    await ctx.close();158-  });159-160-  test("KBE03 - Tab from domain goes to username", async () => {161-    const { popup } = await setupVault(ctx);162-    await popup.locator('input[placeholder="search vault..."]').click();163-    await popup.getByRole("button", { name: /new entry/i }).click();164-    await popup.waitForTimeout(300);165-    await popup.locator(".peach-new-name-input").focus();166-    await popup.keyboard.press("Tab");167-    await popup.keyboard.press("Tab");168-    await popup.waitForTimeout(100);169-    const focusedPlaceholder = await popup.evaluate(() => (document.activeElement as HTMLInputElement)?.placeholder);170-    expect(focusedPlaceholder).toContain("username");171-    await ctx.close();172-  });173-174-  test("KBE04 - Tab from username goes to password", async () => {175-    const { popup } = await setupVault(ctx);176-    await popup.locator('input[placeholder="search vault..."]').click();177-    await popup.getByRole("button", { name: /new entry/i }).click();178-    await popup.waitForTimeout(300);179-    await popup.locator(".peach-new-name-input").focus();180-    await popup.keyboard.press("Tab");181-    await popup.keyboard.press("Tab");182-    await popup.keyboard.press("Tab");183-    await popup.waitForTimeout(100);184-    const focusedClass = await popup.evaluate(() => document.activeElement?.className);185-    expect(focusedClass).toContain("peach-new-field-input");186-    await ctx.close();187-  });188-189-  test("KBE05 - Shift+Tab goes backwards", async () => {190-    const { popup } = await setupVault(ctx);191-    await popup.locator('input[placeholder="search vault..."]').click();192-    await popup.getByRole("button", { name: /new entry/i }).click();193-    await popup.waitForTimeout(300);194-    const pwField = popup.locator(".peach-new-field-input[placeholder='password']");195-    await pwField.focus();196-    await popup.keyboard.press("Shift+Tab");197-    await popup.waitForTimeout(100);198-    const focused = await popup.evaluate(() => document.activeElement?.tagName);199-    expect(focused).toBe("INPUT");200-    await ctx.close();201-  });202-203-  test("KBE06 - Enter submits add entry form", async () => {204-    const { popup } = await setupVault(ctx);205-    await popup.locator('input[placeholder="search vault..."]').click();206-    await popup.getByRole("button", { name: /new entry/i }).click();207-    await popup.waitForTimeout(300);208-    await popup.locator(".peach-new-name-input").fill("EnterSubmit");209-    await popup.locator('input[placeholder="domain.com"]').fill("https://enter.com");210-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");211-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");212-    await popup.keyboard.press("Enter");213-    await popup.waitForTimeout(1500);214-    await expect(popup.locator("text=EnterSubmit")).toBeVisible({ timeout: 3000 });215-    await ctx.close();216-  });217-218-  test("KBE07 - Escape closes add entry dialog", async () => {219-    const { popup } = await setupVault(ctx);220-    await popup.locator('input[placeholder="search vault..."]').click();221-    await popup.getByRole("button", { name: /new entry/i }).click();222-    await popup.waitForTimeout(300);223-    await popup.keyboard.press("Escape");224-    await popup.waitForTimeout(300);225-    const newBody = popup.locator(".peach-new-body");226-    const isHidden = await newBody.isHidden().catch(() => false);227-    expect(isHidden).toBeTruthy();228-    await ctx.close();229-  });230-});231-232-test.describe("Vault List Keyboard Navigation", () => {233-  let ctx: BrowserContext;234-  test.afterEach(async () => { await ctx.close(); });235-  test.beforeEach(async () => { ctx = await makeContext(); });236-237-  test("KBV01 - ArrowDown moves selection down", async () => {238-    const { popup } = await setupVault(ctx);239-    for (const name of ["First", "Second", "Third"]) {240-      await popup.locator('input[placeholder="search vault..."]').click();241-      await popup.getByRole("button", { name: /new entry/i }).click();242-      await popup.locator(".peach-new-name-input").fill(name);243-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);244-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);245-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");246-      await popup.getByRole("button", { name: /create/i }).click();247-      await popup.waitForTimeout(800);248-    }249-    await popup.locator('input[placeholder="search vault..."]').click();250-    await popup.waitForTimeout(200);251-    await popup.keyboard.press("ArrowDown");252-    await popup.waitForTimeout(200);253-    const activeRow = popup.locator(".peach-entry-row.is-active");254-    const hasActive = await activeRow.isVisible({ timeout: 2000 }).catch(() => false);255-    expect(hasActive).toBeTruthy();256-    await ctx.close();257-  });258-259-  test("KBV02 - ArrowUp moves selection up", async () => {260-    const { popup } = await setupVault(ctx);261-    for (const name of ["RowUp1", "RowUp2"]) {262-      await popup.locator('input[placeholder="search vault..."]').click();263-      await popup.getByRole("button", { name: /new entry/i }).click();264-      await popup.locator(".peach-new-name-input").fill(name);265-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);266-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);267-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");268-      await popup.getByRole("button", { name: /create/i }).click();269-      await popup.waitForTimeout(800);270-    }271-    await popup.locator('input[placeholder="search vault..."]').click();272-    await popup.waitForTimeout(200);273-    await popup.keyboard.press("ArrowDown");274-    await popup.keyboard.press("ArrowDown");275-    await popup.waitForTimeout(200);276-    await popup.keyboard.press("ArrowUp");277-    await popup.waitForTimeout(200);278-    const activeRow = popup.locator(".peach-entry-row.is-active");279-    const hasActive = await activeRow.isVisible({ timeout: 2000 }).catch(() => false);280-    expect(hasActive).toBeTruthy();281-    await ctx.close();282-  });283-284-  test("KBV03 - Enter opens selected entry", async () => {285-    const { popup } = await setupVault(ctx);286-    await popup.locator('input[placeholder="search vault..."]').click();287-    await popup.getByRole("button", { name: /new entry/i }).click();288-    await popup.locator(".peach-new-name-input").fill("OpenMe");289-    await popup.locator('input[placeholder="domain.com"]').fill("https://open.com");290-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");291-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");292-    await popup.getByRole("button", { name: /create/i }).click();293-    await popup.waitForTimeout(1500);294-    await popup.locator('input[placeholder="search vault..."]').click();295-    await popup.waitForTimeout(200);296-    await popup.keyboard.press("Enter");297-    await popup.waitForTimeout(500);298-    const usernameField = popup.locator(".peach-field-value").filter({ hasText: "[email protected]" }).first();299-    await expect(usernameField).toBeVisible({ timeout: 3000 });300-    await ctx.close();301-  });302-303-  test("KBV04 - Home key goes to first entry", async () => {304-    const { popup } = await setupVault(ctx);305-    for (const name of ["AlphaHome", "BetaHome"]) {306-      await popup.locator('input[placeholder="search vault..."]').click();307-      await popup.getByRole("button", { name: /new entry/i }).click();308-      await popup.locator(".peach-new-name-input").fill(name);309-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);310-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);311-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");312-      await popup.getByRole("button", { name: /create/i }).click();313-      await popup.waitForTimeout(800);314-    }315-    await popup.locator('input[placeholder="search vault..."]').click();316-    await popup.keyboard.press("End");317-    await popup.waitForTimeout(200);318-    await popup.keyboard.press("Home");319-    await popup.waitForTimeout(200);320-    await ctx.close();321-  });322-323-  test("KBV05 - End key goes to last entry", async () => {324-    const { popup } = await setupVault(ctx);325-    for (const name of ["FirstEnd", "SecondEnd", "ThirdEnd"]) {326-      await popup.locator('input[placeholder="search vault..."]').click();327-      await popup.getByRole("button", { name: /new entry/i }).click();328-      await popup.locator(".peach-new-name-input").fill(name);329-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);330-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);331-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");332-      await popup.getByRole("button", { name: /create/i }).click();333-      await popup.waitForTimeout(800);334-    }335-    await popup.locator('input[placeholder="search vault..."]').click();336-    await popup.keyboard.press("End");337-    await popup.waitForTimeout(200);338-    await ctx.close();339-  });340-341-  test("KBV06 - Delete key triggers delete confirmation", async () => {342-    const { popup } = await setupVault(ctx);343-    await popup.locator('input[placeholder="search vault..."]').click();344-    await popup.getByRole("button", { name: /new entry/i }).click();345-    await popup.locator(".peach-new-name-input").fill("DeleteKey");346-    await popup.locator('input[placeholder="domain.com"]').fill("https://delkey.com");347-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");348-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");349-    await popup.getByRole("button", { name: /create/i }).click();350-    await popup.waitForTimeout(1500);351-    await ctx.close();352-  });353-354-  test("KBV07 - Ctrl+A selects all in search", async () => {355-    const { popup } = await setupVault(ctx);356-    await popup.locator('input[placeholder="search vault..."]').click();357-    await popup.locator('input[placeholder="search vault..."]').fill("test");358-    await popup.waitForTimeout(100);359-    await popup.keyboard.press("Control+a");360-    await popup.waitForTimeout(100);361-    const selected = await popup.evaluate(() => {362-      const input = document.activeElement as HTMLInputElement;363-      return input?.selectionStart === 0 && input?.selectionEnd === input?.value?.length;364-    });365-    expect(selected).toBeTruthy();366-    await ctx.close();367-  });368-});369-370-test.describe("Settings Panel Keyboard", () => {371-  let ctx: BrowserContext;372-  test.afterEach(async () => { await ctx.close(); });373-  test.beforeEach(async () => { ctx = await makeContext(); });374-375-  test("KBS01 - Tab navigates settings options", async () => {376-    const { popup } = await setupVault(ctx);377-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();378-    await popup.waitForTimeout(500);379-    await popup.keyboard.press("Tab");380-    await popup.waitForTimeout(100);381-    const focused = await popup.evaluate(() => document.activeElement?.tagName);382-    expect(focused).toBeTruthy();383-    await ctx.close();384-  });385-386-  test("KBS02 - Arrow keys navigate setting options", async () => {387-    const { popup } = await setupVault(ctx);388-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();389-    await popup.waitForTimeout(500);390-    await popup.locator("text=auto-lock").click();391-    await popup.waitForTimeout(300);392-    await popup.keyboard.press("ArrowDown");393-    await popup.waitForTimeout(200);394-    await popup.keyboard.press("Escape");395-    await ctx.close();396-  });397-398-  test("KBS03 - Escape closes settings panel", async () => {399-    const { popup } = await setupVault(ctx);400-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();401-    await popup.waitForTimeout(500);402-    await popup.keyboard.press("Escape");403-    await popup.waitForTimeout(300);404-    const overlay = popup.locator(".peach-overlay-view");405-    const isHidden = await overlay.isHidden().catch(() => true);406-    expect(isHidden).toBeTruthy();407-    await ctx.close();408-  });409-410-  test("KBS04 - Enter activates focused option", async () => {411-    const { popup } = await setupVault(ctx);412-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();413-    await popup.waitForTimeout(500);414-    const lockVault = popup.locator("text=lock vault").first();415-    if (await lockVault.isVisible({ timeout: 2000 }).catch(() => false)) {416-      await lockVault.focus();417-      await popup.keyboard.press("Enter");418-      await popup.waitForTimeout(1000);419-      const pwField = popup.locator("#master-password");420-      const isVisible = await pwField.isVisible({ timeout: 3000 }).catch(() => false);421-      expect(isVisible).toBeTruthy();422-    }423-    await ctx.close();424-  });425-426-  test("KBS05 - Space activates toggle switches", async () => {427-    const { popup } = await setupVault(ctx);428-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();429-    await popup.waitForTimeout(500);430-    await popup.locator("text=lock on close").click();431-    await popup.waitForTimeout(300);432-    const toggle = popup.locator("[role='switch' i]").first();433-    if (await toggle.isVisible({ timeout: 2000 }).catch(() => false)) {434-      await toggle.focus();435-      await popup.keyboard.press(" ");436-      await popup.waitForTimeout(200);437-    }438-    await ctx.close();439-  });440-});441-442-test.describe("Password Generator Keyboard", () => {443-  let ctx: BrowserContext;444-  test.afterEach(async () => { await ctx.close(); });445-  test.beforeEach(async () => { ctx = await makeContext(); });446-447-  test("KBG01 - Tab navigates generator options", async () => {448-    const { popup } = await setupVault(ctx);449-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();450-    await popup.waitForTimeout(300);451-    await popup.getByRole("button", { name: /password generator/i }).click();452-    await popup.waitForTimeout(500);453-    await popup.keyboard.press("Tab");454-    await popup.waitForTimeout(100);455-    const focused = await popup.evaluate(() => document.activeElement?.tagName);456-    expect(focused).toBeTruthy();457-    await ctx.close();458-  });459-460-  test("KBG02 - Arrow keys adjust length slider", async () => {461-    const { popup } = await setupVault(ctx);462-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();463-    await popup.waitForTimeout(300);464-    await popup.getByRole("button", { name: /password generator/i }).click();465-    await popup.waitForTimeout(500);466-    const slider = popup.locator(".peach-pwgen-slider");467-    await slider.focus();468-    await popup.keyboard.press("ArrowRight");469-    await popup.waitForTimeout(100);470-    await popup.keyboard.press("ArrowLeft");471-    await popup.waitForTimeout(100);472-    await ctx.close();473-  });474-475-  test("KBG03 - Space toggles option switches", async () => {476-    const { popup } = await setupVault(ctx);477-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();478-    await popup.waitForTimeout(300);479-    await popup.getByRole("button", { name: /password generator/i }).click();480-    await popup.waitForTimeout(500);481-    const toggleRow = popup.locator(".peach-pwgen-toggle-row").first();482-    if (await toggleRow.isVisible({ timeout: 2000 }).catch(() => false)) {483-      await toggleRow.focus();484-      await popup.keyboard.press(" ");485-      await popup.waitForTimeout(200);486-    }487-    await ctx.close();488-  });489-490-  test("KBG04 - Escape closes generator", async () => {491-    const { popup } = await setupVault(ctx);492-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();493-    await popup.waitForTimeout(300);494-    await popup.getByRole("button", { name: /password generator/i }).click();495-    await popup.waitForTimeout(500);496-    await popup.keyboard.press("Escape");497-    await popup.waitForTimeout(300);498-    const output = popup.locator(".peach-pwgen-output");499-    await output.isHidden().catch(() => true);500-    await ctx.close();501-  });502-503-  test("KBG05 - Enter copies generated password", async () => {504-    const { popup } = await setupVault(ctx);505-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();506-    await popup.waitForTimeout(300);507-    await popup.getByRole("button", { name: /password generator/i }).click();508-    await popup.waitForTimeout(500);509-    const copyBtn = popup.getByRole("button", { name: /copy/i }).first();510-    if (await copyBtn.isVisible({ timeout: 2000 }).catch(() => false)) {511-      await copyBtn.focus();512-      await popup.keyboard.press("Enter");513-      await popup.waitForTimeout(300);514-    }515-    await ctx.close();516-  });517-});518-519-test.describe("Lock Screen Keyboard", () => {520-  let ctx: BrowserContext;521-  test.afterEach(async () => { await ctx.close(); });522-  test.beforeEach(async () => { ctx = await makeContext(); });523-524-  test("KBL01 - Tab navigates lock screen", async () => {525-    const { popup: _popup } = await setupVault(ctx);526-    const newPage = await ctx.newPage();527-    const extId = await waitForSW(ctx);528-    await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);529-    await newPage.waitForTimeout(2000);530-    await newPage.keyboard.press("Tab");531-    await newPage.waitForTimeout(100);532-    const focused = await newPage.evaluate(() => document.activeElement?.tagName);533-    expect(focused).toBeTruthy();534-    await ctx.close();535-  });536-537-  test("KBL02 - Ctrl+Enter submits unlock", async () => {538-    await setupVault(ctx);539-    const newPage = await ctx.newPage();540-    const extId = await waitForSW(ctx);541-    await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);542-    await newPage.waitForTimeout(2000);543-    await newPage.locator("#master-password").fill("password123");544-    await newPage.keyboard.press("Control+Enter");545-    await newPage.waitForTimeout(2000);546-    const entries = newPage.locator("[class*=entry]");547-    await expect(entries.first()).toBeVisible({ timeout: 5000 });548-    await ctx.close();549-  });550-551-  test("KBL03 - Password field accepts paste", async () => {552-    await setupVault(ctx);553-    const newPage = await ctx.newPage();554-    const extId = await waitForSW(ctx);555-    await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);556-    await newPage.waitForTimeout(2000);557-    await newPage.locator("#master-password").fill("password123");558-    await newPage.keyboard.press("Control+a");559-    await newPage.keyboard.press("Control+c");560-    await newPage.locator("#master-password").fill("");561-    await newPage.locator("#master-password").focus();562-    await newPage.keyboard.press("Control+v");563-    await newPage.waitForTimeout(200);564-    const value = await newPage.locator("#master-password").inputValue();565-    expect(value).toBe("password123");566-    await ctx.close();567-  });568-569-  test("KBL04 - Show password toggle via click", async () => {570-    const { popup: _popup } = await setupVault(ctx);571-    const newPage = await ctx.newPage();572-    const extId = await waitForSW(ctx);573-    await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);574-    await newPage.waitForTimeout(2000);575-    const showBtn = newPage.locator("[aria-label*='show' i], [aria-label*='reveal' i]").first();576-    if (await showBtn.isVisible({ timeout: 2000 }).catch(() => false)) {577-      await showBtn.click();578-      await newPage.waitForTimeout(200);579-      const inputType = await newPage.locator("#master-password").getAttribute("type");580-      expect(inputType).toBe("text");581-    }582-    await ctx.close();583-  });584-585-  test("KBL05 - Shift+Tab navigates backwards on lock", async () => {586-    const { popup: _popup } = await setupVault(ctx);587-    const newPage = await ctx.newPage();588-    const extId = await waitForSW(ctx);589-    await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);590-    await newPage.waitForTimeout(2000);591-    await newPage.keyboard.press("Shift+Tab");592-    await newPage.waitForTimeout(100);593-    const focused = await newPage.evaluate(() => document.activeElement?.tagName);594-    expect(focused).toBeTruthy();595-    await ctx.close();596-  });597-});598-599-test.describe("Edit Entry Keyboard", () => {600-  let ctx: BrowserContext;601-  test.afterEach(async () => { await ctx.close(); });602-  test.beforeEach(async () => { ctx = await makeContext(); });603-604-  test("KBD01 - Tab navigates edit form fields", async () => {605-    const { popup } = await setupVault(ctx);606-    await popup.locator('input[placeholder="search vault..."]').click();607-    await popup.getByRole("button", { name: /new entry/i }).click();608-    await popup.locator(".peach-new-name-input").fill("EditKB");609-    await popup.locator('input[placeholder="domain.com"]').fill("https://editkb.com");610-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");611-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");612-    await popup.getByRole("button", { name: /create/i }).click();613-    await popup.waitForTimeout(1500);614-    await popup.locator(".peach-entry-name-button").first().click();615-    await popup.waitForTimeout(500);616-    await popup.locator("button[aria-label='Edit']").first().click();617-    await popup.waitForTimeout(500);618-    await popup.keyboard.press("Tab");619-    await popup.waitForTimeout(100);620-    const focused = await popup.evaluate(() => document.activeElement?.tagName);621-    expect(focused).toBe("INPUT");622-    await ctx.close();623-  });624-625-  test("KBD02 - Enter saves edit", async () => {626-    const { popup } = await setupVault(ctx);627-    await popup.locator('input[placeholder="search vault..."]').click();628-    await popup.getByRole("button", { name: /new entry/i }).click();629-    await popup.locator(".peach-new-name-input").fill("EditSaveKB");630-    await popup.locator('input[placeholder="domain.com"]').fill("https://editsavekb.com");631-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");632-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");633-    await popup.getByRole("button", { name: /create/i }).click();634-    await popup.waitForTimeout(1500);635-    await popup.locator(".peach-entry-name-button").first().click();636-    await popup.waitForTimeout(500);637-    await popup.locator("button[aria-label='Edit']").first().click();638-    await popup.waitForTimeout(500);639-    await popup.locator(".peach-new-name-input").fill("EditSaveKBChanged");640-    await popup.keyboard.press("Enter");641-    await popup.waitForTimeout(1500);642-    await expect(popup.locator("text=EditSaveKBChanged")).toBeVisible({ timeout: 3000 });643-    await ctx.close();644-  });645-646-  test("KBD03 - Escape cancels edit", async () => {647-    const { popup } = await setupVault(ctx);648-    await popup.locator('input[placeholder="search vault..."]').click();649-    await popup.getByRole("button", { name: /new entry/i }).click();650-    await popup.locator(".peach-new-name-input").fill("CancelEditKB");651-    await popup.locator('input[placeholder="domain.com"]').fill("https://cancelkb.com");652-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");653-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");654-    await popup.getByRole("button", { name: /create/i }).click();655-    await popup.waitForTimeout(1500);656-    await popup.locator(".peach-entry-name-button").first().click();657-    await popup.waitForTimeout(500);658-    await popup.locator("button[aria-label='Edit']").first().click();659-    await popup.waitForTimeout(500);660-    await popup.locator(".peach-new-name-input").fill("ShouldNotChange");661-    await popup.keyboard.press("Escape");662-    await popup.waitForTimeout(300);663-    await expect(popup.locator("text=CancelEditKB")).toBeVisible({ timeout: 3000 });664-    await ctx.close();665-  });666-});667-668-test.describe("Search Field Keyboard", () => {669-  let ctx: BrowserContext;670-  test.afterEach(async () => { await ctx.close(); });671-  test.beforeEach(async () => { ctx = await makeContext(); });672-673-  test("KBS01 - Typing filters results immediately", async () => {674-    const { popup } = await setupVault(ctx);675-    for (const name of ["FilterTest1", "FilterTest2", "OtherEntry"]) {676-      await popup.locator('input[placeholder="search vault..."]').click();677-      await popup.getByRole("button", { name: /new entry/i }).click();678-      await popup.locator(".peach-new-name-input").fill(name);679-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);680-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);681-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");682-      await popup.getByRole("button", { name: /create/i }).click();683-      await popup.waitForTimeout(800);684-    }685-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();686-    await searchInput.click();687-    await searchInput.fill("Filter");688-    await popup.waitForTimeout(300);689-    await expect(popup.locator("text=FilterTest1")).toBeVisible();690-    await expect(popup.locator("text=OtherEntry")).not.toBeVisible();691-    await ctx.close();692-  });693-694-  test("KBS02 - Backspace reduces search query", async () => {695-    const { popup } = await setupVault(ctx);696-    await popup.locator('input[placeholder="search vault..."]').click();697-    await popup.getByRole("button", { name: /new entry/i }).click();698-    await popup.locator(".peach-new-name-input").fill("BackspaceTest");699-    await popup.locator('input[placeholder="domain.com"]').fill("https://back.com");700-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");701-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");702-    await popup.getByRole("button", { name: /create/i }).click();703-    await popup.waitForTimeout(1500);704-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();705-    await searchInput.click();706-    await searchInput.fill("BackspaceTest");707-    await popup.keyboard.press("Backspace");708-    await popup.keyboard.press("Backspace");709-    await popup.keyboard.press("Backspace");710-    await popup.keyboard.press("Backspace");711-    await popup.keyboard.press("Backspace");712-    await popup.keyboard.press("Backspace");713-    await popup.keyboard.press("Backspace");714-    await popup.keyboard.press("Backspace");715-    await popup.keyboard.press("Backspace");716-    await popup.keyboard.press("Backspace");717-    await popup.keyboard.press("Backspace");718-    await popup.waitForTimeout(200);719-    await ctx.close();720-  });721-722-  test("KBS03 - Clear search shows all entries", async () => {723-    const { popup } = await setupVault(ctx);724-    for (const name of ["ShowAll1", "ShowAll2"]) {725-      await popup.locator('input[placeholder="search vault..."]').click();726-      await popup.getByRole("button", { name: /new entry/i }).click();727-      await popup.locator(".peach-new-name-input").fill(name);728-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);729-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);730-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");731-      await popup.getByRole("button", { name: /create/i }).click();732-      await popup.waitForTimeout(800);733-    }734-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();735-    await searchInput.click();736-    await searchInput.fill("ShowAll1");737-    await popup.waitForTimeout(300);738-    await searchInput.fill("");739-    await popup.waitForTimeout(300);740-    await expect(popup.locator("text=ShowAll1")).toBeVisible();741-    await expect(popup.locator("text=ShowAll2")).toBeVisible();742-    await ctx.close();743-  });744-745-  test("KBS04 - Search with no results shows empty state", async () => {746-    const { popup } = await setupVault(ctx);747-    await popup.locator('input[placeholder="search vault..."]').click();748-    await popup.getByRole("button", { name: /new entry/i }).click();749-    await popup.locator(".peach-new-name-input").fill("SingleEntry");750-    await popup.locator('input[placeholder="domain.com"]').fill("https://single.com");751-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");752-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");753-    await popup.getByRole("button", { name: /create/i }).click();754-    await popup.waitForTimeout(1500);755-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();756-    await searchInput.click();757-    await searchInput.fill("NothingMatches");758-    await popup.waitForTimeout(500);759-    const empty = popup.locator("text=no entries, text=no results, text=0 results");760-    const hasEmpty = await empty.isVisible({ timeout: 2000 }).catch(() => false);761-    expect(hasEmpty).toBeTruthy();762-    await ctx.close();763-  });764-765-  test("KBS05 - Search preserves cursor position", async () => {766-    const { popup } = await setupVault(ctx);767-    await popup.locator('input[placeholder="search vault..."]').click();768-    await popup.getByRole("button", { name: /new entry/i }).click();769-    await popup.locator(".peach-new-name-input").fill("CursorPreserve");770-    await popup.locator('input[placeholder="domain.com"]').fill("https://cursor.com");771-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");772-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass");773-    await popup.getByRole("button", { name: /create/i }).click();774-    await popup.waitForTimeout(1500);775-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();776-    await searchInput.click();777-    await searchInput.fill("Cursor");778-    await popup.keyboard.press("ArrowLeft");779-    await popup.keyboard.press("ArrowLeft");780-    await popup.waitForTimeout(100);781-    await popup.keyboard.press("X");782-    await popup.waitForTimeout(100);783-    const value = await searchInput.inputValue();784-    expect(value).toBe("CurXor");785-    await ctx.close();786-  });787-});\ No newline at end of file

view file at the parent

tests/keyboard-edge.spec.ts+0 −512
@@ -1,512 +0,0 @@1-import { test, expect, BrowserContext } from "@playwright/test";2-import { fileURLToPath } from "url";3-import { dirname, join } from "path";4-import os from "os";5-import fs from "fs/promises";6-7-const __filename = fileURLToPath(import.meta.url);8-const __dirname = dirname(__filename);9-const EXTENSION_PATH = join(__dirname, "..", "dist");10-11-async function makeContext() {12-  const { chromium } = await import("playwright");13-  const userDataDir = join(os.tmpdir(), `peach-kbd-${Date.now()}`);14-  await fs.mkdir(userDataDir, { recursive: true });15-  return chromium.launchPersistentContext(userDataDir, {16-    headless: false,17-    args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18-  });19-}20-21-async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22-  const start = Date.now();23-  let sw = ctx.serviceWorkers()[0];24-  while (!sw && Date.now() - start < maxWait) {25-    await new Promise(r => setTimeout(r, 500));26-    sw = ctx.serviceWorkers()[0];27-  }28-  if (!sw) throw new Error(`No SW after ${maxWait}ms`);29-  return new URL(sw.url()).host;30-}31-32-async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33-  const extId = await waitForSW(ctx);34-  const popup = await ctx.newPage();35-  await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36-  await popup.waitForTimeout(3000);37-  const pw = popup.locator("#master-password");38-  if (await pw.isVisible()) {39-    await pw.fill("password123");40-    await popup.locator("#confirm-password").fill("password123");41-    await popup.getByRole("button", { name: /create vault/i }).click();42-    await popup.waitForTimeout(2000);43-  }44-  return { popup, extId };45-}46-47-test.describe("Keyboard Navigation", () => {48-49-  test("KB01 - enter submits password on lock screen", async () => {50-    const ctx = await makeContext();51-    await setupVault(ctx);52-    const newPopup = await ctx.newPage();53-    const extId = await waitForSW(ctx);54-    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);55-    await newPopup.waitForTimeout(2000);56-    await newPopup.locator("#master-password").fill("password123");57-    await newPopup.keyboard.press("Enter");58-    await newPopup.waitForTimeout(2000);59-    const entries = newPopup.locator("[class*=entry]");60-    await expect(entries.first()).toBeVisible({ timeout: 5000 });61-    await ctx.close();62-  });63-64-  test("KB02 - tab navigates through fields", async () => {65-    const ctx = await makeContext();66-    const { popup } = await setupVault(ctx);67-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();68-    await popup.waitForTimeout(500);69-    await popup.keyboard.press("Tab");70-    await popup.keyboard.press("Tab");71-    const focused = await popup.evaluate(() => document.activeElement?.tagName);72-    expect(focused).toBeTruthy();73-    await ctx.close();74-  });75-76-  test("KB03 - escape closes settings panel", async () => {77-    const ctx = await makeContext();78-    const { popup } = await setupVault(ctx);79-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();80-    await popup.waitForTimeout(500);81-    await popup.keyboard.press("Escape");82-    await popup.waitForTimeout(300);83-    const overlay = popup.locator(".peach-overlay-view");84-    const isHidden = await overlay.isHidden().catch(() => true);85-    expect(isHidden).toBeTruthy();86-    await ctx.close();87-  });88-89-  test("KB04 - arrow keys navigate entry list", async () => {90-    const ctx = await makeContext();91-    const { popup } = await setupVault(ctx);92-    for (const name of ["Alpha", "Beta", "Gamma"]) {93-      await popup.locator('input[placeholder="search vault..."]').click();94-      await popup.getByRole("button", { name: /new entry/i }).click();95-      await popup.locator(".peach-new-name-input").fill(name);96-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);97-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@test.com`);98-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");99-      await popup.getByRole("button", { name: /create/i }).click();100-      await popup.waitForTimeout(500);101-    }102-    await popup.locator('input[placeholder="search vault..."]').click();103-    await popup.keyboard.press("ArrowDown");104-    await popup.waitForTimeout(200);105-    await ctx.close();106-  });107-108-  test("KB05 - enter opens entry from list", async () => {109-    const ctx = await makeContext();110-    const { popup } = await setupVault(ctx);111-    await popup.locator('input[placeholder="search vault..."]').click();112-    await popup.getByRole("button", { name: /new entry/i }).click();113-    await popup.locator(".peach-new-name-input").fill("EnterTest");114-    await popup.locator('input[placeholder="domain.com"]').fill("https://entertest.com");115-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");116-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");117-    await popup.getByRole("button", { name: /create/i }).click();118-    await popup.waitForTimeout(1000);119-    await popup.locator('input[placeholder="search vault..."]').click();120-    await popup.waitForTimeout(300);121-    await popup.keyboard.press("Enter");122-    await popup.waitForTimeout(500);123-    await ctx.close();124-  });125-126-  test("KB06 - ctrl+k focuses search", async () => {127-    const ctx = await makeContext();128-    const { popup } = await setupVault(ctx);129-    await popup.keyboard.press("Control+k");130-    await popup.waitForTimeout(200);131-    const searchFocused = await popup.evaluate(() => {132-      const el = document.activeElement;133-      return el?.classList.contains("peach-search-input") || el?.closest(".peach-search-input");134-    });135-    expect(searchFocused).toBeTruthy();136-    await ctx.close();137-  });138-139-  test("KB07 - backspace clears search", async () => {140-    const ctx = await makeContext();141-    const { popup } = await setupVault(ctx);142-    await popup.locator('input[placeholder="search vault..."]').click();143-    await popup.locator('input[placeholder="search vault..."]').fill("test");144-    await popup.keyboard.press("Backspace");145-    await popup.keyboard.press("Backspace");146-    await popup.keyboard.press("Backspace");147-    await popup.keyboard.press("Backspace");148-    await ctx.close();149-  });150-});151-152-test.describe("Edge Cases & Error Handling", () => {153-154-  test("EDGE01 - entry with very long title", async () => {155-    const ctx = await makeContext();156-    const { popup } = await setupVault(ctx);157-    await popup.locator('input[placeholder="search vault..."]').click();158-    await popup.getByRole("button", { name: /new entry/i }).click();159-    const longTitle = "A".repeat(200);160-    await popup.locator(".peach-new-name-input").fill(longTitle);161-    await popup.locator('input[placeholder="domain.com"]').fill("https://longtitle.com");162-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");163-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");164-    await popup.getByRole("button", { name: /create/i }).click();165-    await popup.waitForTimeout(1500);166-    await ctx.close();167-  });168-169-  test("EDGE02 - entry with unicode in all fields", async () => {170-    const ctx = await makeContext();171-    const { popup } = await setupVault(ctx);172-    await popup.locator('input[placeholder="search vault..."]').click();173-    await popup.getByRole("button", { name: /new entry/i }).click();174-    await popup.locator(".peach-new-name-input").fill("日本語テスト");175-    await popup.locator('input[placeholder="domain.com"]').fill("https://日本語.com");176-    await popup.locator('input[placeholder="username or email"]').fill("用户@例子.测试");177-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("密码🔐");178-    await popup.getByRole("button", { name: /create/i }).click();179-    await popup.waitForTimeout(1500);180-    await expect(popup.locator("text=日本語テスト")).toBeVisible({ timeout: 3000 });181-    await ctx.close();182-  });183-184-  test("EDGE03 - very short password accepted", async () => {185-    const ctx = await makeContext();186-    const { popup } = await setupVault(ctx);187-    await popup.locator('input[placeholder="search vault..."]').click();188-    await popup.getByRole("button", { name: /new entry/i }).click();189-    await popup.locator(".peach-new-name-input").fill("ShortPass");190-    await popup.locator('input[placeholder="domain.com"]').fill("https://short.com");191-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");192-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("a");193-    await popup.getByRole("button", { name: /create/i }).click();194-    await popup.waitForTimeout(1500);195-    await ctx.close();196-  });197-198-  test("EDGE04 - entry with special chars in domain", async () => {199-    const ctx = await makeContext();200-    const { popup } = await setupVault(ctx);201-    await popup.locator('input[placeholder="search vault..."]').click();202-    await popup.getByRole("button", { name: /new entry/i }).click();203-    await popup.locator(".peach-new-name-input").fill("SpecialDomain");204-    await popup.locator('input[placeholder="domain.com"]').fill("https://sub.domain.with-dashes.and_underscores.example.com/path?query=value");205-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");206-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("specialpass");207-    await popup.getByRole("button", { name: /create/i }).click();208-    await popup.waitForTimeout(1500);209-    await ctx.close();210-  });211-212-  test("EDGE05 - entry with no username", async () => {213-    const ctx = await makeContext();214-    const { popup } = await setupVault(ctx);215-    await popup.locator('input[placeholder="search vault..."]').click();216-    await popup.getByRole("button", { name: /new entry/i }).click();217-    await popup.locator(".peach-new-name-input").fill("NoUsername");218-    await popup.locator('input[placeholder="domain.com"]').fill("https://nouser.com");219-    await popup.locator('input[placeholder="username or email"]').fill("");220-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");221-    await popup.getByRole("button", { name: /create/i }).click();222-    await popup.waitForTimeout(1500);223-    await ctx.close();224-  });225-226-  test("EDGE06 - entry with no domain", async () => {227-    const ctx = await makeContext();228-    const { popup } = await setupVault(ctx);229-    await popup.locator('input[placeholder="search vault..."]').click();230-    await popup.getByRole("button", { name: /new entry/i }).click();231-    await popup.locator(".peach-new-name-input").fill("NoDomain");232-    await popup.locator('input[placeholder="domain.com"]').fill("");233-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");234-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");235-    await popup.getByRole("button", { name: /create/i }).click();236-    await popup.waitForTimeout(1500);237-    await ctx.close();238-  });239-240-  test("EDGE07 - rapid add entry button clicks", async () => {241-    const ctx = await makeContext();242-    const { popup } = await setupVault(ctx);243-    const addBtn = popup.getByRole("button", { name: /new entry/i });244-    await addBtn.click();245-    await addBtn.click();246-    await addBtn.click();247-    await popup.waitForTimeout(500);248-    const modal = popup.locator(".peach-new-body");249-    await expect(modal).toBeVisible({ timeout: 3000 });250-    await ctx.close();251-  });252-253-  test("EDGE08 - search with special regex chars", async () => {254-    const ctx = await makeContext();255-    const { popup } = await setupVault(ctx);256-    for (const name of ["Test[0]", "Test(1)", "Test{2}"]) {257-      await popup.locator('input[placeholder="search vault..."]').click();258-      await popup.getByRole("button", { name: /new entry/i }).click();259-      await popup.locator(".peach-new-name-input").fill(name);260-      await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase().replace(/[^a-z]/g, "")}.com`);261-      await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@test.com`);262-      await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");263-      await popup.getByRole("button", { name: /create/i }).click();264-      await popup.waitForTimeout(500);265-    }266-    const searchInput = popup.locator(".peach-search-input, .peach-search-input-list").first();267-    await searchInput.click();268-    await searchInput.fill("[0]");269-    await popup.waitForTimeout(300);270-    await ctx.close();271-  });272-273-  test("EDGE09 - copy button during loading state", async () => {274-    const ctx = await makeContext();275-    const { popup } = await setupVault(ctx);276-    await popup.locator('input[placeholder="search vault..."]').click();277-    await popup.getByRole("button", { name: /new entry/i }).click();278-    await popup.locator(".peach-new-name-input").fill("CopyLoading");279-    await popup.locator('input[placeholder="domain.com"]').fill("https://copyloading.com");280-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");281-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");282-    await popup.getByRole("button", { name: /create/i }).click();283-    await popup.waitForTimeout(1500);284-    const copyBtn = popup.locator(".peach-copy-btn").first();285-    if (await copyBtn.isVisible({ timeout: 2000 }).catch(() => false)) {286-      await copyBtn.click();287-      await popup.waitForTimeout(500);288-    }289-    await ctx.close();290-  });291-292-  test("EDGE10 - unlock with whitespace password", async () => {293-    const ctx = await makeContext();294-    const extId = await waitForSW(ctx);295-    const popup = await ctx.newPage();296-    await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);297-    await popup.waitForTimeout(2000);298-    await popup.locator("#master-password").fill("   ");299-    await popup.getByRole("button", { name: /unlock/i }).click();300-    await popup.waitForTimeout(500);301-    const errorMsg = popup.locator(".peach-hint-error, .peach-hint");302-    await errorMsg.first().isVisible({ timeout: 2000 }).catch(() => false);303-    await ctx.close();304-  });305-});306-307-test.describe("Import Export Round-trip", () => {308-309-  test("IMP01 - import CSV and verify entries", async () => {310-    const ctx = await makeContext();311-    const { popup } = await setupVault(ctx);312-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();313-    await popup.waitForTimeout(300);314-    await popup.getByRole("button", { name: /import/i }).click();315-    await popup.waitForTimeout(500);316-    const fileInput = popup.locator('input[type="file"]');317-    const csvContent = "title,username,password,url\nTestImport,importuser,importpass,https://import.com";318-    await fileInput.setInputFiles({319-      name: "test.csv",320-      mimeType: "text/csv",321-      buffer: Buffer.from(csvContent)322-    });323-    await popup.waitForTimeout(1000);324-    await ctx.close();325-  });326-327-  test("IMP02 - export creates valid JSON file", async () => {328-    const ctx = await makeContext();329-    const { popup } = await setupVault(ctx);330-    await popup.locator('input[placeholder="search vault..."]').click();331-    await popup.getByRole("button", { name: /new entry/i }).click();332-    await popup.locator(".peach-new-name-input").fill("ExportTest");333-    await popup.locator('input[placeholder="domain.com"]').fill("https://exporttest.com");334-    await popup.locator('input[placeholder="username or email"]').fill("[email protected]");335-    await popup.locator(".peach-new-field-input[placeholder='password']").fill("exportpass");336-    await popup.getByRole("button", { name: /create/i }).click();337-    await popup.waitForTimeout(1500);338-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();339-    await popup.waitForTimeout(300);340-    await popup.getByRole("button", { name: /export/i }).click();341-    await popup.waitForTimeout(500);342-    await ctx.close();343-  });344-345-  test("IMP03 - empty vault export works", async () => {346-    const ctx = await makeContext();347-    const { popup } = await setupVault(ctx);348-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();349-    await popup.waitForTimeout(300);350-    await popup.getByRole("button", { name: /export/i }).click();351-    await popup.waitForTimeout(500);352-    await ctx.close();353-  });354-355-  test("IMP04 - import 1Password JSON format", async () => {356-    const ctx = await makeContext();357-    const { popup } = await setupVault(ctx);358-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();359-    await popup.waitForTimeout(300);360-    await popup.getByRole("button", { name: /import/i }).click();361-    await popup.waitForTimeout(500);362-    const fileInput = popup.locator('input[type="file"]');363-    const json1pContent = JSON.stringify({364-      accounts: [{365-        vaults: [{366-          items: [{367-            overview: { title: "1P Test", url: "https://1p.com" },368-            details: {369-              loginFields: [370-                { designation: "username", value: "testuser" },371-                { designation: "password", value: "testpass" }372-              ]373-            }374-          }]375-        }]376-      }]377-    });378-    await fileInput.setInputFiles({379-      name: "1p-export.json",380-      mimeType: "application/json",381-      buffer: Buffer.from(json1pContent)382-    });383-    await popup.waitForTimeout(1000);384-    await ctx.close();385-  });386-});387-388-test.describe("Password Generator", () => {389-390-  test("GEN01 - generates passwords of exact length", async () => {391-    const ctx = await makeContext();392-    const { popup } = await setupVault(ctx);393-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();394-    await popup.waitForTimeout(300);395-    await popup.getByRole("button", { name: /password generator/i }).click();396-    await popup.waitForTimeout(500);397-    const slider = popup.locator(".peach-pwgen-slider");398-    await slider.fill("32");399-    await popup.waitForTimeout(300);400-    const output = await popup.locator(".peach-pwgen-output").textContent();401-    expect(output?.length).toBe(32);402-    await ctx.close();403-  });404-405-  test("GEN02 - regenerate produces new password", async () => {406-    const ctx = await makeContext();407-    const { popup } = await setupVault(ctx);408-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();409-    await popup.waitForTimeout(300);410-    await popup.getByRole("button", { name: /password generator/i }).click();411-    await popup.waitForTimeout(500);412-    const pwd1 = await popup.locator(".peach-pwgen-output").textContent();413-    await popup.locator("button[aria-label='Regenerate']").click();414-    await popup.waitForTimeout(300);415-    const pwd2 = await popup.locator(".peach-pwgen-output").textContent();416-    expect(pwd1).not.toEqual(pwd2);417-    await ctx.close();418-  });419-420-  test("GEN03 - toggle uppercase on/off", async () => {421-    const ctx = await makeContext();422-    const { popup } = await setupVault(ctx);423-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();424-    await popup.waitForTimeout(300);425-    await popup.getByRole("button", { name: /password generator/i }).click();426-    await popup.waitForTimeout(500);427-    const toggles = popup.locator(".peach-pwgen-toggle-row");428-    const count = await toggles.count();429-    if (count >= 2) {430-      await toggles.nth(0).click();431-      await popup.waitForTimeout(200);432-    }433-    await ctx.close();434-  });435-436-  test("GEN04 - toggle numbers on/off", async () => {437-    const ctx = await makeContext();438-    const { popup } = await setupVault(ctx);439-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();440-    await popup.waitForTimeout(300);441-    await popup.getByRole("button", { name: /password generator/i }).click();442-    await popup.waitForTimeout(500);443-    const toggles = popup.locator(".peach-pwgen-toggle-row");444-    const count = await toggles.count();445-    if (count >= 3) {446-      await toggles.nth(2).click();447-      await popup.waitForTimeout(200);448-    }449-    await ctx.close();450-  });451-452-  test("GEN05 - toggle symbols on/off", async () => {453-    const ctx = await makeContext();454-    const { popup } = await setupVault(ctx);455-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();456-    await popup.waitForTimeout(300);457-    await popup.getByRole("button", { name: /password generator/i }).click();458-    await popup.waitForTimeout(500);459-    const toggles = popup.locator(".peach-pwgen-toggle-row");460-    const count = await toggles.count();461-    if (count >= 4) {462-      await toggles.nth(3).click();463-      await popup.waitForTimeout(200);464-    }465-    await ctx.close();466-  });467-468-  test("GEN06 - min length 8 enforced", async () => {469-    const ctx = await makeContext();470-    const { popup } = await setupVault(ctx);471-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();472-    await popup.waitForTimeout(300);473-    await popup.getByRole("button", { name: /password generator/i }).click();474-    await popup.waitForTimeout(500);475-    const slider = popup.locator(".peach-pwgen-slider");476-    await slider.fill("4");477-    await popup.waitForTimeout(300);478-    const output = await popup.locator(".peach-pwgen-output").textContent();479-    expect(output?.length).toBeGreaterThanOrEqual(8);480-    await ctx.close();481-  });482-483-  test("GEN07 - max length 64 enforced", async () => {484-    const ctx = await makeContext();485-    const { popup } = await setupVault(ctx);486-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();487-    await popup.waitForTimeout(300);488-    await popup.getByRole("button", { name: /password generator/i }).click();489-    await popup.waitForTimeout(500);490-    const slider = popup.locator(".peach-pwgen-slider");491-    await slider.fill("128");492-    await popup.waitForTimeout(300);493-    const output = await popup.locator(".peach-pwgen-output").textContent();494-    expect(output?.length).toBeLessThanOrEqual(64);495-    await ctx.close();496-  });497-498-  test("GEN08 - use password fills into entry", async () => {499-    const ctx = await makeContext();500-    const { popup } = await setupVault(ctx);501-    await popup.locator(".peach-icon-btn[aria-label='Settings']").click();502-    await popup.waitForTimeout(300);503-    await popup.getByRole("button", { name: /password generator/i }).click();504-    await popup.waitForTimeout(500);505-    const useBtn = popup.getByRole("button", { name: /use/i });506-    if (await useBtn.isVisible({ timeout: 2000 }).catch(() => false)) {507-      await useBtn.click();508-      await popup.waitForTimeout(500);509-    }510-    await ctx.close();511-  });512-});\ No newline at end of file

view file at the parent

tests/passkeys.spec.ts+1 −2
@@ -1,4 +1,4 @@1-import { test, expect, type BrowserContext, type Page } from "@playwright/test";1+import { chromium, test, expect, type BrowserContext, type Page } from "@playwright/test";22 import { createServer, type Server } from "http";33 import { readFile } from "fs/promises";44 import { fileURLToPath } from "url";@@ -34,7 +34,6 @@3434 }3535 3636 async function makeContext() {37-  const { chromium } = await import("playwright");3837   const userDataDir = join(3938     os.tmpdir(),4039     `peach-passkeys-${Date.now()}-${Math.random().toString(36).slice(2)}`

view file

tests/password-generator.spec.ts+0 −464

not shown here: too long for one page

view file at the parent

tests/security-visual.spec.ts+0 −338

not shown here: too long for one page

view file at the parent

tests/settings-persistence.spec.ts+0 −637

not shown here: too long for one page

view file at the parent

tests/totp-edge.spec.ts+0 −503

not shown here: too long for one page

view file at the parent

tests/vault-security.spec.ts+2 −3
@@ -1,4 +1,4 @@1-import { test, expect, BrowserContext } from "@playwright/test";1+import { chromium, test, expect, BrowserContext } from "@playwright/test";22 import { fileURLToPath } from "url";33 import { dirname, join } from "path";44 import os from "os";@@ -116,7 +116,6 @@116116   });117117 118118   test("SECS02 - session does not persist across browser restarts", async () => {119-    const { chromium } = await import("playwright");120119     const userDataDir = join(os.tmpdir(), `peach-sec-restart-${Date.now()}`);121120     await fs.mkdir(userDataDir, { recursive: true });122121     const launch = () => chromium.launchPersistentContext(userDataDir, {@@ -428,4 +427,4 @@428427     expect(uniqueChars).toBeGreaterThan(10);429428     await ctx.close();430429   });431-});\ No newline at end of file430+});

view file

tests/visual-compliance.spec.ts+0 −624

not shown here: too long for one page

view file at the parent