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:
+4
-1
@@ -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
@@ -4,4 +4,4 @@
|
||||
node_modules
|
||||
|
||||
# Playwright E2E outputs (screenshots, html report, traces)
|
||||
e2e/artifacts/
|
||||
e2e-tests/artifacts/
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
@@ -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"
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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: ".",
|
||||
@@ -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
@@ -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
@@ -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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user