-
Notifications
You must be signed in to change notification settings - Fork 10
e2e state pattern
Companion to E2E Architecture. Mandatory pattern for new specs + refactor target for old ones.
Walk a flow via the UI exactly once per workflow. For every variant, edge case, or branch off that flow, seed state via API calls and exercise only the divergent UI surface.
UI walks are expensive:
- Browser nav + paint waits + animation settle = 3–10× slower than API calls
- Each step adds flake surface (selector misses, race conditions, toast stacking)
- Repeated walks bloat CI minutes + test files
API-seeded state hits the same controllers the UI does. State is realistic, drift-resistant, fast.
Currently the suite has ~150–180 sec/run of redundant walks across top 3 hotspots — see duplicate-walks audit.
// e2e/tests/<workflow>/full-workflow.spec.ts
test.slow() // legitimately walks pick → pack → close end-to-end
test("<workflow> full workflow", async ({ page, runtimes }) => {
await importCSV("<workflow>/sample.json")
// walk the UI from start to finish
})// e2e/tests/<workflow>/variants/stacking.spec.ts
test("Stack height 5 allowed, 6 rejected", async ({ runtimes }) => {
const { shipmentId } = await seedShipment({
site: "Berlin",
carrier: "carrier-a",
pallets: 6, // pre-built 6-tall stack via API
})
// single UI assertion: stack-height-6 dialog shows error
})
test("Non-stackable carrier pallets cannot stack", async ({ runtimes }) => {
const { shipmentId } = await seedShipment({
carrier: "carrier-b",
pallets: 2,
})
// assert stackability dropdown disabled for non-stackable carrier
})e2e/helpers/seed/
├── <workflow-a>.ts seedShipment(...) advanceToStepD(...) putCartInTransferredState(...)
├── <workflow-b>.ts seedBatch(...) setupPickedState(...)
├── <workflow-c>.ts seedDeliveryNotes(...)
├── <workflow-d>.ts seedTruck(...) loadTruck(...)
└── _shared.ts loginAs(role), apiClient(ctx)
Each helper calls real controllers — same path the UI takes. No backdoor DB migrations.
const cart = new WorkflowCart(page)
await cart.jumpTo("transferred", { cartId })
// no UI walk; cart now in transferred stateImplemented in terms of seed helpers.
E2E is NOT the place for:
-
Carrier API request structure → unit-test
api/src/services/Ship/*.ts - Stack validator algorithm → pure function, unit test
- Projection flag math → unit/integration tests over repo data
- External-system event serialization → schema tests
- PDF byte-level layout → visual regression only for top-priority labels
- Mailer subject/recipient → integration test w/ test transport
Move these. Keep e2e for: "user clicks button X, sees outcome Y, given state Z".
<workflow>/
├── full-workflow.spec.ts ← single UI happy path, kept thorough
├── variants/
│ ├── <concern-1>.spec.ts ← API-seeded, focused UI assertion
│ ├── <concern-2>.spec.ts
│ └── ...
└── carrier/ ← integration-level carrier tests if needed
└── ...
Aim: variants/*.spec.ts average 5–10 sec each — most steps API, one UI assertion.
New or changed business behavior must be exercised by an e2e spec before it reaches production. Two acceptable paths:
| Path | When OK |
|---|---|
| Tests-with-merge | PR includes the e2e spec covering the new/changed behavior. Merges + ships. |
| Behind feature toggle | PR may merge without e2e only if the new behavior is gated behind a feature flag disabled in prod. A follow-up PR adds the e2e spec before the flag flips on. |
What's not OK: new behavior reaching prod without e2e coverage of the divergence. Manual QA does not count.
This applies to AI agents and humans equally. If an agent ships a behavior change, it must also write the test or open a paired PR doing so.
Note: adding a bad e2e test (full-flow walk for a variant assertion) is worse than adding no test — see Acceptance criteria below.
When opening a PR w/ a new e2e spec, reviewer checks:
- Does it walk a full flow via UI? If yes, is it the canonical happy path for that workflow (no other spec already does this)?
- If it's a variant, does it use an API seed helper to reach the divergent state?
- If the seed helper doesn't exist yet, does this PR add it to
e2e/helpers/seed/? - Could any step be unit-tested instead? (Pure functions, schemas, API requests)
- Does the spec run in under 15 seconds locally? (over 30 → reach for
test.slow()only after investigating per e2e.md)
When refactoring an old spec toward this pattern:
- Extract any repeated prelude (≥3 specs using same setup) into
e2e/helpers/seed/<workflow>.ts - If specs share a sample import, consider suite-scoped
beforeAll()(only if tests don't mutate shared state) - Parameterize variant tests via
for (const variant of variants) test(...)when the only difference is data
e2e/helpers/import.ts:
-
importCSV(path)/importJSON(filePath)/insertItems(req)
e2e/helpers/act.ts:
-
waitForResponse(callback, matcher)/handleToast()/actButton()
Composite seed helpers are missing — see duplicate-walks audit for what to add first.
- Writing a new spec. Default to API-seeded variant unless this is the canonical happy path for the workflow.
- Reviewing a spec. Flag full-flow walks where API-seeded variants would do.
- Refactoring. Look for ≥3 specs sharing a prelude; extract into a helper.
- Copilot reviews. Comment on PRs that add full-flow walks for variant assertions.
- Index
- Import Rules
- Resource & Controller Layout
- Command Pattern
- Command Input Validation
- Query Shape: List vs Get
- Database Query Guidelines
- List Layout
- Streams & Progress
- Vue Conventions
- E2E State Pattern
- E2E
- E2E Toast Wait Audit
- Flow Documentation
- (project-local — create
flows/when first workflow lands)