agentsclimarketplace

Compose multiplatform testing

Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/testing_and_automation/compose-multiplatform-testing

Curated agent skills, conventions, and workflows for building Kotlin Multiplatform (KMP) apps with AI coding agents.

Install
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill compose-multiplatform-testing

Assembled 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 composeTarget uses stock Jetpack Compose underneath.
  • Desktop / JVM: Roborazzi or compose-desktop's ImageComposeScene to 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:desktopTest is the cheapest Compose-UI test runner — use for pure shared logic.
  • ./gradlew :shared:testDebugUnitTest runs 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 runComposeUiTest covering the happy path.
  • Matchers use testTag / contentDescription, not raw text.
  • Paparazzi or similar screenshot coverage exists for critical screens.
  • testTag values 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.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.