Consolidate E2E into e2e-tests/ (Dockerfile + compose.{visual,auth,oauth,full,devstack}.yml, WORKDIR /e2e-tests); move ci.sh to repo root

This commit is contained in:
2026-06-23 23:48:05 +02:00
parent bb612baa2c
commit 913bd6813a
25 changed files with 87 additions and 84 deletions
+4 -1
View File
@@ -4,4 +4,7 @@ npm-debug.log
*.log
.DS_Store
e2e/artifacts
e2e-tests/artifacts
# Orchestration, not test code — keep them out of the runner image (COPY e2e-tests/ ./)
e2e-tests/Dockerfile
e2e-tests/compose.*.yml
+1 -1
View File
@@ -4,4 +4,4 @@
node_modules
# Playwright E2E outputs (screenshots, html report, traces)
e2e/artifacts/
e2e-tests/artifacts/
-12
View File
@@ -1,12 +0,0 @@
# Playwright runner — browsers preinstalled, pinned to match @playwright/test in e2e/.
# Built/run via compose.e2e.yml; targets the `web` service over the network.
FROM mcr.microsoft.com/playwright:v1.49.1-noble
WORKDIR /e2e
COPY e2e/package.json e2e/package-lock.json ./
RUN npm ci
COPY e2e/ ./
CMD ["npx", "playwright", "test"]
+19 -19
View File
@@ -650,8 +650,8 @@ off-canvas layout, icon sprite, CSRF-guarded sign-out, the public landing, the 4
plugin permission-gating.
```bash
docker compose -f compose.yml -f compose.e2e.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f compose.e2e.yml down -v # tear down after
docker compose -f compose.yml -f e2e-tests/compose.visual.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f e2e-tests/compose.visual.yml down -v # tear down after
```
**Auth — token timeout + refresh** (`auth-refresh.spec.ts`) — the full-stack counterpart: it
@@ -662,8 +662,8 @@ live Kratos session (roles re-read from Keto), and once that session is revoked
cookie is **cleared**.
```bash
docker compose -f compose.yml -f compose.e2e-auth.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f compose.e2e-auth.yml down -v # tear down after
docker compose -f compose.yml -f e2e-tests/compose.auth.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f e2e-tests/compose.auth.yml down -v # tear down after
```
**OAuth2 login + consent** (`oauth-login.spec.ts`) — another app logs in *through* us: it
@@ -674,24 +674,24 @@ then shows the consent screen for the third-party client and **Allow** drives Hy
the authorization code.
```bash
docker compose -f compose.yml -f compose.e2e-oauth.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f compose.e2e-oauth.yml down -v # tear down after
docker compose -f compose.yml -f e2e-tests/compose.oauth.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f e2e-tests/compose.oauth.yml down -v # tear down after
```
**Full browser flow** (`full-flow.spec.ts`) — the real Playwright UI against the live stack:
the themed **password login** and a **mocked-SSO** login (an in-network mock OIDC provider,
`e2e/mock-oidc.mjs`), **menu filtering by role**, the **users/groups/roles** admin CRUD, a
`e2e-tests/mock-oidc.mjs`), **menu filtering by role**, the **users/groups/roles** admin CRUD, a
permission-gated **plugin page**, and **logout**. Because the themed form posts straight to
Kratos and cookies are host-scoped, a tiny same-origin gateway (`e2e/proxy.mjs`) fronts web +
Kratos and cookies are host-scoped, a tiny same-origin gateway (`e2e-tests/proxy.mjs`) fronts web +
Kratos on one host (`ory/kratos/e2e-proxy.yml` points Kratos at it) — exactly as a production
reverse proxy would.
```bash
docker compose -f compose.yml -f compose.e2e-full.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f compose.e2e-full.yml down -v # tear down after
docker compose -f compose.yml -f e2e-tests/compose.full.yml run --build --rm e2e # run the suite
docker compose -f compose.yml -f e2e-tests/compose.full.yml down -v # tear down after
```
`--build` rebuilds the runner so spec edits are always picked up (the image bakes in `e2e/`).
`--build` rebuilds the runner so spec edits are always picked up (the image bakes in `e2e-tests/`).
**Dev-stack login regression** (`devstack-login.spec.ts`) — drives the *plain* `docker
compose up` topology (not the same-origin gateway above) with the runner on the **host
@@ -702,26 +702,26 @@ first-run banner advertises (`http://localhost:3000`) **and** from the wrong hos
[canonical-host redirect](#canonical-host-one-public-url). It guards against the regression
where the advertised login URL dumps the user on the `/error` "Page not found" page; the
proxied full-flow suite can't catch this (it fronts web + Kratos on one origin). Part of
`scripts/ci.sh` — it needs host networking and the host ports `3000`/`4433` free (Linux).
`ci.sh` — it needs host networking and the host ports `3000`/`4433` free (Linux).
```bash
docker compose -f compose.yml -f compose.override.yml -f compose.e2e-devstack.yml run --build --rm e2e # run it
docker compose -f compose.yml -f compose.override.yml -f compose.e2e-devstack.yml down -v # tear down
docker compose -f compose.yml -f compose.override.yml -f e2e-tests/compose.devstack.yml run --build --rm e2e # run it
docker compose -f compose.yml -f compose.override.yml -f e2e-tests/compose.devstack.yml down -v # tear down
```
Screenshots + an HTML report land in `e2e/artifacts/` (git-ignored). Every user-facing flow
Screenshots + an HTML report land in `e2e-tests/artifacts/` (git-ignored). Every user-facing flow
is covered end-to-end; tests are independent and run **fully in parallel** for speed
([AGENTS.md](AGENTS.md)) — keep new tests side-effect-free so the suite stays fast.
### The full gate (one command)
`scripts/ci.sh` is the whole gate in one reproducible command — typecheck → unit tests →
`ci.sh` is the whole gate in one reproducible command — typecheck → unit tests →
each E2E suite against its own fresh stack, with a guaranteed `down -v` after each (even on
failure) and a non-zero exit on the first failure. Run it locally before a release, or wire
it into your CI service:
```bash
bash scripts/ci.sh
bash ci.sh
```
Each E2E suite **owns a clean stack** — never point two suites at one backend (auth-refresh
@@ -932,8 +932,8 @@ ory/ Ory service config (kratos/: identity schema, kratos.yml, o
plugins/ Drop-in plugin folders (scanned at /app/plugins; bind-mount or bake in). Ships scheduling/ — the reference plugin (list/form over an upstream + permission-gated nav) you copy
examples/ Non-app helpers; shifts-upstream/ is the dev mock backend the reference plugin reads/writes (stand-in for your real service)
docs/ Reference docs (plugin-contract.md — the authoritative plugin API)
e2e/ Playwright E2E: visual.spec (design system, Ory-free) + auth-refresh.spec (token timeout/re-mint) + oauth-login.spec (OAuth2 login + consent) + full-flow.spec (browser UI: password/SSO login, menu-by-role, admin CRUD, plugin page, logout) + devstack-login.spec (regression: login works from the banner's localhost URL and 127.0.0.1 is canonicalised, on the plain `docker compose up` topology); proxy.mjs (same-origin gateway) + mock-oidc.mjs (mock SSO provider) back full-flow. Dockerfile.e2e + compose.e2e[-auth|-oauth|-full|-devstack].yml run them
scripts/ci.sh The full CI gate: typecheck → unit tests → every E2E suite, each on a fresh, always-torn-down stack (`bash scripts/ci.sh`)
e2e-tests/ Playwright E2E: visual.spec (design system, Ory-free) + auth-refresh.spec (token timeout/re-mint) + oauth-login.spec (OAuth2 login + consent) + full-flow.spec (browser UI: password/SSO login, menu-by-role, admin CRUD, plugin page, logout) + devstack-login.spec (regression: login works from the banner's localhost URL and 127.0.0.1 is canonicalised, on the plain `docker compose up` topology); proxy.mjs (same-origin gateway) + mock-oidc.mjs (mock SSO provider) back full-flow. e2e-tests/Dockerfile + e2e-tests/compose.{visual,auth,oauth,full,devstack}.yml run them
ci.sh The full CI gate: typecheck → unit tests → every E2E suite, each on a fresh, always-torn-down stack (`bash ci.sh`)
```
## Extending the core
+12 -12
View File
@@ -3,20 +3,20 @@
# that is always torn down. One reproducible command — run it locally or wire it into your CI
# service. Docker-only (it drives `docker compose`; node/npm/tsc run inside containers, never the host).
#
# bash scripts/ci.sh
# bash ci.sh
#
# Exits non-zero on the first failure. Each E2E suite OWNS a clean stack — never point two suites at
# one backend (auth-refresh revokes the admin's sessions; full-flow writes users/groups/roles to Keto).
set -euo pipefail
cd "$(dirname "$0")/.."
cd "$(dirname "$0")"
step() { printf '\n\033[1;34m==> %s\033[0m\n' "$1"; }
# Pins that MUST move in lockstep: a browser/runner mismatch yields confusing E2E failures.
step "Playwright pin lockstep (Dockerfile.e2e image == e2e/package.json @playwright/test)"
step "Playwright pin lockstep (e2e-tests/Dockerfile image == e2e-tests/package.json @playwright/test)"
# `|| true` so a no-match doesn't trip `set -e`/`pipefail` before the explicit check below can report.
img=$(grep -oE 'playwright:v[0-9.]+' Dockerfile.e2e | grep -oE '[0-9.]+$' || true)
pkg=$(grep -oE '"@playwright/test": "[0-9.]+"' e2e/package.json | grep -oE '[0-9.]+' || true)
img=$(grep -oE 'playwright:v[0-9.]+' e2e-tests/Dockerfile | grep -oE '[0-9.]+$' || true)
pkg=$(grep -oE '"@playwright/test": "[0-9.]+"' e2e-tests/package.json | grep -oE '[0-9.]+' || true)
[ -n "$img" ] && [ "$img" = "$pkg" ] || { echo "Playwright pin mismatch/unreadable: image v$img vs @playwright/test $pkg"; exit 1; }
echo "ok ($img)"
@@ -41,19 +41,19 @@ e2e() {
[ "$rc" -eq 0 ] || { echo "E2E suite $1 failed (exit $rc)"; exit "$rc"; }
}
e2e compose.e2e.yml # visual / design-system parity (Ory-free)
e2e compose.e2e-auth.yml # token timeout + silent re-mint
e2e compose.e2e-oauth.yml # OAuth2 login + consent
e2e compose.e2e-full.yml # full browser flow: login (password + SSO), menu, CRUD, plugin, logout
e2e e2e-tests/compose.visual.yml # visual / design-system parity (Ory-free)
e2e e2e-tests/compose.auth.yml # token timeout + silent re-mint
e2e e2e-tests/compose.oauth.yml # OAuth2 login + consent
e2e e2e-tests/compose.full.yml # full browser flow: login (password + SSO), menu, CRUD, plugin, logout
# Dev-stack login regression — runs against the PLAIN `docker compose up` topology (base + override)
# with the runner on the HOST network, so it can't use the shared e2e() helper (which merges only
# compose.yml + the suite). Needs host networking + the host ports 3000/4433 free (Linux CI).
step "E2E: compose.e2e-devstack.yml (dev-stack login: localhost works + 127.0.0.1 canonicalised)"
devstack_files=(-f compose.yml -f compose.override.yml -f compose.e2e-devstack.yml)
step "E2E: e2e-tests/compose.devstack.yml (dev-stack login: localhost works + 127.0.0.1 canonicalised)"
devstack_files=(-f compose.yml -f compose.override.yml -f e2e-tests/compose.devstack.yml)
rc=0
docker compose -p plainpages-e2e-devstack "${devstack_files[@]}" run --build --rm e2e || rc=$?
docker compose -p plainpages-e2e-devstack "${devstack_files[@]}" down -v >/dev/null 2>&1 || true
[ "$rc" -eq 0 ] || { echo "E2E suite compose.e2e-devstack.yml failed (exit $rc)"; exit "$rc"; }
[ "$rc" -eq 0 ] || { echo "E2E suite e2e-tests/compose.devstack.yml failed (exit $rc)"; exit "$rc"; }
step "ALL GREEN"
+1 -1
View File
@@ -399,7 +399,7 @@ worked example: thin handlers bound to an injectable upstream client, unit-teste
of the stack.
3. **E2E the user-facing flow.** Per AGENTS.md §6, ship a side-effect-free Playwright test in
`e2e/` for each plugin page/form so the suite stays `fullyParallel`, run against the live `web`
`e2e-tests/` for each plugin page/form so the suite stays `fullyParallel`, run against the live `web`
service with the plugin mounted. The reference's permission-gating is covered in `visual.spec.ts`;
its authenticated list/form happy-path is the full-E2E item (needs cross-host login infra).
+12
View File
@@ -0,0 +1,12 @@
# Playwright runner — browsers preinstalled, pinned to match @playwright/test in e2e-tests/.
# Built/run via e2e-tests/compose.visual.yml; targets the `web` service over the network.
FROM mcr.microsoft.com/playwright:v1.49.1-noble
WORKDIR /e2e-tests
COPY e2e-tests/package.json e2e-tests/package-lock.json ./
RUN npm ci
COPY e2e-tests/ ./
CMD ["npx", "playwright", "test"]
@@ -1,7 +1,7 @@
import { expect, test } from "@playwright/test";
// Full-stack auth E2E: token timeout + silent re-mint ("stay signed in"). Runs against the
// real Ory stack via compose.e2e-auth.yml, where the session→JWT TTL is shortened to 8s and the
// real Ory stack via e2e-tests/compose.auth.yml, where the session→JWT TTL is shortened to 8s and the
// web clock skew is 0 — so the ~10m token lapses in seconds and the hot path re-mints it from the
// still-live Kratos session. We drive the flow over HTTP (fetch, manual cookies) because Kratos
// and web sit on different hosts here; web's own server-side cookie relay is what we exercise.
@@ -1,9 +1,9 @@
# Full-stack auth E2E — token timeout + silent re-mint ("stay signed in"). The Ory-free
# visual suite (compose.e2e.yml) covers the design system; this is its full-stack counterpart:
# visual suite (e2e-tests/compose.visual.yml) covers the design system; this is its full-stack counterpart:
# real Postgres + Kratos + Keto + bootstrap + web, with a SHORT tokenizer TTL (ory/kratos/e2e.yml)
# and zero clock skew, so the JWT lapses and re-mints within seconds instead of ~10m.
# docker compose -f compose.yml -f compose.e2e-auth.yml run --build --rm e2e
# docker compose -f compose.yml -f compose.e2e-auth.yml down -v # tear down after
# docker compose -f compose.yml -f e2e-tests/compose.auth.yml run --build --rm e2e
# docker compose -f compose.yml -f e2e-tests/compose.auth.yml down -v # tear down after
services:
web:
# This suite exercises only the Kratos session → JWT re-mint; it needs Kratos + Keto + bootstrap,
@@ -38,7 +38,7 @@ services:
e2e:
build:
context: .
dockerfile: Dockerfile.e2e
dockerfile: e2e-tests/Dockerfile
depends_on:
web:
condition: service_healthy
@@ -48,4 +48,4 @@ services:
KRATOS_PUBLIC_URL: http://kratos:4433
command: ["npx", "playwright", "test", "auth-refresh.spec.ts"]
volumes:
- ./e2e/artifacts:/e2e/artifacts
- ./e2e-tests/artifacts:/e2e-tests/artifacts
@@ -4,8 +4,8 @@
# runs against the *plain* `docker compose up` topology and drives the browser on the HOST network, so
# it sees http://localhost:3000 (web) and http://127.0.0.1:4433 (Kratos public) exactly as a host
# browser does. Merge the dev override so the live stack is byte-for-byte `docker compose up`:
# docker compose -f compose.yml -f compose.override.yml -f compose.e2e-devstack.yml run --build --rm e2e
# docker compose -f compose.yml -f compose.override.yml -f compose.e2e-devstack.yml down -v # tear down
# docker compose -f compose.yml -f compose.override.yml -f e2e-tests/compose.devstack.yml run --build --rm e2e
# docker compose -f compose.yml -f compose.override.yml -f e2e-tests/compose.devstack.yml down -v # tear down
services:
web:
# Pin APP_URL so the regression is deterministic regardless of any APP_URL exported in the shell
@@ -34,7 +34,7 @@ services:
e2e:
build:
context: .
dockerfile: Dockerfile.e2e
dockerfile: e2e-tests/Dockerfile
command: ["npx", "playwright", "test", "devstack-login.spec.ts"]
# Host network: reach the host-published ports (web 3000, Kratos public 4433) at the very
# hostnames a user types — localhost / 127.0.0.1 — so the cross-host CSRF-cookie split reproduces.
@@ -45,4 +45,4 @@ services:
environment:
BASE_URL: http://localhost:3000
volumes:
- ./e2e/artifacts:/e2e/artifacts
- ./e2e-tests/artifacts:/e2e-tests/artifacts
@@ -1,9 +1,9 @@
# Full browser E2E — the real Playwright UI flow against the live stack: password +
# mocked-SSO login, menu filtering by role, users/groups/roles CRUD, a plugin page, logout. A tiny
# same-origin gateway (proxy, e2e/proxy.mjs) fronts web + Kratos on one host so the browser's cookies
# same-origin gateway (proxy, e2e-tests/proxy.mjs) fronts web + Kratos on one host so the browser's cookies
# round-trip (ory/kratos/e2e-proxy.yml points Kratos at it); a mock OIDC provider backs the SSO test.
# docker compose -f compose.yml -f compose.e2e-full.yml run --build --rm e2e
# docker compose -f compose.yml -f compose.e2e-full.yml down -v # tear down after
# docker compose -f compose.yml -f e2e-tests/compose.full.yml run --build --rm e2e
# docker compose -f compose.yml -f e2e-tests/compose.full.yml down -v # tear down after
services:
web:
# First-party + SSO flows need Kratos + Keto + bootstrap, not Hydra — drop it so the stack is
@@ -60,14 +60,14 @@ services:
ISSUER: http://mock-oidc:9000
SSO_EMAIL: sso-user@plainpages.local
volumes:
- ./e2e/mock-oidc.mjs:/mock-oidc.mjs:ro
- ./e2e-tests/mock-oidc.mjs:/mock-oidc.mjs:ro
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9000/.well-known/openid-configuration"]
interval: 2s
timeout: 4s
retries: 15
# Same-origin gateway: Kratos-owned paths → kratos, everything else → web (e2e/proxy.mjs).
# Same-origin gateway: Kratos-owned paths → kratos, everything else → web (e2e-tests/proxy.mjs).
proxy:
image: node:24.16.0-alpine3.24
command: ["node", "/proxy.mjs"]
@@ -78,7 +78,7 @@ services:
KRATOS_URL: http://kratos:4433
WEB_URL: http://web:3000
volumes:
- ./e2e/proxy.mjs:/proxy.mjs:ro
- ./e2e-tests/proxy.mjs:/proxy.mjs:ro
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost/public/css/styles.css"]
interval: 2s
@@ -88,7 +88,7 @@ services:
e2e:
build:
context: .
dockerfile: Dockerfile.e2e
dockerfile: e2e-tests/Dockerfile
command: ["npx", "playwright", "test", "full-flow.spec.ts"]
depends_on:
mock-oidc:
@@ -99,4 +99,4 @@ services:
BASE_URL: http://proxy
KRATOS_ADMIN_URL: http://kratos:4434
volumes:
- ./e2e/artifacts:/e2e/artifacts
- ./e2e-tests/artifacts:/e2e-tests/artifacts
@@ -3,8 +3,8 @@
# it via the Kratos session and accepts. Runs against the real stack (Postgres + Kratos + Keto +
# Hydra + bootstrap + web). The runner drives the flow over HTTP (fetch, manual cookies), so it
# reaches the Ory services by their compose-network names.
# docker compose -f compose.yml -f compose.e2e-oauth.yml run --build --rm e2e
# docker compose -f compose.yml -f compose.e2e-oauth.yml down -v # tear down after
# docker compose -f compose.yml -f e2e-tests/compose.oauth.yml run --build --rm e2e
# docker compose -f compose.yml -f e2e-tests/compose.oauth.yml down -v # tear down after
services:
web:
# Dev throwaways are fine for the test stack; the runner hits web over http.
@@ -32,7 +32,7 @@ services:
e2e:
build:
context: .
dockerfile: Dockerfile.e2e
dockerfile: e2e-tests/Dockerfile
depends_on:
web:
condition: service_healthy
@@ -43,4 +43,4 @@ services:
KRATOS_PUBLIC_URL: http://kratos:4433
command: ["npx", "playwright", "test", "oauth-login.spec.ts"]
volumes:
- ./e2e/artifacts:/e2e/artifacts
- ./e2e-tests/artifacts:/e2e-tests/artifacts
@@ -1,9 +1,9 @@
# Playwright E2E. Brings up the app + a Playwright runner and exercises the live pages (design
# system, theme switch, mobile layout, CSRF, landing, 404, plugin gating) — Ory-free, so it's fast.
# docker compose -f compose.yml -f compose.e2e.yml run --build --rm e2e
# docker compose -f compose.yml -f compose.e2e.yml down -v # tear down after
# --build rebuilds the runner (the image bakes in e2e/) so spec edits are picked up.
# Screenshots + HTML report land in ./e2e/artifacts/ (git-ignored).
# docker compose -f compose.yml -f e2e-tests/compose.visual.yml run --build --rm e2e
# docker compose -f compose.yml -f e2e-tests/compose.visual.yml down -v # tear down after
# --build rebuilds the runner (the image bakes in e2e-tests/) so spec edits are picked up.
# Screenshots + HTML report land in ./e2e-tests/artifacts/ (git-ignored).
services:
web:
# The dashboard renders mock data — no Ory needed. Drop the base file's kratos/keto
@@ -24,8 +24,8 @@ services:
e2e:
build:
context: .
dockerfile: Dockerfile.e2e
# Just the Ory-free visual suite; the full-stack auth spec runs via compose.e2e-auth.yml.
dockerfile: e2e-tests/Dockerfile
# Just the Ory-free visual suite; the full-stack auth spec runs via e2e-tests/compose.auth.yml.
command: ["npx", "playwright", "test", "visual.spec.ts"]
depends_on:
web:
@@ -36,4 +36,4 @@ services:
# The committed dev tokenizer key — the spec signs a session JWT with it so the gated
# dashboard renders; web verifies it with the same key (the file it mounts read-only).
- ./ory/kratos/tokenizer/jwks.json:/repo/jwks.json:ro
- ./e2e/artifacts:/e2e/artifacts
- ./e2e-tests/artifacts:/e2e-tests/artifacts
@@ -12,7 +12,7 @@ import { expect, test } from "@playwright/test";
// browser URLs derive from it, and a real /error page replaces the 404.
//
// This is faithful to the user's environment: the runner uses the host network
// (compose.e2e-devstack.yml) against the plain `docker compose up` topology, so it sees
// (e2e-tests/compose.devstack.yml) against the plain `docker compose up` topology, so it sees
// http://localhost:3000 (web) and http://127.0.0.1:4433 (Kratos public) exactly as a host browser
// does. The proxied full-flow suite can't catch this regression — it fronts web + Kratos on one origin.
const ADMIN_EMAIL = "admin@plainpages.local"; // seeded by bootstrap
@@ -2,7 +2,7 @@ import { type Browser, type Page, expect, test } from "@playwright/test";
import { randomUUID } from "node:crypto";
// Full browser E2E: the real Playwright UI against the live stack via the same-origin
// gateway (compose.e2e-full.yml) — the browser-UI login the earlier full-stack suites deferred here.
// gateway (e2e-tests/compose.full.yml) — the browser-UI login the earlier full-stack suites deferred here.
// Coverage is the test titles below, plus the standalone SSO test.
//
// Runs on a fresh stack (`down -v` after, like the other full-stack suites). The serial admin
@@ -1,7 +1,7 @@
import { defineConfig, devices } from "@playwright/test";
// Visual + functional checks against the live app (the `web` compose service, BASE_URL). Run via
// compose.e2e.yml. Parallel per the project's E2E principle; deterministic colorScheme/viewport
// e2e-tests/compose.visual.yml. Parallel per the project's E2E principle; deterministic colorScheme/viewport
// so the rendered design is stable across runs.
export default defineConfig({
testDir: ".",
+2 -2
View File
@@ -1,6 +1,6 @@
# Browser-E2E overlay (compose.e2e-full.yml) — merged after kratos.yml via a second `-c`. The
# Browser-E2E overlay (e2e-tests/compose.full.yml) — merged after kratos.yml via a second `-c`. The
# full-flow suite drives the real browser, so web + Kratos must share one origin (the `proxy`
# gateway, e2e/proxy.mjs). Point Kratos' public base_url and every self-service URL at that host so
# gateway, e2e-tests/proxy.mjs). Point Kratos' public base_url and every self-service URL at that host so
# the flow action, the session cookie, and the after-login redirect all stay same-origin as the
# browser sees them. The normal (10m) tokenizer TTL from kratos.yml is kept — no re-mint mid-test.
serve:
+1 -1
View File
@@ -1,4 +1,4 @@
# E2E overlay (compose.e2e-auth.yml) — merged after kratos.yml via a second `-c`. Two changes
# E2E overlay (e2e-tests/compose.auth.yml) — merged after kratos.yml via a second `-c`. Two changes
# that let the auth-refresh suite exercise token timeout + re-mint in seconds:
# 1. A very short session→JWT tokenizer TTL, so the JWT lapses while the Kratos session lives.
# 2. A public base_url on the compose-network hostname, so the Playwright runner can drive the
+3 -3
View File
@@ -11,14 +11,14 @@ import { readFileSync } from "node:fs";
const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
const compose = read("compose.yml");
const override = read("compose.override.yml");
const e2e = read("compose.e2e.yml");
const visual = read("e2e-tests/compose.visual.yml");
// compose.yml lists web first, postgres second — slice the web service block.
const webBlock = compose.slice(compose.indexOf("\n web:"), compose.indexOf("\n postgres:"));
test("every image is pinned to an exact version, never a floating tag", () => {
// One scan over all compose files (postgres, the three Ory pairs, mailpit); web/e2e build.
const images = [...[compose, override, e2e].join("\n").matchAll(/image:\s*(\S+)/g)].map((m) => m[1]!);
const images = [...[compose, override, visual].join("\n").matchAll(/image:\s*(\S+)/g)].map((m) => m[1]!);
assert.ok(images.length >= 8, "scans the pinned images");
for (const img of images) {
assert.match(img.split(":").at(-1)!, /^v?\d+\.\d+/, `${img} pins a version-like tag`);
@@ -91,5 +91,5 @@ test("a one-shot bootstrap seeds the stack before web starts", () => {
test("the visual E2E does not drag in the Ory stack", () => {
// web's Ory deps are reset for E2E (the dashboard is mock data — no Ory needed).
assert.match(e2e, /depends_on:\s*!reset\b/, "E2E resets web's depends_on");
assert.match(visual, /depends_on:\s*!reset\b/, "E2E resets web's depends_on");
});