From 913bd6813a61d74becec059a1a4ca39f1f147822 Mon Sep 17 00:00:00 2001 From: lilleman Date: Tue, 23 Jun 2026 23:48:05 +0200 Subject: [PATCH] Consolidate E2E into e2e-tests/ (Dockerfile + compose.{visual,auth,oauth,full,devstack}.yml, WORKDIR /e2e-tests); move ci.sh to repo root --- .dockerignore | 5 ++- .gitignore | 2 +- Dockerfile.e2e | 12 ------ README.md | 38 +++++++++---------- scripts/ci.sh => ci.sh | 24 ++++++------ docs/plugin-contract.md | 2 +- e2e-tests/Dockerfile | 12 ++++++ {e2e => e2e-tests}/auth-refresh.spec.ts | 2 +- .../compose.auth.yml | 10 ++--- .../compose.devstack.yml | 8 ++-- .../compose.full.yml | 16 ++++---- .../compose.oauth.yml | 8 ++-- .../compose.visual.yml | 14 +++---- {e2e => e2e-tests}/devstack-login.spec.ts | 2 +- {e2e => e2e-tests}/full-flow.spec.ts | 2 +- {e2e => e2e-tests}/mock-oidc.mjs | 0 {e2e => e2e-tests}/oauth-login.spec.ts | 0 {e2e => e2e-tests}/package-lock.json | 0 {e2e => e2e-tests}/package.json | 0 {e2e => e2e-tests}/playwright.config.ts | 2 +- {e2e => e2e-tests}/proxy.mjs | 0 {e2e => e2e-tests}/visual.spec.ts | 0 ory/kratos/e2e-proxy.yml | 4 +- ory/kratos/e2e.yml | 2 +- src/compose.test.ts | 6 +-- 25 files changed, 87 insertions(+), 84 deletions(-) delete mode 100644 Dockerfile.e2e rename scripts/ci.sh => ci.sh (73%) create mode 100644 e2e-tests/Dockerfile rename {e2e => e2e-tests}/auth-refresh.spec.ts (98%) rename compose.e2e-auth.yml => e2e-tests/compose.auth.yml (83%) rename compose.e2e-devstack.yml => e2e-tests/compose.devstack.yml (87%) rename compose.e2e-full.yml => e2e-tests/compose.full.yml (88%) rename compose.e2e-oauth.yml => e2e-tests/compose.oauth.yml (86%) rename compose.e2e.yml => e2e-tests/compose.visual.yml (76%) rename {e2e => e2e-tests}/devstack-login.spec.ts (96%) rename {e2e => e2e-tests}/full-flow.spec.ts (98%) rename {e2e => e2e-tests}/mock-oidc.mjs (100%) rename {e2e => e2e-tests}/oauth-login.spec.ts (100%) rename {e2e => e2e-tests}/package-lock.json (100%) rename {e2e => e2e-tests}/package.json (100%) rename {e2e => e2e-tests}/playwright.config.ts (86%) rename {e2e => e2e-tests}/proxy.mjs (100%) rename {e2e => e2e-tests}/visual.spec.ts (100%) diff --git a/.dockerignore b/.dockerignore index 4d9cd33..b5ac416 100644 --- a/.dockerignore +++ b/.dockerignore @@ -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 diff --git a/.gitignore b/.gitignore index e640087..0c43ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ node_modules # Playwright E2E outputs (screenshots, html report, traces) -e2e/artifacts/ +e2e-tests/artifacts/ diff --git a/Dockerfile.e2e b/Dockerfile.e2e deleted file mode 100644 index 0034b7d..0000000 --- a/Dockerfile.e2e +++ /dev/null @@ -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"] diff --git a/README.md b/README.md index b17b876..60af7ff 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/scripts/ci.sh b/ci.sh similarity index 73% rename from scripts/ci.sh rename to ci.sh index b564015..0c1a048 100755 --- a/scripts/ci.sh +++ b/ci.sh @@ -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" diff --git a/docs/plugin-contract.md b/docs/plugin-contract.md index ceb738f..cc0f8b6 100644 --- a/docs/plugin-contract.md +++ b/docs/plugin-contract.md @@ -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). diff --git a/e2e-tests/Dockerfile b/e2e-tests/Dockerfile new file mode 100644 index 0000000..14a94f1 --- /dev/null +++ b/e2e-tests/Dockerfile @@ -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"] diff --git a/e2e/auth-refresh.spec.ts b/e2e-tests/auth-refresh.spec.ts similarity index 98% rename from e2e/auth-refresh.spec.ts rename to e2e-tests/auth-refresh.spec.ts index 0044ed1..fdbfb80 100644 --- a/e2e/auth-refresh.spec.ts +++ b/e2e-tests/auth-refresh.spec.ts @@ -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. diff --git a/compose.e2e-auth.yml b/e2e-tests/compose.auth.yml similarity index 83% rename from compose.e2e-auth.yml rename to e2e-tests/compose.auth.yml index 79c86b6..ef2f841 100644 --- a/compose.e2e-auth.yml +++ b/e2e-tests/compose.auth.yml @@ -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 diff --git a/compose.e2e-devstack.yml b/e2e-tests/compose.devstack.yml similarity index 87% rename from compose.e2e-devstack.yml rename to e2e-tests/compose.devstack.yml index 46fd8f8..9f5b405 100644 --- a/compose.e2e-devstack.yml +++ b/e2e-tests/compose.devstack.yml @@ -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 diff --git a/compose.e2e-full.yml b/e2e-tests/compose.full.yml similarity index 88% rename from compose.e2e-full.yml rename to e2e-tests/compose.full.yml index 67edbb2..0f29950 100644 --- a/compose.e2e-full.yml +++ b/e2e-tests/compose.full.yml @@ -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 diff --git a/compose.e2e-oauth.yml b/e2e-tests/compose.oauth.yml similarity index 86% rename from compose.e2e-oauth.yml rename to e2e-tests/compose.oauth.yml index 89bdc27..97269af 100644 --- a/compose.e2e-oauth.yml +++ b/e2e-tests/compose.oauth.yml @@ -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 diff --git a/compose.e2e.yml b/e2e-tests/compose.visual.yml similarity index 76% rename from compose.e2e.yml rename to e2e-tests/compose.visual.yml index d11f3fa..c93b7e3 100644 --- a/compose.e2e.yml +++ b/e2e-tests/compose.visual.yml @@ -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 diff --git a/e2e/devstack-login.spec.ts b/e2e-tests/devstack-login.spec.ts similarity index 96% rename from e2e/devstack-login.spec.ts rename to e2e-tests/devstack-login.spec.ts index c949058..f23df79 100644 --- a/e2e/devstack-login.spec.ts +++ b/e2e-tests/devstack-login.spec.ts @@ -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 diff --git a/e2e/full-flow.spec.ts b/e2e-tests/full-flow.spec.ts similarity index 98% rename from e2e/full-flow.spec.ts rename to e2e-tests/full-flow.spec.ts index e7160f6..b976d67 100644 --- a/e2e/full-flow.spec.ts +++ b/e2e-tests/full-flow.spec.ts @@ -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 diff --git a/e2e/mock-oidc.mjs b/e2e-tests/mock-oidc.mjs similarity index 100% rename from e2e/mock-oidc.mjs rename to e2e-tests/mock-oidc.mjs diff --git a/e2e/oauth-login.spec.ts b/e2e-tests/oauth-login.spec.ts similarity index 100% rename from e2e/oauth-login.spec.ts rename to e2e-tests/oauth-login.spec.ts diff --git a/e2e/package-lock.json b/e2e-tests/package-lock.json similarity index 100% rename from e2e/package-lock.json rename to e2e-tests/package-lock.json diff --git a/e2e/package.json b/e2e-tests/package.json similarity index 100% rename from e2e/package.json rename to e2e-tests/package.json diff --git a/e2e/playwright.config.ts b/e2e-tests/playwright.config.ts similarity index 86% rename from e2e/playwright.config.ts rename to e2e-tests/playwright.config.ts index df10b4c..1cc2a9e 100644 --- a/e2e/playwright.config.ts +++ b/e2e-tests/playwright.config.ts @@ -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: ".", diff --git a/e2e/proxy.mjs b/e2e-tests/proxy.mjs similarity index 100% rename from e2e/proxy.mjs rename to e2e-tests/proxy.mjs diff --git a/e2e/visual.spec.ts b/e2e-tests/visual.spec.ts similarity index 100% rename from e2e/visual.spec.ts rename to e2e-tests/visual.spec.ts diff --git a/ory/kratos/e2e-proxy.yml b/ory/kratos/e2e-proxy.yml index f0aed22..76ae361 100644 --- a/ory/kratos/e2e-proxy.yml +++ b/ory/kratos/e2e-proxy.yml @@ -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: diff --git a/ory/kratos/e2e.yml b/ory/kratos/e2e.yml index 1395d9e..e4a63e8 100644 --- a/ory/kratos/e2e.yml +++ b/ory/kratos/e2e.yml @@ -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 diff --git a/src/compose.test.ts b/src/compose.test.ts index 5831519..dd42b60 100644 --- a/src/compose.test.ts +++ b/src/compose.test.ts @@ -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"); });