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>
84 lines
2.9 KiB
TypeScript
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();
|
|
});
|
|
}
|
|
}
|
|
});
|