Skip to content

test(e2e): per-tier × per-page + per-async-state live-UI matrix sweep - #199

Merged
mastermanas805 merged 1 commit into
mainfrom
test/live-ui-tier-error-matrix
Jun 6, 2026
Merged

test(e2e): per-tier × per-page + per-async-state live-UI matrix sweep#199
mastermanas805 merged 1 commit into
mainfrom
test/live-ui-tier-error-matrix

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

What

Closes the inventory's biggest remaining CI gap — docs/ci/00-INTERACTION-PATHS.md Part C: "no per-route × per-tier × per-async-state CI sweep". Two new real-backend (minted cohort) live-UI Playwright specs, entirely under e2e/.

e2e/live-ui-tier-matrix.spec.ts — per-tier × per-page sweep

Registry-iterates the tier ladder (TIER_RANK ∩ factory-mintable tiers — rule 18; a new tier auto-expands the matrix) and, per tier, mints one cohort account and asserts the tier-correct gated/ungated UI across 6 pages: /app, /app/resources, /app/deployments, /app/vault, /app/settings, /app/billing.

Feature gate sub-Pro Pro+
private-deploy (/app/deployments) PRIVATE_DEPLOY_TIERS upsell card configurator
vault multi-env (/app/vault) VAULT_MULTI_ENV_TIERS wall on non-prod env no wall
deploy-TTL edit (/app/settings) isPaidTier upgrade hint (free) radios editable
Overview upgrade CTA (/app) showProUpgrade CTA shown absent
Billing Team CTA (/app/billing) HARD rule Contact sales — never self-serve same

Per-tier expectation is computed in e2e/tier-matrix.ts from the app's soft-gate allowlists (each back-referenced to its source page), so a soft-gate change is a one-line edit in lockstep.

e2e/live-ui-error-states.spec.ts — per-async-state sweep

  • 401 revoked-session → /login?session_expired=1 (REAL — disposable minted account, logout/revoke; never touches the shared JWT) — @pr-smoke
  • 402 at-limit upgrade wall (REAL — sub-Pro pause → 402 → in-UI upgrade CTA)
  • 429 retry-hint banner + 5xx error banner (MOCKED route-stub — labeled, since a real prod 429/5xx isn't safely reachable)
  • empty states (REAL — fresh account → empty-state copy, not an infinite spinner)

Wiring

  • @pr-smoke subset (one sub-Pro gated-CTA assertion + the 401-redirect) rides the existing e2e-pr-smoke.yml (--grep @pr-smoke); full matrix stays on the schedule. No workflow edit needed.
  • Both specs registered in e2e/live-ui.coverage.ts so the prod-coverage done-bar guard tracks them.

Finding F1 (for the src/-owning bug-hunt team)

On a 429/5xx the DeploymentsPage renders the "No deployments yet" empty row alongside the error banner (empty-state gates on items.length===0, not !err). The error banner (role=alert, top) is the dominant signal so the anti-silent-collapse guarantee holds; the simultaneous empty copy is mildly contradictory UX. Filed, not fixed (fix is in src/, out of this round's e2e-only scope).

Verification

  • npm run gate GREEN (tsc + build + prerender + vitest — 81 files, 1144 passed, 3 skipped). New specs are live-*.spec.ts → excluded from the mocked PR suite and vitest, picked up only by playwright.live.config.ts.
  • Ran both specs against REAL prod (mint→assert→reap): 10/10 passed — free/hobby/hobby_plus/pro × 6 pages + all 5 error legs. Ledger empty (no leaks), reap 200.
  • @pr-smoke subset: exactly the 2 intended tests, green against prod.

Cross-ref: docs/ci/00-INTERACTION-PATHS.md Part C.

🤖 Generated with Claude Code

Closes the inventory's biggest remaining gap (docs/ci/00-INTERACTION-PATHS.md
Part C: "no per-route × per-tier × per-async-state CI sweep"). Two new
real-backend (minted cohort) live-UI specs, owned entirely under e2e/:

live-ui-tier-matrix.spec.ts — per-tier × per-page sweep. Registry-iterates the
tier ladder (TIER_RANK ∩ factory-mintable, rule 18) and, per tier, mints one
cohort account and asserts the tier-correct gated/ungated UI across /app,
/app/resources, /app/deployments, /app/vault, /app/settings, /app/billing:
private-deploy configurator vs upsell, vault multi-env wall, deploy-TTL edit
gate, Overview upgrade CTA, and the HARD "Team is never self-serve" rule
(Team CTA = Contact sales). Per-tier expectation is COMPUTED in e2e/tier-matrix.ts
from the app's soft-gate allowlists (back-referenced), so a new tier or a gate
change auto-expands the matrix.

live-ui-error-states.spec.ts — per-async-state sweep. 401 revoked-session →
/login?session_expired=1 (REAL, disposable account logout/revoke); 402 at-limit
upgrade wall (REAL, sub-Pro pause); 429 retry-hint + 5xx error banner (MOCKED
route-stub — labeled, since a real prod 429/5xx isn't safely reachable); empty
states (REAL fresh account → no infinite spinner).

@pr-smoke subset (one sub-Pro gated-CTA assertion + the 401-redirect) rides the
existing e2e-pr-smoke.yml (--grep @pr-smoke); full matrix on the schedule. Both
specs registered in e2e/live-ui.coverage.ts so the prod-coverage done-bar guard
tracks them.

Finding F1 (filed for the src/-owning bug-hunt team): on a 429/5xx the
DeploymentsPage renders the "No deployments yet" empty row ALONGSIDE the error
banner (empty-state gates on items.length===0, not !err). The error banner is
the dominant signal so the anti-silent-collapse guarantee holds; the
simultaneous empty copy is mildly contradictory UX.

Verified: npm run gate green (81 files, 1144 passed); both specs run green
against real prod (10/10 — free/hobby/hobby_plus/pro × 6 pages + 5 error legs),
ledger empty (no leaks), reap 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mastermanas805
mastermanas805 enabled auto-merge (squash) June 6, 2026 07:27
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
dist/assets/index-BZLn7_NF.js 162.22 KB (0%)
dist/assets/index-BsJUZYRr.css 6.13 KB (0%)

@mastermanas805
mastermanas805 merged commit 861f659 into main Jun 6, 2026
19 checks passed
mastermanas805 added a commit that referenced this pull request Jun 6, 2026
On a 429/5xx the DeploymentsPage catch handler sets items=[] (honest —
nothing loaded), so the "No deployments yet" create-CTA (deployments-empty)
rendered at the same time as the deployments-error banner: contradictory UX
that tells the user both "nothing here, create one" and "something went
wrong". Gate the empty-state on `!err` so the error banner is the sole
dominant signal during a load error. The genuine zero-deployments empty
state (no error) is unchanged and still shows the create CTA.

Found by the per-tier error-state matrix sweep (PR #199).

Tests (DeploymentsPage.test.tsx):
- error → error banner shown AND empty-state NOT shown (fails before fix)
- 429 → retry-hint banner, no empty row (fails before fix)
- genuine empty (no error, zero items) → create CTA still shown
- happy-path empty has no error banner

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant