copy-totp.spec.ts

7.3 KB · raw

1import { test, expect } from "@playwright/test";2import type { BrowserContext } from "@playwright/test";3import { fileURLToPath } from "url";4import { dirname, join } from "path";5import os from "os";6import fs from "fs/promises";78const __filename = fileURLToPath(import.meta.url);9const __dirname = dirname(__filename);10const EXTENSION_PATH = join(__dirname, "..", "dist");1112async 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}2122async 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}4445test.describe("Copy & TOTP", () => {46  let ctx: BrowserContext;47  test.afterEach(async () => { await ctx.close(); });48  test.beforeEach(async () => { ctx = await makeContext(); });4950  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  });6768  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  });8586  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  });102103  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  });115116  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  });128129  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  });138139  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});