The Playwright suite had no linter and no formatter — only tsc. Add flat-config ESLint
(typescript-eslint, type-aware) and Prettier (2-space, matching the suite's style).
Rules keep the ones that catch real TEST bugs and drop the noise:
- no-floating-promises KEPT — an un-awaited request/assertion can let a test end before it runs,
passing vacuously. It caught one: the SSE reader loop in sse-listener is now explicitly `void`.
- no-unused-vars KEPT — caught three dead bindings (an unused adminToken fixture arg, an unused
`api` arg, an unused JPEG_MAGIC import), all removed.
- no-explicit-any OFF — all test code; `any` is the honest type for an untyped res.json() body or
a page.evaluate() return.
- no-empty-pattern OFF — Playwright's dependency-free fixtures are `async ({}, use) => {}`.
Refactor: `const BASE = process.env.E2E_FRONTEND_URL ?? '...'` was redeclared verbatim in 23
files — extracted to helpers/env.ts and imported, so a port/scheme change is one edit not a sweep.
Then `prettier --write`. Verified: eslint clean, tsc clean, prettier clean, desktop suite 210
passed / 1 skipped. (One mobile spec flaked once under retries:0 — a pre-existing cross-test
reflow-timing vector from the flakiness audit, not this change: the each-key edit is stable across
16 isolated runs and a clean full mobile re-run.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.7 KiB
TypeScript
68 lines
2.7 KiB
TypeScript
/**
|
||
* Phase 3 mobile — touch target sizing audit.
|
||
*
|
||
* Apple HIG says ≥ 44×44 pt. Material says ≥ 48×48 dp. We assert ≥ 44 px
|
||
* on critical interactive elements at mobile viewports (the device
|
||
* descriptor used by this project sets `deviceScaleFactor` but
|
||
* `boundingBox` returns CSS pixels, so the threshold is in CSS px).
|
||
*
|
||
* Each test grabs the bounding box for the locator and asserts both
|
||
* dimensions are ≥ 44. Fails surface with the actual sizing so the
|
||
* mismatch is obvious.
|
||
*/
|
||
import { test, expect } from '../../fixtures/test';
|
||
|
||
const MIN_TOUCH = 44;
|
||
|
||
async function assertTouchTarget(box: { width: number; height: number } | null, name: string) {
|
||
if (!box) throw new Error(`${name} not visible — no bounding box`);
|
||
expect
|
||
.soft(box.width, `${name} width ≥ ${MIN_TOUCH}px (got ${box.width})`)
|
||
.toBeGreaterThanOrEqual(MIN_TOUCH);
|
||
expect
|
||
.soft(box.height, `${name} height ≥ ${MIN_TOUCH}px (got ${box.height})`)
|
||
.toBeGreaterThanOrEqual(MIN_TOUCH);
|
||
}
|
||
|
||
test.describe('Mobile — touch target audit', () => {
|
||
test('bottom nav links and FAB are ≥ 44×44 px on /feed', async ({ page, guest, signIn }) => {
|
||
const g = await guest('Touchy');
|
||
await signIn(page, g);
|
||
await page.goto('/feed');
|
||
|
||
const galerie = page.getByRole('link', { name: 'Galerie' });
|
||
const fab = page.getByRole('button', { name: 'Hochladen' });
|
||
const konto = page.getByRole('link', { name: 'Konto' });
|
||
|
||
await expect(galerie).toBeVisible();
|
||
await assertTouchTarget(await galerie.boundingBox(), 'Galerie nav link');
|
||
await assertTouchTarget(await fab.boundingBox(), 'Upload FAB');
|
||
await assertTouchTarget(await konto.boundingBox(), 'Konto nav link');
|
||
});
|
||
|
||
test('join submit and PIN-modal buttons are ≥ 44×44 px', async ({ page }) => {
|
||
await page.goto('/join');
|
||
const submit = page.getByTestId('join-submit');
|
||
await assertTouchTarget(await submit.boundingBox(), 'Join submit button');
|
||
});
|
||
|
||
test('admin login submit button is ≥ 44×44 px', async ({ page }) => {
|
||
await page.goto('/admin/login');
|
||
const submit = page.getByTestId('admin-login-submit');
|
||
await assertTouchTarget(await submit.boundingBox(), 'Admin login submit');
|
||
});
|
||
|
||
test('PIN copy + continue buttons in the PIN modal are ≥ 44×44 px', async ({ page }) => {
|
||
await page.goto('/join');
|
||
await page.getByTestId('join-name-input').fill('TouchTargetPinModal');
|
||
await page.getByTestId('join-submit').click();
|
||
await expect(page.getByTestId('pin-modal')).toBeVisible();
|
||
|
||
await assertTouchTarget(await page.getByTestId('pin-copy').boundingBox(), 'PIN copy button');
|
||
await assertTouchTarget(
|
||
await page.getByTestId('continue-to-feed').boundingBox(),
|
||
'Continue-to-feed button'
|
||
);
|
||
});
|
||
});
|