- admin: add exact:true to tab button assertions to avoid strict-mode
violations from "Benutzer löschen" title buttons matching "Benutzer"
- admin: change tag-row locator from hasText regex on <li> to has: span
filter (more robust against whitespace differences); add waitForSelector
after tab click to ensure panel is rendered before hovering
- auth: replace page.request.get('/api/users/me') with a profile page
navigation — direct browser requests don't carry Basic Auth, only
server-side SvelteKit fetches do
- documents: use getByRole('heading') instead of getByText to avoid strict
mode violation when the title appears in both h1 and breadcrumb
- persons: same heading fix for person creation landing page
- profile: remove success-message assertion after password change; the
auth_token cookie still holds old credentials so use:enhance's update()
immediately gets a 401 and redirects to /login before the message renders
— test now asserts the redirect directly, then re-logs in
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
145 lines
6.1 KiB
TypeScript
145 lines
6.1 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Document management E2E tests.
|
|
* Assumes auth setup has run (storageState is applied by playwright.config.ts).
|
|
* Assumes the backend has at least one document in the database.
|
|
*/
|
|
test.describe('Document list', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/');
|
|
// Wait for SvelteKit hydration to complete so onclick/oninput handlers are active.
|
|
await page.waitForSelector('[data-hydrated]');
|
|
});
|
|
|
|
test('renders the search bar and document list', async ({ page }) => {
|
|
await expect(page.getByPlaceholder('Suche in Titel, Inhalt, Ort...')).toBeVisible();
|
|
await expect(page.getByRole('link', { name: /Neues Dokument/i })).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/documents-home.png' });
|
|
});
|
|
|
|
test('navigation bar shows active state for Dokumente', async ({ page }) => {
|
|
const navLink = page.getByRole('navigation').getByRole('link', { name: 'Dokumente' });
|
|
await expect(navLink).toHaveClass(/text-brand-navy/);
|
|
});
|
|
|
|
test('text search filters the document list', async ({ page }) => {
|
|
// Navigate directly with the query param — tests that search results are filtered
|
|
// correctly without depending on the debounced oninput → goto chain in CI.
|
|
await page.goto('/?q=zzz_unlikely_to_match_anything');
|
|
await expect(page.getByText('Keine Dokumente gefunden')).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/documents-search-no-results.png' });
|
|
});
|
|
|
|
test('clearing the search returns all documents', async ({ page }) => {
|
|
// Navigate with an active query first, then click the reset link.
|
|
await page.goto('/?q=xyz_unlikely');
|
|
await page.getByTitle('Filter zurücksetzen').click();
|
|
await page.waitForURL('/');
|
|
await expect(page).toHaveURL('/');
|
|
await page.screenshot({ path: 'test-results/e2e/documents-reset-search.png' });
|
|
});
|
|
|
|
test('advanced filters panel opens and closes', async ({ page }) => {
|
|
const btn = page.getByRole('button', { name: 'Filter', exact: true });
|
|
await btn.click();
|
|
await expect(page.getByLabel('Von')).toBeVisible();
|
|
await expect(page.getByLabel('Bis')).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/documents-filters-open.png' });
|
|
await btn.click();
|
|
await expect(page.getByLabel('Von')).not.toBeVisible();
|
|
});
|
|
|
|
test('date range filter triggers a new search', async ({ page }) => {
|
|
await page.getByRole('button', { name: 'Filter', exact: true }).click();
|
|
await page.getByLabel('Von').fill('2000-01-01');
|
|
await page.waitForURL(/from=2000-01-01/);
|
|
await expect(page).toHaveURL(/from=2000-01-01/);
|
|
await page.screenshot({ path: 'test-results/e2e/documents-date-filter.png' });
|
|
});
|
|
});
|
|
|
|
test.describe('Document detail', () => {
|
|
test('clicking a document opens the detail page', async ({ page }) => {
|
|
await page.goto('/');
|
|
// Click the first document link in the list
|
|
const firstDoc = page.locator('ul li a').first();
|
|
const href = await firstDoc.getAttribute('href');
|
|
await firstDoc.click();
|
|
await expect(page).toHaveURL(href!);
|
|
await page.screenshot({ path: 'test-results/e2e/document-detail.png' });
|
|
});
|
|
});
|
|
|
|
test.describe('New document', () => {
|
|
test('renders the upload form', async ({ page }) => {
|
|
await page.goto('/documents/new');
|
|
await expect(page.getByRole('heading', { name: /Neues Dokument/i })).toBeVisible();
|
|
await expect(page.getByLabel('Titel')).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/document-new.png' });
|
|
});
|
|
});
|
|
|
|
test.describe('Document creation', () => {
|
|
test('user fills in a title and lands on the new document detail page', async ({ page }) => {
|
|
await page.goto('/documents/new');
|
|
await page.waitForSelector('[data-hydrated]');
|
|
|
|
await page.getByLabel('Titel').fill('E2E Testbrief');
|
|
await page.getByRole('button', { name: /Speichern/i }).click();
|
|
|
|
await expect(page).toHaveURL(/\/documents\/[^/]+$/);
|
|
await expect(page.getByRole('heading', { name: 'E2E Testbrief' })).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/document-create.png' });
|
|
});
|
|
});
|
|
|
|
test.describe('Document editing', () => {
|
|
test('user opens an existing document, changes the title, and sees the update', async ({
|
|
page
|
|
}) => {
|
|
// Find the document created in the previous describe
|
|
await page.goto('/?q=E2E+Testbrief');
|
|
await page.waitForSelector('[data-hydrated]');
|
|
const docLink = page.getByRole('link', { name: 'E2E Testbrief' }).first();
|
|
const href = await docLink.getAttribute('href');
|
|
await page.goto(`${href}/edit`);
|
|
await page.waitForSelector('[data-hydrated]');
|
|
|
|
await page.getByLabel('Titel').fill('E2E Testbrief (überarbeitet)');
|
|
await page.getByRole('button', { name: /Speichern/i }).click();
|
|
|
|
await expect(page).toHaveURL(/\/documents\/[^/]+$/);
|
|
await expect(page.getByText('E2E Testbrief (überarbeitet)')).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/document-edit-save.png' });
|
|
});
|
|
});
|
|
|
|
test.describe('Document edit', () => {
|
|
test('renders the edit form with pre-filled data', async ({ page }) => {
|
|
// Navigate to home, find first document, go to its edit page
|
|
await page.goto('/');
|
|
const firstDocLink = page.locator('ul li a').first();
|
|
const href = await firstDocLink.getAttribute('href');
|
|
await page.goto(`${href}/edit`);
|
|
await expect(page.getByRole('heading', { name: /Bearbeiten/i })).toBeVisible();
|
|
await expect(page.getByLabel('Titel')).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/document-edit.png' });
|
|
});
|
|
|
|
test('shows a validation error for an invalid date format', async ({ page }) => {
|
|
await page.goto('/');
|
|
const firstDocLink = page.locator('ul li a').first();
|
|
const href = await firstDocLink.getAttribute('href');
|
|
await page.goto(`${href}/edit`);
|
|
// Wait for hydration so oninput={handleDateInput} is registered.
|
|
await page.waitForSelector('[data-hydrated]');
|
|
const dateInput = page.getByLabel('Datum');
|
|
// Type partial digits: '99' → dateDisplay='99', dateIso='' → dateInvalid=true
|
|
await dateInput.fill('');
|
|
await dateInput.pressSequentially('99');
|
|
await expect(page.getByText(/TT\.MM\.JJJJ/i)).toBeVisible();
|
|
await page.screenshot({ path: 'test-results/e2e/document-edit-date-error.png' });
|
|
});
|
|
});
|