Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m56s
CI / Build container images (pull_request) Successful in 1m27s
CI / Auth e2e pack (pull_request) Successful in 9m18s
CI / Import/export fidelity gate (pull_request) Successful in 1m6s
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 20s
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m25s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 6m5s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Has been cancelled
First @media print support at all: page size/margins, navigation and interactive chrome suppressed, break behaviour for headings, tables, code blocks, figures and plugin blocks. The VS-NfD marking runs as header AND footer on every sheet via a real-table PrintFrame whose thead/tfoot browsers repeat per page — @page margin boxes are unimplemented and position:fixed places unreliably in both engines (verified empirically); on screen the table chain renders as plain blocks, so nothing changes visually. Verified as PDF-from-browser in Chromium 140 and Firefox 153 (2 markings on every page of a multi-page document); the repeatable procedure is documented in apps/web/e2e/README.md. Unclassified pages print without a marking. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
198 lines
11 KiB
Markdown
198 lines
11 KiB
Markdown
# End-to-end tests
|
||
|
||
Playwright suites, most local-only — three run in CI/CD:
|
||
|
||
| Suite | Target | Where it runs |
|
||
| ----------------- | --------------------------------- | --------------------------------------------------------------------------- |
|
||
| `smoke.spec.ts` | any deployed stage | CD pipeline against `https://test.dorfteich.cloud` after every deploy |
|
||
| `auth.spec.ts` | full local stack **with Mailpit** | CI job `auth-e2e` on every PR/push; locally against the dev stack |
|
||
| `content.spec.ts` | full local stack | same CI job `auth-e2e` (a second step), right after the auth pack |
|
||
| everything else | full local stack | locally only — `editor`/`sidebar`/`image`/`link`/`markdown`/`trash`.spec.ts |
|
||
|
||
`content.spec.ts` is the M2 content regression pack (issue #32): page
|
||
lifecycle, editor basics, image paste, Markdown round-trip, and trash —
|
||
enough to catch a regression across the whole content model without
|
||
re-running every edge case the feature-specific packs above already cover.
|
||
Its Markdown round-trip test is a real regression pin, not just a smoke
|
||
check: it compares the seeded "Every Element" fixture page's exported
|
||
Markdown byte-for-byte against the checked-in `content-page.md` (see
|
||
"Content fixtures" below) — any schema/serializer change that alters how a
|
||
node round-trips fails it, once the seed has re-run against the changed
|
||
code (build → migrate → seed → test, exactly CI's order).
|
||
|
||
## Running locally
|
||
|
||
```sh
|
||
# 1. Stack: database + Mailpit, api (3001), web dev server (5173)
|
||
docker compose -f deploy/compose/docker-compose.yml -f deploy/compose/compose.dev.yml up -d db mailpit
|
||
DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich pnpm --filter @dorfteich/api db:seed
|
||
DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich PORT=3001 pnpm --filter @dorfteich/api start:dev &
|
||
pnpm --filter @dorfteich/web dev &
|
||
|
||
# 2. Tests
|
||
E2E_BASE_URL=http://localhost:5173 E2E_MAILPIT_URL=http://localhost:8025 pnpm --filter @dorfteich/web e2e
|
||
```
|
||
|
||
`auth.spec.ts` skips itself when `E2E_MAILPIT_URL` is unset, so the CD
|
||
smoke run never trips over it.
|
||
|
||
## Fixture matrix
|
||
|
||
Seeded by `pnpm --filter @dorfteich/api db:seed` (idempotent — re-running
|
||
never duplicates). Shared password: `fixture passwort 123`. Fixtures exist
|
||
only on dev machines and disposable CI/Test databases.
|
||
|
||
| Username | State | Purpose |
|
||
| ------------------ | ------------------- | ---------------------------------------------------------------------------------------- |
|
||
| `fixture-admin` | active, Site Admin | admin UI/permissions cases |
|
||
| `fixture-user` | active | regular journeys, settings, sessions |
|
||
| `fixture-editor` | active | second regular account for the collab-permissions pack (reader/editor of another's pond) |
|
||
| `fixture-viewer` | active | signed-in non-member for `authenticated`/`public` access-rule cases (issue #55) |
|
||
| `fixture-outsider` | active | the "foreign user" of the permission matrix — member of nothing (issue #60) |
|
||
| `fixture-pending` | e-mail not verified | unverified-login cases |
|
||
|
||
## Permission matrix (`permission-matrix.spec.ts`, issue #60)
|
||
|
||
The cross-feature permission hardening pack pins the security-relevant
|
||
**subject × surface** combinations against regressions. It is API-level (the
|
||
UI adds nothing over the resolved status code) and enforces the 404-vs-403
|
||
policy: an unauthorized _read_ is 404 (existence hidden), an unauthorized
|
||
_write_ on something readable is 403.
|
||
|
||
- **Subjects:** site admin (`fixture-admin`), pond admin / owner
|
||
(`fixture-user`), editor (`fixture-editor`), the same editor _label-restricted_
|
||
by a `secret`-label deny, reader (`fixture-viewer`), public (anonymous), and
|
||
the foreign user (`fixture-outsider`, a member of nothing).
|
||
- **Surfaces:** page read, edit (collab-token `rw`/`ro`), sidebar list, search,
|
||
versions (history = write), media, and the public HTML endpoint.
|
||
- **Extending it:** a new permission-touching feature adds a surface here (one
|
||
`expect` row per subject) rather than a bespoke test, so the matrix stays the
|
||
one place the policy is pinned. A weakened guard is caught here — verified by
|
||
temporarily loosening a route decorator and watching the pack go red.
|
||
|
||
## Plugin sandbox (`plugins.spec.ts`, issue #73)
|
||
|
||
The sandbox security pack drives the Site-Admin plugin preview page
|
||
(`/admin/plugins/:id/preview`) — the exact sandbox runtime pages embed — with
|
||
three fixture plugins built in `plugin-fixtures.ts` and installed through the
|
||
admin API:
|
||
|
||
- **well-behaved**: answers `render` and resizes its own frame via the declared
|
||
`ui` capability;
|
||
- **malicious** (the permanent security regression asset, ADR 0008): probes the
|
||
parent DOM, cookies, `localStorage`, same-origin and external `fetch`, and an
|
||
undeclared capability — every probe must report `blocked`. A `LEAKED` verdict
|
||
is a sandbox escape and fails the build;
|
||
- **hung**: never answers, so the host's 5 s deadline must collapse it to the
|
||
failure placeholder while the surrounding page stays responsive.
|
||
|
||
Runs in the `auth-e2e` CI job (needs the api's writable `PLUGINS_DIR`, satisfied
|
||
by the default `./data/plugins`).
|
||
|
||
## Attachments (`attachments.spec.ts`, issue #61)
|
||
|
||
Non-image attachments: a page's attachments section uploads an allowlisted
|
||
file, lists it, and inserts it into the document as a download link (verified
|
||
to serve with `Content-Disposition: attachment` + `nosniff`, never inline); a
|
||
disallowed extension is rejected with the localized allowlist error; the Pond
|
||
Admin file manager reports storage usage and flags an orphan (a pond-level
|
||
upload with no embedding page). The SVG sanitize/reject policy is covered at
|
||
the api level in `files.e2e.db.test.ts`.
|
||
|
||
## Import (`import.spec.ts`, issue #64)
|
||
|
||
The sidebar "import document" action: pick a file, upload, watch progress, open
|
||
the new page. `.md` imports directly (the response is already `succeeded`);
|
||
`.docx`/`.odt` poll a conversion job. Cases: a `.docx` corpus fixture (#63)
|
||
opens the converted page, a `.md` opens directly, an unsupported `.txt` shows
|
||
the localized error and creates no page, and two concurrent `.md` imports both
|
||
complete. The **`.docx` case self-skips unless `E2E_PANDOC` is set** — CI's e2e
|
||
stack has no reachable pandoc sidecar (jobs are container-networked; same reason
|
||
the api's real-pandoc fixtures test skips in CI, #63), so it runs locally / on a
|
||
stage. Run it locally with a sidecar reachable at the api's `PANDOC_URL`:
|
||
|
||
```sh
|
||
docker run -d -p 3030:3030 pandoc/core:3.6 server # api PANDOC_URL → this
|
||
E2E_PANDOC=1 E2E_BASE_URL=http://localhost:5990 \
|
||
pnpm --filter @dorfteich/web exec playwright test e2e/import.spec.ts
|
||
```
|
||
|
||
## Export (`export.spec.ts`, issue #65)
|
||
|
||
The pond-settings "Download pond as ZIP" link (a Markdown ZIP of the readable
|
||
pages) and the page-menu office export. The ZIP download needs no sidecar; the
|
||
`.docx` export runs a conversion job and, like the import `.docx` case,
|
||
**self-skips unless `E2E_PANDOC` is set**. The permission-filtered ZIP contents,
|
||
the docx pandoc output, and the 500-page streaming path are covered at the api
|
||
level in `export.service.db.test.ts` (+ `export-markdown.test.ts`).
|
||
|
||
## Content fixtures
|
||
|
||
`db:seed` also creates a **shared** pond `content-fixtures` (owned by
|
||
`fixture-user`) with two pages, for the content regression pack and manual
|
||
QA:
|
||
|
||
- **Every Element** (`every-element`) — every editor schema node and mark
|
||
(issue #24: headings 1–4, all list types, table, blockquote, code block,
|
||
horizontal rule, hard break, and all five marks). Loaded from the
|
||
checked-in `apps/api/prisma/fixtures/content-page.yjs`, a Yjs snapshot
|
||
generated from the human-readable `content-page.md` next to it —
|
||
`content-page.md` is the thing to read or edit; the `.yjs` file is a
|
||
build artifact of it, not source.
|
||
- **Fixture Image** (`fixture-image`) — one real, servable uploaded image
|
||
(the placeholder `fileId` inside the Markdown fixture above is not a
|
||
real attachment; this page's image is).
|
||
- **Classified Note** (`classified-note`) — a VS-NfD-marked page
|
||
(issue #206, ADR 0022) so the a11y pack can assert the classification
|
||
banner (top and bottom, both themes).
|
||
|
||
## Print marking check (`print.css`, issue #207)
|
||
|
||
The VS-NfD marking must appear at the top AND bottom of **every printed
|
||
sheet** (ADR 0022). Mechanism: the page content sits in a real
|
||
`<table class="print-frame">` whose `thead`/`tfoot` carry the banners —
|
||
the one construct Chromium and Gecko both repeat per page (`@page` margin
|
||
boxes are unimplemented; `position: fixed` places unreliably). The check,
|
||
repeatable against a running local stack (seeded, signed in as
|
||
`fixture-user`):
|
||
|
||
1. Classify a multi-page document:
|
||
`UPDATE pages SET classification='VS_NFD' WHERE slug='every-element';`
|
||
2. **Chromium**: print `/p/content-fixtures/every-element` to PDF
|
||
(browser print dialog, or Playwright `page.pdf({ preferCSSPageSize:
|
||
true })` after logging in).
|
||
3. **Gecko (Firefox)**: open the same page (or grant it a public reader
|
||
and use `/public/content-fixtures/every-element`), Cmd+P → save as
|
||
PDF. Headless equivalent: geckodriver + WebDriver `POST
|
||
/session/<id>/print`.
|
||
4. Every page of both PDFs must show `VS – NUR FÜR DEN DIENSTGEBRAUCH`
|
||
once at the top and once at the bottom, with no navigation chrome and
|
||
no overlap with content (quick text check:
|
||
`pypdf` → `page.extract_text().count(...) == 2` per page).
|
||
|
||
Last verified 2026-07-31: Chromium 140 (Playwright) and Firefox 153,
|
||
2 markings on every page of a 2-page PDF each.
|
||
|
||
Regenerating after editing `content-page.md`:
|
||
|
||
```sh
|
||
pnpm --filter @dorfteich/api fixtures:regenerate
|
||
```
|
||
|
||
This is deterministic — re-running without editing the Markdown produces a
|
||
byte-identical `.yjs` file (the script pins the Yjs document's `clientID`,
|
||
which is otherwise randomized per `Y.Doc` instance) — and it refuses to
|
||
write a snapshot that isn't a fixed point of the Markdown round-trip
|
||
(`docToMarkdown(markdownToDoc(x)) === x`), so a stale fixture can't get
|
||
checked in silently.
|
||
|
||
## Conventions
|
||
|
||
- New feature packs get their own `<feature>.spec.ts` next to these and
|
||
extend the fixture matrix here (permission matrix arrives with M5,
|
||
issue #60).
|
||
- Use `contextForUser()` from `helpers.ts` for signed-in tests — it logs
|
||
in through the api and hands you a browser context with the session
|
||
cookie, no UI login repetition.
|
||
- Flaky tests are defects (ADR 0014): fix or quarantine immediately.
|