1import { test, expect, 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 makeContext() {12 const { chromium } = await import("playwright");13 const userDataDir = join(os.tmpdir(), `peach-totp-${Date.now()}`);14 await fs.mkdir(userDataDir, { recursive: true });15 return chromium.launchPersistentContext(userDataDir, {16 headless: false,17 args: [`--load-extension=${EXTENSION_PATH}`, `--disable-extensions-except=${EXTENSION_PATH}`],18 });19}2021async function waitForSW(ctx: BrowserContext, maxWait = 15000): Promise<string> {22 const start = Date.now();23 let sw = ctx.serviceWorkers()[0];24 while (!sw && Date.now() - start < maxWait) {25 await new Promise(r => setTimeout(r, 500));26 sw = ctx.serviceWorkers()[0];27 }28 if (!sw) throw new Error(`No SW after ${maxWait}ms`);29 return new URL(sw.url()).host;30}3132async function setupVault(ctx: BrowserContext): Promise<{ popup: any, extId: string }> {33 const extId = await waitForSW(ctx);34 const popup = await ctx.newPage();35 await popup.goto(`chrome-extension://${extId}/src/popup/index.html`);36 await popup.waitForTimeout(3000);37 const pw = popup.locator("#master-password");38 if (await pw.isVisible()) {39 await pw.fill("password123");40 await popup.locator("#confirm-password").fill("password123");41 await popup.getByRole("button", { name: /create vault/i }).click();42 await popup.waitForTimeout(2000);43 }44 return { popup, extId };45}4647test.describe("TOTP Base32 Parsing", () => {48 let ctx: BrowserContext;49 test.afterEach(async () => { await ctx.close(); });50 test.beforeEach(async () => { ctx = await makeContext(); });5152 test("TOTP01 - standard base32 secret accepted", async () => {53 const { popup } = await setupVault(ctx);54 await popup.locator('input[placeholder="search vault..."]').click();55 await popup.getByRole("button", { name: /new entry/i }).click();56 await popup.locator(".peach-new-name-input").fill("Base32Std");57 await popup.locator('input[placeholder="domain.com"]').fill("https://base32.com");58 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");59 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");60 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");61 await popup.getByRole("button", { name: /create/i }).click();62 await popup.waitForTimeout(1500);63 await expect(popup.locator("text=Base32Std")).toBeVisible({ timeout: 3000 });64 await ctx.close();65 });6667 test("TOTP02 - base32 with spaces accepted", 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("Base32Space");72 await popup.locator('input[placeholder="domain.com"]').fill("https://space.com");73 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");74 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");75 await popup.locator("input[placeholder*='totp' i]").fill("JBSW Y3DP EHPK 3PXP");76 await popup.getByRole("button", { name: /create/i }).click();77 await popup.waitForTimeout(1500);78 await expect(popup.locator("text=Base32Space")).toBeVisible({ timeout: 3000 });79 await ctx.close();80 });8182 test("TOTP03 - base32 with lowercase accepted", async () => {83 const { popup } = await setupVault(ctx);84 await popup.locator('input[placeholder="search vault..."]').click();85 await popup.getByRole("button", { name: /new entry/i }).click();86 await popup.locator(".peach-new-name-input").fill("Base32Lower");87 await popup.locator('input[placeholder="domain.com"]').fill("https://lower.com");88 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");89 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");90 await popup.locator("input[placeholder*='totp' i]").fill("jbswy3dpehpk3pxp");91 await popup.getByRole("button", { name: /create/i }).click();92 await popup.waitForTimeout(1500);93 await expect(popup.locator("text=Base32Lower")).toBeVisible({ timeout: 3000 });94 await ctx.close();95 });9697 test("TOTP04 - invalid base32 shows error", async () => {98 const { popup } = await setupVault(ctx);99 await popup.locator('input[placeholder="search vault..."]').click();100 await popup.getByRole("button", { name: /new entry/i }).click();101 await popup.locator(".peach-new-name-input").fill("InvalidBase");102 await popup.locator('input[placeholder="domain.com"]').fill("https://invalid.com");103 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");104 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");105 await popup.locator("input[placeholder*='totp' i]").fill("INVALID!!!");106 await popup.getByRole("button", { name: /create/i }).click();107 await popup.waitForTimeout(500);108 const error = popup.locator(".peach-hint-error, .peach-error, text=invalid");109 const hasError = await error.isVisible({ timeout: 2000 }).catch(() => false);110 expect(hasError).toBeTruthy();111 await ctx.close();112 });113114 test("TOTP05 - base32 with padding accepted", async () => {115 const { popup } = await setupVault(ctx);116 await popup.locator('input[placeholder="search vault..."]').click();117 await popup.getByRole("button", { name: /new entry/i }).click();118 await popup.locator(".peach-new-name-input").fill("Base32Pad");119 await popup.locator('input[placeholder="domain.com"]').fill("https://pad.com");120 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");121 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");122 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP====");123 await popup.getByRole("button", { name: /create/i }).click();124 await popup.waitForTimeout(1500);125 await expect(popup.locator("text=Base32Pad")).toBeVisible({ timeout: 3000 });126 await ctx.close();127 });128});129130test.describe("TOTP Countdown Accuracy", () => {131 let ctx: BrowserContext;132 test.afterEach(async () => { await ctx.close(); });133 test.beforeEach(async () => { ctx = await makeContext(); });134135 test("TOTP06 - countdown shows seconds remaining", async () => {136 const { popup } = await setupVault(ctx);137 await popup.locator('input[placeholder="search vault..."]').click();138 await popup.getByRole("button", { name: /new entry/i }).click();139 await popup.locator(".peach-new-name-input").fill("CountDown");140 await popup.locator('input[placeholder="domain.com"]').fill("https://count.com");141 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");142 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");143 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");144 await popup.getByRole("button", { name: /create/i }).click();145 await popup.waitForTimeout(1500);146 await popup.locator(".peach-entry-name-button").first().click();147 await popup.waitForTimeout(500);148 const countdown = popup.locator(".peach-totp-countdown");149 await expect(countdown).toBeVisible({ timeout: 3000 });150 const text = await countdown.textContent();151 expect(text).toMatch(/\d+s/);152 await ctx.close();153 });154155 test("TOTP07 - countdown decrements in real time", async () => {156 const { popup } = await setupVault(ctx);157 await popup.locator('input[placeholder="search vault..."]').click();158 await popup.getByRole("button", { name: /new entry/i }).click();159 await popup.locator(".peach-new-name-input").fill("Decrement");160 await popup.locator('input[placeholder="domain.com"]').fill("https://decrement.com");161 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");162 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");163 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");164 await popup.getByRole("button", { name: /create/i }).click();165 await popup.waitForTimeout(1500);166 await popup.locator(".peach-entry-name-button").first().click();167 await popup.waitForTimeout(500);168 const countdown = popup.locator(".peach-totp-countdown");169 const val1 = await countdown.textContent();170 await popup.waitForTimeout(2000);171 const val2 = await countdown.textContent();172 expect(parseInt(val2 ?? "0")).toBeLessThan(parseInt(val1 ?? "30"));173 await ctx.close();174 });175176 test("TOTP08 - ring progress animates with countdown", async () => {177 const { popup } = await setupVault(ctx);178 await popup.locator('input[placeholder="search vault..."]').click();179 await popup.getByRole("button", { name: /new entry/i }).click();180 await popup.locator(".peach-new-name-input").fill("RingAnim");181 await popup.locator('input[placeholder="domain.com"]').fill("https://ring.com");182 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");183 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");184 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");185 await popup.getByRole("button", { name: /create/i }).click();186 await popup.waitForTimeout(1500);187 await popup.locator(".peach-entry-name-button").first().click();188 await popup.waitForTimeout(500);189 const ring = popup.locator(".peach-totp-ring-progress");190 await expect(ring).toBeVisible({ timeout: 3000 });191 const dashOffset1 = await ring.getAttribute("stroke-dashoffset");192 await popup.waitForTimeout(2000);193 const dashOffset2 = await ring.getAttribute("stroke-dashoffset");194 expect(dashOffset1).not.toEqual(dashOffset2);195 await ctx.close();196 });197198 test("TOTP09 - code refreshes at 30 second boundary", async () => {199 const { popup } = await setupVault(ctx);200 await popup.locator('input[placeholder="search vault..."]').click();201 await popup.getByRole("button", { name: /new entry/i }).click();202 await popup.locator(".peach-new-name-input").fill("RefreshBound");203 await popup.locator('input[placeholder="domain.com"]').fill("https://refresh.com");204 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");205 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");206 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");207 await popup.getByRole("button", { name: /create/i }).click();208 await popup.waitForTimeout(1500);209 await popup.locator(".peach-entry-name-button").first().click();210 await popup.waitForTimeout(500);211 const code1 = await popup.locator(".peach-totp-code").textContent();212 const countdown = popup.locator(".peach-totp-countdown");213 const remaining = parseInt(await countdown.textContent() ?? "0");214 if (remaining > 5) {215 await popup.waitForTimeout(remaining + 2);216 const code2 = await popup.locator(".peach-totp-code").textContent();217 expect(code1).not.toEqual(code2);218 }219 await ctx.close();220 });221222 test("TOTP10 - 6 digit TOTP format correct", async () => {223 const { popup } = await setupVault(ctx);224 await popup.locator('input[placeholder="search vault..."]').click();225 await popup.getByRole("button", { name: /new entry/i }).click();226 await popup.locator(".peach-new-name-input").fill("SixDigit");227 await popup.locator('input[placeholder="domain.com"]').fill("https://six.com");228 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");229 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");230 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");231 await popup.getByRole("button", { name: /create/i }).click();232 await popup.waitForTimeout(1500);233 await popup.locator(".peach-entry-name-button").first().click();234 await popup.waitForTimeout(500);235 const code = await popup.locator(".peach-totp-code").textContent();236 const clean = code?.replace(/\s/g, "").replace(/[^0-9]/g, "");237 expect(clean?.length).toBe(6);238 await ctx.close();239 });240});241242test.describe("TOTP Copy Behavior", () => {243 let ctx: BrowserContext;244 test.afterEach(async () => { await ctx.close(); });245 test.beforeEach(async () => { ctx = await makeContext(); });246247 test("TOTP11 - copy button copies current code", async () => {248 const { popup } = await setupVault(ctx);249 await popup.locator('input[placeholder="search vault..."]').click();250 await popup.getByRole("button", { name: /new entry/i }).click();251 await popup.locator(".peach-new-name-input").fill("CopyTOTP");252 await popup.locator('input[placeholder="domain.com"]').fill("https://copytotp.com");253 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");254 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");255 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");256 await popup.getByRole("button", { name: /create/i }).click();257 await popup.waitForTimeout(1500);258 await popup.locator(".peach-entry-name-button").first().click();259 await popup.waitForTimeout(500);260 const copyBtn = popup.locator("[aria-label='Copy TOTP code']");261 if (await copyBtn.isVisible({ timeout: 2000 }).catch(() => false)) {262 await copyBtn.click();263 await popup.waitForTimeout(200);264 const clipboard = await popup.evaluate(() => navigator.clipboard.readText());265 expect(clipboard).toMatch(/^\d{6}$/);266 }267 await ctx.close();268 });269270 test("TOTP12 - copy button shows feedback", async () => {271 const { popup } = await setupVault(ctx);272 await popup.locator('input[placeholder="search vault..."]').click();273 await popup.getByRole("button", { name: /new entry/i }).click();274 await popup.locator(".peach-new-name-input").fill("FeedbackTOTP");275 await popup.locator('input[placeholder="domain.com"]').fill("https://feedback.com");276 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");277 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");278 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");279 await popup.getByRole("button", { name: /create/i }).click();280 await popup.waitForTimeout(1500);281 await popup.locator(".peach-entry-name-button").first().click();282 await popup.waitForTimeout(500);283 const copyBtn = popup.locator("[aria-label='Copy TOTP code']");284 if (await copyBtn.isVisible({ timeout: 2000 }).catch(() => false)) {285 await copyBtn.click();286 await popup.waitForTimeout(200);287 const isCopied = await copyBtn.evaluate((el: Element) => el.classList.contains("is-copied"));288 expect(isCopied).toBeTruthy();289 }290 await ctx.close();291 });292293 test("TOTP13 - copied code matches displayed code", async () => {294 const { popup } = await setupVault(ctx);295 await popup.locator('input[placeholder="search vault..."]').click();296 await popup.getByRole("button", { name: /new entry/i }).click();297 await popup.locator(".peach-new-name-input").fill("MatchCode");298 await popup.locator('input[placeholder="domain.com"]').fill("https://match.com");299 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");300 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");301 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");302 await popup.getByRole("button", { name: /create/i }).click();303 await popup.waitForTimeout(1500);304 await popup.locator(".peach-entry-name-button").first().click();305 await popup.waitForTimeout(500);306 const codeText = await popup.locator(".peach-totp-code").textContent();307 const copyBtn = popup.locator("[aria-label='Copy TOTP code']");308 if (await copyBtn.isVisible({ timeout: 2000 }).catch(() => false)) {309 await copyBtn.click();310 await popup.waitForTimeout(200);311 const clipboard = await popup.evaluate(() => navigator.clipboard.readText());312 const cleanCode = codeText?.replace(/\s/g, "").replace(/[^0-9]/g, "");313 expect(clipboard).toBe(cleanCode);314 }315 await ctx.close();316 });317});318319test.describe("TOTP Edge Cases", () => {320 let ctx: BrowserContext;321 test.afterEach(async () => { await ctx.close(); });322 test.beforeEach(async () => { ctx = await makeContext(); });323324 test("TOTP14 - entry with empty TOTP shows no TOTP UI", async () => {325 const { popup } = await setupVault(ctx);326 await popup.locator('input[placeholder="search vault..."]').click();327 await popup.getByRole("button", { name: /new entry/i }).click();328 await popup.locator(".peach-new-name-input").fill("NoTOTPHere");329 await popup.locator('input[placeholder="domain.com"]').fill("https://nototp.com");330 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");331 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");332 await popup.getByRole("button", { name: /create/i }).click();333 await popup.waitForTimeout(1500);334 await popup.locator(".peach-entry-name-button").first().click();335 await popup.waitForTimeout(500);336 const totpBand = popup.locator(".peach-totp-band");337 const isHidden = await totpBand.isHidden().catch(() => true);338 expect(isHidden).toBe(true);339 await ctx.close();340 });341342 test("TOTP15 - TOTP survives vault lock/unlock", async () => {343 const { popup } = await setupVault(ctx);344 await popup.locator('input[placeholder="search vault..."]').click();345 await popup.getByRole("button", { name: /new entry/i }).click();346 await popup.locator(".peach-new-name-input").fill("LockTOTP");347 await popup.locator('input[placeholder="domain.com"]').fill("https://locktotp.com");348 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");349 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");350 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");351 await popup.getByRole("button", { name: /create/i }).click();352 await popup.waitForTimeout(1500);353 const lockBtn = popup.locator(".peach-lock-btn[aria-label='Lock vault']");354 await lockBtn.click();355 await popup.waitForTimeout(1500);356 await popup.locator("#master-password").fill("password123");357 await popup.getByRole("button", { name: /unlock/i }).click();358 await popup.waitForTimeout(2000);359 await popup.locator(".peach-entry-name-button").first().click();360 await popup.waitForTimeout(500);361 const totpCode = popup.locator(".peach-totp-code");362 await expect(totpCode).toBeVisible({ timeout: 3000 });363 await ctx.close();364 });365366 test("TOTP16 - very long base32 secret accepted", async () => {367 const { popup } = await setupVault(ctx);368 await popup.locator('input[placeholder="search vault..."]').click();369 await popup.getByRole("button", { name: /new entry/i }).click();370 await popup.locator(".peach-new-name-input").fill("LongBase");371 await popup.locator('input[placeholder="domain.com"]').fill("https://long.com");372 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");373 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");374 const longSecret = "JBSWY3DPEHPK3PXPEHPK3PXPJBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP";375 await popup.locator("input[placeholder*='totp' i]").fill(longSecret);376 await popup.getByRole("button", { name: /create/i }).click();377 await popup.waitForTimeout(1500);378 await expect(popup.locator("text=LongBase")).toBeVisible({ timeout: 3000 });379 await ctx.close();380 });381382 test("TOTP17 - base32 O and 0 treated correctly", async () => {383 const { popup } = await setupVault(ctx);384 await popup.locator('input[placeholder="search vault..."]').click();385 await popup.getByRole("button", { name: /new entry/i }).click();386 await popup.locator(".peach-new-name-input").fill("BaseOZero");387 await popup.locator('input[placeholder="domain.com"]').fill("https://ozero.com");388 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");389 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");390 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");391 await popup.getByRole("button", { name: /create/i }).click();392 await popup.waitForTimeout(1500);393 await expect(popup.locator("text=BaseOZero")).toBeVisible({ timeout: 3000 });394 await ctx.close();395 });396397 test("TOTP18 - base32 I and l treated correctly", async () => {398 const { popup } = await setupVault(ctx);399 await popup.locator('input[placeholder="search vault..."]').click();400 await popup.getByRole("button", { name: /new entry/i }).click();401 await popup.locator(".peach-new-name-input").fill("BaseIL");402 await popup.locator('input[placeholder="domain.com"]').fill("https://ilandl.com");403 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");404 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");405 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");406 await popup.getByRole("button", { name: /create/i }).click();407 await popup.waitForTimeout(1500);408 await expect(popup.locator("text=BaseIL")).toBeVisible({ timeout: 3000 });409 await ctx.close();410 });411});412413test.describe("TOTP Entry Management", () => {414 let ctx: BrowserContext;415 test.afterEach(async () => { await ctx.close(); });416 test.beforeEach(async () => { ctx = await makeContext(); });417418 test("TOTP19 - edit TOTP in existing entry", async () => {419 const { popup } = await setupVault(ctx);420 await popup.locator('input[placeholder="search vault..."]').click();421 await popup.getByRole("button", { name: /new entry/i }).click();422 await popup.locator(".peach-new-name-input").fill("EditTOTP");423 await popup.locator('input[placeholder="domain.com"]').fill("https://edittotp.com");424 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");425 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");426 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");427 await popup.getByRole("button", { name: /create/i }).click();428 await popup.waitForTimeout(1500);429 await popup.locator(".peach-entry-name-button").first().click();430 await popup.waitForTimeout(500);431 await popup.locator("button[aria-label='Edit']").first().click();432 await popup.waitForTimeout(500);433 const totpInput = popup.locator("input[placeholder*='totp' i]");434 await totpInput.fill("NEWBASE32SECRET");435 await popup.getByRole("button", { name: /save/i }).click();436 await popup.waitForTimeout(1500);437 await ctx.close();438 });439440 test("TOTP20 - delete TOTP from entry", async () => {441 const { popup } = await setupVault(ctx);442 await popup.locator('input[placeholder="search vault..."]').click();443 await popup.getByRole("button", { name: /new entry/i }).click();444 await popup.locator(".peach-new-name-input").fill("DeleteTOTP");445 await popup.locator('input[placeholder="domain.com"]').fill("https://deltotp.com");446 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");447 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");448 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");449 await popup.getByRole("button", { name: /create/i }).click();450 await popup.waitForTimeout(1500);451 await popup.locator(".peach-entry-name-button").first().click();452 await popup.waitForTimeout(500);453 await popup.locator("button[aria-label='Edit']").first().click();454 await popup.waitForTimeout(500);455 const totpInput = popup.locator("input[placeholder*='totp' i]");456 await totpInput.fill("");457 await popup.getByRole("button", { name: /save/i }).click();458 await popup.waitForTimeout(1500);459 await ctx.close();460 });461462 test("TOTP21 - multiple entries with TOTP", async () => {463 const { popup } = await setupVault(ctx);464 for (const name of ["TOTP1", "TOTP2", "TOTP3"]) {465 await popup.locator('input[placeholder="search vault..."]').click();466 await popup.getByRole("button", { name: /new entry/i }).click();467 await popup.locator(".peach-new-name-input").fill(name);468 await popup.locator('input[placeholder="domain.com"]').fill(`https://${name.toLowerCase()}.com`);469 await popup.locator('input[placeholder="username or email"]').fill(`${name.toLowerCase()}@user.com`);470 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");471 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");472 await popup.getByRole("button", { name: /create/i }).click();473 await popup.waitForTimeout(800);474 }475 await expect(popup.locator("text=TOTP1")).toBeVisible({ timeout: 3000 });476 await ctx.close();477 });478479 test("TOTP22 - TOTP entry survives entry edit", async () => {480 const { popup } = await setupVault(ctx);481 await popup.locator('input[placeholder="search vault..."]').click();482 await popup.getByRole("button", { name: /new entry/i }).click();483 await popup.locator(".peach-new-name-input").fill("SurviveEdit");484 await popup.locator('input[placeholder="domain.com"]').fill("https://survive.com");485 await popup.locator('input[placeholder="username or email"]').fill("[email protected]");486 await popup.locator(".peach-new-field-input[placeholder='password']").fill("pass123");487 await popup.locator("input[placeholder*='totp' i]").fill("JBSWY3DPEHPK3PXP");488 await popup.getByRole("button", { name: /create/i }).click();489 await popup.waitForTimeout(1500);490 await popup.locator(".peach-entry-name-button").first().click();491 await popup.waitForTimeout(500);492 await popup.locator("button[aria-label='Edit']").first().click();493 await popup.waitForTimeout(500);494 await popup.locator(".peach-new-name-input").fill("SurviveEditChanged");495 await popup.getByRole("button", { name: /save/i }).click();496 await popup.waitForTimeout(1500);497 await popup.locator(".peach-entry-name-button").first().click();498 await popup.waitForTimeout(500);499 const totpCode = popup.locator(".peach-totp-code");500 await expect(totpCode).toBeVisible({ timeout: 3000 });501 await ctx.close();502 });503});