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>
This commit is contained in:
83
frontend/e2e/dashboard-enrichment-block.spec.ts
Normal file
83
frontend/e2e/dashboard-enrichment-block.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user