dorfteich/apps/web/e2e
Claude Fable 5 923532f5f7
All checks were successful
CD / Promote to Int (push) Successful in 9s
CI / Auth e2e pack (push) Successful in 3m56s
CI / Import/export fidelity gate (push) Successful in 43s
CI / Lint, typecheck, test (push) Successful in 2m53s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m9s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Deploy to Test (push) Successful in 12s
Add block plugins: plugin_block node with sandboxed rendering and editing (#76)
The powerful end of the plugin spectrum (ADR 0008 extension point `block`):

- Shared schema: the reserved `plugin_block` node — a block atom carrying
  pluginId, blockType, and the block data as a JSON object. Its DOM shape
  round-trips the full state in data attributes (clipboard-safe), markdown
  maps to a reserved fence (```dorfteich-plugin <plugin>/<type> + data
  JSON body, fence-escalated when the payload contains backticks), and the
  content-cache HTML renders a data-carrying neutral placeholder until the
  export fallbacks land (#79).
- Editor: a React NodeView hosts the #73 sandbox — render lifecycle on
  mount, an edit affordance switching the frame to the plugin's edit mode,
  and the blockData capability persisting through node attrs (a normal
  editor transaction, so Yjs replicates it; writes are refused on read-only
  editors, and the plugin's own attr echo is suppressed so its edit UI
  never resets mid-typing). Collaborator changes re-invoke the current
  lifecycle, keeping frames live. The page surface (ids, openPage) flows
  through a React context like the wikilink pattern; the toolbar gets an
  insert picker fed from the active code plugins' block extension points.
- Fallback: GET /plugins/:id/fallback resolves the manifest fallback from
  the stored snapshot — it survives uninstall as a tombstone, image
  fallbacks degrade to neutral once assets are gone. Signed-in only.
- e2e plugin-blocks.spec.ts covers all four acceptance criteria: insert →
  edit → reload round-trip, live two-user collab, disable → fallback →
  re-enable without document mutation, and copy/paste within and across
  pages (the markdown clipboard carries the reserved fence).

getBlock (cross-page block embedding) stays deferred as in #74: the
schema has no per-block ids yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-11 12:45:14 +02:00
..
access-rules.spec.ts Add effective-permissions inspector (#57) 2026-07-10 00:00:27 +02:00
admin-quotas.spec.ts Add Site-Admin quota override management UI (#58) 2026-07-10 00:14:28 +02:00
admin-users.spec.ts Add Site-Admin user management (#59) 2026-07-10 00:31:41 +02:00
attachments.spec.ts Add non-image attachments with allowlist, SVG policy, and file managers (#61) 2026-07-10 02:52:40 +02:00
auth.spec.ts Bind the CI dev server to all interfaces and surface login errors 2026-07-05 06:07:49 +02:00
backlinks.spec.ts Add backlinks panel and phantom-pages view (#48) 2026-07-09 13:05:32 +02:00
collab-permissions.spec.ts Wire real permissions into collab tokens and revocation (#53) 2026-07-09 18:48:42 +02:00
collab.spec.ts Wire real permissions into collab tokens and revocation (#53) 2026-07-09 18:48:42 +02:00
content.spec.ts Switch the editor to live collaboration (#36) 2026-07-08 18:00:19 +02:00
editor.spec.ts Modernize stale editor e2e pack to live-collab semantics 2026-07-09 17:01:41 +02:00
export.spec.ts Add PDF export via Gotenberg (#67) 2026-07-10 12:11:12 +02:00
fonts.spec.ts Add per-pond fonts: catalog, build, application, and admin UI (#66) 2026-07-10 11:16:29 +02:00
helpers.ts Add auth e2e regression pack with fixtures and CI stack 2026-07-05 05:43:05 +02:00
image.spec.ts Add image paste and insert in the editor (#28) 2026-07-08 11:30:25 +02:00
import.spec.ts Add document import UI in the sidebar (#64) 2026-07-10 09:38:31 +02:00
labels.spec.ts Add label UI: tree management, page assignment, and sidebar filter (#44) 2026-07-09 11:41:42 +02:00
link.spec.ts Add link UX: edit URL and open in new tab (#29) 2026-07-08 11:50:34 +02:00
markdown.spec.ts Add Markdown copy, paste, and per-page export endpoint (#30) 2026-07-08 12:05:11 +02:00
members.spec.ts Add pond member management UI (#54) 2026-07-09 20:22:52 +02:00
offline.spec.ts Harden the offline e2e for slower CI timing (#38) 2026-07-08 22:24:29 +02:00
permission-matrix.spec.ts Add cross-feature permission hardening matrix (#60) 2026-07-10 00:43:16 +02:00
plugin-admin.spec.ts Add plugin administration UI: instance modes and pond activation (#72) 2026-07-11 09:32:16 +02:00
plugin-blocks.spec.ts Add block plugins: plugin_block node with sandboxed rendering and editing (#76) 2026-07-11 12:45:14 +02:00
plugin-fixtures.ts Add block plugins: plugin_block node with sandboxed rendering and editing (#76) 2026-07-11 12:45:14 +02:00
plugins.spec.ts Add the sandbox host runtime for plugin iframes (#73) 2026-07-11 09:16:30 +02:00
public.spec.ts Add public read access and server-rendered page HTML (#56) 2026-07-09 23:46:25 +02:00
README.md Add the sandbox host runtime for plugin iframes (#73) 2026-07-11 09:16:30 +02:00
reorder.spec.ts Add manual page ordering with drag-and-drop (#45) 2026-07-09 12:18:44 +02:00
search.spec.ts Add search UI with scoping and snippets (#50) 2026-07-09 13:36:06 +02:00
section-styles.spec.ts Complete section-style plugins: CSS gate, injection, picker, export (#75) 2026-07-11 11:43:36 +02:00
sidebar.spec.ts Add pond sidebar with page list, sort modes, and pond switcher (#26) 2026-07-06 12:37:44 +02:00
smoke.spec.ts Add collaboration server skeleton (Hocuspocus) with health, container, and CI/CD (#33) 2026-07-08 14:53:44 +02:00
trash.spec.ts Add page trash: soft delete, restore, and purge job (#31) 2026-07-08 12:48:17 +02:00
wikilink.spec.ts Add wikilink node with autocomplete (#46) 2026-07-09 12:42:01 +02:00

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

# 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:

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 14, 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).

Regenerating after editing content-page.md:

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.