fix(taxonomic-filter): deflake component tests with fake timers - #70307
Conversation
Trunk quarantined several TaxonomicFilter/PropertyFilters component tests as CI timeout flakes. They aren't unstable in outcome, just slow: each search interaction pays taxonomicFilterLogic's real 500ms search breakpoint (plus stacked 100ms ones) on real timers, and under CI's sharded, contended --maxWorkers=2 runners the ~1.3-1.4s baseline occasionally exceeds jest's 5000ms per-test timeout. Fix removes the wall-clock wait instead of raising the timeout: a scoped jest.useFakeTimers() around the search interaction, advanced past the debounce, then jest.useRealTimers() before waiting on the resulting MSW round trip. Excludes setImmediate from the fake set (like queueMicrotask, already excluded in jest.config.ts) since it also drives MSW v2's response pump, and faking it turns a fast search into a slow one under advanceTimersByTime. A handful of scenarios that also exercise the SuggestedFilters "reveal barrier" don't tolerate the fake-to-real timer switch (a pending fake timer is dropped rather than carried over, and the barrier never opens) — those stay on real timers with a comment explaining why. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🤖 CI report✅ Bundle size — no changeUncompressed size of every built Total: 63.03 MiB · no change No file changed by more than 1000 B. Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report ✅ Eager graph — within budgetHow much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 668 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 278.6 KiB | ../node_modules/.pnpm/posthog-js@1.399.2/node_modules/posthog-js/dist/rrweb.js |
| 266.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.37.4_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 234.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 221.5 KiB | ../node_modules/.pnpm/posthog-js@1.399.2/node_modules/posthog-js/dist/module.js |
| 164.0 KiB | src/queries/validators.js |
| 154.3 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 105.9 KiB | src/lib/api.ts |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 90.6 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Dist folder size — no change
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1267.29 MiB · no change
⚠️ Playwright — 1 failed
🎭 Playwright report · View test results →
❌ 1 failed test:
- Retention calculations, period, breakdown, and chart (chromium)
These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!
🦔 Hogbox preview · ❌ build failedThe preview didn't come up for commit |
There was a problem hiding this comment.
Test-only change deflaking two component test files via scoped fake timers; no production code touched, diff matches the description, no risky territory involved.
- 👍 on the PR from hex-security-app[bot].
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 0L, 0F substantive, 87L/2F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T0 auto-approve: T0-deterministic (87L, 2F, single-area, fix) |
| stamphog 2.0.0b3 | .stamphog/policy.yml @ 59f1d23 · reviewed head 29bff02 |
…nfig Faking it deadlocks any test awaiting an MSW v2 response under fake timers (the interceptor response pump rides setImmediate), which is upstream's documented stance rather than a bug (mswjs/msw#1830). With the global doNotFake covering it, a bare jest.useFakeTimers() is safe against MSW everywhere instead of each file rediscovering the recipe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Added a follow-up commit putting |
New commits pushed (delta classified non_trivial_delta) — stamphog approval dismissed; re-review running automatically.
There was a problem hiding this comment.
Test-only deflaking change (scoped fake timers + jest config tweak), no product code touched; author has strong familiarity with these exact files and the approach is well-documented including its known limitations.
- Author wrote 79% of the modified lines and has 625 merged PRs in these paths (familiarity STRONG).
- 👍 on the PR from hex-security-app[bot].
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 4L, 1F substantive, 91L/3F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1b-small (91L, 3F, single-area, fix) |
| stamphog 2.0.0b3 | .stamphog/policy.yml @ 59f1d23 · reviewed head a2d1e2c |
Problem
Trunk quarantined several tests in
TaxonomicFilter.test.tsxandPropertyFilters.component.test.tsxas CI timeout flakes. They aren't unstable in outcome, they're just slow: each search interaction pays taxonomicFilterLogic's realbreakpoint(500)search debounce (plus a few stackedbreakpoint(100)s) on real timers. At ~1.3-1.4s baseline, plus MSW round trips andwaitForpolling, they occasionally exceed jest's 5000ms per-test timeout once CI's sharded--maxWorkers=2runners add contention.Changes
Removes the wall-clock wait instead of raising the timeout or loosening assertions:
withoutDebounceDelayhelper (and an equivalent inline inPropertyFilters.component.test.tsx's sharedsearchFor) scopesjest.useFakeTimers()around just the search interaction, advances past the debounce, then callsjest.useRealTimers()before the test's ownwaitForpicks up the resulting MSW round trip.setImmediatein addition toqueueMicrotask(already excluded globally injest.config.tsfor the same reason).setImmediatealso drives MSW v2's response-body pump — faking it makesadvanceTimersByTimere-run that pump once per virtual ms for every matched row, turning a fast search into a slow one.SuggestedFilters"reveal barrier" don't tolerate the fake-to-real timer switch: a pending fake timer scheduled during the fake window is dropped rather than carried over when timers switch back to real, so the barrier never opens. Those stay on real timers, each with a comment explaining why — forcing it would have hidden a real functional break behind a "passing" test.}, 10000)per-test timeout overrides inTaxonomicFilter.test.tsxthat are no longer needed once the debounce wait is gone.No product code changed — this is test-only.
How did you test this code?
Automated only, run in the branch's worktree.
Before/after timing (
--maxWorkers=1, per-test):TaxonomicFilter› search › typing in the search field filters resultsTaxonomicFilter› search › returns full unfiltered results when search is clearedTaxonomicFilter› no results - switch to all › does not offer a jump to a render-backed group with no real matchesTaxonomicFilter› no results - switch to all › offers a per-category jump when matches live on another tabTaxonomicFilter› edge cases › renders correctly when search matches no itemsTaxonomicFilter› collapseUrlsToContainsRow › (4 non-SuggestedFilters tests)PropertyFilters› shortcut group (pageview URL / screen name / email)PropertyFilters› selecting a property and completing the value records to recentsPropertyFilters› recents show/search hint/prefix disappears/searching in recentsPropertyFilters› multiple selections limited to 3 in suggested filtersTwo tests in
no results - switch to alland one incollapseUrlsToContainsRowstay on real timers (see comments) since they hit the SuggestedFilters reveal-barrier incompatibility above — they're unchanged from baseline (~900-1300ms), still comfortably clear of the 5000ms timeout.Full-file totals:
TaxonomicFilter.test.tsx31.2s → 25.8s (81 tests),PropertyFilters.component.test.tsx16.6s → 12.5s (13 tests). All 94 tests pass.Stability loop: both files together,
--maxWorkers=2, 24/24 runs green (target was 20).Cross-file check:
Combobox.test.tsx(the file whose existing fake-timer idiom this follows) run once, 38/38 pass, unaffected.Docs update
N/A — test-only change.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Paul directed this from a pre-established diagnosis (already root-caused: real-timer search debounces pushing quarantined tests over CI's per-test timeout) and specified the fix approach (scoped fake timers, following the existing
Combobox.test.tsx/recentTaxonomicFiltersLogic.test.tsidiom) plus the validation bar (before/after timing, a 20-run stability loop at--maxWorkers=2).I'm Claude (Sonnet), working in the target worktree. No skills were invoked — this is a test-timing fix, not new test coverage or a DRF/migration/taxonomic-filter behavior change, so
/writing-testsand/modifying-taxonomic-filterdidn't apply.Key decisions along the way:
jest.useFakeTimers()first, per the preferred approach — it deadlocked every test that touched MSW (jest's own per-test 5000ms watchdog killed each one, leaking fake timers into subsequent tests). Root cause:@testing-library/dom'swaitForself-drives fake-timer advancement in a loop, and MSW v2's response pump ridessetImmediate, which was being faked and re-entered on every virtual millisecond.jest.useFakeTimers()/jest.useRealTimers()tightly around just the typing interaction (matchingCombobox.test.tsx's existing per-test idiom), excludingsetImmediatefrom the fake set.SuggestedFiltersreveal-barrier scenarios specifically — confirmed by testing with a much longerwaitFortimeout (4000ms) and observing the assertion still never resolved, i.e. a genuine functional break rather than a timing shortfall. Left those on real timers per the task's explicit fallback instruction rather than forcing a fix that would have masked the break.