You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| modify tests, test helpers, or test runner configuration |[`docs/references/develop-testing.md`](docs/references/develop-testing.md) — apply the test-boundary, observation, and harness rules before editing |
13
14
| review or report a branch/PR, or create/update a PR or publish its branch |[`docs/develop.md#revision-scope-and-publication-binding`](docs/develop.md#revision-scope-and-publication-binding) + [`docs/pull-request.md`](docs/pull-request.md)|
14
15
| change a process/message/service/persistence boundary or add a subsystem |[`docs/architecture.md`](docs/architecture.md) + the relevant `docs/references/architecture-*.md`|
15
16
| build or modify a page, dialog, or block |[`docs/design.md`](docs/design.md) — Core Constraints apply to every UI change |
@@ -48,6 +49,13 @@ downstream prose does not override it.
48
49
Chinese or English titles. The two narrow, non-blanket exceptions are in
Copy file name to clipboardExpand all lines: docs/references/develop-testing.md
+54-1Lines changed: 54 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,57 @@ This guide owns how contributors design, write, review, clean up, and run automa
7
7
merely because it raises coverage: it must protect an observable contract, fail for a relevant regression, and
8
8
cost less to understand and maintain than the confidence it provides.
9
9
10
+
## Test-change route and evidence
11
+
12
+
Before modifying a test, shared test helper, or runner configuration, classify the contract and boundary first:
13
+
14
+
1. State the trigger, observable outcome, and plausible regression.
15
+
2. Search nearby unit, component, service, E2E, and lint coverage before adding or deleting a case.
16
+
3. Run the narrowest baseline or reproduce the failure under the same runner, reporter, coverage, shard, and worker
17
+
conditions that exposed it.
18
+
4. Change one cause at a time, then run the focused test and the relevant broader combination.
19
+
5. Report exact commands and distinguish a passed assertion from an unobserved channel or unverified negative.
20
+
21
+
One passing run is evidence for that run only. Do not treat a timeout increase, retry, deleted assertion, or arbitrary
22
+
sleep as a root-cause repair.
23
+
24
+
### Observation rules for asynchronous tests
25
+
26
+
The test must observe completion of the contract under test. A request being called proves that work started; it does
27
+
not prove that state, persistence, rendering, or the user-visible result completed. Use the narrowest primitive that
28
+
matches the boundary:
29
+
30
+
- Use direct assertions for synchronous effects and one `act` for a Promise-driven React update.
31
+
- Use `findBy*` for a single element that appears asynchronously. Do not wrap `getBy*` in `waitFor` for a lone
32
+
`toBeInTheDocument` assertion.
33
+
- Use `waitFor` for genuinely open-ended async state, multiple related assertions, or a non-DOM boundary that has no
34
+
dedicated completion signal. Keep the callback observational: do not fire events or call `userEvent` inside it,
35
+
because retries repeat the interaction.
36
+
- Use a real timer only when elapsed time is the contract or the only bounded closure window proves a negative result
37
+
(for example, an observer timeout, a runaway retry check, a browser event-loop yield, or a library timer). Add a
38
+
local ESLint disable comment stating that contract. A fixed delay used merely to make a test pass is a defect.
39
+
40
+
The mechanical guards `scriptcat/no-test-waitfor-interaction`, `scriptcat/no-test-waitfor-query`, and
41
+
`scriptcat/no-test-fixed-sleep` cover reliably recognizable forms in committed page tests and E2E specs. They do not
42
+
prove mock fidelity, the sufficiency of a negative observation window, or that coverage was not weakened; those remain
43
+
semantic review duties. Do not disable a whole directory to silence them.
44
+
45
+
The interaction and query guards follow actual Testing Library import bindings, including local aliases, and respect
46
+
lexical shadowing; a same-named ordinary function or object is outside their contract. The sleep guard covers
47
+
Playwright `waitForTimeout` and timer-backed `new Promise` forms, while finite observer timeouts remain valid only with
48
+
a line-level disable comment that names the timeout contract. Scratch files remain excluded by the committed E2E
49
+
configuration; inspect the effective ESLint configuration when a helper moves between tracks.
50
+
51
+
### UI and Playwright examples
52
+
53
+
For a UI mutation, assert the returned state, rendered result, or persisted collaborator result after completion;
54
+
`expect(client.update).toHaveBeenCalled()` alone only proves dispatch. For Playwright, a helper that saves an editor
55
+
must take an explicit success or failure expectation and wait for the matching, operation-specific signal. A negative
56
+
case must request the failure contract; a helper that always waits for success turns a valid rejection into a harness
57
+
failure. An arbitrary toast, an existing toast from an earlier action, or a page-shell anchor is not proof that the
58
+
save completed. Keep real browser API, cross-context, and permission flows in E2E; do not replace them with mocks just
59
+
to avoid waiting.
60
+
10
61
## Applicability gate — read this first
11
62
12
63
Not every section below applies to every change. Before designing or reviewing tests, check which of these the
@@ -246,7 +297,9 @@ before/after in one environment with the JSON-report method below.
246
297
- Co-locate `*.test.ts`/`*.test.tsx` next to source (or place in `tests`).
247
298
- Use `describe.concurrent()` / `it.concurrent()` where independent.
248
299
- Single file: `pnpm test -- --run path/to/file.test.ts`.
249
-
- Playwright tests are `*.spec.ts` files in `e2e`; they run with one worker and retain failure artifacts. Run targeted tests while iterating, then run `pnpm run lint` plus the relevant full suite before a PR.
300
+
- Playwright tests are `*.spec.ts` files in `e2e`; worker count, retries, and artifact settings come from
301
+
[`playwright.config.ts`](../../playwright.config.ts) and the CI matrix. Run targeted tests while iterating, then
302
+
run `pnpm run lint` plus the relevant full suite before a PR.
0 commit comments