Status Update
Comments
fe...@aspire.se <fe...@aspire.se> #2
So IIUC, you're looking for something like assertDoesNotExistOrIsNotDisplayed()
.
I think this could be solved by providing matchers to express "exists" and "isDisplayed", so you can write assert(not(isDisplayed()) or not(exists()))
. I'll have to look back in time to see why we didn't provide those matchers in the first place.
fe...@aspire.se <fe...@aspire.se> #3
`assert(not(isDisplayed())`
as isDisplayed is true when it is displayed. and I want a negation of it. but current IsNotDisplayed() != !isDisplayed() as it fails if the node does not exist. but I don't care why exactly it is not displayed
an...@google.com <an...@google.com> #4
I found a way to do this using public API. It is not elegant, but users could use this while we come up with a better fix:
fun SemanticsNodeInteraction.assertNotDisplayed() {
try {
// If the node does not exist, it implies that it is also not displayed.
assertDoesNotExist()
} catch (e: AssertionError) {
// If the node exists, we need to assert that it is not displayed
assertIsNotDisplayed()
}
}
We can similarly use assertNotPlaced to determine if the node is composed but not placed:
fun SemanticsNodeInteraction.assertNotPlaced() {
try {
// If the node does not exist, it implies that it is also not placed.
assertDoesNotExist()
} catch (e: AssertionError) {
// If the node exists, we need to assert that it is not placed.
val errorMessageOnFail = "Assert failed: The component is placed!"
if (fetchSemanticsNode().layoutInfo.isPlaced) {
throw AssertionError(errorMessageOnFail)
}
}
}
al...@google.com <al...@google.com> #5
Sharing some of the thoughts here that were formed yesterday:
My thinking is that we can define a few levels of "existence" of a node:
- Does not exist: not in the semantics tree.
- Exists, but not placed: is in the semantics tree, but isPlaced = false. A few unanswered questions: Do cached nodes end up here? Should they? Can we recognize cached nodes?
- Placed, but not visible: in the semantics tree, isPlaced = true, but the visible region is empty.
- Partially visible: composed, placed, visible, but only X% visible (includes not visible (X=0) and fully visible (X=100)).
- Fully visible: everything (but also thinking of a parameter to indicate that "visible region == parent size" must be treated as fully visible too)
Potentially there could be "cached" in between exists and placed, but that requires that we can recognize cached nodes. I expect that is an implementation detail though, but not sure.
Currently, the negated versions of each of the above (as far as they exist in the current API) do not pass if the node does not exist, which I think was the original problem here, and which we should change while we're reshaping this API.
For the visibility assertions we need to be careful with any form of occlusion and alpha values, which makes it a bit tricky to implement. For example, what about a node that's not hidden by anything, but the draw translates it and half of it is clipped off. 100% or 50% visible? I'm tempted to use the bounds as the source of truth, which would make this case 100% visible.
bu...@google.com <bu...@google.com> #6
fe...@aspire.se <fe...@aspire.se> #7
I agree that this is not ideal, but don't think it is super hacky. We don't have an API to check if an item exists, but we have APIs to assert whether a node exists or not. So this solution just catches the assertion and turns it into a conditional API. Users can use something like this in their codebase for now. There is only one line in the try block so it shouldn't have any false positives:
fun SemanticsNodeInteraction.isNodePresent(): Boolean {
try {
assertExists()
} catch (e: AssertionError) {
return false
}
return true
}
Meanwhile we have aosp/2105330 which does not use this hack, and we can land it as experimental API to unblock users. However if we want to hold off and think about this more before landing that change, I think we can take the time to do so, because users have a workaround.
But as a longer term fix, maybe we should provide APIs at both levels:
Lower level API val SemanticsNodeInteraction.isNodePresent : Boolean
Higher level API fun SemanticsNodeInteraction.assertPlaced(): SemanticsNodeInteraction
and fun SemanticsNodeInteraction.assertNotPlaced()
al...@google.com <al...@google.com>
an...@google.com <an...@google.com> #8
Actually if you use onAllNodes(someMatcher())
you can use fetchSemanticsNodes()
which will return a list that can be empty. That is what we mostly recommend for doing these kind of checks. For example, in DemoTest we implemented SemanticsNodeInteractionCollection.isNotEmpty()
isNodePresent()
.
an...@google.com <an...@google.com> #9
Andrey is there a clear path forward here we could take or is this worth putting on the back burner?
Description
Jetpack Compose version:
BOM
2023.10.01
,2024.11.00
Jetpack Compose component(s) used:
androidx.activity:activity-compose
androidx.compose.material:material
androidx.compose.material:material-icons-extended
androidx.compose.ui:ui
androidx.compose.ui:ui-graphics
androidx.navigation:navigation-compose
androidx.constraintlayout:constraintlayout-compose
Android Studio Build:
Kotlin version:
1.9.22
Steps to Reproduce or Code Sample to Reproduce:
I don't really have a reproduction available at this time. The following is an untested simplification where I'd start writing a new repro from. Also, be aware that this code is executing from a dynamic feature module.
Stack trace (if applicable):