import { expect, type Page } from "@playwright/test"; import { TestApiClient } from "./fixtures"; const DEFAULT_E2E_NAME = "E2E User"; const E2E_WORKER = process.env.TEST_PARALLEL_INDEX ?? process.env.TEST_WORKER_INDEX ?? "0"; const E2E_RUN_ID = process.env.E2E_RUN_ID ?? `${Date.now().toString(36)}-${process.pid.toString(36)}`; const DEFAULT_E2E_EMAIL = `e2e-${E2E_WORKER}-${E2E_RUN_ID}@multica.ai`; const DEFAULT_E2E_WORKSPACE = `e2e-workspace-${E2E_WORKER}-${E2E_RUN_ID}`; async function waitForIssuesPage(page: Page) { // The Issues panel's create button is icon-only; its label is the name. await expect(page.getByRole("button", { name: "New Issue" }).first()).toBeVisible({ timeout: 15000, }); } export async function waitForPageText(page: Page, text: string, timeout = 30000) { await page.waitForFunction( (expected) => document.body?.innerText.includes(expected), text, { timeout }, ); } export async function reloadAppPage(page: Page) { await page.reload({ waitUntil: "domcontentloaded" }); await waitForPageText(page, "Issues"); } /** * Log in as the default E2E user and ensure the workspace exists first. * Authenticates via API (send-code → DB read → verify-code), then injects * the token into localStorage so the browser session is authenticated. * * Returns the E2E workspace slug so callers can build workspace-scoped URLs. */ export async function loginAsDefault(page: Page): Promise { const api = new TestApiClient(); await api.login(DEFAULT_E2E_EMAIL, DEFAULT_E2E_NAME); const workspace = await api.ensureWorkspace( `E2E Workspace ${E2E_WORKER}`, DEFAULT_E2E_WORKSPACE, ); await api.markUserOnboarded(); const token = api.getToken(); if (!token) { throw new Error("E2E login did not return an auth token"); } await page.addInitScript((t) => { localStorage.setItem("multica_token", t); localStorage.setItem("multica:chat:isOpen", "false"); }, token); await page.goto(`/${workspace.slug}/issues`, { waitUntil: "domcontentloaded" }); await waitForIssuesPage(page); return workspace.slug; } /** * Create a TestApiClient logged in as the default E2E user. * Call api.cleanup() in afterEach to remove test data created during the test. */ export async function createTestApi(): Promise { const api = new TestApiClient(); await api.login(DEFAULT_E2E_EMAIL, DEFAULT_E2E_NAME); await api.ensureWorkspace(`E2E Workspace ${E2E_WORKER}`, DEFAULT_E2E_WORKSPACE); await api.markUserOnboarded(); return api; } export async function preferManualCreateMode(page: Page) { await page.evaluate(() => { localStorage.setItem( "multica_create_mode", JSON.stringify({ state: { lastMode: "manual" }, version: 0 }), ); }); await reloadAppPage(page); await waitForIssuesPage(page); } /** The app rail: workspace tiles, spaces, and the account menu. */ export function appRail(page: Page) { return page.getByRole("navigation", { name: "App navigation" }); } /** The current workspace's tile in the rail; its accessible name is the workspace name. */ export function currentWorkspaceTile(page: Page) { return appRail(page) .getByRole("link", { name: /E2E Workspace|Renamed WS/ }) .and(page.locator('[aria-current="page"]')); } export async function openAccountMenu(page: Page) { const accountButton = appRail(page).getByRole("button", { name: "Account" }); await expect(accountButton).toBeVisible({ timeout: 15000 }); await accountButton.click(); await expect(page.getByRole("menu")).toBeVisible(); }