Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m35s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Successful in 3m40s
CI / fail2ban Regex (pull_request) Successful in 45s
CI / Semgrep Security Scan (pull_request) Successful in 21s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m7s
Strengthen the zoom-clamp test to assert z floors at 0.25 in the URL (was a 'does not throw' smoke test) and move the affordance localStorage reset to a beforeEach so the e2e tests are order-independent (QA review). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.9 KiB
TypeScript
73 lines
2.9 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
// Visual + structural coverage for the #692 mobile read path (pan/zoom/fit,
|
|
// first-load affordance, bottom-sheet person panel).
|
|
//
|
|
// Snapshot assertions are gated on VISUAL=1 because they need pre-captured
|
|
// baselines — regenerate in CI with `playwright test --update-snapshots` after
|
|
// intentional UI changes. Structural assertions run unconditionally. The whole
|
|
// suite is also subject to the project-wide Chromium-in-CI gate (#363); it
|
|
// captures new snapshots rather than replacing the #361 desktop baselines.
|
|
const VISUAL = process.env.VISUAL === '1';
|
|
|
|
const WIDTHS = [320, 414, 768] as const;
|
|
|
|
test.describe('Stammbaum — mobile read path (#692)', () => {
|
|
// Touch emulation so the canvas reports pointer:coarse and the first-load
|
|
// affordance appears; reduced-motion is already forced project-wide.
|
|
test.use({ hasTouch: true, isMobile: true });
|
|
|
|
// Clear the affordance-dismissed flag before every test so the first-load
|
|
// hint state is deterministic regardless of test order.
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.addInitScript(() => localStorage.removeItem('stammbaumAffordanceDismissedAt'));
|
|
});
|
|
|
|
for (const width of WIDTHS) {
|
|
test(`affordance + controls render at ${width}px`, async ({ page }) => {
|
|
await page.setViewportSize({ width, height: 720 });
|
|
await page.goto('/stammbaum');
|
|
await expect(page.getByRole('heading', { name: 'Stammbaum' })).toBeVisible();
|
|
|
|
const empty = page.getByRole('heading', { name: 'Noch keine Familienmitglieder' });
|
|
if (await empty.isVisible().catch(() => false)) {
|
|
test.skip(true, 'no seeded family tree in this environment');
|
|
}
|
|
|
|
// Bottom-right control cluster with the fit-to-screen affordance.
|
|
await expect(page.getByTestId('fit-to-screen')).toBeVisible();
|
|
// First-load interactive hint (touch only).
|
|
await expect(page.getByRole('status')).toBeVisible();
|
|
|
|
if (VISUAL) {
|
|
await expect(page).toHaveScreenshot(`stammbaum-affordance-${width}.png`, {
|
|
animations: 'disabled'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
test('bottom sheet opens on node tap at 414px and preserves the canvas', async ({ page }) => {
|
|
await page.setViewportSize({ width: 414, height: 720 });
|
|
await page.goto('/stammbaum');
|
|
await expect(page.getByRole('heading', { name: 'Stammbaum' })).toBeVisible();
|
|
|
|
const node = page.locator('svg[aria-label="Stammbaum"] g[role="button"]').first();
|
|
if ((await node.count()) === 0) test.skip(true, 'no seeded nodes to tap');
|
|
|
|
await node.tap();
|
|
const sheet = page.getByRole('dialog');
|
|
await expect(sheet).toBeVisible();
|
|
|
|
if (VISUAL) {
|
|
await expect(page).toHaveScreenshot('stammbaum-bottom-sheet-414.png', {
|
|
animations: 'disabled'
|
|
});
|
|
}
|
|
|
|
// Dismiss via the backdrop and confirm the sheet closes (state survives).
|
|
await page.getByRole('button', { name: 'Schließen' }).first().click();
|
|
await expect(sheet).toBeHidden();
|
|
});
|
|
});
|