The authenticated Playwright suite (P4 #316) logs in as a single identity via E2E_USER_USERNAME / E2E_USER_PASSWORD (e2e/support/auth.ts, e2e/playwright/auth.setup.ts). As part of the OpenBao CI-secret migration (#545) we set up two Keycloak test users in the live spikersoft realm and stored both credential pairs in OpenBao:
To unblock the migration, the suite currently runs as staff only — e2e-nightly.yml fetches staff_username/staff_password into E2E_USER_*; the student pair is parked in Bao, unused. This ticket tracks wiring the suite to exercise both roles so we get role-differentiated coverage (staff reaches management/admin routes a student must not).
Scope
auth.setup.ts: perform two Keycloak UI logins → persist two storage states (e.g. e2e/.auth/staff.json, e2e/.auth/student.json).
playwright.config.ts: add authenticated-staff / authenticated-student projects (each starting from its storage state); keep anonymous as-is. Name specs authenticated-staff-*.spec.ts / authenticated-student-*.spec.ts.
e2e/support/auth.ts: expose the two identities (e.g. E2E_STAFF / E2E_STUDENT) reading E2E_STAFF_USERNAME/PASSWORD + E2E_STUDENT_USERNAME/PASSWORD; keep graceful self-skip when unset.
e2e/support/route-manifest.ts: classify routes per role and split the coverage ratchet (a route walked as staff but forbidden to student should assert accordingly) — see the coverage-baseline.json per-environment ratchet note.
.gitea/workflows/e2e-nightly.yml: change the OpenBao fetch from the single staff mapping to both pairs:
No new secrets/infra needed — both pairs are already in Bao and ci-angular.hcl already grants read on secret/ci/angular/*.
Purely a code + one workflow-fetch change; the Bao side is done.
Epic #307 (E2E) · builds on P4 #316 · context #545 (OpenBao CI migration).
**Context / why now**
The authenticated Playwright suite (P4 #316) logs in as a **single** identity via `E2E_USER_USERNAME` / `E2E_USER_PASSWORD` (`e2e/support/auth.ts`, `e2e/playwright/auth.setup.ts`). As part of the OpenBao CI-secret migration (#545) we set up **two** Keycloak test users in the live `spikersoft` realm and stored **both** credential pairs in OpenBao:
```
secret/ci/angular/e2e
staff_username = e2e.staff staff_password = <rotated>
student_username = e2e.student student_password = <rotated>
```
To unblock the migration, the suite currently runs **as staff only** — `e2e-nightly.yml` fetches `staff_username`/`staff_password` into `E2E_USER_*`; the student pair is parked in Bao, unused. This ticket tracks wiring the suite to exercise **both roles** so we get role-differentiated coverage (staff reaches management/admin routes a student must not).
**Scope**
- `auth.setup.ts`: perform **two** Keycloak UI logins → persist **two** storage states (e.g. `e2e/.auth/staff.json`, `e2e/.auth/student.json`).
- `playwright.config.ts`: add `authenticated-staff` / `authenticated-student` projects (each starting from its storage state); keep `anonymous` as-is. Name specs `authenticated-staff-*.spec.ts` / `authenticated-student-*.spec.ts`.
- `e2e/support/auth.ts`: expose the two identities (e.g. `E2E_STAFF` / `E2E_STUDENT`) reading `E2E_STAFF_USERNAME/PASSWORD` + `E2E_STUDENT_USERNAME/PASSWORD`; keep graceful self-skip when unset.
- `e2e/support/route-manifest.ts`: classify routes per role and split the coverage ratchet (a route walked as staff but forbidden to student should assert accordingly) — see the `coverage-baseline.json` per-environment ratchet note.
- `.gitea/workflows/e2e-nightly.yml`: change the OpenBao fetch from the single staff mapping to both pairs:
```
E2E_STAFF_USERNAME secret/ci/angular/e2e staff_username
E2E_STAFF_PASSWORD secret/ci/angular/e2e staff_password
E2E_STUDENT_USERNAME secret/ci/angular/e2e student_username
E2E_STUDENT_PASSWORD secret/ci/angular/e2e student_password
```
**Notes**
- No new secrets/infra needed — both pairs are already in Bao and `ci-angular.hcl` already grants read on `secret/ci/angular/*`.
- Purely a code + one workflow-fetch change; the Bao side is done.
Epic #307 (E2E) · builds on P4 #316 · context #545 (OpenBao CI migration).
Persona-selection hydration (--identity <persona>) landed via #782 (angular PR #514 / infra PR #137).
Not done (the core of this ticket): playwright.config.ts still defines a single authenticated project with one auth-setup → one storage state. There are no separate staff/student projects, no role-scoped storage states, and no specs asserting role-based access differences. (Also note #628 — e2e.staff credential drift — currently blocks the staff half.)
Reconciliation check 2026-07-22 (code vs. ticket): **credential tooling is done, the actual suite split is not** — stays open.
Done:
- e2e.staff account exists; `pnpm e2e:secrets -- --staff` hydrates staff creds (spikersoft-angular PR #197).
- Persona-selection hydration (`--identity <persona>`) landed via #782 (angular PR #514 / infra PR #137).
Not done (the core of this ticket): `playwright.config.ts` still defines a single `authenticated` project with one `auth-setup` → one storage state. There are no separate staff/student projects, no role-scoped storage states, and no specs asserting role-based access differences. (Also note #628 — e2e.staff credential drift — currently blocks the staff half.)
Audited against origin/master — PARTIAL, exactly as the 2026-07-22 reconciliation comment says. The credential tooling shipped; the suite split has not started.
Done — persona hydration:tools/e2e/hydrate-env-from-bao.mjs:42 defines KNOWN_PERSONAS = ["staff","student","parent1","parent2","child1","child2","child3"], with --identity / shorthand resolution at :39-52 and per-persona key lookup at :140-143. So the credentials side is genuinely solved.
Not done — all four of this ticket's code items:
playwright.config.ts still has a single authenticated project (one testMatch: /authenticated-.*\.spec\.ts$/, one storageState: AUTH_STATE_PATH) and one auth-setup. No authenticated-staff / authenticated-student projects.
e2e/support/auth.ts:24 still has a single AUTH_STATE_PATH = ".../.auth/user.json" and a single E2E_USER reading E2E_USER_USERNAME/E2E_USER_PASSWORD (:35-42).
.gitea/workflows/e2e-nightly.yml:52-53 still maps only the one pair — E2E_USER_USERNAME → staff_username, E2E_USER_PASSWORD → staff_password. The student pair remains parked and unused.
No role classification in e2e/support/route-manifest.ts.
The grep that establishes all of it: git grep -n -iE 'E2E_STAFF|E2E_STUDENT|staff\.json|student\.json|authenticated-staff|authenticated-student' origin/master → zero hits repo-wide.
Blocked in practice on #628 for the staff half, and that interaction is worth spelling out because it's currently masking itself: e2e-nightly.yml feeds staff_password into E2E_USER_PASSWORD, and authenticated specs self-skip when credentials don't work (auth.ts:45, NO_CREDENTIALS_REASON). So a drifted staff password produces a green nightly that tested nothing at all — no failure, no signal. Splitting the suite without first settling #628 would just give you two projects that both silently skip.
Audited against `origin/master` — **PARTIAL, exactly as the 2026-07-22 reconciliation comment says. The credential tooling shipped; the suite split has not started.**
**Done — persona hydration:** `tools/e2e/hydrate-env-from-bao.mjs:42` defines `KNOWN_PERSONAS = ["staff","student","parent1","parent2","child1","child2","child3"]`, with `--identity` / shorthand resolution at `:39-52` and per-persona key lookup at `:140-143`. So the credentials side is genuinely solved.
**Not done — all four of this ticket's code items:**
- `playwright.config.ts` still has a single `authenticated` project (one `testMatch: /authenticated-.*\.spec\.ts$/`, one `storageState: AUTH_STATE_PATH`) and one `auth-setup`. No `authenticated-staff` / `authenticated-student` projects.
- `e2e/support/auth.ts:24` still has a single `AUTH_STATE_PATH = ".../.auth/user.json"` and a single `E2E_USER` reading `E2E_USER_USERNAME`/`E2E_USER_PASSWORD` (`:35-42`).
- `.gitea/workflows/e2e-nightly.yml:52-53` still maps only the one pair — `E2E_USER_USERNAME → staff_username`, `E2E_USER_PASSWORD → staff_password`. The student pair remains parked and unused.
- No role classification in `e2e/support/route-manifest.ts`.
The grep that establishes all of it: `git grep -n -iE 'E2E_STAFF|E2E_STUDENT|staff\.json|student\.json|authenticated-staff|authenticated-student' origin/master` → **zero hits repo-wide**.
**Blocked in practice on #628** for the staff half, and that interaction is worth spelling out because it's currently masking itself: `e2e-nightly.yml` feeds `staff_password` into `E2E_USER_PASSWORD`, and authenticated specs **self-skip** when credentials don't work (`auth.ts:45`, `NO_CREDENTIALS_REASON`). So a drifted staff password produces a **green nightly that tested nothing at all** — no failure, no signal. Splitting the suite without first settling #628 would just give you two projects that both silently skip.
**Remaining:** all four scope items, after #628.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context / why now
The authenticated Playwright suite (P4 #316) logs in as a single identity via
E2E_USER_USERNAME/E2E_USER_PASSWORD(e2e/support/auth.ts,e2e/playwright/auth.setup.ts). As part of the OpenBao CI-secret migration (#545) we set up two Keycloak test users in the livespikersoftrealm and stored both credential pairs in OpenBao:To unblock the migration, the suite currently runs as staff only —
e2e-nightly.ymlfetchesstaff_username/staff_passwordintoE2E_USER_*; the student pair is parked in Bao, unused. This ticket tracks wiring the suite to exercise both roles so we get role-differentiated coverage (staff reaches management/admin routes a student must not).Scope
auth.setup.ts: perform two Keycloak UI logins → persist two storage states (e.g.e2e/.auth/staff.json,e2e/.auth/student.json).playwright.config.ts: addauthenticated-staff/authenticated-studentprojects (each starting from its storage state); keepanonymousas-is. Name specsauthenticated-staff-*.spec.ts/authenticated-student-*.spec.ts.e2e/support/auth.ts: expose the two identities (e.g.E2E_STAFF/E2E_STUDENT) readingE2E_STAFF_USERNAME/PASSWORD+E2E_STUDENT_USERNAME/PASSWORD; keep graceful self-skip when unset.e2e/support/route-manifest.ts: classify routes per role and split the coverage ratchet (a route walked as staff but forbidden to student should assert accordingly) — see thecoverage-baseline.jsonper-environment ratchet note..gitea/workflows/e2e-nightly.yml: change the OpenBao fetch from the single staff mapping to both pairs:Notes
ci-angular.hclalready grants read onsecret/ci/angular/*.Epic #307 (E2E) · builds on P4 #316 · context #545 (OpenBao CI migration).
Reconciliation check 2026-07-22 (code vs. ticket): credential tooling is done, the actual suite split is not — stays open.
Done:
pnpm e2e:secrets -- --staffhydrates staff creds (spikersoft-angular PR #197).--identity <persona>) landed via #782 (angular PR #514 / infra PR #137).Not done (the core of this ticket):
playwright.config.tsstill defines a singleauthenticatedproject with oneauth-setup→ one storage state. There are no separate staff/student projects, no role-scoped storage states, and no specs asserting role-based access differences. (Also note #628 — e2e.staff credential drift — currently blocks the staff half.)Audited against
origin/master— PARTIAL, exactly as the 2026-07-22 reconciliation comment says. The credential tooling shipped; the suite split has not started.Done — persona hydration:
tools/e2e/hydrate-env-from-bao.mjs:42definesKNOWN_PERSONAS = ["staff","student","parent1","parent2","child1","child2","child3"], with--identity/ shorthand resolution at:39-52and per-persona key lookup at:140-143. So the credentials side is genuinely solved.Not done — all four of this ticket's code items:
playwright.config.tsstill has a singleauthenticatedproject (onetestMatch: /authenticated-.*\.spec\.ts$/, onestorageState: AUTH_STATE_PATH) and oneauth-setup. Noauthenticated-staff/authenticated-studentprojects.e2e/support/auth.ts:24still has a singleAUTH_STATE_PATH = ".../.auth/user.json"and a singleE2E_USERreadingE2E_USER_USERNAME/E2E_USER_PASSWORD(:35-42)..gitea/workflows/e2e-nightly.yml:52-53still maps only the one pair —E2E_USER_USERNAME → staff_username,E2E_USER_PASSWORD → staff_password. The student pair remains parked and unused.e2e/support/route-manifest.ts.The grep that establishes all of it:
git grep -n -iE 'E2E_STAFF|E2E_STUDENT|staff\.json|student\.json|authenticated-staff|authenticated-student' origin/master→ zero hits repo-wide.Blocked in practice on #628 for the staff half, and that interaction is worth spelling out because it's currently masking itself:
e2e-nightly.ymlfeedsstaff_passwordintoE2E_USER_PASSWORD, and authenticated specs self-skip when credentials don't work (auth.ts:45,NO_CREDENTIALS_REASON). So a drifted staff password produces a green nightly that tested nothing at all — no failure, no signal. Splitting the suite without first settling #628 would just give you two projects that both silently skip.Remaining: all four scope items, after #628.