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("Autofill Content Script", () => {46 let ctx: BrowserContext;47 test.afterEach(async () => { await ctx.close(); });48 test.beforeEach(async () => { ctx = await makeContext(); });4950 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 });6667 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 });8990 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 });107108 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});121122test.describe("Import Export", () => {123 let ctx: BrowserContext;124 test.afterEach(async () => { await ctx.close(); });125 test.beforeEach(async () => { ctx = await makeContext(); });126127 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 });146147 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});