import { test, expect } from '@playwright/test'; import path from 'path'; /** * 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' }); }); }); // ─── PDF Viewer ─────────────────────────────────────────────────────────────── const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf'); test.describe('PDF viewer', () => { let pdfDocHref: string; test.beforeAll(async ({ browser }) => { // Create a document and upload the PDF fixture so later tests have a // real file attached. Runs once for the whole describe block. const ctx = await browser.newContext(); const p = await ctx.newPage(); await p.goto('/documents/new'); await p.waitForSelector('[data-hydrated]'); await p.getByLabel('Titel').fill('E2E PDF Viewer Test'); await p.getByRole('button', { name: /Speichern/i }).click(); await p.waitForURL(/\/documents\/[^/]+$/); // Upload the PDF on the edit page const href = p.url().replace(/\/$/, ''); pdfDocHref = href; await p.goto(`${href}/edit`); await p.waitForSelector('[data-hydrated]'); await p.locator('input[type="file"][name="file"]').setInputFiles(PDF_FIXTURE); await p.getByRole('button', { name: /Speichern/i }).click(); await p.waitForURL(/\/documents\/[^/]+$/); await ctx.close(); }); test('PDF renders in the custom viewer — canvas is present instead of iframe', async ({ page }) => { await page.goto(pdfDocHref); await page.waitForSelector('[data-hydrated]'); // There must be NO iframe — we replaced it with PDF.js canvas rendering. await expect(page.locator('iframe')).not.toBeAttached(); // At least one canvas element must be visible (one per rendered page). await expect(page.locator('canvas').first()).toBeVisible({ timeout: 15000 }); await page.screenshot({ path: 'test-results/e2e/pdf-viewer-canvas.png' }); }); test('page navigation controls are visible', async ({ page }) => { await page.goto(pdfDocHref); await page.waitForSelector('[data-hydrated]'); await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 15000 }); await expect(page.getByRole('button', { name: /prev|previous|zurück|vorige/i })).toBeVisible(); await expect(page.getByRole('button', { name: /next|weiter|nächste/i })).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/pdf-viewer-nav.png' }); }); test('non-PDF attachment renders as an img element, not canvas', async ({ page }) => { // The seed document "Urlaubspostkarte Ostsee" has a .jpg original filename. // Navigate to it and confirm an is used (no canvas, no iframe). await page.goto('/'); await page.waitForSelector('[data-hydrated]'); await page.goto('/?q=Urlaubspostkarte'); const link = page.getByRole('link', { name: /Urlaubspostkarte/i }).first(); const href = await link.getAttribute('href'); await page.goto(href!); await page.waitForSelector('[data-hydrated]'); // No canvas — this is an image document await expect(page.locator('canvas')).not.toBeAttached(); await page.screenshot({ path: 'test-results/e2e/pdf-viewer-image-fallback.png' }); }); });