Dialog boxes are untestable on Compose Desktop
Problem
Using the Compose UI Testing library, we cannot test dialog boxes on Compose Desktop. The following example construct works on Android, but not on Desktop.
Example Code
// Test Logic
@Test
fun `reproduce dialog test failure`() {
ui(compose) {
setContent {
var isDialogOpen by remember { mutableStateOf(false) }
DialogHolder(isDialogOpen) {v -> isDialogOpen = v}
}
onNodeWithText("Open").performClick()
awaitIdle()
onNodeWithText("Close").assertExists()
}
} }
// --------------
@Composable fun DialogHolder(isDialogOpen: Boolean, updateDialog: (Boolean) -> Unit) {
Button(onClick = { updateDialog(true) }) {
Text("Open")
}
if (isDialogOpen) {
Dialog(onCloseRequest = { updateDialog(false) }) {
Button(onClick = { updateDialog(false) }) {
Text("Close")
}
}
}
}
Expectation
Test passes. Content within dialog box is detected.
Actual Result
Test fails with the following error:
Failed: assertExists.
Reason: Expected exactly '1' node but could not find any node that satisfies: (Text + EditableText contains 'Close' (ignoreCase: false))
java.lang.AssertionError: Failed: assertExists.
Reason: Expected exactly '1' node but could not find any node that satisfies: (Text + EditableText contains 'Close' (ignoreCase: false))
at androidx.compose.ui.test.SemanticsNodeInteraction.fetchOneOrDie(SemanticsNodeInteraction.kt:162)
at androidx.compose.ui.test.SemanticsNodeInteraction.assertExists(SemanticsNodeInteraction.kt:137)
...
Version Info
- Kotlin: 1.6.10
- Jetpack Compose: 1.1.1
- JVM Target: 11
Links
Relevant Stackoverflow question (asked by me): https://stackoverflow.com/q/72507535/3477606.
Are we able to confirm this issue? Is there a workaround?
I think this goes beyond dialog boxes, Windows of any kind can't be 'seen through'.
@Test
fun `compose test with a window`() {
runComposeRule(composeRule) {
setContent {
Window({}) {
Row {
Text("Test.")
}
}
}
...
composeRule.onRoot(useUnmergedTree = true).printToString(5)
returns
Printing with useUnmergedTree = 'true'
Node #1 at (l=0.0, t=0.0, r=0.0, b=0.0)px
For comparison, removing the Window
@Test
fun `compose test without a window`() {
runComposeRule(composeRule) {
setContent {
Row {
Text("Test.")
}
}
...
composeRule.onRoot(useUnmergedTree = true).printToString(5)
returns
Printing with useUnmergedTree = 'true'
Node #1 at (l=0.0, t=0.0, r=30.0, b=17.0)px
|-Node #2 at (l=0.0, t=0.0, r=30.0, b=17.0)px
Text = '[Test.]'
Actions = [GetTextLayoutResult]
Is anyone aware whether end to end testing is workable by any means right now?
Seeing as a simple assertIsDisplayed fails because a TODO() right at the moment, I think it's obvious that UI testing is not currently available on desktop.
@serandel Enough works for basic UI testing at the moment (you can verify that nodes with specific matchers exist in the composition using assertExists()) but it doesn't seem like even slightly complex usecases are present.
On that note, I'm not entirely sure what assertIsDisplayed() is supposed to do differently than assertExists(). Is there a way to have elements live in the composition which are somehow hidden?
@serandel Enough works for basic UI testing at the moment (you can verify that nodes with specific matchers exist in the composition using
assertExists())
Looking at the number of TODO() in this package I respectfully disagree.
On that note, I'm not entirely sure what
assertIsDisplayed()is supposed to do differently thanassertExists(). Is there a way to have elements live in the composition which are somehow hidden?
Looking at the source code, a main difference is that it tests that the view is not outside the clipped rect of the window. Because it's using the Espresso ViewMatchers under the hood, I'm not exactly sure if it also checks that the view is not hidden behind any other UI component, but I suspect it's not.
Bumping this for attention. Can I at least have an idea of when/if this is planned to be fixed?
This has been fixed in Compose Multiplatform 1.5:
@Composable
fun DialogHolder(isDialogOpen: Boolean, updateDialog: (Boolean) -> Unit) {
Button(onClick = { updateDialog(true) }) {
Text("Open")
}
if (isDialogOpen) {
DialogWindow(onCloseRequest = { updateDialog(false) }) {
Button(onClick = { updateDialog(false) }) {
Text("Close")
}
}
}
}
@Test
fun `reproduce dialog test failure`() {
rule.setContent {
var isDialogOpen by remember { mutableStateOf(false) }
DialogHolder(isDialogOpen) {v -> isDialogOpen = v}
}
rule.onNodeWithText("Open").performClick()
rule.onNodeWithText("Close").assertExists()
}
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.