import { test, expect } from '@playwright/test'; test.describe('Person list', () => { test.beforeEach(async ({ page }) => { await page.goto('/persons'); }); test('renders the persons list page', async ({ page }) => { await expect(page.getByRole('heading', { name: /Personen/i })).toBeVisible(); await expect(page.getByRole('link', { name: /Neue Person/i })).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/persons-list.png' }); }); test('search filters the persons 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('/persons?q=zzz_unlikely_match'); await expect(page.getByText(/Keine Personen gefunden/i)).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/persons-search-empty.png' }); }); test('clicking a person opens the detail page', async ({ page }) => { const firstPerson = page.locator('a[href^="/persons/"]').first(); await firstPerson.click(); await expect(page).toHaveURL(/\/persons\/.+/); await page.screenshot({ path: 'test-results/e2e/person-detail.png' }); }); }); test.describe('Person detail', () => { test('shows the person name and their documents', async ({ page }) => { await page.goto('/persons'); const firstPerson = page.locator('a[href^="/persons/"]').first(); await firstPerson.click(); // The detail page shows the person's name as the top-level heading await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/person-detail-documents.png' }); }); test('can enter and cancel edit mode', async ({ page }) => { await page.goto('/persons'); const firstPerson = page.locator('a[href^="/persons/"]').first(); await firstPerson.click(); // Click the edit button const editBtn = page.getByRole('button', { name: /Bearbeiten/i }); if (await editBtn.isVisible()) { await editBtn.click(); await expect(page.getByLabel('Vorname')).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/person-edit-form.png' }); // Cancel await page.getByRole('button', { name: /Abbrechen/i }).click(); await expect(page.getByLabel('Vorname')).not.toBeVisible(); } }); }); test.describe('New person', () => { test('renders the new person form', async ({ page }) => { await page.goto('/persons/new'); await expect(page.getByLabel('Vorname')).toBeVisible(); await expect(page.getByLabel('Nachname')).toBeVisible(); await expect(page.getByRole('button', { name: /Erstellen/i })).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/person-new.png' }); }); test('shows a validation error when submitting with empty fields', async ({ page }) => { await page.goto('/persons/new'); // HTML required attribute prevents submission without filling required fields await page.getByRole('button', { name: /Erstellen/i }).click(); // The form should not have navigated away await expect(page).toHaveURL('/persons/new'); }); }); test.describe('Person detail — sort toggle', () => { test('sort toggle changes the button label when person has documents', async ({ page }) => { await page.goto('/persons'); const firstPerson = page.locator('a[href^="/persons/"]').first(); await firstPerson.click(); await page.waitForSelector('[data-hydrated]'); const sortBtn = page.getByRole('button', { name: /Neueste zuerst|Älteste zuerst/i }); if (await sortBtn.isVisible()) { await expect(sortBtn).toContainText('Neueste zuerst'); await sortBtn.click(); await expect(sortBtn).toContainText('Älteste zuerst'); await sortBtn.click(); await expect(sortBtn).toContainText('Neueste zuerst'); await page.screenshot({ path: 'test-results/e2e/person-sort-toggle.png' }); } }); }); test.describe('Person detail — conversations link', () => { test('has a conversations link that pre-fills the person', async ({ page }) => { await page.goto('/persons'); const firstLink = page.locator('a[href^="/persons/"]').first(); const href = await firstLink.getAttribute('href'); const personId = href!.split('/persons/')[1]; await firstLink.click(); const convLink = page.getByRole('link', { name: /Konversationen/i }); await expect(convLink).toBeVisible(); await expect(convLink).toHaveAttribute('href', `/conversations?senderId=${personId}`); }); }); test.describe('Conversations', () => { test('shows the empty state when no persons are selected', async ({ page }) => { await page.goto('/conversations'); await expect(page.getByText(/Wählen Sie zwei Personen aus/i)).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/conversations-empty.png' }); }); test('nav link is active on the conversations page', async ({ page }) => { await page.goto('/conversations'); const navLink = page.getByRole('link', { name: 'Konversationen' }); await expect(navLink).toHaveClass(/text-brand-navy/); }); test('sort toggle changes the button label', async ({ page }) => { await page.goto('/conversations'); await page.waitForSelector('[data-hydrated]'); const btn = page.getByRole('button', { name: /Sortierung/i }); await expect(btn).toContainText('Neueste zuerst'); await btn.click(); await expect(page).toHaveURL(/dir=ASC/); await expect(btn).toContainText('Älteste zuerst'); await page.screenshot({ path: 'test-results/e2e/conversations-sort.png' }); }); });