Files
F-r-ldraledigplannerare/frontend/e2e/global-setup.ts
T
jacobh2 ed5680253e Check in a browser what the unit tests cannot see
Every serious defect in this project was found by opening the app, most
recently a feature that rendered blank cards to every user while its tests
passed, and a progress bar labelled in the wrong unit. Five checks now run
without a person: that the age-four figures on screen match what the API
computed, that a multi-week period draws as one bar rather than fragments,
that Sundays and holidays are red where Saturdays are not, that no identifier
leaks into visible text, and that no page logs an error.

Three of them were confirmed by reintroducing the original bug and watching
the right test fail. Dates are derived rather than written down: Easter is
computed independently instead of asked of the system under test, and the
fixture period is placed relative to today, so the suite does not expire.

It runs as its own job and does not block a merge until it has proved itself
stable.
2026-09-17 19:46:17 +02:00

104 lines
4.0 KiB
TypeScript

/**
* Runs once before the whole E2E suite. Two jobs:
*
* 1. Seed a known-good demo workspace deterministically, by shelling out to
* the same `backend/scripts/seed_demo.py --reset` a human would run —
* see that script's docstring for exactly what it creates (couple
* workspace, Alex/Sam/Nova, wishes, overrides, a few applied days).
*
* 2. Materialize one extra, deterministic multi-week band of planned leave
* days via the API (never by clicking through the UI) — the calendar
* only renders committed `planned_leave_day` rows, not raw wishes, and
* the seed script's own applied days are only 3 days long (too short to
* exercise cross-week-row banding). The band's dates are computed
* relative to "today", never hardcoded, and written to
* `e2e/.auth/fixture.json` for `calendar-band.spec.ts` to read.
*
* Defensive design: this polls the backend's health endpoint before doing
* anything else, regardless of what order Playwright happens to start
* `webServer` vs. `globalSetup` in — a setup failure here should always be
* "the backend never came up", never a flaky race.
*/
import { mkdirSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { execFileSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { apiCreatePlannedDay, apiGetContext, apiLogin, DEMO_EMAIL, DEMO_PASSWORD, waitForBackendHealth } from './support/api'
import { FIXTURE_PATH } from './support/fixture'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const BACKEND_DIR = path.resolve(__dirname, '../../backend')
// A deterministic multi-week band, independent of the leave-wish allocation
// engine's own date choices: 15 consecutive WEEKDAYS (~3 calendar-week
// rows), starting on the 1st of the month that is two months after
// "today" — always safely in the future, always entirely inside one
// calendar month (so the Calendar page shows both the band's true start and
// true end in a single month view), and never tied to a hardcoded year.
function computeBandDates(): { startDate: string; endDate: string; dates: string[] } {
const now = new Date()
const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 2, 1))
const dates: string[] = []
const cursor = new Date(monthStart.getTime())
while (dates.length < 15) {
const weekday = cursor.getUTCDay()
if (weekday !== 0 && weekday !== 6) {
dates.push(cursor.toISOString().slice(0, 10))
}
cursor.setUTCDate(cursor.getUTCDate() + 1)
}
const first = dates[0]
const last = dates[dates.length - 1]
if (!first || !last) throw new Error('failed to compute band dates')
return { startDate: first, endDate: last, dates }
}
export default async function globalSetup(): Promise<void> {
await waitForBackendHealth()
execFileSync('uv', ['run', 'python', 'scripts/seed_demo.py', '--reset'], {
cwd: BACKEND_DIR,
stdio: 'inherit',
})
const token = await apiLogin(DEMO_EMAIL, DEMO_PASSWORD)
const context = await apiGetContext(token)
const child = context.children[0]
if (!child) {
throw new Error('seed_demo.py did not create a child for the demo workspace')
}
const band = computeBandDates()
for (const date of band.dates) {
await apiCreatePlannedDay(token, {
workspace_id: context.workspace_id,
child_id: child.id,
parent_personal_data_uuid: context.personal_data_uuid,
date,
day_type: 'vacation',
is_dubbeldag: false,
})
}
mkdirSync(path.dirname(FIXTURE_PATH), { recursive: true })
writeFileSync(
FIXTURE_PATH,
JSON.stringify(
{
workspaceId: context.workspace_id,
childId: child.id,
personalDataUuid: context.personal_data_uuid,
childBirthDate: child.birth_date,
band: {
startDate: band.startDate,
endDate: band.endDate,
parentPersonalDataUuid: context.personal_data_uuid,
dayType: 'vacation',
},
},
null,
2,
),
)
}