Accessible Mobile Developer
FerroxLabs/wayland
Mobile accessibility development expertise covering iOS VoiceOver and Android TalkBack integration, accessibility APIs for SwiftUI and Jetpack Compose, touch target sizing, gesture alternatives…
A skill your agent uses to choose between host (Robolectric/JVM) and device (instrumentation) tests for Jetpack Compose, and to configure each correctly.
$ npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install skydoves/android-testing-skills setting-up-host-vs-device-tests --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/compose/setup/setting-up-host-vs-device-tests .claude/skills/setting-up-host-vs-device-tests && rm -rf skills-srcUse ~/.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/
Install the "setting-up-host-vs-device-tests" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/compose/setup/setting-up-host-vs-device-tests into .claude/skills/setting-up-host-vs-device-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "setting-up-host-vs-device-tests", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/skydoves/android-testing-skills/tree/main/compose/setup/setting-up-host-vs-device-testsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install skydoves/android-testing-skills setting-up-host-vs-device-tests --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/compose/setup/setting-up-host-vs-device-tests .agents/skills/setting-up-host-vs-device-tests && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "setting-up-host-vs-device-tests" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/compose/setup/setting-up-host-vs-device-tests into .agents/skills/setting-up-host-vs-device-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "setting-up-host-vs-device-tests", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install skydoves/android-testing-skills setting-up-host-vs-device-tests --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/compose/setup/setting-up-host-vs-device-tests .cursor/skills/setting-up-host-vs-device-tests && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "setting-up-host-vs-device-tests" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/compose/setup/setting-up-host-vs-device-tests into .cursor/skills/setting-up-host-vs-device-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "setting-up-host-vs-device-tests", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/skydoves/android-testing-skills.git --path compose/setup/setting-up-host-vs-device-tests--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install skydoves/android-testing-skills setting-up-host-vs-device-tests --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/compose/setup/setting-up-host-vs-device-tests .gemini/skills/setting-up-host-vs-device-tests && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "setting-up-host-vs-device-tests" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/compose/setup/setting-up-host-vs-device-tests into .gemini/skills/setting-up-host-vs-device-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "setting-up-host-vs-device-tests", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install skydoves/android-testing-skills setting-up-host-vs-device-testsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/compose/setup/setting-up-host-vs-device-tests .github/skills/setting-up-host-vs-device-tests && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "setting-up-host-vs-device-tests" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/compose/setup/setting-up-host-vs-device-tests into .github/skills/setting-up-host-vs-device-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "setting-up-host-vs-device-tests", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install skydoves/android-testing-skills setting-up-host-vs-device-tests --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/skydoves/android-testing-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/compose/setup/setting-up-host-vs-device-tests .opencode/skills/setting-up-host-vs-device-tests && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "setting-up-host-vs-device-tests" agent skill from https://github.com/skydoves/android-testing-skills/tree/main/compose/setup/setting-up-host-vs-device-tests into .opencode/skills/setting-up-host-vs-device-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "setting-up-host-vs-device-tests", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
setting-up-host-vs-device-testsA skill your agent uses to choose between host (Robolectric/JVM) and device (instrumentation) tests for Jetpack Compose, and to configure each correctly.
Setting Up Host Vs Device Tests is an agent skill from skydoves/android-testing-skills. Use this skill to choose between host (Robolectric/JVM) and device (instrumentation) tests for Jetpack Compose, and to configure each correctly. Covers the androidHostTest (a.k.a. src/test/) vs androidDeviceTest (a.k.a. src/androidTest/) source set split, what each flavor can and cannot drive (RenderThread, screenshots, accessibility), the @RunWith(AndroidJUnit4::class) @Config(minSdk = 23) setup for Robolectric, and why Thread.sleep is forbidden everywhere except screenshot tests waiting on the RenderThread. Use…
Its SKILL.md is about 4.8k 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 Mobile, covering Android development and Accessibility. It works with Jetpack Compose and Android. 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.
Read from SKILL.md and the folder at commit 8665ed5. It shows what the files ask for, not the result of running them.
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.
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.
Links to these hosts (documentation or services it may open):
developer.android.comrobolectric.orgFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Setting Up Host Vs Device Tests loads about 4.8k tokens when it runs. Until then it costs about 201 tokens; SKILL.md has 1,373 words of instructions outside code blocks.
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.
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.
The full file from skydoves/android-testing-skills at commit 8665ed5, republished under its Apache-2.0 licence (© skydoves). 1,373 words, ~4,822 tokens.
.claude/skills/setting-up-host-vs-device-tests/SKILL.md (or your agent's skills folder).Compose tests run unchanged on either Robolectric (JVM, fast, no emulator) or on a real/virtual device (full Android stack, RenderThread, accessibility). The same runComposeUiTest { setContent { … } } block compiles in both — only the underlying Looper and Choreographer differ. This skill encodes which flavor each test should live in, the Robolectric class skeleton, and the one legitimate Thread.sleep exception (screenshot tests waiting on the RenderThread).
Build.FINGERPRINT == "robolectric" warnings from enableAccessibilityChecks(...).pressInteraction test renders no ripple on Robolectric and the user is debugging why.Thread.sleep(1000) to "wait for an animation" and is asking why it is flaky.androidDeviceTest / androidHostTest source sets (used in androidx itself)../configuring-test-dependencies/SKILL.md.createComposeRule() and runComposeUiTest { } — see ./choosing-test-rule-vs-runtest/SKILL.md.../../synchronization/synchronizing-with-idle/SKILL.md and ../../synchronization/testing-animations-deterministically/SKILL.md.androidx.compose.ui:ui-test, ui-test-junit4, and ui-test-manifest on the correct configurations for the chosen flavor — see ./configuring-test-dependencies/SKILL.md.org.robolectric:robolectric on testImplementation, testOptions { unitTests.isIncludeAndroidResources = true } in the Android block.androidx.test.runner.AndroidJUnitRunner (or a Hilt subclass) as the testInstrumentationRunner.MainTestClock semantics — see the synchronization skill set.1. Decide the flavor by capability, not by speed. The matrix:
| Capability | Host (Robolectric, src/test/) | Device (instrumentation, src/androidTest/) |
|---|---|---|
| Recomposition + state changes | Works | Works |
| Layout + measurement | Works | Works |
Touch input via performTouchInput | Works (synthetic events) | Works (real input pipeline) |
Modifier.indication ripples | No (RenderThread not driven) | Works |
Screenshot capture (captureToImage) | No (no real surface) | Works |
enableAccessibilityChecks(...) | Inconclusive — ComposeUiTestExt.android.kt:50-53 logs Log.w("ComposeUiTest", "Accessibility checks are currently not supported by Robolectric") and still installs the validator, but Robolectric does not faithfully drive accessibility services (b/332778271). Treat passing as inconclusive. | Works (@RequiresApi(34)) |
| Gesture detectors needing real-time clock changes (double-tap, long-press timing) | Works only with manual mainClock.advanceTimeBy(...) | Works automatically |
Activity lifecycle (real onPause/onResume) | Approximated | Real |
| Min SDK | 23 (internal const val RobolectricMinSdk = 23 from compose/ui/ui-test/src/androidHostTest/.../Constants.kt) | The module's minSdk |
| Speed (rough order) | Seconds | Tens of seconds + emulator boot |
| CI footprint | JVM only | Emulator service or Firebase Test Lab |
Choose host for logic/recomposition/finder coverage. Choose device for anything that touches the RenderThread, real animations involving Modifier.indication, screenshot golden tests, or accessibility validation.
2. Place files in the matching source set. The androidx convention is:
src/
├── androidHostTest/ # = test/ — Robolectric on JVM
│ └── kotlin/.../FooTest.kt
├── androidDeviceTest/ # = androidTest/ — instrumentation
│ └── kotlin/.../FooScreenshotTest.kt
└── androidCommonTest/ # helpers shared by both
└── kotlin/.../FooTestHelpers.ktIn a typical app module without KMP source sets, the equivalent is src/test/ (host) and src/androidTest/ (device). The same runComposeUiTest { setContent { … } } body compiles unchanged in both — only the Looper/Choreographer differs at runtime.
3. Configure the host test class skeleton. Copy the canonical androidx pattern from compose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt:
@RunWith(AndroidJUnit4::class)
@Config(minSdk = 23)
@OptIn(ExperimentalTestApi::class)
class MyHostTest {
@Before
fun setup() {
// capture the master timeout so we can restore it afterwards
masterTimeout = IdlingPolicies.getMasterIdlingPolicy()
}
@After
fun tearDown() {
masterTimeout?.let {
IdlingPolicies.setMasterPolicyTimeout(it.idleTimeout, it.idleTimeoutUnit)
}
}
@Test
fun stateChange() = runComposeUiTest {
setContent { ClickCounter() }
onNodeWithText("Click me").performClick()
onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 1")
}
private var masterTimeout: IdlingPolicy? = null
} AndroidJUnit4::class delegates to Robolectric on the JVM and to AndroidJUnit4ClassRunner on a device — making the same class portable. @RunWith(RobolectricTestRunner::class) also works but ties the class to host-only.
@RunWith(AndroidJUnit4::class)
class MyDeviceTest {
@get:Rule val rule = createComposeRule() // or v2 import — see ./choosing-test-rule-vs-runtest/
@Test
fun stateChange() {
rule.setContent { ClickCounter() }
rule.onNodeWithText("Click me").performClick()
rule.onNodeWithText("Click count", substring = true).assertTextEquals("Click count: 1")
}
} 5. Compose mainClock.advanceTimeBy(...) for any animation/gesture-detection test. On Robolectric, gesture detectors (double-tap, long-press) rely on the test clock advancing — they do NOT receive real wall-clock ticks. The RobolectricComposeTest.kt source quotes this directly: gesture detectors require manual mainClock.advanceTimeBy(...) because they detect events through clock changes. Same applies to any animateFloatAsState driven by the recomposer's frame clock — see ../../synchronization/testing-animations-deterministically/SKILL.md.
6. Identify the one legitimate Thread.sleep use case: screenshot tests waiting on the RenderThread. Compose's IdlingResource aggregates the recomposer, snapshot, and frame-clock awaiters, but the RenderThread is outside that aggregation. Ripple animations (Modifier.indication) and any draw-time animation owned by the platform render pipeline cannot be waited on through mainClock.advanceTimeBy or waitForIdle. From compose/material3/material3/src/androidDeviceTest/.../ToggleButtonScreenshotTest.kt:115-123:
rule.mainClock.autoAdvance = false
rule.onNode(isToggleable()).performTouchInput { down(center) }
rule.mainClock.advanceTimeByFrame()
rule.waitForIdle() // wait for measure
rule.mainClock.advanceTimeBy(milliseconds = 200)
// Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
// synchronization. Instead just wait until after the ripples are finished animating.
Thread.sleep(300)
assertAgainstGolden("toggleButton_lightTheme_defaultToPressed") This is the only case. Anywhere else, Thread.sleep is a smell — it desyncs from MainTestClock and produces flakes that wear the developer down. Use mainClock.advanceTimeBy(durationMs) (test clock) for animations or rule.waitUntil(timeoutMillis = …) { … } (wall clock) for external state.
7. Enforce: NO Thread.sleep in host tests. Host tests cannot drive the RenderThread anyway, so the screenshot exception does not apply. Replace every Thread.sleep(N) in a host test with mainClock.advanceTimeBy(N) (animation case) or rule.waitUntil { … } (external state).
8. Place screenshot, ripple, and accessibility tests in the device source set. The host runner cannot satisfy them. Specifically:
captureToImage() requires a real Surface. Host returns no pixel data.Modifier.indication draws on the RenderThread — no observable state on host.enableAccessibilityChecks(...) is @RequiresApi(34). On Robolectric, both extension implementations check Build.FINGERPRINT.lowercase() == "robolectric", emit a Log.w warning, AND still install the validator — but Robolectric does not faithfully drive accessibility services, so any pass is inconclusive (b/332778271). Run accessibility checks on a real device for trustworthy results.9. Place fast logic/recomposition/state tests in the host source set. Examples that thrive on Robolectric:
StateRestorationTester.emulateSavedInstanceStateRestore() flows.assertTextEquals, assertIsOn).mainClock.advanceTimeBy(durationMs) driven animation snapshots that don't need rasterization.// WRONG — placed in src/test/ (Robolectric)
@RunWith(AndroidJUnit4::class)
@Config(minSdk = 23)
class ButtonScreenshotTest {
@Test
fun pressed() = runComposeUiTest {
setContent { Button(onClick = {}) { Text("OK") } }
onNode(hasText("OK")).performTouchInput { down(center) }
onNode(hasText("OK")).captureToImage().assertAgainstGolden("pressed")
}
}
// WRONG because: Robolectric has no real Surface, so captureToImage() returns black/empty
// pixels and the ripple from Modifier.indication never draws (RenderThread is not driven).
// The test will produce a false-positive pass or a meaningless golden file.// RIGHT — placed in src/androidTest/ (device)
@RunWith(AndroidJUnit4::class)
class ButtonScreenshotTest {
@get:Rule val rule = createComposeRule()
@Test
fun pressed() {
rule.setContent { Button(onClick = {}) { Text("OK") } }
rule.mainClock.autoAdvance = false
rule.onNode(hasText("OK")).performTouchInput { down(center) }
rule.mainClock.advanceTimeByFrame()
rule.waitForIdle()
rule.mainClock.advanceTimeBy(milliseconds = 200)
Thread.sleep(300) // legitimate — RenderThread for ripples
rule.onNode(hasText("OK")).captureToImage().assertAgainstGolden("pressed")
}
}Thread.sleep in a host animation test// WRONG — host test
@Test
fun fadeIn() = runComposeUiTest {
val target = mutableStateOf(0f)
setContent { Box(Modifier.alpha(animateFloatAsState(target.value).value)) }
target.value = 1f
Thread.sleep(500) // smell
onNode(isRoot()).captureToImage() // also broken on host
}
// WRONG because: Thread.sleep desyncs from MainTestClock. The test clock has not advanced,
// the recomposer has not run withFrameNanos, and the animation made zero observable progress.// RIGHT — host test (recomposition only — no pixel capture)
@Test
fun fadeIn() = runComposeUiTest {
mainClock.autoAdvance = false
val target = mutableStateOf(0f)
setContent { Box(Modifier.alpha(animateFloatAsState(target.value).value).testTag("box")) }
runOnUiThread { target.value = 1f }
mainClock.advanceTimeByFrame() // kick off
mainClock.advanceTimeBy(durationMillis = 500) // step forward by test clock
onNodeWithTag("box").assertIsDisplayed()
}For pixel verification of the fade, move the test to the device source set and use captureToImage().
// WRONG — host
@Test
fun submitIsLabelled() = runComposeUiTest {
enableAccessibilityChecks() // logs warning, runs no checks
setContent { Submit() }
onNodeWithTag("submit").performClick()
}
// WRONG because: both ui-test-accessibility and ui-test-junit4-accessibility detect
// Build.FINGERPRINT.lowercase() == "robolectric", emit a Log.w, AND still install the
// validator — but Robolectric does not faithfully drive accessibility services, so the
// result is inconclusive (b/332778271). Run accessibility checks on a real device.// RIGHT — device
@RunWith(AndroidJUnit4::class)
class SubmitA11yTest {
@get:Rule val rule = createAndroidComposeRule<MainActivity>()
@Test fun submitIsLabelled() {
rule.enableAccessibilityChecks()
rule.setContent { Submit() }
rule.onNodeWithTag("submit").performClick() // accessibility checks run automatically
}
}The same runComposeUiTest { ... } body can compile in both source sets when the test only uses the common API. Place the body in androidCommonTest/ and create two thin wrappers — one in androidHostTest/ (with @RunWith(AndroidJUnit4::class) @Config(minSdk = 23)), one in androidDeviceTest/ (no @Config). This is how androidx's own ui-test module exercises both flavors without code duplication. For an app module without KMP source sets, prefer keeping the body inline in whichever flavor is appropriate.
captureToImage / ripple / Modifier.indication-dependent tests in the host source set. The RenderThread is not driven; the test is meaningless even when it appears to pass.enableAccessibilityChecks(...) results from a host test. Both extensions log a warning under Build.FINGERPRINT.lowercase() == "robolectric" and still install the validator, but Robolectric does not faithfully drive accessibility services so the result is inconclusive — run accessibility checks on a real device API 34+.Thread.sleep in a host test under any circumstance. The screenshot exception does not apply (host has no RenderThread).Thread.sleep in a device test except when waiting on the RenderThread for ripple/screenshot golden capture. Skydoves hot take #7: Thread.sleep is a smell. Anywhere else, replace it with mainClock.advanceTimeBy(durationMs) (test clock) or rule.waitUntil(timeoutMillis) { ... } (wall clock) — see ../../synchronization/synchronizing-with-idle/SKILL.md.@Config(minSdk = 23) or higher. Lower SDK levels are not supported by androidx's host test infrastructure (internal const val RobolectricMinSdk = 23).mainClock.advanceTimeBy(...). Robolectric does not advance the test clock from real-time signals.testOptions { unitTests.isIncludeAndroidResources = true } in the module's android { } block so Robolectric can read merged resources during host Compose tests.androidCommonTest/ (KMP) or a helper function the two flavors call../gradlew :<module>:testDebugUnitTest runs and passes for all host tests; ./gradlew :<module>:connectedDebugAndroidTest runs and passes for all device tests.Thread.sleep appears in any host test (grep -r "Thread.sleep" src/test src/androidHostTest).captureToImage, no enableAccessibilityChecks, and no test that depends on Modifier.indication ripples appears in the host source set.Thread.sleep in the device source set has a comment explaining the RenderThread / screenshot rationale.@RunWith(AndroidJUnit4::class) (or @RunWith(RobolectricTestRunner::class)) AND @Config(minSdk = 23) (or higher).mainClock.autoAdvance = false and call mainClock.advanceTimeBy(durationMs) explicitly.compose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/RobolectricComposeTest.kt — canonical host class skeleton, @RunWith(AndroidJUnit4::class) @Config(minSdk = RobolectricMinSdk), gesture-detector clock comment, IdlingPolicies setup/teardown.compose/ui/ui-test/src/androidHostTest/kotlin/androidx/compose/ui/test/Constants.kt — internal const val RobolectricMinSdk = 23.compose/material3/material3/src/androidDeviceTest/kotlin/androidx/compose/material3/ToggleButtonScreenshotTest.kt:115-123 — the canonical legitimate Thread.sleep(300) waiting on the RenderThread for ripple completion before assertAgainstGolden.compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/RobolectricIdlingStrategy.android.kt — Robolectric idling strategy that drives the host idle loop.compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeIdlingResource.android.kt — the recomposer + snapshot + frame-clock aggregator (caps at 100 frames/call); does NOT include the RenderThread, which is why ripple tests need Thread.sleep.© 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
Just SKILL.md in compose/setup/setting-up-host-vs-device-tests of skydoves/android-testing-skills.
Open the folder on GitHubat commit 8665ed5
Setting Up Host Vs Device Tests 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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Setting Up Host Vs Device Tests this skillskydoves/android-testing-skills | 333 | — | ~4.8k | Automated safety check: Pass | Apache-2.0 | |
| Accessible Mobile DeveloperFerroxLabs/wayland | 608 | — | ~3.6k | Automated safety check: Pass | Apache-2.0 | |
| Compose Agenthamen/compose_skill | 373 | — | ~4k | Automated safety check: Notes | MIT | |
| Android App FactoryJasonColapietro/suede-creator-skills | 127 | — | ~2.6k | Automated safety check: Pass | MIT | |
| Android AccessibilityMoustachauve/WLED-Android | 169 | 3 repos | ~471 | Automated safety check: Pass | Apache-2.0 | |
| Compose Multiplatform Patternsmonta-app/ocpp-emulator | 180 | 5 repos | ~2k | Automated safety check: Pass | Apache-2.0 |
FerroxLabs/wayland
Mobile accessibility development expertise covering iOS VoiceOver and Android TalkBack integration, accessibility APIs for SwiftUI and Jetpack Compose, touch target sizing, gesture alternatives…
hamen/compose_skill
Helps AI coding assistants write modern Jetpack Compose: correct state, side effects, performance-aware modifiers, Navigation 3, Paging 3 in Compose, coroutines on lifecycle, animations, UI tests…
JasonColapietro/suede-creator-skills
Takes a native Android app from product idea to Google Play release, covering Compose architecture, policy checks, privacy, billing, testing, signing and rollout.
Moustachauve/WLED-Android
Expert checklist and prompts for auditing and fixing Android accessibility issues, especially in Jetpack Compose.
monta-app/ocpp-emulator
Compose Multiplatform and Jetpack Compose patterns for KMP projects — state management, navigation, theming, performance, and platform-specific UI.
arindamxd/camerax-android
A skill your agent uses to integrate the Jetpack Compose Styles API into an Android project.
skydoves/android-testing-skills
A skill your agent uses to verify Compose layout measurements from a UI test using assertWidthIsEqualTo, assertHeightIsEqualTo, assertWidthIsAtLeast, assertHeightIsAtLeast…
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…
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.
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.
skydoves/android-testing-skills
A skill your agent uses to pick the correct Compose UI test entry point.
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…
Works with
Categories
A skill your agent uses to choose between host (Robolectric/JVM) and device (instrumentation) tests for Jetpack Compose, and to configure each correctly. Setting Up Host Vs Device Tests is an agent skill from skydoves/android-testing-skills. Use this skill to choose between host (Robolectric/JVM) and device (instrumentation) tests for Jetpack Compose, and to configure each correctly.
Setting Up Host Vs Device Tests fits situations like: choose between host (Robolectric/JVM) and device (instrumentation) tests for Jetpack Compose; to configure each correctly; the user reports tests pass locally but fail on CI; asks Robolectric vs instrumentation.
Run `npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a claude-code`. Or copy the skill folder (compose/setup/setting-up-host-vs-device-tests in skydoves/android-testing-skills) into .claude/skills/setting-up-host-vs-device-tests in your project. Claude Code loads it when a task matches its description.
Run `npx skills add skydoves/android-testing-skills --skill setting-up-host-vs-device-tests -a codex`. Or copy the skill folder (compose/setup/setting-up-host-vs-device-tests in skydoves/android-testing-skills) into .agents/skills/setting-up-host-vs-device-tests in your project. Codex loads it when a task matches its description.
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 setting-up-host-vs-device-tests -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/setting-up-host-vs-device-tests, .gemini/skills/setting-up-host-vs-device-tests, .github/skills/setting-up-host-vs-device-tests and .opencode/skills/setting-up-host-vs-device-tests in your project.
SKILL.md names no scripts, command-line tools or credentials: Setting Up Host Vs Device Tests is instructions for the agent only.
SKILL.md names 2 domains. As links in the text: developer.android.com and robolectric.org. This is read from the text; nothing was executed.
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.
Setting Up Host Vs Device Tests 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.
About 4.8k tokens (SKILL.md is roughly 19k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Setting Up Host Vs Device Tests: Accessible Mobile Developer (FerroxLabs/wayland, 608 stars), Compose Agent (hamen/compose_skill, 373 stars), Android App Factory (JasonColapietro/suede-creator-skills, 127 stars) and Android Accessibility (Moustachauve/WLED-Android, 169 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
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.