Reference catalog of the eight flake patterns - async/timing, test ordering, shared parallel state, resource leaks, network, locator drift, environment variance, randomness - with detection heuristics, remediation per pattern, and the concrete code-level fixes: replacing fixed sleeps with framework auto-waits, isolating state in beforeEach fixtures, per-worker DB schemas via workerIndex, try/finally teardown, mocking network + clock at the boundary, stable role-based locators, TZ pinning, and RNG seeding. Use when triaging an unknown flake to identify the category before bisecting, or when a classified flake needs the specific code change to apply.
98
91%
Does it follow best practices?
Impact
99%
1.07xAverage score across 10 eval scenarios
Passed
No findings from the security scan
Deep reference for flake-pattern-reference SKILL.md. The Pattern 7
(environment variance) and Pattern 8 (randomness) code fixes, split out
of the main guide so the four core-pattern fixes stay in front.
Root cause: path separators, line endings, timezones, or fonts differ across OS / CI environments.
Set TZ=UTC in every CI job that contains time-sensitive assertions.
This eliminates the class of failures where new Date().toISOString()
produces a different date in UTC-8 vs. UTC+9.
# .github/workflows/test.yml
env:
TZ: UTC// Before - breaks on Windows CI
const fixture = path.join('tests', 'fixtures', 'data.json');
// After - works on Linux, macOS, and Windows
import { join } from 'node:path';
const fixture = join('tests', 'fixtures', 'data.json');When the test asserts a displayed date or a timer-driven behavior, use
page.clock.install() to stop the system clock at a fixed instant
(pw-clock):
// Install the fake clock before the page loads; freeze at a known UTC instant
await page.clock.install({ time: new Date('2026-01-15T12:00:00Z') });
await page.goto('/dashboard');
// "Last seen" label will always read "Jan 15, 2026" regardless of
// which machine or timezone the test runs on
await expect(page.getByTestId('last-seen')).toHaveText('Jan 15, 2026');page.clock.install() overrides Date, setTimeout, setInterval,
requestAnimationFrame, and performance (pw-clock).
For pixel-level snapshot tests, regenerate baselines only in CI (never
from a developer laptop). OS font rendering and anti-aliasing differ
between macOS and Linux - a baseline captured locally will produce
false positives on the CI runner. See playwright-snapshots
for the full update workflow.
Root cause: tests generate random data without a controlled seed, so the failing combination cannot be reproduced.
Faker.js - call faker.seed(N) before generating any test data.
The same integer seed produces the same data sequence on every run
(faker-api):
import { faker } from '@faker-js/faker';
beforeEach(() => {
faker.seed(12345); // deterministic; any integer works
});
test('long product name does not overflow card', async ({ page }) => {
const name = faker.commerce.productName(); // same value every run
await page.goto(`/products/new`);
await page.getByLabel('Name').fill(name);
await expect(page.getByTestId('product-card')).toBeVisible();
});Math.random - replace with a seeded PRNG such as
seedrandom:
import seedrandom from 'seedrandom';
const rng = seedrandom('fixed-seed');
const id = Math.floor(rng() * 1_000_000);Vitest / Jest fake timers - vi.useFakeTimers({ seed: N }) or
jest.useFakeTimers({ now: N }) seeds the internal PRNG as well as
the system clock.
Log the seed used per run so a flake on CI can be replayed locally:
const SEED = Number(process.env.TEST_SEED ?? Date.now());
console.log(`faker seed: ${SEED}`); // visible in CI job log
faker.seed(SEED);Pass TEST_SEED=<failing-seed> to reproduce the exact failure.
When a property-based test (fast-check, jqwik) fails, it has found a real edge case. Copy the failing seed into a regression test and fix the production bug.