test(e2e): add minimal Geschichten writer + reader Playwright spec
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m2s
CI / OCR Service Tests (pull_request) Successful in 41s
CI / Backend Unit Tests (pull_request) Failing after 3m6s
CI / Unit & Component Tests (push) Failing after 3m35s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 3m15s
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m2s
CI / OCR Service Tests (pull_request) Successful in 41s
CI / Backend Unit Tests (pull_request) Failing after 3m6s
CI / Unit & Component Tests (push) Failing after 3m35s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 3m15s
Three e2e tests against the real stack: - admin can navigate to /geschichten, create a draft, publish, and see the story appear on the index - a reader (or admin) can click a story card and reach the detail page with an <article> landmark visible - AxeBuilder scan of /geschichten reports no serious or critical WCAG violations Partial fix for Sara's review B1 on PR #382. The deeper 5-spec a11y suite and visual-regression coverage are deferred to a follow-up issue. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
78
frontend/e2e/geschichten.spec.ts
Normal file
78
frontend/e2e/geschichten.spec.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Minimal Geschichten coverage. The deeper a11y / visual-regression suite is
|
||||
* tracked separately; this file proves the core writer + reader journey works
|
||||
* end-to-end against the real stack.
|
||||
*
|
||||
* Pre-requisite: V59 has granted BLOG_WRITE to the Administrators group, so
|
||||
* the seeded admin user can author. The auth.setup project handles login.
|
||||
*/
|
||||
|
||||
const stamp = () => new Date().toISOString().replace(/[^0-9]/g, '');
|
||||
|
||||
test.describe('Geschichten — writer + reader journey', () => {
|
||||
test('admin can create a draft, publish it, and see it on the index', async ({ page }) => {
|
||||
const title = `E2E story ${stamp()}`;
|
||||
|
||||
// Land on the index — empty state or pre-existing demo data is fine
|
||||
await page.goto('/geschichten');
|
||||
await page.waitForSelector('[data-hydrated]');
|
||||
await expect(page.getByRole('heading', { name: 'Geschichten', level: 1 })).toBeVisible();
|
||||
|
||||
// Click "Neue Geschichte" — visible because admin has BLOG_WRITE
|
||||
await page.getByRole('link', { name: 'Neue Geschichte' }).click();
|
||||
await page.waitForURL('/geschichten/new');
|
||||
|
||||
// Fill in title — the body editor is Tiptap and harder to script reliably
|
||||
await page.getByPlaceholder('Titel der Geschichte').fill(title);
|
||||
|
||||
// Save as draft and verify we land on the detail page
|
||||
await page.getByRole('button', { name: 'Entwurf speichern' }).click();
|
||||
await page.waitForURL(/\/geschichten\/[^/]+$/);
|
||||
|
||||
// Capture the new id from the URL
|
||||
const detailUrl = page.url();
|
||||
const id = detailUrl.split('/').pop();
|
||||
expect(id).toBeTruthy();
|
||||
|
||||
// Publish from the edit page
|
||||
await page.getByRole('link', { name: 'Bearbeiten' }).click();
|
||||
await page.waitForURL(/\/edit$/);
|
||||
await page.getByRole('button', { name: 'Veröffentlichen' }).click();
|
||||
await page.waitForURL(detailUrl);
|
||||
|
||||
// Index now shows the published story
|
||||
await page.goto('/geschichten');
|
||||
await expect(page.getByRole('link', { name: title })).toBeVisible();
|
||||
});
|
||||
|
||||
test('reader is taken to a story detail when clicking a card', async ({ page }) => {
|
||||
await page.goto('/geschichten');
|
||||
await page.waitForSelector('[data-hydrated]');
|
||||
|
||||
// Use the first story link in the list (demo data exists; if not, the
|
||||
// previous test seeded one). The link wraps the whole card.
|
||||
const firstStory = page.locator('a[href^="/geschichten/"]').filter({ hasText: /.+/ }).first();
|
||||
await expect(firstStory).toBeVisible();
|
||||
await firstStory.click();
|
||||
|
||||
await page.waitForURL(/\/geschichten\/[^/]+$/);
|
||||
await expect(page.locator('article')).toBeVisible();
|
||||
});
|
||||
|
||||
test('AxeBuilder finds no critical violations on the index', async ({ page }) => {
|
||||
await page.goto('/geschichten');
|
||||
await page.waitForSelector('[data-hydrated]');
|
||||
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze();
|
||||
|
||||
// Filter to non-deferred severity. We don't gate the whole PR on a clean
|
||||
// AxeBuilder run yet — Sara's review tracks the broader a11y backlog —
|
||||
// but any "serious" or "critical" finding from this scan would block merge.
|
||||
const blocking = results.violations.filter(
|
||||
(v) => v.impact === 'serious' || v.impact === 'critical'
|
||||
);
|
||||
expect(blocking, JSON.stringify(blocking, null, 2)).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user