1234567891011121314151617181920212223242526import { test, expect } from "@playwright/test";27import type { BrowserContext, Page } from "@playwright/test";28import { fileURLToPath } from "url";29import { dirname, join } from "path";30import os from "os";31import fs from "fs/promises";3233const __filename = fileURLToPath(import.meta.url);34const __dirname = dirname(__filename);35const EXTENSION_PATH = join(__dirname, "..", "..", "..", "dist");3637const FIXTURE = join(__dirname, "login-form.fixture.html");38const ICON = 'button[aria-label="Peach autofill"]';3940async function makeContext(): Promise<BrowserContext> {41 const { chromium } = await import("playwright");42 const userDataDir = join(os.tmpdir(), `peach-e2e-${Date.now()}`);43 await fs.mkdir(userDataDir, { recursive: true });44 return chromium.launchPersistentContext(userDataDir, {45 headless: false,46 args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],47 });48}4950515253async function setupVaultWithEntry(_ctx: BrowserContext, _origin: string): Promise<void> {54 55 56}5758596061async function openFixture(ctx: BrowserContext): Promise<{ page: Page; origin: string }> {62 const page = await ctx.newPage();63 await page.goto(`file://${FIXTURE}`); 64 return { page, origin: new URL(page.url()).origin };65}6667test.describe("Real-page autofill e2e (T-E01–T-E10)", () => {68 let ctx: BrowserContext;6970 test.beforeAll(async () => {71 ctx = await makeContext();72 });73 test.afterAll(async () => {74 await ctx?.close();75 });7677 78 test("T-E01: field icon appears in the password field", async () => {79 const { page } = await openFixture(ctx);80 await expect(page.locator(ICON)).toBeVisible();81 await page.close();82 });8384 85 86 test("T-E02: clicking the icon opens the selector for domain-matched entries", async () => {87 const { page, origin } = await openFixture(ctx);88 await setupVaultWithEntry(ctx, origin);89 await page.locator(ICON).click();90 await expect(page.locator('div[style*="2147483647"]')).toBeVisible();91 await page.close();92 });9394 95 96 test("T-E03: selecting an entry fills username and password", async () => {97 const { page, origin } = await openFixture(ctx);98 await setupVaultWithEntry(ctx, origin);99 await page.locator(ICON).click();100 101 102 103 await page.keyboard.press("Enter");104 await expect(page.locator("#username")).toHaveValue("alice");105 await expect(page.locator("#password")).toHaveValue("s3cret");106 await page.close();107 });108109 110 test("T-E04: after fill the selector closes, the icon remains", async () => {111 const { page, origin } = await openFixture(ctx);112 await setupVaultWithEntry(ctx, origin);113 await page.locator(ICON).click();114 await page.keyboard.press("Enter");115 await expect(page.locator('div[style*="2147483647"]')).toHaveCount(0);116 await expect(page.locator(ICON)).toBeVisible();117 await page.close();118 });119120 121 122 test("T-E05: clicking the filled field within the grace window does not reopen", async () => {123 const { page, origin } = await openFixture(ctx);124 await setupVaultWithEntry(ctx, origin);125 await page.locator(ICON).click();126 await page.keyboard.press("Enter");127 await page.locator("#password").click();128 await expect(page.locator('div[style*="2147483647"]')).toHaveCount(0);129 await page.close();130 });131132 133 test("T-E06: after the grace window the selector opens again", async () => {134 const { page, origin } = await openFixture(ctx);135 await setupVaultWithEntry(ctx, origin);136 await page.locator(ICON).click();137 await page.keyboard.press("Enter");138 await page.waitForTimeout(3200); 139 await page.locator("#password").click();140 await expect(page.locator('div[style*="2147483647"]')).toBeVisible();141 await page.close();142 });143144 145 146 test("T-E07: form submit after fill does not re-prompt to save unchanged creds", async () => {147 const { page, origin } = await openFixture(ctx);148 await setupVaultWithEntry(ctx, origin);149 await page.locator(ICON).click();150 await page.keyboard.press("Enter");151 await page.evaluate(() => (document.getElementById("login-form") as HTMLFormElement).requestSubmit());152 153 await page.waitForTimeout(500);154 await expect(page.locator('div[style*="2147483647"]')).toHaveCount(0);155 await page.close();156 });157158 159 160 test("T-E08: email-first form gets an email icon and opens", async () => {161 const { page } = await openFixture(ctx);162 await page.evaluate(() => ((document.getElementById("email-first-form") as HTMLElement).style.display = "block"));163 await expect(page.locator(ICON)).toHaveCount(1); 164 await page.close();165 });166167 168 test("T-E09: signup form detection shows the identity surface", async () => {169 const { page } = await openFixture(ctx);170 await page.evaluate(() => ((document.getElementById("signup-form") as HTMLElement).style.display = "block"));171 await page.locator("#new-password").click();172 await expect(page.locator('div[style*="2147483647"]')).toBeVisible();173 await page.close();174 });175176 177 178 test("T-E10: cross-origin iframe present → no crash, main form still fills", async () => {179 const { page, origin } = await openFixture(ctx);180 await setupVaultWithEntry(ctx, origin);181 await page.evaluate(() => {182 const f = document.createElement("iframe");183 f.src = "https://example.com/";184 document.body.appendChild(f);185 });186 await page.locator(ICON).first().click();187 await page.keyboard.press("Enter");188 await expect(page.locator("#password")).toHaveValue("s3cret");189 await page.close();190 });191});