Two gaps behind the same near-miss. 1. **No spec covered hermes.franksdank.cloud** — the one guarded vhost whose upstream is a host address (`reverse_proxy 192.168.0.143:8787`) rather than a container name, because its service is host-networked. That is how a loopback-only bind could take the vhost down with a 502 and leave every gate green. tests/e2e/specs/17-hermes-vhost.spec.ts asserts what a dead upstream violates: no 5xx through the guard chain, no Caddy error page, and no app bytes to an unauthenticated raw client. It does not assert SSO-silence, since this app has its own password login. 2. **Specs are matched by number prefix, not globbed.** A spec file whose prefix is in no project silently never runs — which is exactly what happened to the new one until it was registered. The prefix is registered now, the comment in playwright.config.ts says why, and tests/e2e_scaffold.bats fails if any spec file matches no project. The hermes-webui seam assertion is corrected too: it asserted the loopback bind I wrongly imposed, and now asserts the real contract — the unit's bind must serve the address the Caddyfile fronts it on.
70 lines
3.0 KiB
Bash
Executable File
70 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
# Seam: tests/e2e — Playwright suite structure. The suite drives the LIVE
|
|
# fleet (homepage + Anubis/Authentik guard chain) and is deliberately not
|
|
# part of ./tests/run.sh; this seam only asserts the scaffold is present,
|
|
# well-formed, and that no credentials or browser auth state are tracked.
|
|
|
|
load helpers
|
|
|
|
@test "e2e: runner, config, lib, and specs exist" {
|
|
for f in run.sh package.json playwright.config.ts .env.local.example \
|
|
lib/env.ts lib/login.ts \
|
|
specs/00-auth.setup.ts specs/10-homepage.spec.ts specs/11-admin.spec.ts \
|
|
specs/12-crawl.spec.ts specs/13-sso.spec.ts specs/14-guard.spec.ts; do
|
|
[ -f "$REPO_ROOT/tests/e2e/$f" ] || {
|
|
echo "missing tests/e2e/$f"
|
|
return 1
|
|
}
|
|
done
|
|
}
|
|
|
|
@test "e2e: package.json is valid JSON with the playwright dependency" {
|
|
python3 - "$REPO_ROOT/tests/e2e/package.json" <<'PY'
|
|
import json, sys
|
|
pkg = json.load(open(sys.argv[1]))
|
|
assert "@playwright/test" in pkg.get("devDependencies", {}), "missing @playwright/test"
|
|
PY
|
|
}
|
|
|
|
@test "e2e: run.sh parses" {
|
|
bash -n "$REPO_ROOT/tests/e2e/run.sh"
|
|
}
|
|
|
|
@test "e2e: example env file carries only placeholders" {
|
|
if grep -E '^E2E_[A-Z_]*PASS=' "$REPO_ROOT/tests/e2e/.env.local.example" | grep -vq REPLACE_ME; then
|
|
echo ".env.local.example must hold REPLACE_ME placeholders, never real passwords"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
@test "e2e: no credentials or auth state are tracked" {
|
|
cd "$REPO_ROOT"
|
|
tracked="$(git ls-files tests/e2e)"
|
|
if echo "$tracked" | grep -Eq '(^|/)\.env\.local$|(^|/)\.auth(/|$)|node_modules'; then
|
|
echo "tracked files must not include creds or auth state:"
|
|
echo "$tracked" | grep -E '(^|/)\.env\.local$|(^|/)\.auth(/|$)|node_modules'
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
@test "every e2e spec is matched by a playwright project" {
|
|
# Playwright projects match specs by NUMBER PREFIX, not by glob, so a spec
|
|
# whose prefix is not listed in any project silently never runs: the suite
|
|
# reports green while the test does not exist. That happened on 2026-09-16
|
|
# with 17-hermes-vhost.spec.ts — written to catch a real regression (a
|
|
# loopback-only bind that killed hermes.franksdank.cloud's upstream while
|
|
# every gate stayed green), and invisible until the prefix was registered.
|
|
local cfg="$REPO_ROOT/tests/e2e/playwright.config.ts"
|
|
python3 - "$cfg" <<'PYEOF'
|
|
import pathlib, re, sys
|
|
cfg = pathlib.Path(sys.argv[1]).read_text()
|
|
patterns = [re.compile(p) for p in re.findall(r"testMatch:\s*/(.+?)/,", cfg)]
|
|
assert patterns, "no testMatch patterns found in the playwright config"
|
|
specs = sorted(pathlib.Path(sys.argv[1]).parent.joinpath("specs").glob("*.spec.ts"))
|
|
assert specs, "no spec files found"
|
|
unmatched = [s.name for s in specs if not any(p.search(s.name) for p in patterns)]
|
|
assert not unmatched, f"specs matched by no project (they will never run): {unmatched}"
|
|
print(f"{len(specs)} specs, all matched by {len(patterns)} projects")
|
|
PYEOF
|
|
}
|