// Shared navigation helpers. Every spec used to inline these; they live // here so a change to the app shell is a one-file fix. import { expect, type Page } from '@playwright/test'; export const DEV_PASSWORD = 'pablan-dev'; export async function login(page: Page, email: string, password = DEV_PASSWORD) { await page.goto('/login'); await page.locator('body[data-hydrated]').waitFor(); await page.getByLabel('Email').fill(email); await page.getByLabel('Password').fill(password); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page).toHaveURL('/'); // The specs assert English copy and pin it with Accept-Language, which is // the strategy for a visitor with no ACCOUNT preference. A language picked // in the settings dialog outranks it (that is the product rule), so a dev // stack where somebody switched to German would fail every spec that reads // a label. Hand the account back to "follow the browser" on the way in. await page.request.put('/api/account/locale', { data: { locale: null } }); // Wait for the landing page to hydrate: filling an input before Svelte // binds it leaves the state empty and submit buttons disabled. await page.locator('body[data-hydrated]').waitFor(); } /** Log out through the sidebar user menu. */ export async function logout(page: Page) { // Login/logout are full document reloads (session state must not survive // the boundary), so the page may be freshly server-rendered and not yet // interactive when a test reaches here; a click before hydration does // nothing. A real user is far slower than hydration. await page.locator('body[data-hydrated]').waitFor(); await page.getByTestId('user-menu').click(); await page.getByTestId('logout').click(); await expect(page).toHaveURL(/\/login$/); } export async function openChat(page: Page) { // By testid: an untitled conversation row carries the same label. await page.getByTestId('sidebar-new-conversation').click(); await expect(page).toHaveURL(/\/chat/); } export async function openDocuments(page: Page) { await page.getByTestId('sidebar').getByRole('link', { name: 'Documents' }).click(); await expect(page).toHaveURL(/\/documents$/); } export async function openAdmin(page: Page) { await page.getByTestId('sidebar').getByRole('link', { name: 'Admin' }).click(); await expect(page).toHaveURL(/\/admin$/); } /** The admin page is four unrelated jobs behind four tabs; every spec has to * say which one it is doing. * * By testid, not by label: the account's own language setting outranks the * Accept-Language this suite pins (that is the product rule — a language a * person chose follows them), and the admin used here has picked one. */ export async function openAdminTab(page: Page, tab: 'people' | 'templates' | 'llm' | 'prompts') { await page.locator('body[data-hydrated]').waitFor(); await page.getByTestId(`tab-${tab}`).click(); } /** Call right after triggering a turn: waits for it to actually start (the * stop button appears) and then to finish. Waiting on the send button alone * races, because it is still visible for a moment after the click. */ export async function waitForTurnEnd(page: Page) { await expect(page.getByTestId('stop-button')).toBeVisible({ timeout: 30_000 }); await expect(page.getByTestId('stop-button')).toHaveCount(0, { timeout: 120_000 }); }