Flaky test reduction
Skill almasumdev/awesome-mobile-testing-agent-skills/.github/skills/automation/flaky-test-reduction
Agent skills for unit, widget, UI, and end-to-end testing of mobile apps across platforms.
npx -y skills add almasumdev/awesome-mobile-testing-agent-skills --skill flaky-test-reductionAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Root-cause and eliminate flaky tests in mobile suites. Use when a test passes sometimes and fails sometimes, when CI reruns are masking real bugs, or when a suite's flake rate is above budget.
SKILL.md
4.9 KB, as published. Nobody here has run it
Flaky Test Reduction
Instructions
Flake is not a test problem — it is usually a design problem in either the test or the code under test. Retrying a flaky test without understanding why makes the suite untrustworthy and slow. This skill is a root-cause checklist and a set of fixes.
1. First Principle: Isolate the Cause
A test is flaky for exactly one of a handful of reasons. Work the list in order; do not skip:
- Time (clocks, timeouts, delays).
- Concurrency (race conditions, main-thread assumptions).
- Shared state (singletons, disk, DB, DI graph reused).
- Ordering (test depends on another test's side effects).
- External dependency (network, device service, third-party SDK).
- Resource pressure (OOM, CPU-starved emulator).
- Framework bug (rare; only conclude this last).
2. Kill Time-Based Flake
- Replace
System.currentTimeMillis(),Date(),DateTime.now(),Date.now()with an injected clock. - Replace
Thread.sleep,Task.sleep,Future.delayed,setTimeoutin tests with condition-based waits. - Use virtual time for retry/backoff logic:
@Test fun retries_with_backoff() = runTest {
val dispatcher = StandardTestDispatcher(testScheduler)
val sut = Sync(dispatcher = dispatcher, clock = FixedClock(T0))
sut.start()
testScheduler.advanceTimeBy(5_000) // virtual
assertEquals(3, sut.attempts)
}
3. Kill Concurrency Flake
- Use test dispatchers / schedulers (
TestDispatcher,TestScheduler,fakeAsync,jest.useFakeTimers). - Never assert on state right after
launch { }/Task { }/then()without joining or awaiting. - In UI tests, use the framework's idling primitive (
IdlingResource,waitForExistence,pumpAndSettle,waitFor(...)in Detox). - Beware "it works because my laptop is fast" — run the suite under CPU throttling occasionally to surface races.
4. Kill Shared-State Flake
- Build and tear down per test: DB (in-memory), HTTP server, DI container.
- Purge global caches in
@AfterEach/tearDown. - On Android, use
@Afterto resetDispatchers.setMain/Dispatchers.resetMain. - On iOS, reset
UserDefaultstest suite viaremovePersistentDomain(forName:). - Avoid
object/singletoncollaborators in production code — inject them so tests get fresh instances.
5. Kill Ordering Flake
Run tests in random order in CI. Most runners support it (-Dspock.configuration.runner.reverseOrder, JUnit5 random, XCTest -randomize-tests, Jest --testSequencer). A test that fails only in some order is leaking state; fix the leak, not the order.
6. Kill External-Dependency Flake
Any dependency on a live backend or third-party SDK will flake. Stub them:
- Run E2E against a dedicated seeded environment, not shared staging.
- Replace analytics, push, and crash SDKs with no-op fakes in tests.
- Pin time-sensitive flows so they do not depend on real wall-clock (holidays, weekends, token expiry).
7. Kill Resource-Pressure Flake
- Reduce parallelism on under-powered CI runners.
- Cap emulator animation and transition duration (
adb shell settings put global window_animation_scale 0). - Pre-boot simulators / emulators before the test phase starts so boot cost is not inside a test timeout.
- Raise device-farm test timeouts proportionally to device class.
8. Retries — Use Cautiously
Retries hide flake, they do not fix it. Use them only as a bridge:
- Retry once at the suite level; report the retry count as a metric.
- Quarantine any test that needs retry to pass more than twice in a week.
- Never retry inside the test body itself — it masks the root cause.
9. Quarantine and Budget
- Move flaky tests to a
@Flaky/@Tag("flaky")group that still runs but does not fail the build. - Set a time box: 30 days to fix or delete.
- Track flake rate per test; surface the top 10 in a weekly report.
10. Tooling
- Kotlin:
kotlinx-coroutines-test, MockWebServer,runTest. - Swift:
XCTWaiter,XCTNSPredicateExpectation,Task.yield()when awaiting a state flip. - Flutter:
fakeAsync,tester.pumpAndSettle(Duration(seconds: 5))with explicit timeout. - RN:
jest.useFakeTimers({ advanceTimers: true }),@testing-library/react-native'swaitFor.
11. Checklist
- No real clock, RNG, or wall-clock sleep in tests.
- Every async assertion is gated by a condition wait, not a fixed delay.
- Test suite runs in random order in CI.
- DB, HTTP server, and DI graph are rebuilt per test.
- External SDKs are replaced with fakes.
- Retries are a bridge, not a fix; retry counts are tracked.
- Quarantine has a 30-day fix-or-delete rule.
- Top-10 flakiest tests are published weekly.