Compose multiplatform testing
Curated agent skills, conventions, and workflows for building Kotlin Multiplatform (KMP) apps with AI coding agents.
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill compose-multiplatform-testingAssembled 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
Testing Compose Multiplatform UI — semantic assertions in commonTest, running UI tests on Android / Desktop / iOS, and screenshot regression strategies. Use when covering shared UI with tests.
SKILL.md
4.7 KB, as published. Nobody here has run it
Compose Multiplatform Testing
Instructions
Compose MP 1.7+ ships a multiplatform UI-test API: the same composeTestRule you know from Android works in commonTest, driving semantic-tree assertions.
1. Dependencies
// shared/build.gradle.kts
kotlin.sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
@OptIn(ExperimentalComposeLibrary::class)
implementation(compose.uiTest) // multiplatform Compose test
}
androidUnitTest.dependencies {
implementation(compose.uiTestJUnit4)
implementation("androidx.compose.ui:ui-test-manifest:1.7.0")
}
desktopTest.dependencies {
implementation(compose.desktop.uiTestJUnit4)
}
}
2. A shared UI test in commonTest
class CounterScreenTest {
@OptIn(ExperimentalTestApi::class)
@Test
fun incrementingUpdatesLabel() = runComposeUiTest {
val viewModel = CounterViewModel()
setContent { App(viewModel) }
onNodeWithText("Count: 0").assertIsDisplayed()
onNodeWithText("Increment").performClick()
onNodeWithText("Count: 1").assertIsDisplayed()
}
}
runComposeUiTest { … } replaces createComposeRule(); it's the multiplatform entry point. Runs on JVM-desktop by default; Android and iOS require their target source sets.
3. Semantic matchers
onNodeWithContentDescription("Remove from favorites").performClick()
onNodeWithTag("loginButton").assertIsEnabled()
onAllNodesWithText("Item", substring = true).assertCountEquals(3)
onNode(hasText("Save") and isEnabled()).performClick()
Prefer contentDescription and testTag over text — localized strings break text-based matchers.
4. Screenshot / pixel tests
There is no first-class shared screenshot API. Options by platform:
- Android (preferred for CI): Paparazzi renders Compose without a device. Works with Compose MP because the Android
composeTargetuses stock Jetpack Compose underneath. - Desktop / JVM: Roborazzi or
compose-desktop'sImageComposeSceneto dump PNGs. - iOS: drive a real simulator from Xcode UI Tests against the Compose view controller; compare with
XCTAttachment.
5. Paparazzi sample
// androidUnitTest
class CounterScreenshotTest {
@get:Rule val paparazzi = Paparazzi(
deviceConfig = DeviceConfig.PIXEL_5,
theme = "android:Theme.Material.Light.NoActionBar",
)
@Test
fun default() { paparazzi.snapshot { App(CounterViewModel()) } }
@Test
fun dark() {
paparazzi.snapshot {
CompositionLocalProvider(LocalInspectionMode provides true) {
MaterialTheme(colorScheme = darkColorScheme()) { App(CounterViewModel()) }
}
}
}
}
6. iOS UI tests via XCUITest
Your iosApp target gets a UI Test bundle in Xcode. Exercise the Compose screen via accessibility identifiers set on the Kotlin side:
Button(
onClick = ::increment,
modifier = Modifier.testTag("increment-btn").semantics { contentDescription = "increment-btn" },
) { Text("Increment") }
func testIncrement() {
let app = XCUIApplication(); app.launch()
app.buttons["increment-btn"].tap()
XCTAssert(app.staticTexts["Count: 1"].exists)
}
testTag maps to the accessibility identifier on iOS.
7. Animations & clock control
@OptIn(ExperimentalTestApi::class)
runComposeUiTest {
mainClock.autoAdvance = false
setContent { AnimatedCard(expanded = true) }
mainClock.advanceTimeBy(16) // one frame
onNodeWithTag("card").assertHeightIsAtLeast(120.dp)
}
Always disable autoAdvance for deterministic animation assertions — otherwise flakes appear in CI.
8. CI strategy
./gradlew :shared:desktopTestis the cheapest Compose-UI test runner — use for pure shared logic../gradlew :shared:testDebugUnitTestruns Paparazzi on Android.- Real-device iOS UI tests run on macOS runners; keep them small and smoke-style.
Checklist
- Shared screens have at least one
runComposeUiTestcovering the happy path. - Matchers use
testTag/contentDescription, not raw text. - Paparazzi or similar screenshot coverage exists for critical screens.
-
testTagvalues mirror iOS accessibility identifiers used by XCUITest. - Animation tests disable
mainClock.autoAdvance. - Desktop UI tests run in CI on every push; iOS UI tests run nightly or pre-release.