From cf78d28d7420ff7c7e422b12dfb9aa7cee0e2676 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 12 Jan 2026 17:31:53 +0000 Subject: [PATCH] test(browser): add regression coverage --- src/browser/pw-role-snapshot.test.ts | 15 ++++++- src/browser/pw-session.test.ts | 53 +++++++++++++++++++++++ src/browser/pw-tools-core.test.ts | 65 ++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/browser/pw-session.test.ts diff --git a/src/browser/pw-role-snapshot.test.ts b/src/browser/pw-role-snapshot.test.ts index 7a1c7c493..b3d604015 100644 --- a/src/browser/pw-role-snapshot.test.ts +++ b/src/browser/pw-role-snapshot.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from "vitest"; -import { buildRoleSnapshotFromAriaSnapshot } from "./pw-role-snapshot.js"; +import { + buildRoleSnapshotFromAriaSnapshot, + getRoleSnapshotStats, +} from "./pw-role-snapshot.js"; describe("pw-role-snapshot", () => { it("adds refs for interactive elements", () => { @@ -42,4 +45,14 @@ describe("pw-role-snapshot", () => { expect(res.snapshot).toContain(" - group"); expect(res.snapshot).not.toContain("button"); }); + + it("computes stats", () => { + const aria = ['- button "OK"', '- button "Cancel"'].join("\n"); + const res = buildRoleSnapshotFromAriaSnapshot(aria); + const stats = getRoleSnapshotStats(res.snapshot, res.refs); + expect(stats.refs).toBe(2); + expect(stats.interactive).toBe(2); + expect(stats.lines).toBeGreaterThan(0); + expect(stats.chars).toBeGreaterThan(0); + }); }); diff --git a/src/browser/pw-session.test.ts b/src/browser/pw-session.test.ts new file mode 100644 index 000000000..0d36f55ef --- /dev/null +++ b/src/browser/pw-session.test.ts @@ -0,0 +1,53 @@ +import type { Page } from "playwright-core"; +import { describe, expect, it, vi } from "vitest"; + +import { ensurePageState, refLocator } from "./pw-session.js"; + +function fakePage(): Page { + const handlers = new Map void>>(); + const on = vi.fn((event: string, cb: (...args: unknown[]) => void) => { + const list = handlers.get(event) ?? []; + list.push(cb); + handlers.set(event, list); + return undefined as unknown; + }); + const getByRole = vi.fn(() => ({ nth: vi.fn(() => ({ ok: true })) })); + const frameLocator = vi.fn(() => ({ + getByRole: vi.fn(() => ({ nth: vi.fn(() => ({ ok: true })) })), + })); + const locator = vi.fn(() => ({ nth: vi.fn(() => ({ ok: true })) })); + + return { + on, + getByRole, + frameLocator, + locator, + } as unknown as Page; +} + +describe("pw-session refLocator", () => { + it("uses frameLocator for role refs when snapshot was scoped to a frame", () => { + const page = fakePage(); + const state = ensurePageState(page); + state.roleRefs = { e1: { role: "button", name: "OK" } }; + state.roleRefsFrameSelector = "iframe#main"; + + refLocator(page, "e1"); + + expect( + page.frameLocator as unknown as ReturnType, + ).toHaveBeenCalledWith("iframe#main"); + }); + + it("uses page getByRole for role refs by default", () => { + const page = fakePage(); + const state = ensurePageState(page); + state.roleRefs = { e1: { role: "button", name: "OK" } }; + + refLocator(page, "e1"); + + expect( + page.getByRole as unknown as ReturnType, + ).toHaveBeenCalled(); + }); +}); diff --git a/src/browser/pw-tools-core.test.ts b/src/browser/pw-tools-core.test.ts index 0275a1fac..7bf4e6831 100644 --- a/src/browser/pw-tools-core.test.ts +++ b/src/browser/pw-tools-core.test.ts @@ -232,4 +232,69 @@ describe("pw-tools-core", () => { expect(dismiss).toHaveBeenCalled(); expect(accept).not.toHaveBeenCalled(); }); + + it("waits for selector, url, load state, and function", async () => { + const waitForSelector = vi.fn(async () => {}); + const waitForURL = vi.fn(async () => {}); + const waitForLoadState = vi.fn(async () => {}); + const waitForFunction = vi.fn(async () => {}); + const waitForTimeout = vi.fn(async () => {}); + + currentPage = { + locator: vi.fn(() => ({ + first: () => ({ waitFor: waitForSelector }), + })), + waitForURL, + waitForLoadState, + waitForFunction, + waitForTimeout, + getByText: vi.fn(() => ({ first: () => ({ waitFor: vi.fn() }) })), + }; + + const mod = await importModule(); + await mod.waitForViaPlaywright({ + cdpUrl: "http://127.0.0.1:18792", + selector: "#main", + url: "**/dash", + loadState: "networkidle", + fn: "window.ready===true", + timeoutMs: 1234, + timeMs: 50, + }); + + expect(waitForTimeout).toHaveBeenCalledWith(50); + expect( + currentPage.locator as ReturnType, + ).toHaveBeenCalledWith("#main"); + expect(waitForSelector).toHaveBeenCalledWith({ + state: "visible", + timeout: 1234, + }); + expect(waitForURL).toHaveBeenCalledWith("**/dash", { timeout: 1234 }); + expect(waitForLoadState).toHaveBeenCalledWith("networkidle", { + timeout: 1234, + }); + expect(waitForFunction).toHaveBeenCalledWith("window.ready===true", { + timeout: 1234, + }); + }); + + it("rewrites strict mode violations into snapshot hints", async () => { + const click = vi.fn(async () => { + throw new Error( + 'Error: strict mode violation: locator("aria-ref=1") resolved to 2 elements', + ); + }); + currentRefLocator = { click }; + currentPage = {}; + + const mod = await importModule(); + await expect( + mod.clickViaPlaywright({ + cdpUrl: "http://127.0.0.1:18792", + targetId: "T1", + ref: "1", + }), + ).rejects.toThrow(/Run a new snapshot/i); + }); });