/** * AI Software Factory — E2E wait helpers. * * Thin, intent-named wrappers around Playwright waits. Prefer the built-in * `expect(locator).toBeVisible()` auto-asserting waits where possible; use * these when you need: text-content locators, network-idle settling, or a * custom polling condition. */ import { expect, type Page, type Locator } from "@playwright/test"; /** * Wait until an element matching `[data-testid]` containing the given text is * visible. Returns the locator so the caller can act on it. */ export async function waitForSelectorByText( page: Page, testId: string, text: string | RegExp, timeoutMs = 15_000, ): Promise { const locator = page .getByTestId(testId) .filter({ hasText: text }); await expect(locator.first()).toBeVisible({ timeout: timeoutMs }); return locator.first(); } /** * Wait for the page to reach a network-idle state (no in-flight requests for * `quietMs`). Playwright's built-in "networkidle" load state is what this * wraps; it is flaky when the page keeps a WebSocket open (realtime), so for * realtime assertions prefer explicit event waits (locator visibility / * count / a value you poll with waitForCondition) over network idle. */ export async function waitForNetworkIdle( page: Page, _quietMs = 500, timeoutMs = 15_000, ): Promise { await page .waitForLoadState("networkidle", { timeout: timeoutMs }) .catch((err) => { throw new Error( `waitForNetworkIdle: not idle within ${timeoutMs}ms ` + `(a live WebSocket keeps the socket busy — wait on an explicit UI ` + `state instead). Cause: ${String(err)}`, ); }); } /** * Poll `fn` until it returns truthy or the timeout elapses. */ export async function waitForCondition( page: Page, fn: () => Promise | T, opts: { timeoutMs?: number; intervalMs?: number; message?: string } = {}, ): Promise { const timeoutMs = opts.timeoutMs ?? 15_000; const intervalMs = opts.intervalMs ?? 250; const deadline = Date.now() + timeoutMs; // eslint-disable-next-line no-constant-condition while (true) { const result = await fn(); if (result) return result; if (Date.now() >= deadline) { throw new Error( `waitForCondition: timed out after ${timeoutMs}ms` + (opts.message ? ` — ${opts.message}` : ""), ); } await page.waitForTimeout(intervalMs); } }