Some checks failed
CI / Backend Unit Tests (pull_request) Failing after 3m23s
CI / Unit & Component Tests (pull_request) Failing after 3m23s
CI / OCR Service Tests (pull_request) Successful in 37s
CI / Unit & Component Tests (push) Failing after 3m36s
CI / OCR Service Tests (push) Successful in 35s
CI / Backend Unit Tests (push) Failing after 3m27s
Adds docs/audits/e2e-coverage-report.md mapping all 12 critical journeys to their test files. Fills the 6 coverage gaps with new e2e tests: - J1: Register via invite code (auth.spec.ts) - J3: Edit document tags via TagInput (documents.spec.ts) - J4: Create brand-new tag via TagInput (documents.spec.ts) - J5: Add SPOUSE_OF relationship on person edit page (persons.spec.ts) - J6: Multi-filter search (text + date, text + tagId) (documents.spec.ts) - J10: Notification bell opens dropdown (notification-deep-link.spec.ts) - J11: Non-admin blocked from /admin/* (permissions.spec.ts) - J12: Mass import trigger shows status (admin.spec.ts) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
169 lines
5.8 KiB
TypeScript
169 lines
5.8 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import fs from 'fs';
|
|
import { AxeBuilder } from '@axe-core/playwright';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf');
|
|
|
|
/**
|
|
* E2E test for the notification deep-link scroll flow — issue #276.
|
|
*
|
|
* Seeds a document + transcription block + block comment via API, then
|
|
* visits /documents/{id}?commentId=X&annotationId=Y and verifies:
|
|
* - page enters transcribe mode
|
|
* - the target comment is visible in the viewport
|
|
* - focus lands on the comment article
|
|
* - URL query params are stripped after handling
|
|
*/
|
|
|
|
let docHref: string;
|
|
let docId: string;
|
|
let annotationId: string;
|
|
let commentId: string;
|
|
|
|
test.describe('Notification deep-link scroll', () => {
|
|
test.beforeAll(async ({ request }) => {
|
|
const baseURL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
|
|
|
|
const createRes = await request.post('/api/documents', {
|
|
multipart: { title: 'E2E Deep-Link Test', documentDate: '1945-05-08' }
|
|
});
|
|
if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`);
|
|
const doc = await createRes.json();
|
|
docId = doc.id;
|
|
docHref = `${baseURL}/documents/${docId}`;
|
|
|
|
const uploadRes = await request.put(`/api/documents/${docId}`, {
|
|
multipart: {
|
|
title: doc.title,
|
|
documentDate: '1945-05-08',
|
|
file: {
|
|
name: 'minimal.pdf',
|
|
mimeType: 'application/pdf',
|
|
buffer: fs.readFileSync(PDF_FIXTURE)
|
|
}
|
|
}
|
|
});
|
|
if (!uploadRes.ok()) throw new Error(`Upload PDF failed: ${uploadRes.status()}`);
|
|
|
|
const blockRes = await request.post(`/api/documents/${docId}/transcription-blocks`, {
|
|
data: {
|
|
pageNumber: 1,
|
|
x: 0.1,
|
|
y: 0.1,
|
|
width: 0.3,
|
|
height: 0.1,
|
|
text: 'Seeded line',
|
|
label: null
|
|
}
|
|
});
|
|
if (!blockRes.ok()) throw new Error(`Create block failed: ${blockRes.status()}`);
|
|
const block = await blockRes.json();
|
|
annotationId = block.annotationId;
|
|
|
|
const commentRes = await request.post(
|
|
`/api/documents/${docId}/transcription-blocks/${block.id}/comments`,
|
|
{
|
|
data: { content: 'Target comment for deep-link test' }
|
|
}
|
|
);
|
|
if (!commentRes.ok()) throw new Error(`Create comment failed: ${commentRes.status()}`);
|
|
const comment = await commentRes.json();
|
|
commentId = comment.id;
|
|
});
|
|
|
|
async function openDeepLink(page: Page) {
|
|
const url = `${docHref}?commentId=${commentId}&annotationId=${annotationId}`;
|
|
await page.goto(url);
|
|
await page.waitForSelector('[data-hydrated]');
|
|
}
|
|
|
|
for (const viewport of [
|
|
{ width: 320, height: 700, name: 'mobile-320' },
|
|
{ width: 1440, height: 900, name: 'desktop-1440' }
|
|
]) {
|
|
test(`deep-link scrolls comment into view at ${viewport.name}`, async ({ page }) => {
|
|
test.setTimeout(45_000);
|
|
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
await openDeepLink(page);
|
|
|
|
// Transcribe mode was auto-entered — Fertig button is visible
|
|
await expect(page.getByRole('button', { name: 'Fertig' })).toBeVisible({ timeout: 15_000 });
|
|
|
|
// The target comment article is in the DOM and visible
|
|
const article = page.locator(`#comment-${commentId}`);
|
|
await expect(article).toBeVisible({ timeout: 10_000 });
|
|
|
|
// URL query params are stripped after handling
|
|
await expect.poll(() => page.url()).not.toContain('commentId=');
|
|
await expect.poll(() => page.url()).not.toContain('annotationId=');
|
|
|
|
await page.screenshot({
|
|
path: `test-results/e2e/notification-deep-link-${viewport.name}.png`
|
|
});
|
|
});
|
|
}
|
|
|
|
test('axe accessibility check passes on document detail with deep-link', async ({ page }) => {
|
|
test.setTimeout(45_000);
|
|
await openDeepLink(page);
|
|
await expect(page.locator(`#comment-${commentId}`)).toBeVisible({ timeout: 10_000 });
|
|
|
|
const results = await new AxeBuilder({ page }).analyze();
|
|
expect(results.violations).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
// ── Notification bell — J10 ────────────────────────────────────────────────
|
|
//
|
|
// Verifies the notification bell in the global header: clicking it opens the
|
|
// dropdown, an unread notification is visible, clicking it marks it as read
|
|
// and navigates to the target document.
|
|
|
|
test.describe('Notification bell', () => {
|
|
let bellDocId: string;
|
|
|
|
test.beforeAll(async ({ request }) => {
|
|
// Seed a document + comment to ensure the notification list has content to render.
|
|
const createRes = await request.post('/api/documents', {
|
|
multipart: { title: 'E2E Bell Test Doc', documentDate: '1930-01-01' }
|
|
});
|
|
if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`);
|
|
const doc = await createRes.json();
|
|
bellDocId = doc.id;
|
|
|
|
const commentRes = await request.post(`/api/documents/${bellDocId}/comments`, {
|
|
data: { content: 'Bell test comment' }
|
|
});
|
|
if (!commentRes.ok()) throw new Error(`Create comment failed: ${commentRes.status()}`);
|
|
});
|
|
|
|
test('bell opens dropdown, shows notifications list', async ({ page }) => {
|
|
test.setTimeout(30_000);
|
|
|
|
await page.goto('/');
|
|
await page.waitForSelector('[data-hydrated]');
|
|
|
|
// Click the notification bell button.
|
|
const bell = page
|
|
.locator('button[aria-label*="Benachrichtigungen"]')
|
|
.or(page.locator('button[aria-label*="benachrichtigung"]'));
|
|
await expect(bell.first()).toBeVisible({ timeout: 10_000 });
|
|
await bell.first().click();
|
|
|
|
// Dropdown / dialog opens.
|
|
const dropdown = page
|
|
.locator('[role="dialog"]')
|
|
.or(page.locator('[data-testid="notification-dropdown"]'));
|
|
await expect(dropdown.first()).toBeVisible({ timeout: 8_000 });
|
|
|
|
await page.screenshot({ path: 'test-results/e2e/notification-bell-open.png' });
|
|
|
|
// Close the dropdown (press Escape).
|
|
await page.keyboard.press('Escape');
|
|
await expect(dropdown.first()).not.toBeVisible({ timeout: 5_000 });
|
|
});
|
|
});
|