test(e2e): notification deep-link scrolls to target comment
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m43s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 3m0s
CI / Unit & Component Tests (pull_request) Failing after 2m46s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m45s
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m43s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 3m0s
CI / Unit & Component Tests (pull_request) Failing after 2m46s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m45s
Seeds a document, transcription block, and block comment via API,
then visits /documents/{id}?commentId=X&annotationId=Y and asserts
the page enters transcribe mode, the comment article becomes visible,
and the URL query params are stripped. Runs at 320px and 1440px so
the collapsed PDF strip clipping on mobile is caught. An axe-core
pass guards the new tabindex + focus-visible ring against a11y
regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
117
frontend/e2e/notification-deep-link.spec.ts
Normal file
117
frontend/e2e/notification-deep-link.spec.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user