feat(e2e): seed admin notifications + resume strip for dashboard proofshots
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 2m34s
CI / Backend Unit Tests (pull_request) Failing after 2m49s
CI / E2E Tests (pull_request) Failing after 1h24m49s
- captureProofshots() now accepts an optional setup(page) callback that runs before each screenshot's page.goto(), so localStorage can be injected reliably without loading a backend-dependent page - dashboard-screenshots.spec.ts seeds 2 notifications (MENTION + REPLY) for admin via direct DB insert in beforeAll, cleans up in afterAll - localStorage.familienarchiv.lastVisited injected directly via page.evaluate() — no fragile document page navigation needed - Updated screenshots committed (all 6 now show all 4 widgets) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@@ -1,3 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Dashboard proofshots — seeds the admin account with test data so every
|
||||||
|
* widget is visible, then captures 6 screenshots (3 viewports × 2 themes).
|
||||||
|
*
|
||||||
|
* Seeded data is removed in afterAll so it doesn't pollute other tests.
|
||||||
|
*/
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
import { captureProofshots } from './proofshots';
|
import { captureProofshots } from './proofshots';
|
||||||
|
|
||||||
captureProofshots('/', 'dashboard');
|
// A real document that exists in the dev DB (most recently updated)
|
||||||
|
const SEED_DOC_ID = '24580ce9-9765-40b1-ac59-b0ab15160ce0';
|
||||||
|
const SEED_DOC_TITLE = 'Brief aus dem Krieg';
|
||||||
|
|
||||||
|
// Real comment IDs used as reference_id for deep-linking
|
||||||
|
const COMMENT_IDS = [
|
||||||
|
'46c5171f-1721-4085-a7ed-1eef7b4effb8',
|
||||||
|
'a09cefe4-ddf8-47fa-addc-5c582183b459'
|
||||||
|
];
|
||||||
|
|
||||||
|
const psql = (sql: string) =>
|
||||||
|
execSync(
|
||||||
|
`docker exec archive-db psql -U archive_user family_archive_db -c "${sql.replace(/"/g, '\\"')}"`
|
||||||
|
);
|
||||||
|
|
||||||
|
test.beforeAll(() => {
|
||||||
|
// Insert a MENTION and a REPLY notification for the admin user so the
|
||||||
|
// notifications widget is populated in the screenshots.
|
||||||
|
psql(`
|
||||||
|
INSERT INTO notifications (recipient_id, type, document_id, reference_id, read, actor_name)
|
||||||
|
SELECT id, 'MENTION', '${SEED_DOC_ID}', '${COMMENT_IDS[0]}', false, 'Berit Hoffmann'
|
||||||
|
FROM users WHERE username = 'admin';
|
||||||
|
|
||||||
|
INSERT INTO notifications (recipient_id, type, document_id, reference_id, read, actor_name)
|
||||||
|
SELECT id, 'REPLY', '${SEED_DOC_ID}', '${COMMENT_IDS[1]}', false, 'Marcel Raddatz'
|
||||||
|
FROM users WHERE username = 'admin';
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(() => {
|
||||||
|
// Remove only the seeded rows (identified by the sentinel actor names)
|
||||||
|
psql(`
|
||||||
|
DELETE FROM notifications
|
||||||
|
WHERE actor_name IN ('Berit Hoffmann', 'Marcel Raddatz')
|
||||||
|
AND recipient_id = (SELECT id FROM users WHERE username = 'admin');
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
captureProofshots('/', 'dashboard', {
|
||||||
|
setup: async (page) => {
|
||||||
|
// Navigate to '/' first so the browser has an origin for localStorage,
|
||||||
|
// then inject the lastVisited entry directly — no document page load needed.
|
||||||
|
await page.goto('/');
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
await page.evaluate(
|
||||||
|
({ id, title }) => {
|
||||||
|
localStorage.setItem('familienarchiv.lastVisited', JSON.stringify({ id, title }));
|
||||||
|
},
|
||||||
|
{ id: SEED_DOC_ID, title: SEED_DOC_TITLE }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
/**
|
/**
|
||||||
* Shared proofshot helper for Playwright.
|
* Shared proofshot helper for Playwright.
|
||||||
*
|
*
|
||||||
* Usage in any spec file:
|
* Basic usage:
|
||||||
* import { captureProofshots } from './proofshots';
|
* import { captureProofshots } from './proofshots';
|
||||||
* captureProofshots('/persons', 'persons');
|
* captureProofshots('/persons', 'persons');
|
||||||
*
|
*
|
||||||
* This registers one test per viewport × theme combination.
|
* With per-test setup (e.g. seed localStorage before navigation):
|
||||||
|
* captureProofshots('/persons', 'persons', {
|
||||||
|
* setup: async (page) => {
|
||||||
|
* await page.goto('/persons/some-id'); // populates any localStorage state
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* The setup callback runs before each screenshot's page.goto(url), so any
|
||||||
|
* localStorage values it writes persist into the main navigation.
|
||||||
|
*
|
||||||
* Screenshots are saved to proofshot-artifacts/{featureName}/.
|
* Screenshots are saved to proofshot-artifacts/{featureName}/.
|
||||||
*/
|
*/
|
||||||
import { test } from '@playwright/test';
|
import { type Page, test } from '@playwright/test';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
@@ -21,20 +30,38 @@ const viewports = [
|
|||||||
{ name: 'desktop', width: 1440, height: 900 }
|
{ name: 'desktop', width: 1440, height: 900 }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface ProofshotOptions {
|
||||||
|
/**
|
||||||
|
* Optional async callback that runs before each screenshot's page.goto(url).
|
||||||
|
* Use it to seed localStorage, visit a prerequisite page, etc.
|
||||||
|
*/
|
||||||
|
setup?: (page: Page) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers Playwright tests that navigate to `url`, apply each theme,
|
* Registers Playwright tests that navigate to `url`, apply each theme,
|
||||||
* and capture full-page screenshots at all standard viewports.
|
* and capture full-page screenshots at all standard viewports.
|
||||||
*
|
*
|
||||||
* @param url The path to screenshot (e.g. '/', '/persons', '/admin')
|
* @param url The path to screenshot (e.g. '/', '/persons', '/admin')
|
||||||
* @param featureName Used as the output directory name and screenshot file prefix
|
* @param featureName Used as the output directory name and screenshot file prefix
|
||||||
|
* @param options Optional setup callback and other options
|
||||||
*/
|
*/
|
||||||
export function captureProofshots(url: string, featureName: string): void {
|
export function captureProofshots(
|
||||||
|
url: string,
|
||||||
|
featureName: string,
|
||||||
|
options?: ProofshotOptions
|
||||||
|
): void {
|
||||||
const outDir = path.join(__dirname, '../../proofshot-artifacts', featureName);
|
const outDir = path.join(__dirname, '../../proofshot-artifacts', featureName);
|
||||||
fs.mkdirSync(outDir, { recursive: true });
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
|
||||||
for (const vp of viewports) {
|
for (const vp of viewports) {
|
||||||
for (const theme of ['light', 'dark'] as const) {
|
for (const theme of ['light', 'dark'] as const) {
|
||||||
test(`${featureName} – ${vp.name} – ${theme}`, async ({ page }) => {
|
test(`${featureName} – ${vp.name} – ${theme}`, async ({ page }) => {
|
||||||
|
// Run optional setup before main navigation (e.g. seed localStorage)
|
||||||
|
if (options?.setup) {
|
||||||
|
await options.setup(page);
|
||||||
|
}
|
||||||
|
|
||||||
await page.setViewportSize({ width: vp.width, height: vp.height });
|
await page.setViewportSize({ width: vp.width, height: vp.height });
|
||||||
await page.goto(url);
|
await page.goto(url);
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 66 KiB |