vault-lifecycle.spec.ts

6.2 KB · raw

1import { test, expect, type BrowserContext } from "@playwright/test";2import {3  UNLOCKED,4  VAULT_PASSWORD,5  addEntry,6  loadPopup,7  lockVault,8  makeContext,9  setupVault,10  unlockVault,11} from "./helpers/vault";1213// Every test creates a real vault (onboarding + Argon2id KDF + recovery14// confirmation ≈ 12s) and several unlock again, which runs Argon2 a second15// time. The global 30s cap is too tight for those.16test.describe.configure({ timeout: 90_000 });1718// This file used to hold 55 tests written against a UI that no longer exists19// (an 8-character password minimum, a "1.0." version string, a pro badge, a20// settings sheet reached by aria-label='settings'). None of them ever ran21// against a real vault, because the setup helper looked for #master-password22// while the onboarding carousel was still on screen and silently skipped23// vault creation. What is left is the part that is both real and not already24// covered by lock-screen.spec.ts: the create-vault gate, and persistence.2526/** Advance the intro carousel until the create-vault form is on screen. */27async function reachCreateScreen(popup: import("@playwright/test").Page) {28  const pw = popup.locator("#master-password");29  for (let i = 0; i < 5 && !(await pw.isVisible().catch(() => false)); i++) {30    await popup.locator(".peach-onboarding-card").click();31    await popup.waitForTimeout(300);32  }33  await expect(pw).toBeVisible();34}3536test.describe("Vault creation gate", () => {37  let ctx: BrowserContext;38  test.afterEach(async () => { if (ctx) await ctx.close(); });39  test.beforeEach(async () => { ctx = await makeContext("peach-lifecycle"); });4041  test("VL01 - fresh install opens the intro carousel", async () => {42    const { popup } = await loadPopup(ctx);43    await expect(popup.locator(".peach-onboarding-card")).toBeVisible({ timeout: 5000 });44    await expect(popup.locator("#master-password")).toHaveCount(0);45  });4647  test("VL02 - the carousel leads to the create-vault form", async () => {48    const { popup } = await loadPopup(ctx);49    await reachCreateScreen(popup);50    await expect(popup.locator("#confirm-password")).toBeVisible();51    await expect(popup.locator(".peach-newvault-submit")).toBeDisabled();52  });5354  test("VL03 - a password under 12 characters blocks creation", async () => {55    const { popup } = await loadPopup(ctx);56    await reachCreateScreen(popup);57    await popup.locator("#master-password").fill("shortpw12");58    await popup.locator("#confirm-password").fill("shortpw12");59    const submit = popup.locator(".peach-newvault-submit");60    await expect(submit).toBeDisabled();61    await expect(submit).toHaveText(/more characters/i);62  });6364  test("VL04 - a long but weak password blocks creation", async () => {65    const { popup } = await loadPopup(ctx);66    await reachCreateScreen(popup);67    await popup.locator("#master-password").fill("password123456");68    await popup.locator("#confirm-password").fill("password123456");69    const submit = popup.locator(".peach-newvault-submit");70    await expect(submit).toBeDisabled();71    await expect(submit).toHaveText(/too weak/i);72  });7374  test("VL05 - mismatched confirmation blocks creation", async () => {75    const { popup } = await loadPopup(ctx);76    await reachCreateScreen(popup);77    await popup.locator("#master-password").fill(VAULT_PASSWORD);78    await popup.locator("#confirm-password").fill("correct horse battery stapl");79    const submit = popup.locator(".peach-newvault-submit");80    await expect(submit).toBeDisabled();81    await expect(submit).toHaveText(/don't match/i);82  });8384  test("VL06 - the strength meter reacts to the password", async () => {85    const { popup } = await loadPopup(ctx);86    await reachCreateScreen(popup);87    const label = popup.locator(".peach-strength-label");88    await popup.locator("#master-password").fill("aaaa");89    await expect(label).toHaveText("weak");90    await popup.locator("#master-password").fill(VAULT_PASSWORD);91    await expect(label).toHaveText(/good|strong/);92  });9394  test("VL07 - a passing password creates the vault and unlocks it", async () => {95    const { popup } = await setupVault(ctx);96    await expect(popup.locator(UNLOCKED)).toBeVisible();97  });98});99100test.describe("Vault persistence", () => {101  let ctx: BrowserContext;102  test.afterEach(async () => { if (ctx) await ctx.close(); });103  test.beforeEach(async () => { ctx = await makeContext("peach-lifecycle"); });104105  test("VL08 - a new vault shows the empty state", async () => {106    const { popup } = await setupVault(ctx);107    await expect(popup.locator(".peach-empty-copy")).toBeVisible({ timeout: 5000 });108  });109110  test("VL09 - entries survive lock and unlock", async () => {111    const { popup } = await setupVault(ctx);112    await addEntry(popup, { title: "LockRoundTrip", url: "https://roundtrip.test", username: "[email protected]", password: "entry-secret" });113    await lockVault(popup);114    await unlockVault(popup);115    await expect(popup.locator(".peach-entry-name-text").first()).toHaveText("LockRoundTrip", { timeout: 10000 });116  });117118  test("VL10 - reopening the popup requires the password again", async () => {119    const { popup, extId } = await setupVault(ctx);120    await addEntry(popup, { title: "ReopenTest", url: "https://reopen.test", username: "[email protected]", password: "entry-secret" });121    await popup.close();122123    const reopened = await ctx.newPage();124    await reopened.goto(`chrome-extension://${extId}/src/popup/index.html`);125    await expect(reopened.locator("#unlock-password")).toBeVisible({ timeout: 15000 });126    await unlockVault(reopened);127    await expect(reopened.locator(".peach-entry-name-text").first()).toHaveText("ReopenTest", { timeout: 10000 });128  });129130  test("VL11 - a wrong password does not unlock a reopened popup", async () => {131    const { popup, extId } = await setupVault(ctx);132    await popup.close();133134    const reopened = await ctx.newPage();135    await reopened.goto(`chrome-extension://${extId}/src/popup/index.html`);136    await expect(reopened.locator("#unlock-password")).toBeVisible({ timeout: 15000 });137    await reopened.locator("#unlock-password").fill("not the right password");138    await reopened.getByRole("button", { name: /unlock/i }).click();139    await expect(reopened.locator(".peach-hint-error")).toBeVisible({ timeout: 15000 });140    await expect(reopened.locator(UNLOCKED)).toHaveCount(0);141  });142});