vault-lifecycle.spec.ts

27.0 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}5152test.describe("Vault Lifecycle", () => {53  let ctx: BrowserContext;5455  test.afterEach(async () => { if (ctx) await ctx.close(); });56  test.beforeEach(async () => { ctx = await makeContext(); });5758  test("01 - fresh install shows onboarding create flow", async () => {59    const { popup } = await loadPopup(ctx);60    await popup.waitForTimeout(500);61    const preLock = popup.locator("#pre-lock");62    if (await preLock.isVisible().catch(() => false)) {63      await preLock.waitFor({ state: "hidden", timeout: 5000 }).catch(() => {});64    }65    const headline = popup.locator(".peach-onboarding-headline, .peach-unlock-headline").first();66    await expect(headline).toBeVisible({ timeout: 5000 });67    const createBtn = popup.getByRole("button", { name: /create vault/i });68    await expect(createBtn).toBeVisible();69  });7071  test("02 - create vault with minimum 8-char password", async () => {72    const { popup } = await createVault(ctx, "12345678");73    const entry = popup.locator(".peach-entry-name, .peach-ghost, [class*=entry]").first();74    await expect(entry).toBeVisible({ timeout: 5000 });75  });7677  test("03 - create vault with 8-char password succeeds", async () => {78    const { popup } = await createVault(ctx, "abcdefgh");79    const entries = popup.locator("[class*=entry]");80    await expect(entries.first()).toBeVisible({ timeout: 5000 });81  });8283  test("04 - create vault with long password succeeds", async () => {84    const { popup } = await createVault(ctx, "thisisaverylongpasswordthatwemightuse123!@#");85    const entries = popup.locator("[class*=entry]");86    await expect(entries.first()).toBeVisible({ timeout: 5000 });87  });8889  test("05 - create vault with special chars in password", async () => {90    const { popup } = await createVault(ctx, "P@$$w0rd!#%^&*()_+-=[]{}|;':\",./<>?");91    const entries = popup.locator("[class*=entry]");92    await expect(entries.first()).toBeVisible({ timeout: 5000 });93  });9495  test("06 - create vault with unicode password", async () => {96    const { popup } = await createVault(ctx, "пароль密码🔐🔑");97    const entries = popup.locator("[class*=entry]");98    await expect(entries.first()).toBeVisible({ timeout: 5000 });99  });100101  test("07 - create vault with emoji password", async () => {102    const { popup } = await createVault(ctx, "🔐🔑🌟💻🖥️⌨️");103    const entries = popup.locator("[class*=entry]");104    await expect(entries.first()).toBeVisible({ timeout: 5000 });105  });106107  test("08 - wrong password shows shake animation", async () => {108    await createVault(ctx);109    await new Promise(r => setTimeout(r, 500));110    const newPopup = await ctx.newPage();111    const extId = (await loadPopup(ctx)).extId;112    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);113    await newPopup.waitForTimeout(1500);114    await newPopup.locator("#master-password").fill("wrongpassword");115    await newPopup.getByRole("button", { name: /unlock/i }).click();116    await newPopup.waitForTimeout(500);117    const error = newPopup.locator(".peach-hint-error, .peach-unlock-headline");118    await expect(error).toBeVisible({ timeout: 3000 });119  });120121  test("09 - wrong password does not unlock vault", async () => {122    await createVault(ctx);123    const newPopup = await ctx.newPage();124    const extId = (await loadPopup(ctx)).extId;125    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);126    await newPopup.waitForTimeout(1500);127    await newPopup.locator("#master-password").fill("wrongpassword");128    await newPopup.getByRole("button", { name: /unlock/i }).click();129    await newPopup.waitForTimeout(1000);130    const lockStillVisible = await newPopup.locator("#master-password").isVisible();131    expect(lockStillVisible).toBe(true);132  });133134  test("10 - correct password unlocks vault", async () => {135    await createVault(ctx);136    const newPopup = await ctx.newPage();137    const extId = (await loadPopup(ctx)).extId;138    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);139    await newPopup.waitForTimeout(1500);140    await newPopup.locator("#master-password").fill("testpassword123");141    await newPopup.getByRole("button", { name: /unlock/i }).click();142    await newPopup.waitForTimeout(2000);143    const entries = newPopup.locator("[class*=entry]");144    await expect(entries.first()).toBeVisible({ timeout: 5000 });145  });146147  test("11 - lock button returns to lock screen", async () => {148    const { popup } = await createVault(ctx);149    await popup.waitForTimeout(500);150    const lockBtn = popup.locator(".peach-lock-btn[aria-label='Lock vault']");151    await expect(lockBtn).toBeVisible({ timeout: 3000 });152    await lockBtn.click();153    await popup.waitForTimeout(1500);154    const pwField = popup.locator("#master-password");155    await expect(pwField).toBeVisible({ timeout: 5000 });156  });157158  test("12 - clicking lock icon in header locks vault", async () => {159    const { popup } = await createVault(ctx);160    const headerLock = popup.locator("[aria-label*='lock' i]").first();161    if (await headerLock.isVisible()) {162      await headerLock.click();163      await popup.waitForTimeout(1000);164      await expect(popup.locator("#master-password")).toBeVisible({ timeout: 3000 });165    }166  });167168  test("13 - lock preserves vault data", async () => {169    const { popup } = await createVault(ctx);170    await popup.locator('input[placeholder*="search" i]').click();171    await popup.getByRole("button", { name: /new entry/i }).click();172    await popup.locator(".peach-new-name-input").fill("TestEntry");173    await popup.locator('input[placeholder*="domain" i]').fill("https://test.com");174    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");175    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("secret123");176    await popup.getByRole("button", { name: /create/i }).click();177    await popup.waitForTimeout(1500);178    const lockBtn = popup.locator(".peach-lock-btn, [aria-label*='lock' i]").first();179    await lockBtn.click();180    await popup.waitForTimeout(1000);181    await popup.locator("#master-password").fill("testpassword123");182    await popup.getByRole("button", { name: /unlock/i }).click();183    await popup.waitForTimeout(2000);184    await expect(popup.locator("text=TestEntry").first()).toBeVisible({ timeout: 5000 });185  });186187  test("14 - auto-lock after 1 minute configured in settings", async () => {188    const { popup } = await createVault(ctx);189    await popup.locator("[aria-label*='settings' i]").click();190    await popup.waitForTimeout(500);191    const autoLockRow = popup.locator("text=auto-lock").first();192    await expect(autoLockRow).toBeVisible();193  });194195  test("15 - auto-lock options: 1min, 5min, 15min, 1hr, never", async () => {196    const { popup } = await createVault(ctx);197    await popup.locator("[aria-label*='settings' i]").click();198    await popup.waitForTimeout(500);199    await popup.locator("text=auto-lock").click();200    await popup.waitForTimeout(300);201    const options = popup.locator("[role='option' i], [role='menuitem' i], .peach-select-option");202    const count = await options.count();203    expect(count).toBeGreaterThanOrEqual(4);204  });205206  test("16 - service worker restart shows lock screen", async () => {207    const { extId } = await loadPopup(ctx);208    await createVault(ctx);209    const newPopup = await ctx.newPage();210    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);211    await newPopup.waitForTimeout(1500);212    await expect(newPopup.locator("#master-password")).toBeVisible({ timeout: 3000 });213  });214215  test("17 - multiple rapid lock/unlock cycles work", async () => {216    const { popup } = await createVault(ctx);217    for (let i = 0; i < 3; i++) {218      const lockBtn = popup.locator(".peach-lock-btn, [aria-label*='lock' i]").first();219      await lockBtn.click();220      await popup.waitForTimeout(500);221      await popup.locator("#master-password").fill("testpassword123");222      await popup.getByRole("button", { name: /unlock/i }).click();223      await popup.waitForTimeout(1000);224    }225    await expect(popup.locator("[class*=entry]").first()).toBeVisible({ timeout: 5000 });226  });227228  test("18 - vault data persists after extension reload", async () => {229    const { popup } = await createVault(ctx);230    await popup.locator('input[placeholder*="search" i]').click();231    await popup.getByRole("button", { name: /new entry/i }).click();232    await popup.locator(".peach-new-name-input").fill("PersistTest");233    await popup.locator('input[placeholder*="domain" i]').fill("https://persist.com");234    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");235    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("persistentpass");236    await popup.getByRole("button", { name: /create/i }).click();237    await popup.waitForTimeout(1500);238    const extId = (await loadPopup(ctx)).extId;239    const newPopup = await ctx.newPage();240    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);241    await newPopup.waitForTimeout(2000);242    await newPopup.locator("#master-password").fill("testpassword123");243    await newPopup.getByRole("button", { name: /unlock/i }).click();244    await newPopup.waitForTimeout(2000);245    await expect(newPopup.locator("text=PersistTest")).toBeVisible({ timeout: 5000 });246  });247248  test("19 - creating vault shows empty state", async () => {249    const { popup } = await createVault(ctx);250    const emptyState = popup.locator("text=Your vault is ready, text=first entry, .peach-empty-copy").first();251    const hasEmptyState = await emptyState.isVisible().catch(() => false);252    expect(hasEmptyState).toBeTruthy();253  });254255  test("20 - vault with entries does not show empty state", async () => {256    const { popup } = await createVault(ctx);257    await popup.locator('input[placeholder*="search" i]').click();258    await popup.getByRole("button", { name: /new entry/i }).click();259    await popup.locator(".peach-new-name-input").fill("FirstEntry");260    await popup.locator('input[placeholder*="domain" i]').fill("https://first.com");261    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");262    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("password123");263    await popup.getByRole("button", { name: /create/i }).click();264    await popup.waitForTimeout(1500);265    const emptyState = popup.locator("text=empty, text=no entries").first();266    await expect(emptyState).not.toBeVisible();267  });268269  test("21 - password field shows dots by default on lock screen", async () => {270    await loadPopup(ctx);271    await createVault(ctx);272    const newPopup = await ctx.newPage();273    const extId = (await loadPopup(ctx)).extId;274    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);275    await newPopup.waitForTimeout(1500);276    const pwInput = newPopup.locator("#master-password");277    const type = await pwInput.getAttribute("type");278    expect(type).toBe("password");279  });280281  test("22 - reveal password toggle on lock screen", async () => {282    await loadPopup(ctx);283    await createVault(ctx);284    const newPopup = await ctx.newPage();285    const extId = (await loadPopup(ctx)).extId;286    await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);287    await newPopup.waitForTimeout(1500);288    const revealBtn = newPopup.locator("[aria-label*='show' i], [aria-label*='reveal' i]").first();289    if (await revealBtn.isVisible({ timeout: 2000 }).catch(() => false)) {290      await revealBtn.click();291      const pwInput = newPopup.locator("#master-password");292      const type = await pwInput.getAttribute("type");293      expect(type).toBe("text");294    }295  });296297  test("23 - clipboard clear option in settings", async () => {298    const { popup } = await createVault(ctx);299    await popup.locator("[aria-label*='settings' i]").click();300    await popup.waitForTimeout(500);301    await expect(popup.locator("text=clipboard clear")).toBeVisible();302  });303304  test("24 - clipboard clear time options: 30s, 60s, 90s, never", async () => {305    const { popup } = await createVault(ctx);306    await popup.locator("[aria-label*='settings' i]").click();307    await popup.waitForTimeout(500);308    await popup.locator("text=clipboard clear").click();309    await popup.waitForTimeout(300);310    const options = popup.locator("[role='option' i], [role='menuitem' i]");311    const count = await options.count();312    expect(count).toBeGreaterThanOrEqual(3);313  });314315  test("25 - lock on close option in settings", async () => {316    const { popup } = await createVault(ctx);317    await popup.locator("[aria-label*='settings' i]").click();318    await popup.waitForTimeout(500);319    await expect(popup.locator("text=lock on close")).toBeVisible();320  });321322  test("26 - lock on close toggle works", async () => {323    const { popup } = await createVault(ctx);324    await popup.locator("[aria-label*='settings' i]").click();325    await popup.waitForTimeout(500);326    const toggle = popup.locator("text=lock on close").locator("..").locator("[role='switch' i], [type='checkbox']").first();327    if (await toggle.isVisible({ timeout: 2000 }).catch(() => false)) {328      await toggle.click();329      await popup.waitForTimeout(300);330    }331  });332333  test("27 - breach monitoring toggle in settings", async () => {334    const { popup } = await createVault(ctx);335    await popup.locator("[aria-label*='settings' i]").click();336    await popup.waitForTimeout(500);337    await expect(popup.locator("text=breach monitoring")).toBeVisible();338  });339340  test("28 - vault name displays in header", async () => {341    const { popup } = await createVault(ctx);342    const header = popup.locator("[class*='header' i], [class*='title' i]").first();343    const hasHeader = await header.isVisible({ timeout: 2000 }).catch(() => false);344    expect(hasHeader).toBeTruthy();345  });346347  test("29 - vault entry count shows in settings", async () => {348    const { popup } = await createVault(ctx);349    await popup.locator("[aria-label*='settings' i]").click();350    await popup.waitForTimeout(500);351    await expect(popup.locator("text=entries").first()).toBeVisible();352  });353354  test("30 - version number shows in settings", async () => {355    const { popup } = await createVault(ctx);356    await popup.locator("[aria-label*='settings' i]").click();357    await popup.waitForTimeout(500);358    const version = popup.locator("text=1.0.");359    const hasVersion = await version.isVisible({ timeout: 2000 }).catch(() => false);360    expect(hasVersion).toBeTruthy();361  });362363  test("31 - syncing indicator shows for pro users", async () => {364    const { popup } = await createVault(ctx);365    await popup.locator("[aria-label*='settings' i]").click();366    await popup.waitForTimeout(500);367    const syncRow = popup.locator("text=sync");368    await expect(syncRow).toBeVisible();369  });370371  test("32 - pro badge shows next to sync option", async () => {372    const { popup } = await createVault(ctx);373    await popup.locator("[aria-label*='settings' i]").click();374    await popup.waitForTimeout(500);375    const proBadge = popup.locator(".peach-pro-badge, [class*='pro' i]");376    const hasBadge = await proBadge.isVisible({ timeout: 2000 }).catch(() => false);377    expect(hasBadge).toBeTruthy();378  });379380  test("33 - lock screen has peach branding", async () => {381    const { popup } = await loadPopup(ctx);382    const brandElements = popup.locator(".peach-wordmark, .wordmark, text=peach, [class*='brand' i]");383    const hasBrand = await brandElements.first().isVisible({ timeout: 3000 }).catch(() => false);384    expect(hasBrand).toBeTruthy();385  });386387  test("34 - unlock screen shows tagline", async () => {388    const { popup } = await loadPopup(ctx);389    const tagline = popup.locator("text=secrets, text=your secrets, [class*='tagline' i]");390    const hasTagline = await tagline.first().isVisible({ timeout: 3000 }).catch(() => false);391    expect(hasTagline).toBeTruthy();392  });393394  test("35 - password strength indicator during vault creation", async () => {395    const { popup } = await loadPopup(ctx);396    const pw = popup.locator("#master-password");397    if (await pw.isVisible()) {398      await pw.fill("weak");399      await popup.waitForTimeout(300);400      const strength = popup.locator("[class*='strength' i], text=weak, text=strong");401      const hasStrength = await strength.first().isVisible({ timeout: 2000 }).catch(() => false);402      expect(hasStrength).toBeTruthy();403    }404  });405406  test("36 - password strength updates as user types", async () => {407    const { popup } = await loadPopup(ctx);408    const pw = popup.locator("#master-password");409    if (await pw.isVisible()) {410      await pw.fill("w");411      await popup.waitForTimeout(200);412      await pw.fill("we");413      await popup.waitForTimeout(200);414      await pw.fill("weak");415      await popup.waitForTimeout(200);416      await pw.fill("WeakPassword123!");417      await popup.waitForTimeout(200);418    }419  });420421  test("37 - passwords must match on create", async () => {422    const { popup } = await loadPopup(ctx);423    const pw = popup.locator("#master-password");424    const confirm = popup.locator("#confirm-password");425    if (await pw.isVisible()) {426      await pw.fill("password123");427      await confirm.fill("differentpassword");428      await popup.getByRole("button", { name: /create vault/i }).click();429      await popup.waitForTimeout(500);430      const error = popup.locator("text=mismatch, text=do not match, text=not match");431      await expect(error.first()).toBeVisible({ timeout: 3000 });432    }433  });434435  test("38 - passwords must be at least 8 chars", async () => {436    const { popup } = await loadPopup(ctx);437    const pw = popup.locator("#master-password");438    const confirm = popup.locator("#confirm-password");439    if (await pw.isVisible()) {440      await pw.fill("1234567");441      await confirm.fill("1234567");442      await popup.getByRole("button", { name: /create vault/i }).click();443      await popup.waitForTimeout(500);444      const error = popup.locator("text=8 characters, text=minimum, text=at least");445      await expect(error.first()).toBeVisible({ timeout: 3000 });446    }447  });448449  test("39 - vault creation shows loading state", async () => {450    const { popup } = await loadPopup(ctx);451    const pw = popup.locator("#master-password");452    const confirm = popup.locator("#confirm-password");453    if (await pw.isVisible()) {454      await pw.fill("testpassword123");455      await confirm.fill("testpassword123");456      await popup.getByRole("button", { name: /create vault/i }).click();457      await popup.waitForTimeout(300);458      const loading = popup.locator("[class*='loading' i], [class*='spinner' i], text=creating");459      const hasLoading = await loading.first().isVisible({ timeout: 3000 }).catch(() => false);460      expect(hasLoading).toBeTruthy();461    }462  });463464  test("40 - successful vault creation transitions to unlocked state", async () => {465    const { popup } = await loadPopup(ctx);466    const pw = popup.locator("#master-password");467    const confirm = popup.locator("#confirm-password");468    if (await pw.isVisible()) {469      await pw.fill("testpassword123");470      await confirm.fill("testpassword123");471      await popup.getByRole("button", { name: /create vault/i }).click();472      await popup.waitForTimeout(2500);473      const entries = popup.locator("[class*=entry]");474      await expect(entries.first()).toBeVisible({ timeout: 5000 });475    }476  });477478  test("41 - empty vault shows encouraging empty state message", async () => {479    const { popup } = await createVault(ctx);480    const emptyCopy = popup.locator("text=empty, text=no entries, text=get started, text=add your first");481    const hasMessage = await emptyCopy.first().isVisible({ timeout: 3000 }).catch(() => false);482    expect(hasMessage).toBeTruthy();483  });484485  test("42 - adding first entry has prominent CTA", async () => {486    const { popup } = await createVault(ctx);487    const cta = popup.locator("text=add entry, text=new entry, button:has-text('new')");488    await expect(cta.first()).toBeVisible();489  });490491  test("43 - header shows vault status (locked/unlocked)", async () => {492    const { popup } = await createVault(ctx);493    const status = popup.locator("[class*='status' i], [class*='badge' i]");494    const hasStatus = await status.first().isVisible({ timeout: 2000 }).catch(() => false);495    expect(hasStatus).toBeTruthy();496  });497498  test("44 - biometric unlock option shows if available", async () => {499    const { popup } = await loadPopup(ctx);500    const biometric = popup.locator("text=biometric, text=fingerprint, text=face id, text=touch id");501    await biometric.first().isVisible({ timeout: 2000 }).catch(() => false);502  });503504  test("45 - keyboard shortcut hint on lock screen", async () => {505    const { popup } = await loadPopup(ctx);506    const shortcut = popup.locator("text=enter, text=shortcut, text=ctrl+enter");507    await shortcut.first().isVisible({ timeout: 2000 }).catch(() => false);508  });509510  test("46 - remember master password option", async () => {511    const { popup } = await createVault(ctx);512    await popup.locator("[aria-label*='settings' i]").click();513    await popup.waitForTimeout(500);514    const remember = popup.locator("text=remember master password, text=master password reminder");515    await remember.first().isVisible({ timeout: 2000 }).catch(() => false);516  });517518  test("47 - vault export encrypts with password", async () => {519    const { popup } = await createVault(ctx);520    await popup.locator("[aria-label*='settings' i]").click();521    await popup.waitForTimeout(300);522    await popup.getByRole("button", { name: /export/i }).click();523    await popup.waitForTimeout(500);524    const exportPw = popup.locator('input[type="password"]').first();525    await expect(exportPw).toBeVisible();526  });527528  test("48 - vault import shows file picker", async () => {529    const { popup } = await createVault(ctx);530    await popup.locator("[aria-label*='settings' i]").click();531    await popup.waitForTimeout(300);532    await popup.getByRole("button", { name: /import/i }).click();533    await popup.waitForTimeout(500);534    const fileInput = popup.locator('input[type="file"]');535    await expect(fileInput).toBeAttached();536  });537538  test("49 - password generator accessible from main view", async () => {539    const { popup } = await createVault(ctx);540    const genBtn = popup.locator("[aria-label*='generate' i], text=generator, text=generate password");541    await expect(genBtn.first()).toBeVisible({ timeout: 2000 });542  });543544  test("50 - search vault with no entries shows no results", async () => {545    const { popup } = await createVault(ctx);546    await popup.locator('input[placeholder*="search" i]').click();547    await popup.locator('input[placeholder*="search" i]').fill("nonexistent");548    await popup.waitForTimeout(500);549    const noResults = popup.locator("text=no results, text=not found, text=0 results");550    const hasNoResults = await noResults.first().isVisible({ timeout: 2000 }).catch(() => false);551    expect(hasNoResults).toBeTruthy();552  });553});554555test.describe("Vault Lifecycle Part 2", () => {556  let ctx: BrowserContext;557  test.afterEach(async () => { if (ctx) await ctx.close(); });558  test.beforeEach(async () => { ctx = await makeContext(); });559560  test("51 - changing auto-lock time persists", async () => {561    const { popup } = await createVault(ctx);562    await popup.locator("[aria-label*='settings' i]").click();563    await popup.waitForTimeout(300);564    await popup.locator("text=auto-lock").click();565    await popup.waitForTimeout(300);566    const option = popup.locator("[role='option' i], [role='menuitem' i]").nth(1);567    await option.click();568    await popup.waitForTimeout(300);569    await popup.keyboard.press("Escape");570    await popup.waitForTimeout(200);571    await popup.locator("[aria-label*='settings' i]").click();572    await popup.waitForTimeout(300);573    await popup.locator("text=auto-lock").click();574    await popup.waitForTimeout(300);575  });576577  test("52 - changing clipboard clear time persists", async () => {578    const { popup } = await createVault(ctx);579    await popup.locator("[aria-label*='settings' i]").click();580    await popup.waitForTimeout(300);581    await popup.locator("text=clipboard clear").click();582    await popup.waitForTimeout(300);583    const option = popup.locator("[role='option' i], [role='menuitem' i]").nth(1);584    await option.click();585    await popup.waitForTimeout(300);586    await popup.keyboard.press("Escape");587  });588589  test("53 - vault data survives browser restart", async () => {590    const { popup } = await createVault(ctx);591    await popup.locator('input[placeholder*="search" i]').click();592    await popup.getByRole("button", { name: /new entry/i }).click();593    await popup.locator(".peach-new-name-input").fill("RestartTest");594    await popup.locator('input[placeholder*="domain" i]').fill("https://restart.com");595    await popup.locator('input[placeholder*="username" i]').fill("[email protected]");596    await popup.locator(".peach-new-field-input[placeholder*='password' i]").fill("restartpass");597    await popup.getByRole("button", { name: /create/i }).click();598    await popup.waitForTimeout(1500);599  });600601  test("54 - clearing vault shows confirmation", async () => {602    const { popup } = await createVault(ctx);603    await popup.locator("[aria-label*='settings' i]").click();604    await popup.waitForTimeout(300);605    const dangerZone = popup.locator("text=clear vault, text=delete all, text=destroy vault");606    await dangerZone.first().isVisible({ timeout: 2000 }).catch(() => false);607  });608609  test("55 - clear vault requires password confirmation", async () => {610    const { popup } = await createVault(ctx);611    await popup.locator("[aria-label*='settings' i]").click();612    await popup.waitForTimeout(300);613  });614});