vault-lifecycle.spec.ts

27.5 KB · raw

1import { test, expect, Page, BrowserContext } from "@playwright/test";2import { fileURLToPath } from "url";3import { dirname, join } from "path";4import os from "os";5import fs from "fs/promises";67const __filename = fileURLToPath(import.meta.url);8const __dirname = dirname(__filename);9const EXTENSION_PATH = join(__dirname, "..", "dist");1011async 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}2122async 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}2930async 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}3940async function createVault(ctx: BrowserContext, password = "testpassword123") {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}5152async function unlockVault(ctx: BrowserContext, password = "testpassword123") {53  const { popup } = await loadPopup(ctx);54  const pw = popup.locator("#master-password");55  if (await pw.isVisible()) {56    await pw.fill(password);57    await popup.getByRole("button", { name: /unlock/i }).click();58    await popup.waitForTimeout(1500);59  }60  return { popup };61}6263test.describe("Vault Lifecycle", () => {64  let ctx: BrowserContext;6566  test.afterEach(async () => { if (ctx) await ctx.close(); });67  test.beforeEach(async () => { ctx = await makeContext(); });6869  test("01 - fresh install shows onboarding create flow", async () => {70    const { popup } = await loadPopup(ctx);71    await popup.waitForTimeout(500);72    const preLock = popup.locator("#pre-lock");73    if (await preLock.isVisible().catch(() => false)) {74      await preLock.waitFor({ state: "hidden", timeout: 5000 }).catch(() => {});75    }76    const headline = popup.locator(".peach-onboarding-headline, .peach-unlock-headline").first();77    await expect(headline).toBeVisible({ timeout: 5000 });78    const createBtn = popup.getByRole("button", { name: /create vault/i });79    await expect(createBtn).toBeVisible();80  });8182  test("02 - create vault with minimum 8-char password", async () => {83    const { popup } = await createVault(ctx, "12345678");84    const entry = popup.locator(".peach-entry-name, .peach-ghost, [class*=entry]").first();85    await expect(entry).toBeVisible({ timeout: 5000 });86  });8788  test("03 - create vault with 8-char password succeeds", async () => {89    const { popup } = await createVault(ctx, "abcdefgh");90    const entries = popup.locator("[class*=entry]");91    await expect(entries.first()).toBeVisible({ timeout: 5000 });92  });9394  test("04 - create vault with long password succeeds", async () => {95    const { popup } = await createVault(ctx, "thisisaverylongpasswordthatwemightuse123!@#");96    const entries = popup.locator("[class*=entry]");97    await expect(entries.first()).toBeVisible({ timeout: 5000 });98  });99100  test("05 - create vault with special chars in password", async () => {101    const { popup } = await createVault(ctx, "P@$$w0rd!#%^&*()_+-=[]{}|;':\",./<>?");102    const entries = popup.locator("[class*=entry]");103    await expect(entries.first()).toBeVisible({ timeout: 5000 });104  });105106  test("06 - create vault with unicode password", async () => {107    const { popup } = await createVault(ctx, "пароль密码🔐🔑");108    const entries = popup.locator("[class*=entry]");109    await expect(entries.first()).toBeVisible({ timeout: 5000 });110  });111112  test("07 - create vault with emoji password", async () => {113    const { popup } = await createVault(ctx, "🔐🔑🌟💻🖥️⌨️");114    const entries = popup.locator("[class*=entry]");115    await expect(entries.first()).toBeVisible({ timeout: 5000 });116  });117118  test("08 - wrong password shows shake animation", async () => {119    await createVault(ctx);120    await new Promise(r => setTimeout(r, 500));121    const newPopup = await ctx.newPage();122    const extId = (await loadPopup(ctx)).extId;123    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);124    await newPopup.waitForTimeout(1500);125    await newPopup.locator("#master-password").fill("wrongpassword");126    await newPopup.getByRole("button", { name: /unlock/i }).click();127    await newPopup.waitForTimeout(500);128    const error = newPopup.locator(".peach-hint-error, .peach-unlock-headline");129    await expect(error).toBeVisible({ timeout: 3000 });130  });131132  test("09 - wrong password does not unlock vault", async () => {133    await createVault(ctx);134    const newPopup = await ctx.newPage();135    const extId = (await loadPopup(ctx)).extId;136    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);137    await newPopup.waitForTimeout(1500);138    await newPopup.locator("#master-password").fill("wrongpassword");139    await newPopup.getByRole("button", { name: /unlock/i }).click();140    await newPopup.waitForTimeout(1000);141    const lockStillVisible = await newPopup.locator("#master-password").isVisible();142    expect(lockStillVisible).toBe(true);143  });144145  test("10 - correct password unlocks vault", async () => {146    await createVault(ctx);147    const newPopup = await ctx.newPage();148    const extId = (await loadPopup(ctx)).extId;149    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);150    await newPopup.waitForTimeout(1500);151    await newPopup.locator("#master-password").fill("testpassword123");152    await newPopup.getByRole("button", { name: /unlock/i }).click();153    await newPopup.waitForTimeout(2000);154    const entries = newPopup.locator("[class*=entry]");155    await expect(entries.first()).toBeVisible({ timeout: 5000 });156  });157158  test("11 - lock button returns to lock screen", async () => {159    const { popup } = await createVault(ctx);160    await popup.waitForTimeout(500);161    const lockBtn = popup.locator(".peach-lock-btn[aria-label='Lock vault']");162    await expect(lockBtn).toBeVisible({ timeout: 3000 });163    await lockBtn.click();164    await popup.waitForTimeout(1500);165    const pwField = popup.locator("#master-password");166    await expect(pwField).toBeVisible({ timeout: 5000 });167  });168169  test("12 - clicking lock icon in header locks vault", async () => {170    const { popup } = await createVault(ctx);171    const headerLock = popup.locator("[aria-label*='lock' i]").first();172    if (await headerLock.isVisible()) {173      await headerLock.click();174      await popup.waitForTimeout(1000);175      await expect(popup.locator("#master-password")).toBeVisible({ timeout: 3000 });176    }177  });178179  test("13 - lock preserves vault data", async () => {180    const { popup } = await createVault(ctx);181    await popup.locator('input[placeholder*="search" i]').click();182    await popup.getByRole("button", { name: /new entry/i }).click();183    await popup.locator(".peach-new-name-input").fill("TestEntry");184    await popup.locator('input[placeholder*="domain" i]').fill("https://test.com");185    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");186    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("secret123");187    await popup.getByRole("button", { name: /create/i }).click();188    await popup.waitForTimeout(1500);189    const lockBtn = popup.locator(".peach-lock-btn, [aria-label*='lock' i]").first();190    await lockBtn.click();191    await popup.waitForTimeout(1000);192    await popup.locator("#master-password").fill("testpassword123");193    await popup.getByRole("button", { name: /unlock/i }).click();194    await popup.waitForTimeout(2000);195    await expect(popup.locator("text=TestEntry").first()).toBeVisible({ timeout: 5000 });196  });197198  test("14 - auto-lock after 1 minute configured in settings", async () => {199    const { popup } = await createVault(ctx);200    await popup.locator("[aria-label*='settings' i]").click();201    await popup.waitForTimeout(500);202    const autoLockRow = popup.locator("text=auto-lock").first();203    await expect(autoLockRow).toBeVisible();204  });205206  test("15 - auto-lock options: 1min, 5min, 15min, 1hr, never", async () => {207    const { popup } = await createVault(ctx);208    await popup.locator("[aria-label*='settings' i]").click();209    await popup.waitForTimeout(500);210    await popup.locator("text=auto-lock").click();211    await popup.waitForTimeout(300);212    const options = popup.locator("[role='option' i], [role='menuitem' i], .peach-select-option");213    const count = await options.count();214    expect(count).toBeGreaterThanOrEqual(4);215  });216217  test("16 - service worker restart shows lock screen", async () => {218    const { popup, extId } = await loadPopup(ctx);219    await createVault(ctx);220    const newPopup = await ctx.newPage();221    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);222    await newPopup.waitForTimeout(1500);223    await expect(newPopup.locator("#master-password")).toBeVisible({ timeout: 3000 });224  });225226  test("17 - multiple rapid lock/unlock cycles work", async () => {227    const { popup } = await createVault(ctx);228    for (let i = 0; i < 3; i++) {229      const lockBtn = popup.locator(".peach-lock-btn, [aria-label*='lock' i]").first();230      await lockBtn.click();231      await popup.waitForTimeout(500);232      await popup.locator("#master-password").fill("testpassword123");233      await popup.getByRole("button", { name: /unlock/i }).click();234      await popup.waitForTimeout(1000);235    }236    await expect(popup.locator("[class*=entry]").first()).toBeVisible({ timeout: 5000 });237  });238239  test("18 - vault data persists after extension reload", async () => {240    const { popup } = await createVault(ctx);241    await popup.locator('input[placeholder*="search" i]').click();242    await popup.getByRole("button", { name: /new entry/i }).click();243    await popup.locator(".peach-new-name-input").fill("PersistTest");244    await popup.locator('input[placeholder*="domain" i]').fill("https://persist.com");245    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");246    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("persistentpass");247    await popup.getByRole("button", { name: /create/i }).click();248    await popup.waitForTimeout(1500);249    const extId = (await loadPopup(ctx)).extId;250    const newPopup = await ctx.newPage();251    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);252    await newPopup.waitForTimeout(2000);253    await newPopup.locator("#master-password").fill("testpassword123");254    await newPopup.getByRole("button", { name: /unlock/i }).click();255    await newPopup.waitForTimeout(2000);256    await expect(newPopup.locator("text=PersistTest")).toBeVisible({ timeout: 5000 });257  });258259  test("19 - creating vault shows empty state", async () => {260    const { popup } = await createVault(ctx);261    const emptyState = popup.locator("text=Your vault is ready, text=first entry, .peach-empty-copy").first();262    const hasEmptyState = await emptyState.isVisible().catch(() => false);263    expect(hasEmptyState).toBeTruthy();264  });265266  test("20 - vault with entries does not show empty state", async () => {267    const { popup } = await createVault(ctx);268    await popup.locator('input[placeholder*="search" i]').click();269    await popup.getByRole("button", { name: /new entry/i }).click();270    await popup.locator(".peach-new-name-input").fill("FirstEntry");271    await popup.locator('input[placeholder*="domain" i]').fill("https://first.com");272    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");273    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("password123");274    await popup.getByRole("button", { name: /create/i }).click();275    await popup.waitForTimeout(1500);276    const emptyState = popup.locator("text=empty, text=no entries").first();277    await expect(emptyState).not.toBeVisible();278  });279280  test("21 - password field shows dots by default on lock screen", async () => {281    const { popup } = await loadPopup(ctx);282    await createVault(ctx);283    const newPopup = await ctx.newPage();284    const extId = (await loadPopup(ctx)).extId;285    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);286    await newPopup.waitForTimeout(1500);287    const pwInput = newPopup.locator("#master-password");288    const type = await pwInput.getAttribute("type");289    expect(type).toBe("password");290  });291292  test("22 - reveal password toggle on lock screen", async () => {293    const { popup } = await loadPopup(ctx);294    await createVault(ctx);295    const newPopup = await ctx.newPage();296    const extId = (await loadPopup(ctx)).extId;297    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);298    await newPopup.waitForTimeout(1500);299    const revealBtn = newPopup.locator("[aria-label*='show' i], [aria-label*='reveal' i]").first();300    if (await revealBtn.isVisible({ timeout: 2000 }).catch(() => false)) {301      await revealBtn.click();302      const pwInput = newPopup.locator("#master-password");303      const type = await pwInput.getAttribute("type");304      expect(type).toBe("text");305    }306  });307308  test("23 - clipboard clear option in settings", async () => {309    const { popup } = await createVault(ctx);310    await popup.locator("[aria-label*='settings' i]").click();311    await popup.waitForTimeout(500);312    await expect(popup.locator("text=clipboard clear")).toBeVisible();313  });314315  test("24 - clipboard clear time options: 30s, 60s, 90s, never", async () => {316    const { popup } = await createVault(ctx);317    await popup.locator("[aria-label*='settings' i]").click();318    await popup.waitForTimeout(500);319    await popup.locator("text=clipboard clear").click();320    await popup.waitForTimeout(300);321    const options = popup.locator("[role='option' i], [role='menuitem' i]");322    const count = await options.count();323    expect(count).toBeGreaterThanOrEqual(3);324  });325326  test("25 - lock on close option in settings", async () => {327    const { popup } = await createVault(ctx);328    await popup.locator("[aria-label*='settings' i]").click();329    await popup.waitForTimeout(500);330    await expect(popup.locator("text=lock on close")).toBeVisible();331  });332333  test("26 - lock on close toggle works", async () => {334    const { popup } = await createVault(ctx);335    await popup.locator("[aria-label*='settings' i]").click();336    await popup.waitForTimeout(500);337    const toggle = popup.locator("text=lock on close").locator("..").locator("[role='switch' i], [type='checkbox']").first();338    if (await toggle.isVisible({ timeout: 2000 }).catch(() => false)) {339      await toggle.click();340      await popup.waitForTimeout(300);341    }342  });343344  test("27 - breach monitoring toggle in settings", async () => {345    const { popup } = await createVault(ctx);346    await popup.locator("[aria-label*='settings' i]").click();347    await popup.waitForTimeout(500);348    await expect(popup.locator("text=breach monitoring")).toBeVisible();349  });350351  test("28 - vault name displays in header", async () => {352    const { popup } = await createVault(ctx);353    const header = popup.locator("[class*='header' i], [class*='title' i]").first();354    const hasHeader = await header.isVisible({ timeout: 2000 }).catch(() => false);355    expect(hasHeader).toBeTruthy();356  });357358  test("29 - vault entry count shows in settings", async () => {359    const { popup } = await createVault(ctx);360    await popup.locator("[aria-label*='settings' i]").click();361    await popup.waitForTimeout(500);362    await expect(popup.locator("text=entries").first()).toBeVisible();363  });364365  test("30 - version number shows in settings", async () => {366    const { popup } = await createVault(ctx);367    await popup.locator("[aria-label*='settings' i]").click();368    await popup.waitForTimeout(500);369    const version = popup.locator("text=1.0.");370    const hasVersion = await version.isVisible({ timeout: 2000 }).catch(() => false);371    expect(hasVersion).toBeTruthy();372  });373374  test("31 - syncing indicator shows for pro users", async () => {375    const { popup } = await createVault(ctx);376    await popup.locator("[aria-label*='settings' i]").click();377    await popup.waitForTimeout(500);378    const syncRow = popup.locator("text=sync");379    await expect(syncRow).toBeVisible();380  });381382  test("32 - pro badge shows next to sync option", async () => {383    const { popup } = await createVault(ctx);384    await popup.locator("[aria-label*='settings' i]").click();385    await popup.waitForTimeout(500);386    const proBadge = popup.locator(".peach-pro-badge, [class*='pro' i]");387    const hasBadge = await proBadge.isVisible({ timeout: 2000 }).catch(() => false);388    expect(hasBadge).toBeTruthy();389  });390391  test("33 - lock screen has peach branding", async () => {392    const { popup } = await loadPopup(ctx);393    const brandElements = popup.locator(".peach-wordmark, .wordmark, text=peach, [class*='brand' i]");394    const hasBrand = await brandElements.first().isVisible({ timeout: 3000 }).catch(() => false);395    expect(hasBrand).toBeTruthy();396  });397398  test("34 - unlock screen shows tagline", async () => {399    const { popup } = await loadPopup(ctx);400    const tagline = popup.locator("text=secrets, text=your secrets, [class*='tagline' i]");401    const hasTagline = await tagline.first().isVisible({ timeout: 3000 }).catch(() => false);402    expect(hasTagline).toBeTruthy();403  });404405  test("35 - password strength indicator during vault creation", async () => {406    const { popup } = await loadPopup(ctx);407    const pw = popup.locator("#master-password");408    if (await pw.isVisible()) {409      await pw.fill("weak");410      await popup.waitForTimeout(300);411      const strength = popup.locator("[class*='strength' i], text=weak, text=strong");412      const hasStrength = await strength.first().isVisible({ timeout: 2000 }).catch(() => false);413      expect(hasStrength).toBeTruthy();414    }415  });416417  test("36 - password strength updates as user types", async () => {418    const { popup } = await loadPopup(ctx);419    const pw = popup.locator("#master-password");420    const confirm = popup.locator("#confirm-password");421    if (await pw.isVisible()) {422      await pw.fill("w");423      await popup.waitForTimeout(200);424      await pw.fill("we");425      await popup.waitForTimeout(200);426      await pw.fill("weak");427      await popup.waitForTimeout(200);428      await pw.fill("WeakPassword123!");429      await popup.waitForTimeout(200);430    }431  });432433  test("37 - passwords must match on create", async () => {434    const { popup } = await loadPopup(ctx);435    const pw = popup.locator("#master-password");436    const confirm = popup.locator("#confirm-password");437    if (await pw.isVisible()) {438      await pw.fill("password123");439      await confirm.fill("differentpassword");440      await popup.getByRole("button", { name: /create vault/i }).click();441      await popup.waitForTimeout(500);442      const error = popup.locator("text=mismatch, text=do not match, text=not match");443      await expect(error.first()).toBeVisible({ timeout: 3000 });444    }445  });446447  test("38 - passwords must be at least 8 chars", async () => {448    const { popup } = await loadPopup(ctx);449    const pw = popup.locator("#master-password");450    const confirm = popup.locator("#confirm-password");451    if (await pw.isVisible()) {452      await pw.fill("1234567");453      await confirm.fill("1234567");454      await popup.getByRole("button", { name: /create vault/i }).click();455      await popup.waitForTimeout(500);456      const error = popup.locator("text=8 characters, text=minimum, text=at least");457      await expect(error.first()).toBeVisible({ timeout: 3000 });458    }459  });460461  test("39 - vault creation shows loading state", async () => {462    const { popup } = await loadPopup(ctx);463    const pw = popup.locator("#master-password");464    const confirm = popup.locator("#confirm-password");465    if (await pw.isVisible()) {466      await pw.fill("testpassword123");467      await confirm.fill("testpassword123");468      await popup.getByRole("button", { name: /create vault/i }).click();469      await popup.waitForTimeout(300);470      const loading = popup.locator("[class*='loading' i], [class*='spinner' i], text=creating");471      const hasLoading = await loading.first().isVisible({ timeout: 3000 }).catch(() => false);472      expect(hasLoading).toBeTruthy();473    }474  });475476  test("40 - successful vault creation transitions to unlocked state", async () => {477    const { popup } = await loadPopup(ctx);478    const pw = popup.locator("#master-password");479    const confirm = popup.locator("#confirm-password");480    if (await pw.isVisible()) {481      await pw.fill("testpassword123");482      await confirm.fill("testpassword123");483      await popup.getByRole("button", { name: /create vault/i }).click();484      await popup.waitForTimeout(2500);485      const entries = popup.locator("[class*=entry]");486      await expect(entries.first()).toBeVisible({ timeout: 5000 });487    }488  });489490  test("41 - empty vault shows encouraging empty state message", async () => {491    const { popup } = await createVault(ctx);492    const emptyCopy = popup.locator("text=empty, text=no entries, text=get started, text=add your first");493    const hasMessage = await emptyCopy.first().isVisible({ timeout: 3000 }).catch(() => false);494    expect(hasMessage).toBeTruthy();495  });496497  test("42 - adding first entry has prominent CTA", async () => {498    const { popup } = await createVault(ctx);499    const cta = popup.locator("text=add entry, text=new entry, button:has-text('new')");500    await expect(cta.first()).toBeVisible();501  });502503  test("43 - header shows vault status (locked/unlocked)", async () => {504    const { popup } = await createVault(ctx);505    const status = popup.locator("[class*='status' i], [class*='badge' i]");506    const hasStatus = await status.first().isVisible({ timeout: 2000 }).catch(() => false);507    expect(hasStatus).toBeTruthy();508  });509510  test("44 - biometric unlock option shows if available", async () => {511    const { popup } = await loadPopup(ctx);512    const biometric = popup.locator("text=biometric, text=fingerprint, text=face id, text=touch id");513    const hasBio = await biometric.first().isVisible({ timeout: 2000 }).catch(() => false);514  });515516  test("45 - keyboard shortcut hint on lock screen", async () => {517    const { popup } = await loadPopup(ctx);518    const shortcut = popup.locator("text=enter, text=shortcut, text=ctrl+enter");519    const hasShortcut = await shortcut.first().isVisible({ timeout: 2000 }).catch(() => false);520  });521522  test("46 - remember master password option", async () => {523    const { popup } = await createVault(ctx);524    await popup.locator("[aria-label*='settings' i]").click();525    await popup.waitForTimeout(500);526    const remember = popup.locator("text=remember master password, text=master password reminder");527    const hasRemember = await remember.first().isVisible({ timeout: 2000 }).catch(() => false);528  });529530  test("47 - vault export encrypts with password", async () => {531    const { popup } = await createVault(ctx);532    await popup.locator("[aria-label*='settings' i]").click();533    await popup.waitForTimeout(300);534    await popup.getByRole("button", { name: /export/i }).click();535    await popup.waitForTimeout(500);536    const exportPw = popup.locator('input[type="password"]').first();537    await expect(exportPw).toBeVisible();538  });539540  test("48 - vault import shows file picker", async () => {541    const { popup } = await createVault(ctx);542    await popup.locator("[aria-label*='settings' i]").click();543    await popup.waitForTimeout(300);544    await popup.getByRole("button", { name: /import/i }).click();545    await popup.waitForTimeout(500);546    const fileInput = popup.locator('input[type="file"]');547    await expect(fileInput).toBeAttached();548  });549550  test("49 - password generator accessible from main view", async () => {551    const { popup } = await createVault(ctx);552    const genBtn = popup.locator("[aria-label*='generate' i], text=generator, text=generate password");553    await expect(genBtn.first()).toBeVisible({ timeout: 2000 });554  });555556  test("50 - search vault with no entries shows no results", async () => {557    const { popup } = await createVault(ctx);558    await popup.locator('input[placeholder*="search" i]').click();559    await popup.locator('input[placeholder*="search" i]').fill("nonexistent");560    await popup.waitForTimeout(500);561    const noResults = popup.locator("text=no results, text=not found, text=0 results");562    const hasNoResults = await noResults.first().isVisible({ timeout: 2000 }).catch(() => false);563    expect(hasNoResults).toBeTruthy();564  });565});566567test.describe("Vault Lifecycle Part 2", () => {568  let ctx: BrowserContext;569  test.afterEach(async () => { if (ctx) await ctx.close(); });570  test.beforeEach(async () => { ctx = await makeContext(); });571572  test("51 - changing auto-lock time persists", async () => {573    const { popup } = await createVault(ctx);574    await popup.locator("[aria-label*='settings' i]").click();575    await popup.waitForTimeout(300);576    await popup.locator("text=auto-lock").click();577    await popup.waitForTimeout(300);578    const option = popup.locator("[role='option' i], [role='menuitem' i]").nth(1);579    await option.click();580    await popup.waitForTimeout(300);581    await popup.keyboard.press("Escape");582    await popup.waitForTimeout(200);583    await popup.locator("[aria-label*='settings' i]").click();584    await popup.waitForTimeout(300);585    await popup.locator("text=auto-lock").click();586    await popup.waitForTimeout(300);587  });588589  test("52 - changing clipboard clear time persists", async () => {590    const { popup } = await createVault(ctx);591    await popup.locator("[aria-label*='settings' i]").click();592    await popup.waitForTimeout(300);593    await popup.locator("text=clipboard clear").click();594    await popup.waitForTimeout(300);595    const option = popup.locator("[role='option' i], [role='menuitem' i]").nth(1);596    await option.click();597    await popup.waitForTimeout(300);598    await popup.keyboard.press("Escape");599  });600601  test("53 - vault data survives browser restart", async () => {602    const { popup } = await createVault(ctx);603    await popup.locator('input[placeholder*="search" i]').click();604    await popup.getByRole("button", { name: /new entry/i }).click();605    await popup.locator(".peach-new-name-input").fill("RestartTest");606    await popup.locator('input[placeholder*="domain" i]').fill("https://restart.com");607    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");608    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("restartpass");609    await popup.getByRole("button", { name: /create/i }).click();610    await popup.waitForTimeout(1500);611  });612613  test("54 - clearing vault shows confirmation", async () => {614    const { popup } = await createVault(ctx);615    await popup.locator("[aria-label*='settings' i]").click();616    await popup.waitForTimeout(300);617    const dangerZone = popup.locator("text=clear vault, text=delete all, text=destroy vault");618    const hasDanger = await dangerZone.first().isVisible({ timeout: 2000 }).catch(() => false);619  });620621  test("55 - clear vault requires password confirmation", async () => {622    const { popup } = await createVault(ctx);623    await popup.locator("[aria-label*='settings' i]").click();624    await popup.waitForTimeout(300);625  });626});