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
59 lines
2.3 KiB
TypeScript
59 lines
2.3 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import { login, logout } from './helpers';
|
|
|
|
// The landing is the front door: one input that starts a real conversation,
|
|
// and three chips.
|
|
|
|
test.setTimeout(120_000);
|
|
|
|
test.afterEach(async ({ page }) => {
|
|
await page.request.post('/api/auth/logout');
|
|
});
|
|
|
|
test('the landing input starts a conversation and streams an answer', async ({ page }) => {
|
|
await login(page, 'pablo@pablan.dev');
|
|
|
|
await page.getByTestId('landing-input').fill('Wie beantrage ich Urlaub?');
|
|
await page.getByTestId('landing-send').click();
|
|
|
|
// Hand-off to the chat page, which sends the question straight away.
|
|
await expect(page).toHaveURL(/\/chat/);
|
|
const assistant = page.getByTestId('assistant-message').last();
|
|
await expect(assistant).toBeVisible({ timeout: 45_000 });
|
|
// The turn is over once the stop button is gone.
|
|
await expect(page.getByTestId('stop-button')).toHaveCount(0, { timeout: 90_000 });
|
|
expect((await assistant.innerText()).trim().length).toBeGreaterThan(40);
|
|
|
|
// The conversation shows up in the sidebar.
|
|
await expect(page.getByTestId('conversation-list')).toContainText('Urlaub');
|
|
|
|
// Reloading reopens the same conversation instead of re-asking: the
|
|
// hand-off parameter is replaced by the conversation's own route.
|
|
const list = await (await page.request.get('/api/conversations')).json();
|
|
const created = list.find((c: { title: string }) => c.title?.includes('Urlaub'));
|
|
expect(created).toBeTruthy();
|
|
await expect(page).toHaveURL(`/chat/${created.id}`);
|
|
|
|
await page.reload();
|
|
await page.locator('body[data-hydrated]').waitFor();
|
|
await expect(page.getByTestId('assistant-message')).toHaveCount(1);
|
|
const after = await (await page.request.get(`/api/conversations/${created.id}`)).json();
|
|
expect(after.messages).toHaveLength(2); // still one question, one answer
|
|
|
|
await page.request.delete(`/api/conversations/${created.id}`);
|
|
});
|
|
|
|
test('the chips lead to capture and documents', async ({ page }) => {
|
|
await login(page, 'pablo@pablan.dev');
|
|
|
|
await page.getByTestId('chip-find').click();
|
|
await expect(page).toHaveURL(/\/documents$/);
|
|
|
|
await page.goBack();
|
|
await page.locator('body[data-hydrated]').waitFor();
|
|
await page.getByTestId('chip-capture').click();
|
|
await expect(page).toHaveURL(/\/documents\/new$/);
|
|
await expect(page.getByTestId('capture-template').first()).toBeVisible({ timeout: 15_000 });
|
|
await logout(page);
|
|
});
|