agentsclimarketplace

Compose multiplatform testing

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

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.From its SKILL.md

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.

SKILL.md

4.7 KB, ~1.1k tokens by cl100k_base, 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.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. 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.