test: add the end-to-end suite, on desktop and on a phone

Twenty-six tests across two viewports, covering what the spec asks for:
changing today's hours reaches the panel, a message survives the
translation service being unavailable, a closure period closes the days
it covers — plus the authorisation paths and the sign-out regression.

They run against the standalone build served the way the container
serves it, not `next start`, which refuses to work with standalone
output anyway. The suite therefore exercises the artifact that ships
rather than a second arrangement that could drift from it.

Sign-in mints the session cookie Auth.js would have issued rather than
driving Authentik. What is under test is the application's behaviour for
a given role; the handshake itself is verified against the live provider
separately, and standing up an identity provider per run would trade a
lot of machinery for coverage of somebody else's code. The secret lives
in one module imported by both the config and the fixtures — when it
differed, every signed-in test failed at once while looking like an
authorisation bug.

Database access goes through plain SQL rather than the Prisma client,
whose generated module format Playwright's loader and Next's bundler
disagree about. That traded one problem for a subtler one: node-postgres
parses a DATE column into a local-midnight Date, so reading it back
shifted the day at UTC+2. Dates are read as text now.

The mobile profile runs on Chromium: WebKit needs system packages only
root can install, and a suite nobody can run locally is a suite nobody
runs. The config says how to switch to the real engine.

Two real defects surfaced, both found by the tests rather than by
reading. The seven "Ouvert" checkboxes on the hours page were
indistinguishable to a screen reader; each now names its day. And on a
phone the signed-in address appeared nowhere at all — the header hides
it to save room — so nobody could tell which account was about to sign
an audit entry on a device the shop shares. It is on the dashboard now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
This commit is contained in:
2026-09-20 23:13:58 +02:00
co-authored by Claude Opus 5
parent f9f11ae88b
commit 446021cd75
13 changed files with 565 additions and 6 deletions
+80
View File
@@ -0,0 +1,80 @@
// Loaded here, not in globalSetup: this file is evaluated first, and the
// webServer environment below is read from it. Without this, TEST_DATABASE_URL
// is still undefined when the server is started and every page fails on a
// missing DATABASE_URL.
import 'dotenv/config';
import { defineConfig, devices } from '@playwright/test';
import { E2E_AUTH_SECRET, E2E_BASE_URL, E2E_PORT } from './e2e/constants';
/**
* End-to-end tests.
*
* They run against a real server and a real database — the E2E database, which
* the global setup empties. That is the whole point: these are the only tests
* that exercise the middleware, the server actions and the rendering together,
* the way a person in the shop does.
*
* Port 3020 so a development server on 3010 can stay running.
*/
const PORT = E2E_PORT;
const BASE_URL = E2E_BASE_URL;
export default defineConfig({
testDir: './e2e',
fullyParallel: false,
workers: 1,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
reporter: process.env.CI ? [['github'], ['list']] : 'list',
globalSetup: './e2e/global-setup.ts',
use: {
baseURL: BASE_URL,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
locale: 'fr-CH',
timezoneId: 'Europe/Zurich',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{
// The shop uses a phone behind the counter, so the narrow viewport is
// the primary case, not an afterthought.
//
// Run on Chromium rather than WebKit: WebKit needs system packages that
// only root can install, and a suite nobody can run locally is a suite
// nobody runs. This still covers the layout, the touch targets and the
// mobile keyboard types. To exercise the real Safari engine — worth
// doing before trusting an iPhone-only bug report — install the
// dependencies once (`sudo npx playwright install-deps webkit`) and drop
// the browserName override.
name: 'mobile',
use: { ...devices['iPhone 15'], browserName: 'chromium' },
},
],
webServer: {
// A production build rather than the dev server: it is what actually ships,
// and Next refuses to start a second dev server in the same directory, so
// this also lets the suite run while someone is developing.
command: 'sh scripts/e2e-server.sh',
url: `${BASE_URL}/api/health`,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
env: {
PORT: String(PORT),
NODE_ENV: 'production',
// Never the development database: the setup empties this one.
DATABASE_URL: process.env.TEST_DATABASE_URL ?? '',
AUTH_URL: BASE_URL,
AUTH_SECRET: E2E_AUTH_SECRET,
AUTHENTIK_ADMIN_GROUP: 'horaires-admins',
// The translation service is stubbed per-test; never called for real.
TRANSLATION_API_URL: '',
TRANSLATION_API_KEY: '',
},
},
});