Agent skill

Synchronizing With Idle

by skydoves in skydoves/android-testing-skills

A skill your agent uses to choose the right idle-synchronization primitive in Compose UI tests — waitForIdle, awaitIdle, waitUntil(conditionDescription, timeoutMillis, condition)…

Apache-2.0Auto-check passedTesting & QA

Install Synchronizing With Idle

skills CLI
$ npx skills add skydoves/android-testing-skills --skill synchronizing-with-idle -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install skydoves/android-testing-skills synchronizing-with-idle --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/compose/synchronization/synchronizing-with-idle .claude/skills/synchronizing-with-idle && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
synchronizing-with-idle
GitHub stars
333
Token cost
~5.5k tokens
SKILL.md length
1,567 words
Files
1
Skills in repo
50
Repo updated
First seen
Licence
Apache-2.0

At a glance

A skill your agent uses to choose the right idle-synchronization primitive in Compose UI tests — waitForIdle, awaitIdle, waitUntil(conditionDescription, timeoutMillis, condition)…

  • Works in 5 steps: Default to runOnIdle for state interaction → Reach for runWhenIdle for read-only… → Reach for the experimental… → …
  • Choose the right idle-synchronization primitive in Compose UI tests — waitForIdle
  • SKILL.md covers When to use this skill, When NOT to use this skill, Prerequisites and What "idle" means, plus 9 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Synchronizing With Idle is an agent skill from skydoves/android-testing-skills. Use this skill to choose the right idle-synchronization primitive in Compose UI tests — waitForIdle, awaitIdle, waitUntil(conditionDescription, timeoutMillis, condition), waitUntilNodeCount, waitUntilExactlyOneExists, waitUntilAtLeastOneExists, waitUntilDoesNotExist, runOnIdle, runOnUiThread, runWhenIdle, awaitAndRunWhenIdle, hasPendingWork, and IdlingResource. Explains what "idle" means (no pending recomposition or draw, every IdlingResource isIdleNow), the wall-clock vs test-clock timeout split, the…

Its SKILL.md is about 5.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Testing & QA, covering Mobile testing and debugging and Failing and flaky tests. The repository describes itself as: ⚡️ A set of skills for Android testing: Compose UI, AndroidX Test, JVM unit tests, and ADB. The licence is Apache-2.0.

When your agent uses it

  • Choose the right idle-synchronization primitive in Compose UI tests — waitForIdle
  • WaitUntil(conditionDescription
  • WaitUntilNodeCount
  • WaitUntilExactlyOneExists

Example prompts

  • “test is flaky”
  • “test passes locally fails on CI”
  • “Thread.sleep waiting for state”
  • “/synchronizing-with-idle”

Workflow steps

5 steps, taken from the step headings in SKILL.md.

  1. Default to runOnIdle for state interaction
  2. Reach for runWhenIdle for read-only assertion blocks
  3. Reach for the experimental waitUntilExactlyOneExists / friends for "wait until N nodes match"
  4. Reach for waitUntil { … } only for non-Compose conditions
  5. Register an IdlingResource for genuine external async work

What it can do on your machine

Read from SKILL.md and the folder at commit 8665ed5. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are kotlin).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • developer.android.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Synchronizing With Idle loads about 5.5k tokens when it runs. Until then it costs about 236 tokens; SKILL.md has 1,567 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~236
When it runs · the whole SKILL.md, loaded when a task matches
~5.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from skydoves/android-testing-skills at commit 8665ed5, republished under its Apache-2.0 licence (© skydoves). 1,567 words, ~5,550 tokens.

Download SKILL.mdSave it as .claude/skills/synchronizing-with-idle/SKILL.md (or your agent's skills folder).
name
synchronizing-with-idle
description
Use this skill to choose the right idle-synchronization primitive in Compose UI tests — waitForIdle, awaitIdle, waitUntil(conditionDescription, timeoutMillis, condition), waitUntilNodeCount, waitUntilExactlyOneExists, waitUntilAtLeastOneExists, waitUntilDoesNotExist, runOnIdle, runOnUiThread, runWhenIdle, awaitAndRunWhenIdle, hasPendingWork, and IdlingResource. Explains what "idle" means (no pending recomposition or draw, every IdlingResource isIdleNow), the wall-clock vs test-clock timeout split, the Espresso/Robolectric strategy bridge, and why direct state reads from the test thread race the recomposer. If the user mentions "test is flaky", "test passes locally fails on CI", "Thread.sleep waiting for state", "Espresso IdlingResource", "ComposeTimeoutException waitUntil", "AndroidComposeUiTestTimeoutException", waitUntilExactlyOneExists, runOnIdle, runWhenIdle, or "register IdlingResource", use this skill.
license
Apache-2.0. See LICENSE for complete terms.
metadata.author
Jaewoong Eum (skydoves)
metadata.keywords
jetpack-compose, ui-testing, wait-for-idle, wait-until, idling-resource, run-on-idle, run-on-ui-thread, flaky-test, espresso-bridge

Synchronizing With Idle — Pick the Right Wait

A Compose test that waits on the wrong primitive is the #1 source of flakiness. This skill enumerates every idle/wait API on ComposeTestRule and ComposeUiTest, locks down what "idle" actually means, and gives a decision matrix for choosing among them. Animation-specific waits live in ../testing-animations-deterministically/SKILL.md; the underlying clock semantics live in ../controlling-the-test-clock/SKILL.md.

When to use this skill

  • The test occasionally fails with "node not found" or "node count mismatch" but the production code is correct.
  • The developer reaches for Thread.sleep(2000) to wait for a screen, a snackbar, a navigation transition, or an IO-backed state.
  • A ViewModel posts state from a coroutine and the test needs to wait until the UI reflects it.
  • The developer asks about IdlingResource, the Espresso bridge, runOnIdle vs runOnUiThread, or whether runWhenIdle is faster.
  • The developer mentions waitUntil, waitUntilExactlyOneExists, waitUntilNodeCount, AndroidComposeUiTestTimeoutException, or IdlingPolicies.setMasterPolicyTimeout.
  • The developer reads or mutates state from the test thread (e.g. state.firstVisibleItemIndex) and asks why it sometimes returns stale data.

When NOT to use this skill

  • The condition is a paused-clock animation. Use ../testing-animations-deterministically/SKILL.md and mainClock.advanceTimeUntil.
  • The mechanics of MainTestClock itself (frames, rounding, dispatchers) are unclear. Read ../controlling-the-test-clock/SKILL.md first.
  • The test is failing because of test-tag/finder issues, not async timing. Use ../../debug/printing-the-semantics-tree/SKILL.md and ../../finders/finding-nodes-by-tag-text-content/SKILL.md.
  • Espresso interop is the actual question. Use ../../interop/testing-with-espresso-interop/SKILL.md (sibling skill, separate scope).

Prerequisites

  • androidx.compose.ui:ui-test and androidx.compose.ui:ui-test-junit4 (or runComposeUiTest).
  • A ComposeContestTestRule from createComposeRule() (PREFERRED v2: androidx.compose.ui.test.junit4.v2.createComposeRule).
  • For experimental waitUntilNodeCount/waitUntilExactlyOneExists/waitUntilAtLeastOneExists/waitUntilDoesNotExist: @OptIn(ExperimentalTestApi::class) on the test class or method.

What "idle" means

Three conditions, all true at once (compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt:174-186):

  1. No pending recompositions. No snapshot apply notifications outstanding, no recomposer pending work, no withFrameNanos awaiters on the test frame clock.
  2. No pending draw call. Measure and layout passes have run; on Android the draw pass has been requested by the framework (Choreographer drives draw on Android, the test clock does not — MainTestClock.kt:36-41).
  3. All registered IdlingResources report isIdleNow == true. This is how non-Compose async work (an OkHttp call, a Room query) participates in synchronization.

Compose's own work registers automatically through ComposeIdlingResource (compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeIdlingResource.android.kt), which drains the recomposer + snapshot + frame-clock awaiters in a loop capped at 100 frames per call (line 109). A test that needs more than 100 frames to settle is doing too much per waitForIdle.

The primitive surface

Every entry below is from ComposeUiTest.kt / ComposeTestRule.jvmAndAndroid.kt.

APIEffectTimeout source
waitForIdle()Blocks current thread until the three idle conditions hold. With autoAdvance = true, also auto-advances the clock to drain pending work.Wall clock — IdlingPolicies.getMasterIdlingPolicy()
awaitIdle()suspend variant of waitForIdle.Same
waitUntil(conditionDescription, timeoutMillis = 1_000, condition)Blocks until condition() returns true. Each iteration calls mainClock.advanceTimeByFrame() (when autoAdvance == true) AND Thread.sleep(10) wall clock (ComposeUiTest.android.kt:899-902). The timeout is measured against System.nanoTime(), so it expires on wall-clock time even though the test clock also advances.Wall clock — per-call timeoutMillis
waitUntilNodeCount(matcher, count, timeoutMillis = 1_000L)@ExperimentalTestApi. waitUntil over onAllNodes(matcher).fetchSemanticsNodes(atLeastOneRootRequired = false).size == count (ComposeUiTest.kt:275-285).Wall clock
waitUntilExactlyOneExists(matcher, timeoutMillis = 1_000L)@ExperimentalTestApi. Sugar for waitUntilNodeCount(matcher, 1, timeoutMillis).Wall clock
waitUntilAtLeastOneExists(matcher, timeoutMillis = 1_000L)@ExperimentalTestApi. (ComposeUiTest.kt:298-305)Wall clock
waitUntilDoesNotExist(matcher, timeoutMillis = 1_000L)@ExperimentalTestApi. Sugar for waitUntilNodeCount(matcher, 0, timeoutMillis).Wall clock
runOnIdle { … }waitForIdle() then runOnUiThread { … }. The default for state mutations and reads.Wall clock (the inner waitForIdle)
runOnUiThread { … }Posts a FutureTask via Instrumentation.runOnMainSync (AndroidSynchronization.android.kt). If already on the UI thread, runs in-place. Does NOT wait for idle.None
runWhenIdle { … }waitForIdle() then runOnUiThread { … }, but suppresses the implicit waitForIdle triggered by node queries inside the lambda. Faster for assert-only blocks. MUST NOT mutate state inside.Wall clock
awaitAndRunWhenIdle { … }suspend variant of runWhenIdle.Wall clock
hasPendingWork(): BooleanPassive snapshot: are there awaiters on the main clock, snapshot changes, or recomposer pending work? Reliable only when autoAdvance = false (ComposeUiTest.kt:251-256).None
IdlingResource registrationrule.registerIdlingResource(myResource) / unregisterIdlingResource.Wall clock

Timeout sources — do not mix them

APISourceDefaultOverride
waitForIdle / awaitIdleWall clock — Espresso IdlingPolicies master timeout26 sIdlingPolicies.setMasterPolicyTimeout(...) in @Before
waitUntil(…) familyWall clock1000 ms per callper-call timeoutMillis
MainTestClock.advanceTimeUntil(…)Test clock1000 ms per callper-call timeoutMillis
runComposeUiTest(testTimeout = …)Wall clock60.secondsrunComposeUiTest(testTimeout = 5.minutes) { … }; throws AndroidComposeUiTestTimeoutException

Skydoves hot take #4: waitUntil timeouts are wall clock; advanceTimeUntil is test clock. Always prefer mainClock.advanceTimeUntil when the awaited condition is observable through Compose state. Reserve waitUntil for conditions outside Compose's snapshot system — a Job.isCompleted, a counter incremented from a LaunchedEffect, an external service.

RobolectricIdlingStrategy reads the same Espresso IdlingPolicies.getMasterIdlingPolicy(), so a global setMasterPolicyTimeout lift applies to host (Robolectric) and device tests alike.

Espresso bridge

Espresso has its own androidx.test.espresso.IdlingResource interface, which is not the same as Compose's androidx.compose.ui.test.IdlingResource. The bridge is EspressoLink (compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/EspressoLink.android.kt):

text
Espresso (instrumentation tests)
        │ withStrategy { … }
        ▼
EspressoLink   ──implements──>  androidx.test.espresso.IdlingResource
        │
        │  delegates isIdleNow() to ──>  IdlingResourceRegistry
        ▼
IdlingResourceRegistry — Compose's registry
        │
        ├── ComposeIdlingResource   (recomposer + snapshot + frame-clock awaiters)
        └── any rule.registerIdlingResource(...)

The same registry is read by RobolectricIdlingStrategy for host tests. The developer registers a Compose IdlingResource; the framework does the bridging.

kotlin
interface IdlingResource {
    val isIdleNow: Boolean
    fun getDiagnosticMessageIfBusy(): String? = null
}

(From compose/ui/ui-test/src/jvmAndAndroidMain/kotlin/androidx/compose/ui/test/IdlingResource.kt:37-52.) Override getDiagnosticMessageIfBusy() to surface a useful message when a wait times out — it is appended to the timeout exception text.

Decision matrix

Need to wait for…                         | Use
------------------------------------------|----------------------------------------------
A node count to stabilize                 | waitUntilNodeCount / waitUntilExactlyOneExists
A node to appear                          | waitUntilAtLeastOneExists
A node to disappear                       | waitUntilDoesNotExist
A Compose state condition                 | mainClock.advanceTimeUntil { state }
A non-Compose condition (Job, counter)    | waitUntil(conditionDescription) { condition }
"Just settle the UI"                      | waitForIdle()  (or runOnIdle for read+act)
External async work (HTTP, DB)            | rule.registerIdlingResource(MyIdlingResource)
A paused-clock animation frame            | mainClock.advanceTimeBy(...)  -- different skill
A particular state.value to read safely   | rule.runOnIdle { state.value }

Workflow

1. Default to runOnIdle for state interaction

Reading or mutating Compose state from the test thread races the recomposer. The recomposer applies snapshot writes on the main thread; the test thread can observe a half-applied state. runOnIdle solves this by waiting for idle then dispatching to the UI thread:

kotlin
val firstVisibleIndex = rule.runOnIdle { state.firstVisibleItemIndex }

(skydoves hot take #5 — funnel state mutations through runOnIdle or runOnUiThread.)

2. Reach for runWhenIdle for read-only assertion blocks
kotlin
rule.runWhenIdle {
    val node = rule.onNodeWithTag("counter").fetchSemanticsNode()
    assertEquals("3", node.config[SemanticsProperties.Text].first().text)
    val rect = node.boundsInRoot
    // ... more reads ...
}

Each node query inside a normal runOnIdle re-runs waitForIdle(). Inside runWhenIdle, those implicit waits are suppressed because the block already entered with idle witnessed. This matters for tests that step frames manually — node queries inside runOnIdle would call waitForIdle() repeatedly, which (under autoAdvance = true) auto-advances the clock and undoes the test's manual control. MUST NOT mutate state inside runWhenIdle.

3. Reach for the experimental waitUntilExactlyOneExists / friends for "wait until N nodes match"
kotlin
@OptIn(ExperimentalTestApi::class)
@Test fun snackbarAppears() {
    rule.setContent { /* … triggers a snackbar after 200 ms … */ }
    rule.onNodeWithTag("trigger").performClick()
    rule.waitUntilExactlyOneExists(hasTestTag("snackbar"), timeoutMillis = 2_000)
    rule.onNodeWithTag("snackbar").assertTextEquals("Saved")
}

The default 1000 ms timeout (ComposeUiTest.kt:278, ComposeUiTest.kt:301, ComposeUiTest.kt:321, ComposeUiTest.kt:334) is often too tight for snackbars and bottom-sheet animations. Lift it explicitly per call.

Show full SKILL.md (622 more words)Show less
4. Reach for waitUntil { … } only for non-Compose conditions
kotlin
// SnackbarHostTest.kt:77-88
val job = scope.launch {
    hostState.showSnackbar("1")
    Truth.assertThat(resultedInvocation).isEqualTo("1")
    hostState.showSnackbar("2")
    Truth.assertThat(resultedInvocation).isEqualTo("12")
    hostState.showSnackbar("3")
    Truth.assertThat(resultedInvocation).isEqualTo("123")
}
rule.waitUntil { job.isCompleted }

Job.isCompleted is not Compose state; mainClock.advanceTimeUntil { job.isCompleted } would loop and time out because no amount of clock advancement makes the Job complete on its own.

5. Register an IdlingResource for genuine external async work
kotlin
class HttpIdlingResource(private val client: OkHttpClient) : IdlingResource {
    override val isIdleNow: Boolean
        get() = client.dispatcher.runningCallsCount() == 0
    override fun getDiagnosticMessageIfBusy(): String? =
        "${client.dispatcher.runningCallsCount()} HTTP calls still in flight"
}

@Before fun setUp() { rule.registerIdlingResource(HttpIdlingResource(client)) }
@After  fun tearDown() { rule.unregisterIdlingResource(HttpIdlingResource(client)) }

After registration, every waitForIdle (and the implicit waitForIdle inside every node query) will block until isIdleNow == true. MUST keep isIdleNow lightweight — it is called from the main thread (IdlingResource.kt:38-46).

Patterns

Pattern: WRONG — Thread.sleep to wait for a node
kotlin
// WRONG
@Test fun successScreen() {
    rule.setContent { App() }
    rule.onNodeWithTag("login").performClick()
    Thread.sleep(2_000)
    rule.onNodeWithTag("dashboard").assertExists()
}
// WRONG because: Thread.sleep is unrelated to Compose's synchronization. It either over-
// waits (slowing the test) or under-waits (flake when CI is slow). Compose's own
// IdlingResource is already wired; just wait on the node directly.
kotlin
// RIGHT
@OptIn(ExperimentalTestApi::class)
@Test fun successScreen() {
    rule.setContent { App() }
    rule.onNodeWithTag("login").performClick()
    rule.waitUntilExactlyOneExists(hasTestTag("dashboard"), timeoutMillis = 2_000)
    rule.onNodeWithTag("dashboard").assertExists()
}
Pattern: WRONG — waitUntil for a Compose-state-observable condition
kotlin
// WRONG
rule.waitUntil(timeoutMillis = 5_000) { state.value == Phase.Done }
// WRONG because: timeout is measured in wall clock and each iteration burns ~10 ms
// of real time on Thread.sleep(10) (ComposeUiTest.android.kt:899-902). For a Compose
// state condition, advanceTimeUntil drives the test clock and is deterministic.
kotlin
// RIGHT
rule.mainClock.advanceTimeUntil(timeoutMillis = 5_000) { state.value == Phase.Done }

(skydoves hot take #4.)

Pattern: WRONG — reading state from the test thread
kotlin
// WRONG
val i = state.firstVisibleItemIndex
assertEquals(2, i)
// WRONG because: the test thread races the recomposer. The read may observe a half-applied
// snapshot, especially right after a click or scroll. Compose's docs are explicit: state
// reads from outside the UI thread are not synchronized.
kotlin
// RIGHT
val i = rule.runOnIdle { state.firstVisibleItemIndex }
assertEquals(2, i)

(skydoves hot take #5.)

Pattern: hasPendingWork() — passive check, paused-clock only
kotlin
rule.mainClock.autoAdvance = false
rule.setContent { /* … */ }
rule.mainClock.advanceTimeByFrame()
rule.runOnUiThread { trigger = true }
assertTrue(rule.hasPendingWork())              // recomposition queued, no frame yet
rule.mainClock.advanceTimeByFrame()
assertFalse(rule.hasPendingWork())             // frame applied

From ComposeUiTest.kt:251-256:

If autoAdvance is true, the testing framework continuously processes pending work. In that scenario, calling this method acts as a momentary snapshot and will generally return false. It may briefly return true if work is queued but the framework hasn't auto-advanced yet, making the result fleeting and unreliable for driving test logic.

MUST NOT drive test logic off hasPendingWork() while autoAdvance == true.

Pattern: lift the master idling timeout for slow CI
kotlin
@Before fun setUp() {
    IdlingPolicies.setMasterPolicyTimeout(60, TimeUnit.SECONDS)
}

Apply when waitForIdle (not waitUntil) times out on CI but passes locally. Both EspressoLink and RobolectricIdlingStrategy honor this policy.

Mandatory rules

  • MUST prefer mainClock.advanceTimeUntil { state } over rule.waitUntil { state } whenever the awaited condition is a Compose-state read (skydoves hot take #4).
  • MUST wrap state reads from the test thread in runOnIdle { … } or runOnUiThread { … }. Direct state.value reads from the test thread race the recomposer (skydoves hot take #5).
  • MUST call runOnUiThread (not runOnIdle) for state mutations under a paused clock (autoAdvance = false); runOnIdle's implicit waitForIdle is wrong for that mode. See ../testing-animations-deterministically/SKILL.md.
  • MUST use runWhenIdle { … } for assert-only blocks that do many node reads — it suppresses the redundant per-query waitForIdle. MUST NOT mutate state inside.
  • MUST prefer waitUntilExactlyOneExists / waitUntilAtLeastOneExists / waitUntilDoesNotExist over hand-rolled waitUntil { onAllNodes(...).fetchSemanticsNodes().isNotEmpty() }. They are clearer and cite a conditionDescription in the timeout message.
  • MUST NOT treat hasPendingWork() as actionable while autoAdvance == true. It is a passive snapshot; reliable only with the clock paused (ComposeUiTest.kt:251-256).
  • MUST NOT use Thread.sleep to wait for state to settle. Replace with waitUntil*, mainClock.advanceTimeUntil, or an IdlingResource (skydoves hot take #7). The one legitimate Thread.sleep is on the RenderThread for ripple/screenshot tests.
  • PREFERRED: an IdlingResource per genuine external async source (HTTP, DB, an external counter), registered in @Before and unregistered in @After. Override getDiagnosticMessageIfBusy() so timeout messages are useful.
  • PREFERRED: IdlingPolicies.setMasterPolicyTimeout(...) in @Before for CI flakes due to slow agents — uniformly lifts both Espresso and Robolectric.

Verification

  • No Thread.sleep exists in any test method except the documented RenderThread/screenshot exceptions.
  • Every state read from the test thread is wrapped in runOnIdle { … } (search: grep -nP 'state\.\w+' src/androidTest).
  • Every waitUntil { state.value … } for a Compose-state condition has been migrated to mainClock.advanceTimeUntil { state.value … }.
  • Every "wait for node" path uses waitUntilExactlyOneExists / waitUntilAtLeastOneExists / waitUntilDoesNotExist rather than a hand-rolled waitUntil over fetchSemanticsNodes().
  • Every external async source has a registered IdlingResource, registered in @Before and unregistered in @After.
  • No call site relies on hasPendingWork() while autoAdvance == true.
  • CI run completes 50 iterations without an AndroidComposeUiTestTimeoutException or ComposeTimeoutException flake.

References

  • Android Developers — Compose testing: https://developer.android.com/develop/ui/compose/testing
  • Android Developers — Compose testing cheat sheet: https://developer.android.com/develop/ui/compose/testing-cheatsheet
  • Espresso IdlingPolicies reference: https://developer.android.com/reference/androidx/test/espresso/IdlingPolicies
  • compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt:174-260 — waitForIdle, awaitIdle, waitUntil, runOnIdle, runOnUiThread, runWhenIdle, awaitAndRunWhenIdle, hasPendingWork.
  • compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt:275-335 — the experimental waitUntilNodeCount / waitUntilExactlyOneExists / waitUntilAtLeastOneExists / waitUntilDoesNotExist extensions.
  • compose/ui/ui-test/src/jvmAndAndroidMain/kotlin/androidx/compose/ui/test/IdlingResource.kt — the public IdlingResource interface.
  • compose/ui/ui-test/src/jvmAndAndroidMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvmAndAndroid.kt — the registry that aggregates user-registered resources + Compose's own.
  • compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeIdlingResource.android.kt — Compose's automatically-registered resource; 100-frame cap at line 109.
  • compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/EspressoLink.android.kt — the androidx.test.espresso.IdlingResource bridge.
  • compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt:885-908 — waitUntil's 10 ms wall-clock poll loop.
  • compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidSynchronization.android.kt — runOnUiThread posts a FutureTask via Instrumentation.runOnMainSync.
  • compose/material3/material3/src/androidDeviceTest/kotlin/androidx/compose/material3/SnackbarHostTest.kt:77-88 — canonical rule.waitUntil { job.isCompleted }.
  • Sibling skill: ../controlling-the-test-clock/SKILL.md — MainTestClock, frame model, advanceTimeUntil.
  • Sibling skill: ../testing-animations-deterministically/SKILL.md — paused-clock animation tests.
  • Sibling skill: ../../patterns/structuring-a-compose-test/SKILL.md — JUnit lifecycle around idle waits.
  • Sibling skill: ../../debug/printing-the-semantics-tree/SKILL.md — when "node not found" turns out to be a finder issue, not an async issue.

© skydoves, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in compose/synchronization/synchronizing-with-idle of skydoves/android-testing-skills.

Open the folder on GitHubat commit 8665ed5

Compare with similar skills

Synchronizing With Idle next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Synchronizing With Idle compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Synchronizing With Idle this skillskydoves/android-testing-skills333—~5.5kAutomated safety check: PassApache-2.0
MAUI UI Test Writerdotnet/maui23k—~3kAutomated safety check: PassMIT
Mobile App Testingsecondsky/claude-skills227—~643Automated safety check: PassMIT
iOS Testing Patternsconorluddy/xclaude-plugin183—~4.5kAutomated safety check: PassMIT
BrowserStack Live Testinghandsontable/handsontable22k—~950Automated safety check: PassCustom licence
iOS Accessibility Testingconorluddy/xclaude-plugin183—~5kAutomated safety check: PassMIT

Similar skills

  • Official

    Writes UI tests that reproduce a GitHub issue in .NET MAUI and keeps iterating until the tests actually fail, proving they catch the bug.

    23k GitHub stars~3k tokensUpdated today
    Testing & QAAuto-check passed
  • Mobile App Testing

    secondsky/claude-skills

    Mobile app testing with unit tests, UI automation, performance testing.

    227 GitHub stars~643 tokensUpdated 10 days ago
    Testing & QAAuto-check passed
  • iOS Testing Patterns

    conorluddy/xclaude-plugin

    XCTest and XCUITest execution workflows and flaky test detection patterns

    183 GitHub stars~4.5k tokensUpdated 26 days ago
    Testing & QAAuto-check passed
  • BrowserStack Live Testing

    handsontable/handsontable

    Opens a live BrowserStack session on a real Android, iOS or desktop browser for a local or public URL, tunneling localhost through Cloudflare when needed.

    22k GitHub stars~950 tokensUpdated today
    Testing & QAAuto-check passed
  • iOS Accessibility Testing

    conorluddy/xclaude-plugin

    Guides WCAG 2.1 and VoiceOver accessibility testing for iOS apps, working from the accessibility tree and not from screenshots.

    183 GitHub stars~5k tokensUpdated 26 days ago
    Testing & QAAuto-check passed
  • SoloPi AI Control

    alipay/SoloPi

    Drives Android devices through SoloPi's typed command line to record, replay and verify app behavior, with device pools and signed on-device decision models.

    6.3k GitHub stars~3.6k tokensUpdated 1 mo ago
    Testing & QAAuto-check passed

More from skydoves/android-testing-skills

All 50 skills in this repo
  • Asserting Bounds And Dimensions

    skydoves/android-testing-skills

    A skill your agent uses to verify Compose layout measurements from a UI test using assertWidthIsEqualTo, assertHeightIsEqualTo, assertWidthIsAtLeast, assertHeightIsAtLeast…

    333 GitHub stars~3.3k tokensUpdated 4 mo ago
    Auto-check passed
  • Asserting Node State And Text

    skydoves/android-testing-skills

    A skill your agent uses to verify a Compose semantics node's properties from a UI test using assertExists, assertDoesNotExist, assertIsDisplayed, assertIsNotDisplayed, assertIsDeactivated…

    333 GitHub stars~3.6k tokensUpdated 4 mo ago
    Auto-check passed
  • Capturing Preview Screenshots In CI

    skydoves/android-testing-skills

    A skill your agent uses to render every Jetpack Compose @Preview as a screenshot on a real Android device or emulator and publish a browsable HTML catalog from CI.

    333 GitHub stars~4.5k tokensUpdated 4 mo ago
    Auto-check: notes
  • Capturing Screenshots And Screenrecord

    skydoves/android-testing-skills

    A skill your agent uses to capture visual artefacts from a device for test failures, golden image generation, QA repro, and demo videos.

    333 GitHub stars~3.7k tokensUpdated 4 mo ago
    Auto-check passed
  • Choosing Test Rule Vs Runtest

    skydoves/android-testing-skills

    A skill your agent uses to pick the correct Compose UI test entry point.

    333 GitHub stars~4.5k tokensUpdated 4 mo ago
    Auto-check passed
  • Choosing What To Test

    skydoves/android-testing-skills

    A skill your agent uses to pick which behaviors to cover in an Android test suite using Google's five-category state vocabulary plus the explicit "what NOT to test" list from…

    333 GitHub stars~4.6k tokensUpdated 4 mo ago
    Auto-check passed

Questions about Synchronizing With Idle

What does Synchronizing With Idle do?

A skill your agent uses to choose the right idle-synchronization primitive in Compose UI tests — waitForIdle, awaitIdle, waitUntil(conditionDescription, timeoutMillis, condition)…. Synchronizing With Idle is an agent skill from skydoves/android-testing-skills. Use this skill to choose the right idle-synchronization primitive in Compose UI tests — waitForIdle, awaitIdle, waitUntil(conditionDescription, timeoutMillis, condition), waitUntilNodeCount, waitUntilExactlyOneExists, waitUntilAtLeastOneExists, waitUntilDoesNotExist, runOnIdle, runOnUiThread, runWhenIdle, awaitAndRunWhenIdle, hasPendingWork, and IdlingResource.

When should I use Synchronizing With Idle?

Synchronizing With Idle fits situations like: choose the right idle-synchronization primitive in Compose UI tests — waitForIdle; waitUntil(conditionDescription; waitUntilNodeCount; waitUntilExactlyOneExists.

How do I install Synchronizing With Idle in Claude Code?

Run `npx skills add skydoves/android-testing-skills --skill synchronizing-with-idle -a claude-code`. Or copy the skill folder (compose/synchronization/synchronizing-with-idle in skydoves/android-testing-skills) into .claude/skills/synchronizing-with-idle in your project. Claude Code loads it when a task matches its description.

How do I install Synchronizing With Idle in Codex?

Run `npx skills add skydoves/android-testing-skills --skill synchronizing-with-idle -a codex`. Or copy the skill folder (compose/synchronization/synchronizing-with-idle in skydoves/android-testing-skills) into .agents/skills/synchronizing-with-idle in your project. Codex loads it when a task matches its description.

Can I use Synchronizing With Idle in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add skydoves/android-testing-skills --skill synchronizing-with-idle -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/synchronizing-with-idle, .gemini/skills/synchronizing-with-idle, .github/skills/synchronizing-with-idle and .opencode/skills/synchronizing-with-idle in your project.

What does Synchronizing With Idle need to run?

SKILL.md names no scripts, command-line tools or credentials: Synchronizing With Idle is instructions for the agent only.

Does Synchronizing With Idle access the network?

SKILL.md names 1 domain. As links in the text: developer.android.com. This is read from the text; nothing was executed.

Is Synchronizing With Idle safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Synchronizing With Idle use?

Synchronizing With Idle is published under the Apache-2.0 licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Synchronizing With Idle use?

About 5.5k tokens (SKILL.md is roughly 22k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Synchronizing With Idle?

Skills that share tags, products or a category with Synchronizing With Idle: MAUI UI Test Writer (dotnet/maui, 23k stars), Mobile App Testing (secondsky/claude-skills, 227 stars), iOS Testing Patterns (conorluddy/xclaude-plugin, 183 stars) and BrowserStack Live Testing (handsontable/handsontable, 22k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Synchronizing With Idle?

skydoves (a GitHub user) maintains it in skydoves/android-testing-skills, which has 333 GitHub stars. The repository holds 50 skills in this directory. The repository was last updated on May 25, 2026.

Source: skydoves/android-testing-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.