Identifying State Leak Signatures #
Look for DOM elements retaining stale data or unexpected network requests firing on test start. Use browser memory snapshots and performance traces to track object retention across test boundaries. Compare heap allocations before and after each spec execution to isolate retention spikes.
Isolating the Leak Source #
Run tests sequentially with --retries=0 and isolate failing specs. Inject beforeEach/afterEach hooks that explicitly clear React state stores and reset mocked timers. Correlate leak timing with specific useEffect dependency arrays. Understanding the broader Root Causes of JavaScript Test Flakiness helps prioritize which isolation strategy to deploy first.
Implementing Deterministic Cleanup #
Enforce strict return functions in all useEffect hooks to abort fetches and clear intervals. Configure E2E runners to spawn fresh browser contexts per spec. Inject teardown scripts that force React unmounting and trigger explicit garbage collection cycles.
Validation & Regression Prevention #
Add automated leak detection to CI by monitoring heap size deltas and tracking unhandled promise rejections. Implement snapshot assertions on global state stores post-test to catch silent pollution. Enforce strict lint rules for missing cleanup returns in async hooks.
Framework Configuration & Cleanup Snippets #
// Playwright: Force fresh context per test
test.use({ storageState: undefined });
// React Cleanup: Strict useEffect teardown
useEffect(() => {
const id = setInterval(fetchData, 5000);
return () => clearInterval(id);
}, []);
// Cypress Config: Optimize for deterministic runs
module.exports = {
experimentalRunAllSpecs: true,
retries: { runMode: 2, openMode: 0 }
};
// Leak Detection Hook: Force GC post-test
afterEach(() => {
cy.window().then(win => win.gc?.());
});
Common Pitfalls #
- Relying on
setTimeoutorcy.wait()instead of explicit state assertions - Missing cleanup returns in
useEffectfor async data fetches - Sharing browser contexts or localStorage across E2E test files
- Ignoring React StrictMode double-invoke behavior during test initialization
- Failing to mock or intercept pending XHR/fetch calls before unmount
FAQ #
Q: How do I distinguish a state leak from a network race condition? A: State leaks persist across test boundaries and affect DOM/memory baselines, while network races cause transient timeouts within a single test run. Isolate by disabling network intercepts and observing if the failure persists in offline mode.
Q: Does React StrictMode cause false-positive flakiness in E2E tests? A: Yes, StrictMode intentionally double-invokes effects to surface missing cleanup. Ensure all async operations have proper teardown returns, or conditionally disable StrictMode in the test build environment.
Q: What is the most reliable way to force React unmounting between tests?
A: Navigate to a blank route (about:blank or /reset) before each test, trigger a forced DOM clear, and verify the React root container is empty before mounting the next component.
Reliability Metrics #
| Metric | Target |
|---|---|
| Target Pass Rate | 99.5%+ |
| Max Flake Rate | <0.5% |
| Avg Cleanup Latency | <150 ms |
| Heap Growth Threshold | 5 MB |
| Cross-Test Pollution Incidents | 0 |