
Expose an HTTP server enabling agents to control a running desktop app: click buttons, input text, wait for elements, capture screenshots, add custom endpoints, zero-configuration launcher.
A library that enables coding agents and automation tools to control Compose Desktop applications at runtime via HTTP. Agents can click buttons, enter text, wait for UI elements, and capture screenshots through simple REST API calls.
This library is designed for AI coding agents (like Claude Code) to interact with running Compose Desktop applications. When enabled, the app exposes an HTTP server that agents can use to:
Add the dependency to your desktop source set in build.gradle.kts:
kotlin {
sourceSets {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
val desktopMain by getting {
dependencies {
implementation("io.github.forketyfork:compose-ui-test-server:0.2.0")
// Required: Compose UI Test framework
implementation(compose.uiTest)
}
}
}
}Replace your main() function with runApplication:
import io.github.forketyfork.composeuittest.WindowConfig
import io.github.forketyfork.composeuittest.runApplication
fun main() =
runApplication(
windowConfig = WindowConfig(
title = "My App",
minimumWidth = 1024,
minimumHeight = 768,
),
) {
App()
}That's it! Your app now supports agent control with zero additional configuration.
For agents to interact with UI elements, add test tags:
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
Button(
onClick = { /* ... */ },
modifier = Modifier.testTag("login_button")
) {
Text("Login")
}
TextField(
value = username,
onValueChange = { username = it },
modifier = Modifier.testTag("username_field")
)Normal mode (default):
./gradlew runAgent-controlled mode:
COMPOSE_UI_TEST_SERVER_ENABLED=true ./gradlew runWhen the environment variable is set, your app automatically starts with an HTTP server that agents can use to control the UI.
Once running in agent-controlled mode:
# Health check
curl http://localhost:54345/health
# Click a button by test tag
curl http://localhost:54345/onNodeWithTag/submit_button/performClick
# Enter text into a field
curl "http://localhost:54345/onNodeWithTag/username_field/performTextInput?text=myuser"
# Wait for an element to appear
curl "http://localhost:54345/waitUntilExactlyOneExists/tag/welcome_screen?timeout=5000"
# Capture a screenshot
curl "http://localhost:54345/captureScreenshot?path=/tmp/current_state.png"| Endpoint | Description |
|---|---|
GET /health |
Health check - returns "OK" |
GET /onNodeWithTag/{tag}/performClick |
Click element by test tag |
GET /onNodeWithTag/{tag}/performTextInput?text=... |
Enter text into element |
GET /onNodeWithText/{text}/performClick |
Click element by display text |
GET /waitUntilExactlyOneExists/tag/{tag}?timeout=5000 |
Wait for element by tag |
GET /waitUntilExactlyOneExists/text/{text}?exact=true&timeout=5000 |
Wait for element by text |
GET /waitForIdle |
Wait for UI to become idle |
GET /captureScreenshot?path=/tmp/screenshot.png |
Capture screenshot to file |
| Variable | Description | Default |
|---|---|---|
COMPOSE_UI_TEST_SERVER_ENABLED |
Enable agent-controlled mode | false |
COMPOSE_UI_TEST_SERVER_PORT |
Server port | 54345 |
For more control over the test server, pass a server configuration:
import io.github.forketyfork.composeuittest.ComposeUiTestServerConfig
import io.github.forketyfork.composeuittest.WindowConfig
import io.github.forketyfork.composeuittest.runApplication
fun main() =
runApplication(
windowConfig = WindowConfig(title = "My App"),
serverConfig = ComposeUiTestServerConfig(
port = 8080,
host = "0.0.0.0",
defaultScreenshotPath = "/tmp/app_screenshot.png",
defaultTimeout = 10_000L,
),
) {
App()
}Add app-specific shortcuts for common agent workflows:
import io.github.forketyfork.composeuittest.TestEndpoint
import androidx.compose.ui.test.ComposeUiTest
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performClick
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.response.respondText
class LoginEndpoint(
private val username: String,
private val password: String
) : TestEndpoint {
override fun Route.configure(composeTest: ComposeUiTest) {
get("/shortcuts/login") {
composeTest.onNodeWithTag("username").performTextInput(username)
composeTest.onNodeWithTag("password").performTextInput(password)
composeTest.onNodeWithTag("login_button").performClick()
call.respondText("Login completed")
}
}
}To use custom endpoints, use the lower-level API:
import androidx.compose.ui.test.runComposeUiTest
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import io.github.forketyfork.composeuittest.ComposeUiTestServer
import io.github.forketyfork.composeuittest.isTestServerEnabled
fun main() {
if (isTestServerEnabled()) {
runComposeUiTest {
setContent { App() }
ComposeUiTestServer(this)
.registerEndpoint(LoginEndpoint("test@example.com", "password"))
.start()
.awaitTermination()
}
} else {
application {
Window(onCloseRequest = ::exitApplication, title = "My App") {
App()
}
}
}
}Currently supports Desktop (JVM) only due to Skia dependencies for screenshot capture.
This library includes a skill that teaches Claude Code how to control Compose Desktop apps and set up new projects. Install it to enable automatic UI control capabilities.
mkdir -p ~/.claude/skills/compose-ui-control
curl -o ~/.claude/skills/compose-ui-control/SKILL.md \
https://raw.githubusercontent.com/forketyfork/compose-ui-test-server/main/SKILL.mdmkdir -p .claude/skills/compose-ui-control
curl -o .claude/skills/compose-ui-control/SKILL.md \
https://raw.githubusercontent.com/forketyfork/compose-ui-test-server/main/SKILL.mdOnce installed, Claude Code can:
/compose-ui-control
Example prompts that trigger the skill:
See SKILL.md for the full skill documentation that Claude Code uses.
MIT License - see LICENSE for details.
A library that enables coding agents and automation tools to control Compose Desktop applications at runtime via HTTP. Agents can click buttons, enter text, wait for UI elements, and capture screenshots through simple REST API calls.
This library is designed for AI coding agents (like Claude Code) to interact with running Compose Desktop applications. When enabled, the app exposes an HTTP server that agents can use to:
Add the dependency to your desktop source set in build.gradle.kts:
kotlin {
sourceSets {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
val desktopMain by getting {
dependencies {
implementation("io.github.forketyfork:compose-ui-test-server:0.2.0")
// Required: Compose UI Test framework
implementation(compose.uiTest)
}
}
}
}Replace your main() function with runApplication:
import io.github.forketyfork.composeuittest.WindowConfig
import io.github.forketyfork.composeuittest.runApplication
fun main() =
runApplication(
windowConfig = WindowConfig(
title = "My App",
minimumWidth = 1024,
minimumHeight = 768,
),
) {
App()
}That's it! Your app now supports agent control with zero additional configuration.
For agents to interact with UI elements, add test tags:
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
Button(
onClick = { /* ... */ },
modifier = Modifier.testTag("login_button")
) {
Text("Login")
}
TextField(
value = username,
onValueChange = { username = it },
modifier = Modifier.testTag("username_field")
)Normal mode (default):
./gradlew runAgent-controlled mode:
COMPOSE_UI_TEST_SERVER_ENABLED=true ./gradlew runWhen the environment variable is set, your app automatically starts with an HTTP server that agents can use to control the UI.
Once running in agent-controlled mode:
# Health check
curl http://localhost:54345/health
# Click a button by test tag
curl http://localhost:54345/onNodeWithTag/submit_button/performClick
# Enter text into a field
curl "http://localhost:54345/onNodeWithTag/username_field/performTextInput?text=myuser"
# Wait for an element to appear
curl "http://localhost:54345/waitUntilExactlyOneExists/tag/welcome_screen?timeout=5000"
# Capture a screenshot
curl "http://localhost:54345/captureScreenshot?path=/tmp/current_state.png"| Endpoint | Description |
|---|---|
GET /health |
Health check - returns "OK" |
GET /onNodeWithTag/{tag}/performClick |
Click element by test tag |
GET /onNodeWithTag/{tag}/performTextInput?text=... |
Enter text into element |
GET /onNodeWithText/{text}/performClick |
Click element by display text |
GET /waitUntilExactlyOneExists/tag/{tag}?timeout=5000 |
Wait for element by tag |
GET /waitUntilExactlyOneExists/text/{text}?exact=true&timeout=5000 |
Wait for element by text |
GET /waitForIdle |
Wait for UI to become idle |
GET /captureScreenshot?path=/tmp/screenshot.png |
Capture screenshot to file |
| Variable | Description | Default |
|---|---|---|
COMPOSE_UI_TEST_SERVER_ENABLED |
Enable agent-controlled mode | false |
COMPOSE_UI_TEST_SERVER_PORT |
Server port | 54345 |
For more control over the test server, pass a server configuration:
import io.github.forketyfork.composeuittest.ComposeUiTestServerConfig
import io.github.forketyfork.composeuittest.WindowConfig
import io.github.forketyfork.composeuittest.runApplication
fun main() =
runApplication(
windowConfig = WindowConfig(title = "My App"),
serverConfig = ComposeUiTestServerConfig(
port = 8080,
host = "0.0.0.0",
defaultScreenshotPath = "/tmp/app_screenshot.png",
defaultTimeout = 10_000L,
),
) {
App()
}Add app-specific shortcuts for common agent workflows:
import io.github.forketyfork.composeuittest.TestEndpoint
import androidx.compose.ui.test.ComposeUiTest
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performClick
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.response.respondText
class LoginEndpoint(
private val username: String,
private val password: String
) : TestEndpoint {
override fun Route.configure(composeTest: ComposeUiTest) {
get("/shortcuts/login") {
composeTest.onNodeWithTag("username").performTextInput(username)
composeTest.onNodeWithTag("password").performTextInput(password)
composeTest.onNodeWithTag("login_button").performClick()
call.respondText("Login completed")
}
}
}To use custom endpoints, use the lower-level API:
import androidx.compose.ui.test.runComposeUiTest
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import io.github.forketyfork.composeuittest.ComposeUiTestServer
import io.github.forketyfork.composeuittest.isTestServerEnabled
fun main() {
if (isTestServerEnabled()) {
runComposeUiTest {
setContent { App() }
ComposeUiTestServer(this)
.registerEndpoint(LoginEndpoint("test@example.com", "password"))
.start()
.awaitTermination()
}
} else {
application {
Window(onCloseRequest = ::exitApplication, title = "My App") {
App()
}
}
}
}Currently supports Desktop (JVM) only due to Skia dependencies for screenshot capture.
This library includes a skill that teaches Claude Code how to control Compose Desktop apps and set up new projects. Install it to enable automatic UI control capabilities.
mkdir -p ~/.claude/skills/compose-ui-control
curl -o ~/.claude/skills/compose-ui-control/SKILL.md \
https://raw.githubusercontent.com/forketyfork/compose-ui-test-server/main/SKILL.mdmkdir -p .claude/skills/compose-ui-control
curl -o .claude/skills/compose-ui-control/SKILL.md \
https://raw.githubusercontent.com/forketyfork/compose-ui-test-server/main/SKILL.mdOnce installed, Claude Code can:
/compose-ui-control
Example prompts that trigger the skill:
See SKILL.md for the full skill documentation that Claude Code uses.
MIT License - see LICENSE for details.