Files
familienarchiv/frontend/e2e/dashboard-enrichment-block.spec.ts
Marcel 3eda482000 test(e2e): dashboard enrichment block — upload + axe sweep
Happy-path journey (upload 2 PDFs → banner → CTA → /enrich) plus axe
sweep at 320/768/1440 × light/dark for the dashboard route. Seeded
docs are cleaned up in afterEach via psql so repeated runs stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:13:37 +02:00

84 lines
2.9 KiB
TypeScript

/**
* Dashboard enrichment-list-block (issue #296) — full upload → banner → CTA journey,
* plus axe sweep in light and dark mode at 320 / 768 / 1440 viewports.
*
* The uploaded PDFs are deleted in afterEach so this spec does not pollute
* the dev DB between runs.
*/
import AxeBuilder from '@axe-core/playwright';
import { test, expect, type Page } from '@playwright/test';
import { execSync } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const VIEWPORTS = [
{ name: '320', width: 320, height: 720 },
{ name: '768', width: 768, height: 1024 },
{ name: '1440', width: 1440, height: 900 }
];
const psql = (sql: string) =>
execSync(
`docker exec archive-db psql -U archive_user family_archive_db -c "${sql.replace(/"/g, '\\"')}"`
);
test.afterEach(() => {
// Remove any document whose filename matches the seeded sentinel — keeps the
// DB clean for subsequent test runs.
psql(`DELETE FROM documents WHERE original_filename IN ('minimal.pdf', 'minimal2.pdf');`);
});
async function uploadViaDropZone(page: Page, files: string[]) {
const inputLocator = page.locator('input[type="file"][accept*="pdf"]');
await inputLocator.first().setInputFiles(files);
}
test.describe('Dashboard enrichment block — upload → banner → CTA', () => {
test('banner appears after upload and CTA navigates to /enrich', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('[data-hydrated]');
const fixturePath = (name: string) => path.join(__dirname, 'fixtures', name);
await uploadViaDropZone(page, [fixturePath('minimal.pdf'), fixturePath('minimal2.pdf')]);
const banner = page.getByRole('status').filter({ hasText: /hochgeladen/ });
await expect(banner).toBeVisible();
await expect(banner).toContainText(/2 Dokumente/);
await banner.getByRole('link', { name: /ergänzen/i }).click();
await expect(page).toHaveURL(/\/enrich(\/|$)/);
});
});
test.describe('Dashboard enrichment block — axe sweep', () => {
for (const viewport of VIEWPORTS) {
for (const scheme of ['light', 'dark'] as const) {
test(`no wcag2a/wcag2aa violations at ${viewport.name}px (${scheme})`, async ({
browser
}) => {
const context = await browser.newContext({
colorScheme: scheme,
viewport: { width: viewport.width, height: viewport.height },
storageState: path.join(__dirname, '.auth/user.json')
});
const page = await context.newPage();
await page.goto('/');
await page.waitForSelector('[data-hydrated]');
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze();
if (results.violations.length > 0) {
console.log(
`Violations on dashboard @ ${viewport.name}px ${scheme}:\n` +
results.violations.map((v) => `[${v.impact}] ${v.id}`).join('\n')
);
}
expect(results.violations).toEqual([]);
await context.close();
});
}
}
});