diff --git a/.github/workflows/browser-e2e.yml b/.github/workflows/browser-e2e.yml new file mode 100644 index 0000000..4be1b32 --- /dev/null +++ b/.github/workflows/browser-e2e.yml @@ -0,0 +1,48 @@ +name: Browser E2E (manual) + +on: + workflow_dispatch: + +concurrency: + group: browser-e2e-manual-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + browser: + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install Chromium + run: npx playwright install --with-deps chromium + + - name: Run browser tests + run: npm run test:browser + + - name: Upload browser failure artifacts + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: browser-e2e-artifacts + path: | + playwright-report/ + test-results/ + if-no-files-found: ignore + retention-days: 7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f77ce19..08bd6aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ permissions: jobs: tests: - timeout-minutes: 15 + timeout-minutes: 25 runs-on: ubuntu-latest steps: - name: Checkout repository @@ -42,6 +42,12 @@ jobs: timeout-minutes: 10 run: npm run quality + - name: Install Chromium for required browser gate + run: npx playwright install --with-deps chromium + + - name: Run required browser regression suite + run: npm run test:browser + - name: Upload test artifacts if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -49,5 +55,6 @@ jobs: name: node-test-results path: | test-results/ + playwright-report/ npm-debug.log* if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index 6e8f6a2..121b9cd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/ dist/ data/ .DS_Store +playwright-report/ +test-results/ diff --git a/README.md b/README.md index ee2e8ff..0d16215 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,7 @@ Need to know which module owns a given storage field? Jump to the [Storage-to-Mo [](../../actions/workflows/codeql.yml) - **Automated coverage is mandatory:** Every feature pull request must add or update tests that assert the behaviours introduced or modified. +- **Required real-browser regression:** The branch-protected `tests` CI status runs `npm run quality` and then the Playwright/axe suite via `npm run test:browser`. Browser failures therefore block the existing required check. The deterministic browser server does not depend on Vercel previews or production credentials; the separate Browser E2E workflow is manual-only for focused reruns. - **Security scanning is automatic:** GitHub CodeQL runs on every push to `main`, pull request, and a weekly schedule to flag JavaScript/TypeScript issues without manual setup. - **Dependency review gate:** A GitHub dependency review workflow now blocks merges when a pull request introduces new high or critical advisories, so expect PRs to fail even if unit tests succeed until vulnerable dependencies are replaced or patched. - **Run the coverage guard:** Execute `npm run verify:tests` after staging runtime changes. The guard ensures any updates under `src/` or `components/` are paired with refreshed suites in `tests/**/*.test.mjs`. @@ -234,7 +235,7 @@ Need to know which module owns a given storage field? Jump to the [Storage-to-Mo - **Manual regression:** Open `index.html`, fill representative data, click **Generate Summary**, then refresh to ensure state persistence. - **Storage changes:** Run `npm run update:storage-docs` after altering persisted fields. CI can enforce freshness with `npm run check:storage-docs`. -Continuous integration runs automatically on pull requests and pushes to `main`, using the repository's Node.js version via `actions/setup-node` with npm caching for faster installs. The workflow executes `npm ci`, `npm test` (emitting JUnit results for artifact upload on failure), and `npm run check:storage-docs` so Codex-driven contributions keep tests and storage docs in sync. +Continuous integration runs automatically on pull requests and pushes to `main`, using the repository's Node.js version via `actions/setup-node` with npm caching. The required `tests` job executes `npm ci`, the canonical `npm run quality` gate, installs Chromium, and then runs `npm run test:browser`. Node/browser failure artifacts are retained only on failure; the focused Browser E2E workflow remains available through manual dispatch. ## Additional Documentation - See `AGENTS.md` for global UI principles, module isolation rules, and contribution contracts. diff --git a/docs/AI-ONBOARDING.md b/docs/AI-ONBOARDING.md index 86b869d..6ca7fee 100644 --- a/docs/AI-ONBOARDING.md +++ b/docs/AI-ONBOARDING.md @@ -136,6 +136,10 @@ function boot() { ``` ## Testing & QA Notes +- The real-browser layer is required in CI. The protected `tests` status runs the canonical `npm run quality` gate and then `npm run test:browser`; do not treat Playwright as optional advisory evidence for merge. +- Browser specs live under `tests/e2e/`, use `playwright.config.mjs`, and target the deterministic HTTP/API fixture in `scripts/serve-browser-tests.mjs`. Normal PR validation must not depend on Vercel preview availability. +- Keep stateful Classroom browser fixtures unique by spec/project/retry so fully-parallel tests cannot share mutable workspace/coaching state accidentally. +- `.github/workflows/browser-e2e.yml` is manual-only for focused diagnostics; failure traces/screenshots are retained only when a browser run fails. - Always run a manual smoke test by opening `index.html` in a modern browser, entering sample data, generating a summary, and refreshing to confirm persistence. - Automated suites should rely on `collectAppState()` / `applyAppState()` for deterministic state setup and on `generateSummary()` for output verification. - Core local intake workflows can still run browser-first, but collaboration uses Vercel Functions under `api/` and Neon persistence. Use a deployed/HTTP environment with the required database variable when testing shared sessions; do not infer collaboration health from a `file://` smoke test. diff --git a/docs/classroom-roadmap.md b/docs/classroom-roadmap.md index d019bd6..75df2b7 100644 --- a/docs/classroom-roadmap.md +++ b/docs/classroom-roadmap.md @@ -164,7 +164,7 @@ Primary deliverables: **Issue:** #295 -**Implementation:** draft PR #307 on `feature/classroom-protected-cases`; implementation, repository validation, and documentation are complete. Final deployed `dist/` verification remains blocked only by Vercel build capacity / preview-auth tooling. +**Implementation:** PR #307 merged to `main` as `09f034f1f1776a3aaf92be7dec86b902549c8eb9`; protected delivery, minimal `dist/` public output, deployment controls, and production verification are complete. Requires #290 semantics and #291 authorization; both prerequisites are merged. @@ -190,18 +190,38 @@ Validation evidence: - canonical CI built 45 public `dist/` files with no internal docs or protected Case Study identifiers; - prior deliberate READY preview returned HTTP 404 for the formerly public Microcomputer Cabinets authored JSON path. -Remaining before merge: +## Slice 8 — Browser/E2E and required CI -- obtain one deliberate `verify/**` preview of the latest `dist/` head when Vercel build capacity permits; -- confirm app root loads and internal `/docs/*`, `/scripts/*`, and protected `/templates/*.json` paths return 404; -- execute the deployed protected-API matrix only if an approved Preview Authentication bypass path is available; do not weaken preview protection to manufacture this evidence; -- explicit merge authorization. +**Issue:** #296, using #279 +**Implementation:** draft PR #308 on `feature/classroom-browser-e2e` -## Slice 8 — Browser/E2E and required CI +Implemented browser foundation / critical paths: + +- Playwright 1.63 + axe 4.13 with desktop Chromium and Pixel 7 projects; +- deterministic local HTTP/API fixture; no Vercel preview dependency; +- first-run role chooser, Standalone summary/persistence/resources, mobile smoke; +- real Save-to-File / Load-from-File round trip and Intake-mode restoration; +- Student join/resume and shared collaboration persistence; +- same-team multi-browser synchronization plus cross-workspace isolation; +- Instructor roster / workspace switching / read-only observation / resume; +- Instructor coaching → Student feedback → changed-since-review; +- protected Case Study unauthorized/authorized catalog/payload journey; +- invalid authority, expired Student resume, and Student non-enumeration; +- serious/critical axe scans and uncaught-page-error assertions on covered journeys; +- deterministic fixture isolation by test/project/retry. + +CI decision: the browser suite is now part of the already-required `tests` status. The required job runs `npm run quality` and then `npm run test:browser`. The separate Browser E2E workflow is manual-only for focused diagnostics. + +Latest functional suite checkpoint before final docs: **22 passed, 6 intentionally project-scoped skips, 0 failed**. + +## Post-foundation Classroom direction + +The eight-slice #288 program establishes the secure Classroom foundation; it does not represent the final facilitated teaching product. -**Issue:** #296, using #279 +- **#312 — Instructor-run live class, roster, and dynamic team management:** Instructor-created classes, one human-friendly Student join path, waiting/late roster, accessible team formation, live reassignment, automatic Student workspace switching, and high-signal team monitoring. +- **#313 — Instructor-orchestrated case simulation and staged debrief:** staged protected Case Study release, case reference beside Intake, Instructor pause/debrief/advance controls, progressive disclosure, team progress, and server-gated future-stage / Instructor-only material. -Do not wait until the end to write all browser tests. Add tests with the slice that owns behavior, then finish the program by promoting stable critical journeys to required CI as appropriate. +#312 is the next product slice after #308 closes; #313 should build on #312 rather than bypassing the live-class/team-management substrate. ## Stacked PR rules diff --git a/docs/classroom-workstream.md b/docs/classroom-workstream.md index d595d9c..0ddf627 100644 --- a/docs/classroom-workstream.md +++ b/docs/classroom-workstream.md @@ -19,9 +19,9 @@ This is the live restart document for the Classroom Experience program (#288). | Student join/resume | #292 | Complete | PR #302 / `feature/classroom-student-experience` | 194 tests: 193 pass, 0 fail, 1 skip; CI/CodeQL/dependency/template guard green | | Instructor observer | #293 | Complete | PR #305 merged | 208 tests: 207 pass, 0 fail, 1 skip; repository gates green | | Coaching | #294 | Complete | PR #306 merged | 224 tests: 223 pass, 0 fail, 1 skip; all repository gates green; production deployment READY | -| Protected cases | #295 | In progress | draft PR #307 / `feature/classroom-protected-cases` | Runtime/security implementation complete; final docs/gates and deployment verification remain | -| Browser E2E/CI | #296 | Not started | — | Uses #279 infrastructure | -| Browser test foundation | #279 | Existing open issue | — | Shared Playwright/accessibility foundation | +| Protected cases | #295 | Complete | PR #307 merged | Protected catalog/payload delivery, minimal `dist/` public boundary, deployment controls, and production verification complete | +| Browser E2E/CI | #296 | In progress — final stabilization | draft PR #308 / `feature/classroom-browser-e2e` | Critical Standalone/Student/Instructor/security journeys complete; Playwright now runs inside required `tests` CI gate | +| Browser test foundation | #279 | In progress — final stabilization via #296 | draft PR #308 | Deterministic local HTTP + Playwright/axe complete; focused Browser E2E workflow retained as manual diagnostic | ## Current architecture decisions @@ -56,7 +56,7 @@ See `docs/classroom-architecture.md` for the full contract. ## Active work -#297–#306 are merged to `main`. #295 is active on `feature/classroom-protected-cases`. +#297–#306 are merged to `main`. #295 is active in draft PR #307 on `feature/classroom-protected-cases`. #296 / #279 is active in stacked draft PR #308 on `feature/classroom-browser-e2e`. ## Last completed action @@ -218,6 +218,127 @@ Continue #295 on draft PR #307 from the current branch head. - Therefore the deployed evidence is: exact final-head preview READY + build-time protected/public-boundary checks passed + one formerly exposed internal document returned 404. Remaining protected API HTTP matrix cannot be executed from the current connector without an approved preview-auth bypass path. - Exact next action: synchronize PR #307 / issue #295 with this final preview evidence, then keep the PR draft until the user explicitly accepts the remaining preview-auth limitation or provides/approves a bypass path. Do not merge without explicit authorization. +## Active #296 / #279 implementation + +- Branch: `feature/classroom-browser-e2e`. +- Draft PR: #308. +- Base: `main` at merged #307 (`09f034f1f1776a3aaf92be7dec86b902549c8eb9`). +- The branch was refreshed onto current #307 because the old stack had diverged (8 ahead / 21 behind) and GitHub could no longer create the PR merge ref; subsequent pull-request workflows were not launching. +- Refreshed stack checkpoint: `593b9766d3a993c4834f475a90a362cfaf1aa6f0` — one commit ahead / zero behind #307 at refresh time, carrying only the browser/E2E surface. +- Browser foundation is real and running in GitHub Actions: + - `@playwright/test@1.63.0`; + - `@axe-core/playwright@4.13.0`; + - desktop Chromium + Pixel 7 projects; + - deterministic local HTTP server; + - traces retained on failure, screenshots only on failure; + - serious/critical axe violations fail covered journeys. +- Existing smoke coverage is green for first-run role chooser/accessibility and for absence of authored template JSON/server-source files from the browser fixture. +- First Standalone E2E checkpoint added real-browser input → summary → persistence/reload and Templates-only resource coverage. +- The first run correctly exposed test-assumption failures: + - the test's init script cleared localStorage again during reload, invalidating persistence verification; + - Standalone renames the drawer title to `Templates`, so `Templates Library` was a stale accessible-name assertion; + - mobile menu-item clicks were not a stable primary-interaction contract. +- Test-only stabilization checkpoint: `ac8ab6311d1c3f24d0f0418a650de9973c168e8b`: + - storage is cleared once before the journey rather than on every navigation; + - drawer assertion uses stable `#templatesDrawer` and verifies title `Templates`; + - desktop-only menu journeys stay desktop; + - mobile gets a separate Standalone primary-input + persistence/reload smoke; + - uncaught page errors remain fatal assertions. +- Normal repository quality gate is green on the refreshed stack. +- Browser E2E on the stabilized Standalone test checkpoint then exposed real accessibility defects in the Templates drawer: + - `aria-required-children` / `aria-required-parent` / `listitem`: native `ul/li` markup was mixed with listbox/option roles; + - `color-contrast`: the Apply resource button's white-on-`#007aff` contrast measured 4.01:1. +- Product fixes: + - `e2ec381a39d1819deabb634a0ea3b0e63d9b6c8d` — resource chooser now uses a direct `div[role=listbox] > button[role=option]` pattern; + - `b1b686aa6b3901db1b4a55f8997c7c4245b92c83` — Apply resource uses the existing darker blue `#1769aa` locally rather than altering the global accent. +- The same run confirmed the repaired behavioral coverage before axe executed: desktop input → summary → persistence/reload passed; mobile Standalone primary-input + persistence/reload passed. +- Browser E2E validation on `65204eb85ee2cbcc4eb6a5159313b944859fbf24`: **7 passed, 3 intentionally project-scoped skips, 0 failed**. Failure artifacts were not uploaded because the run was clean. +- Confirmed journeys now include: + - first-run role chooser + axe on desktop/mobile; + - protected public-boundary 404 smoke on desktop/mobile; + - desktop Standalone input → summary → persisted reload; + - desktop Standalone Templates-only drawer + serious/critical axe scan; + - mobile Standalone primary input → persisted reload; + - uncaught browser page errors fail the journeys. +- Normal repository quality gate is also green on the same stack. +- Deterministic Classroom fixture checkpoint: `f0f81d9e2e2b738eae47a5db20d2b0c972d108a5`. + - local browser server now implements Student admission, collaboration session GET/PUT, presence PUT/PATCH/DELETE, empty Student coaching, and empty Student protected-resource catalog; + - workspace state is keyed by the issued workspace capability so parallel desktop/mobile journeys do not require a destructive global reset; + - the initial shared snapshot comes from the validated public Checkout Latency Standard Template. +- Student real-browser journey checkpoint: `6f7a03fcd0cde23944e94a4513374a289ec3af81`. + - runs on desktop + mobile; + - exercises Join a class with real form controls; + - verifies class/assignment admission codes are cleared and not retained in the Student resume envelope; + - verifies the issued workspace capability is retained for same-device resume; + - edits the shared Intake, waits for a real collaboration PUT, reloads, reconnects, and verifies the shared edit returns from the fixture workspace; + - includes serious/critical axe scan and uncaught-page-error assertion after resumed Student state. +- Student stabilization after the #307 merge/restack: + - #307 merged to `main` as `09f034f1f1776a3aaf92be7dec86b902549c8eb9`; production deployment `dpl_C7cwWcBSHRjaDwvZtc8VpYbdzFKf` reached READY and #295/#304 were closed with production evidence. + - PR #308 was restacked onto merged `main` without force rewriting via sync commit `224dfcad016935417f88607ad2cd1b1b7ed2a013`. + - `1cff933dcab9536e92dfd86ed65a5aa9a136aa7f` removes an ambiguous Student display-name selector. + - `fba3841480b21a04b8b37b9d757b9d8240d631fe` fixes the real Student-admission layout bug where the floating Notes Intake surface intercepted the Join class button. + - axe-driven product repairs then fixed footer/resource/action/presence contrast and made the closed Templates dialog inert; regression coverage protects the inert lifecycle. + - Student gate on `b39c1c16b6e9cded47e0f1db32b697614030a222`: Browser E2E, CI, CodeQL, Dependency Review, and Template Manifest Guard all green on desktop/mobile. +- Instructor observer checkpoint: + - `a38bd5e312684be36f414e07696a87a21d65688c` adds deterministic Instructor roster and observation fixtures with two stable workspaces. + - `f70aa734e24fd2b3d97f25b6a298c2106cad3ce5` adds the real-browser Instructor journey: class open, roster, workspace switch, read-only projection, persisted class resume, no Student join or editable collaboration endpoint traffic, axe, and uncaught-page-error protection. + - the first browser run exposed invalid `aria-readonly` on the generic Intake wrapper; `ce4738be9fcf37c723387fe94d112b7f0d27cf87` removes that invalid container ARIA while preserving read-only/disabled control semantics, with regression coverage in `845f0059a3a2ea9a3dd75d5143b61069f9c4909d`. + - Instructor gate on `845f0059a3a2ea9a3dd75d5143b61069f9c4909d`: Browser E2E, CI, CodeQL, Dependency Review, and Template Manifest Guard all green on desktop/mobile. +- Coaching browser checkpoint: + - `tests/e2e/coaching.spec.mjs` exercises Instructor feedback save, Student read-only feedback, and changed-since-review after a Student edit. + - The checkpoint exposed and repaired a collaboration presence-badge contrast defect; the pre-protected-case head `f0740c1043ef0866827071a4800e3aa4376b365c` was green across Browser E2E, CI, CodeQL, Dependency Review, and Template Manifest Guard. +- Protected Case Study browser checkpoint: + - `88c2928dbcf137940fb12b0e06e645d7b2b7866b` extends the deterministic browser server with protected Case Study catalog/payload responses while retaining authorization checks. + - `7d1770e30e66bdfb66f93efba3001f5f1034848b` adds the real-browser security journey: unauthenticated Student catalog and payload retrieval return 404; an admitted Student receives metadata-only catalog records; the protected payload is POSTed only when applying the selected Case Study through the real resource drawer and password flow. + - The first run exposed a test-only assumption: a closed off-canvas drawer remains rendered and therefore Playwright-visible even though it is `aria-hidden` and `inert`. `1d181d22e77507e87377ac8b2798ed6b927c3b98` now asserts the actual closed-drawer interaction contract. + - Browser E2E on `1d181d22...`: **14 passed, 4 intentionally project-scoped skips, 0 failed**. + - Browser E2E, CI, CodeQL, Dependency Review, and Template Manifest Guard are all green on `1d181d22...`. +- Negative authorization browser checkpoint: + - `0802e012bcc0f863d912304da2dabd1d286f1825` tightens the deterministic fixture so Instructor, class-join, and assignment credentials are distinct authorities rather than any syntactically valid 43-character token. + - `e7bc8ed4af04b8c1061c29a8d8e67c0a36bc5795` adds real-browser coverage proving an active Student workspace capability cannot enumerate the Instructor roster, well-formed non-Instructor authority cannot open an Instructor class, and an expired saved Student session is discarded before stale workspace content resumes. + - The tests intentionally assert durable authorization/recovery properties rather than requiring the current class+assignment-code Student UX, because #312 will replace that admission flow with Instructor-managed assignment. + - Browser E2E on `e7bc8ed...`: **20 passed, 4 intentionally project-scoped skips, 0 failed**. + - Browser E2E, CI, CodeQL, Dependency Review, and Template Manifest Guard are all green on `e7bc8ed...`. +- Team collaboration / isolation checkpoint: + - `94ab842b09301ef95114522636de81538017f620` adds a real three-browser journey: two Students in one group converge on one Intake while a Student in a different workspace remains isolated. + - The first fully-parallel run exposed deterministic-fixture cross-test contamination rather than a product defect. Student, coaching, and collaboration assignment capabilities are now isolated by spec/project/retry; final isolation head `bfaf307e80b4c014646080834fbef475c52204f2` restored a clean **21 passed, 5 intentional skips, 0 failed** Browser E2E run. +- File / workflow-mode checkpoint: + - `5e4ea9bb2a35bc7f8ee013471a91d3944c13499a` covers a real Standalone **Save to File → mutate → Load from File** round trip and proves the exported Major Incident workflow mode and mode-controlled sections restore with the Intake. + - Browser E2E on that head: **22 passed, 6 intentionally project-scoped skips, 0 failed**; CI, CodeQL, Dependency Review, and Template Manifest Guard green. +- Required CI decision: + - Browser E2E is **required**, not advisory. + - `c9b03f30bd300cc5528ebf36e7ca07f1ed0575d5` keeps the existing branch-protected `tests` status context and extends it to run `npm run quality`, install Chromium, and execute `npm run test:browser`. + - This avoids a repository-settings migration: the existing required `tests` rule now fails if browser regression fails. + - The separate Browser E2E workflow is retained as **manual-only** for focused reruns and failure diagnostics rather than duplicating every PR run. + - First required-gate validation on `c9b03f30...`: repository quality succeeded and browser regression reported **22 passed, 6 intentional skips**; CodeQL, Dependency Review, and Template Manifest Guard also green. +- Exact next action: synchronize final docs/PR/issues, review the complete `main → #308` diff and unresolved review/security state, then make #308 ready for review/merge if the documented head remains green. After #308 / #296 / #279 close, begin #312 as the next product slice; #313 follows on the live-class/team-management substrate. + +## Approved Classroom follow-on direction + +The current #288 program establishes the secure Classroom foundation, but it is **not** the final Instructor-led teaching experience. + +- **#312 — Instructor-run live class, roster, and dynamic team management** + - normal Instructor path becomes **Start a class**, not manually supplying a pre-existing secret; + - one human-friendly Student join code/link for class admission; + - late joiners can enter an unassigned/waiting roster after an exercise has begun; + - Instructor creates teams/individual workspaces and assigns/reassigns Students live; + - accessible drag/drop-style grouping may be offered, with keyboard/button alternatives; + - moving a Student changes the server-authoritative workspace they synchronize with, without merging old/new team Intake snapshots; + - old team edit authority must not remain indefinitely after reassignment; + - Instructor dashboard should expose team membership, activity/progress/coaching signals, and fast observer/coaching navigation. + +- **#313 — Instructor-orchestrated case simulation and staged debrief** + - builds on #312 rather than expanding PR #308; + - Instructor selects/starts a Case Study and controls exercise stage; + - Student sees only case pages/evidence released for the current stage, ideally referenceable beside Intake; + - future-stage material and Instructor-only facilitation/model-answer material remain server-gated; + - teams document their reasoning/results continuously in Intake; + - Instructor can pause for debrief, review team work, coach, and then advance the class together; + - late join and team reassignment must preserve current-stage authorization; + - progressive disclosure, shared pacing, preserved Student work, and debrief-before-exemplar are the target simulation principles. + +#296/#279 should finish the present real-browser quality layer without encoding the current class+assignment-code Student admission UI as the permanent product contract. Negative authorization tests should protect credential isolation and non-enumeration in ways that remain valid when #312 replaces the current admission UX. + ## Known risks / watch items - Existing collaboration secret links grant equal edit access and have no administrator role; classroom authorization must layer over rather than silently reinterpret those links. diff --git a/docs/testing-guidelines.md b/docs/testing-guidelines.md index d021e01..786dc60 100644 --- a/docs/testing-guidelines.md +++ b/docs/testing-guidelines.md @@ -7,6 +7,31 @@ This project relies on automated tests to keep the zero-backend KT Intake experi - **DOM integration tests**: Add a DOM integration test whenever behaviour spans multiple modules, updates UI anchors, or mutates the application state through user-like interactions. These tests can render snippets of the DOM, exercise event listeners, and verify that state snapshots align with expectations. - **Mixed updates**: If a change affects both a pure helper and the UI wiring, cover each side in the appropriate suite. A unit test should confirm the helper’s contract while a DOM integration test proves the end-to-end user workflow. +## Real-browser Playwright and accessibility gate + +Use Playwright for behavior that jsdom cannot prove reliably: browser persistence/reload, downloads/uploads, responsive entry flows, cross-browser-context collaboration, real focus/ARIA behavior, and integration across the mounted application. + +- Browser specs live under `tests/e2e/*.spec.mjs`. +- Run locally with `npm run test:browser`; use `npm run test:browser:headed` only when interactive diagnosis is useful. +- `playwright.config.mjs` starts `scripts/serve-browser-tests.mjs`, a deterministic local HTTP/API fixture. Normal browser CI must not depend on Vercel previews, production data, Neon credentials, or build quota. +- Desktop Chromium and Pixel 7 projects are canonical. Scope a journey to one project only when the interaction contract is genuinely project-specific; document every intentional skip. +- Covered accessibility journeys use `@axe-core/playwright` and fail on serious/critical violations. Keep explicit focus/keyboard assertions when they protect a specific interaction contract. +- Uncaught `pageerror` events should fail critical journeys. +- Traces/screenshots are retained only on failure. +- The branch-protected `tests` CI status is the required merge gate: it runs `npm run quality`, installs Chromium, then runs `npm run test:browser`. A browser regression therefore fails the existing required check without a separate repository-settings rule. +- `.github/workflows/browser-e2e.yml` is manual-only and exists for focused browser reruns/diagnostics. + +### Deterministic Classroom fixture rules + +The browser fixture is shared by fully-parallel specs, so stateful test identities must not collide. + +- Give stateful Classroom assignment/workspace capabilities a namespace unique to the spec/project. +- If a test can mutate server fixture state and CI retries are enabled, include `testInfo.retry` (or an equivalent deterministic retry marker) in its fixture capability. +- Preserve semantic token distinctions used by the fixture (Instructor vs class join vs assignment vs Student workspace). +- Do not weaken authorization in the fixture merely to make a browser journey convenient. +- Multi-context collaboration tests should create separate browser contexts to prove independent local storage/profile identity. +- Prefer asserting semantic state (`hidden` / `aria-hidden` / `inert`, persisted data, HTTP method/status) over assumptions about off-canvas CSS visibility. + ## Working with `collectAppState()` and `applyAppState()` The application exposes snapshot helpers that make DOM integration tests reliable: diff --git a/index.html b/index.html index 586a74c..461f5e4 100644 --- a/index.html +++ b/index.html @@ -49,7 +49,7 @@