Files
ProfessorNovaandClaude Opus 5 784b76baf7 Pablan, as it stands
Self-hosted knowledge management for SMEs: a split-screen Markdown editor
whose sections an LLM refines while you write, and RAG question answering
over the documents that result. FastAPI + Postgres/pgvector on the back,
SvelteKit on the front, everything OpenAI-compatible and self-hostable.

Squashed into a single commit; the development history stays local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
2026-09-04 09:21:37 +02:00

72 lines
3.3 KiB
TypeScript

// 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 });
}