TestFailureHandler


Handles Compose UI test failures for custom diagnostics or artifact processing.

Implementations are registered in a TestFailurePolicy within a ComposeUiTestConfig and execute in the order provided. If a handler throws an exception, the framework catches and attaches it as a suppressed exception to FailureContext.error, ensuring the original test failure is never masked.

Handlers execute synchronously on the test thread in a post-mortem state where the Compose hierarchy, coroutine scopes, and UI registries have already been torn down. As a result, interactive testing APIs like ComposeUiTest.waitForIdle or onNodeWithTag cannot be called within a handler.

Because handlers execute after the test timeout has elapsed, blocking calls such as file IO or network operations can delay or stall the test runner indefinitely. Handlers that perform heavy IO should enforce their own tight timeouts or dispatch work to background threads.

import androidx.compose.ui.test.ComposeUiTestConfig
import androidx.compose.ui.test.FailureArtifact
import androidx.compose.ui.test.TestFailureHandler
import androidx.compose.ui.test.TestFailurePolicy
import androidx.compose.ui.test.TestFailurePolicy.CaptureMode
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.v2.runComposeUiTest
import androidx.test.platform.io.PlatformTestStorageRegistry

val customFailureHandler = TestFailureHandler { context ->
    val storage = PlatformTestStorageRegistry.getInstance()

    context.artifacts.forEach { artifact ->
        when (artifact.type) {
            FailureArtifact.Type.Screenshot -> {
                // Example: Read the screenshot bytes to upload to a custom dashboard
                // val inputStream = storage.openInputFile(artifact.fileName)
            }
            FailureArtifact.Type.UiHierarchy -> {
                // Example: Get the URI to share or process further
                // val uri = storage.getOutputFileUri(artifact.fileName)
            }
        }
    }
}

val testConfig =
    ComposeUiTestConfig(
        failurePolicy =
            TestFailurePolicy(
                screenshotCaptureMode = CaptureMode.Enabled,
                uiHierarchyCaptureMode = CaptureMode.Enabled,
                failureHandlers = listOf(customFailureHandler),
            )
    )

runComposeUiTest(config = testConfig) {
    setContent { /* Your Compose UI here */ }

    // If this assertion fails, the framework will:
    // 1. Take a screenshot
    // 2. Dump the UI hierarchy
    // 3. Call customFailureHandler.onTestFailed
    onNodeWithTag("non_existent_button").assertExists()
}

Summary

Public functions

Unit

Invoked synchronously on the test thread when a Compose UI test fails.

Cmn

Public functions

onTestFailed

fun onTestFailed(context: FailureContext): Unit

Invoked synchronously on the test thread when a Compose UI test fails.

Parameters
context: FailureContext

The FailureContext containing the root Throwable and any generated FailureArtifacts.