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
This commit is contained in:
co-authored by
Claude Opus 5
parent
68d3a43191
commit
784b76baf7
@@ -0,0 +1,71 @@
|
||||
// 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 });
|
||||
}
|
||||
Reference in New Issue
Block a user