Skip to content

Commit

Permalink
childOf selector (#1586)
Browse files Browse the repository at this point in the history
* feat: added support for childOf selector

* added support to find element from specific ViewHierarchy
* every childOf element can have its own childOf element
* we are recusilvely searching parent ViewHierarchy to find child from it

* feat: return parentparentViewHierarchy.root in place of absolute root node in case of exception

* fix: correct ViewHierarchy created from element returned

* refactor: pr comments resolved

* formatting issues fixed,
* added viewHierarchy as a parameter to findElementWithTimeout and passes its value as null for existing support
* removed findElementFromViewHierarchyWithTimeout as its functionality is added to findElementWithTimeout

* fix: fixed missing viewHierarchy in parameter issue

* test: added tests to test chilOf selector

* fix: id typo in child_of_selector.yaml fixed

* refactor: pr comments resolved

* made viewviewHierarchy's default value null in findElementWithTimeout.
* removed null from function call of findElementWithTimeout.
* if parentViewHierarchy is present using it to create exceprion

---------

Co-authored-by: Sandeep Joshi <[email protected]>
Co-authored-by: Sandeep Kumar Joshi <[email protected]>
  • Loading branch information
3 people authored Nov 17, 2023
1 parent 743e62a commit 4eca23f
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 6 deletions.
13 changes: 10 additions & 3 deletions maestro-client/src/main/java/maestro/Maestro.kt
Original file line number Diff line number Diff line change
Expand Up @@ -425,22 +425,29 @@ class Maestro(private val driver: Driver) : AutoCloseable {
fun findElementBySize(width: Int?, height: Int?, tolerance: Int?, timeoutMs: Long): UiElement? {
LOGGER.info("Looking for element by size: $width x $height (tolerance $tolerance) (timeout $timeoutMs)")

return findElementWithTimeout(timeoutMs, Filters.sizeMatches(width, height, tolerance).asFilter())?.element
return findElementWithTimeout(
timeoutMs,
Filters.sizeMatches(width, height, tolerance).asFilter()
)?.element
}

fun findElementWithTimeout(
timeoutMs: Long,
filter: ElementFilter,
viewHierarchy: ViewHierarchy? = null
): FindElementResult? {
var hierarchy = ViewHierarchy(TreeNode())
var hierarchy = viewHierarchy ?: ViewHierarchy(TreeNode())
val element = MaestroTimer.withTimeout(timeoutMs) {
hierarchy = viewHierarchy()
hierarchy = viewHierarchy ?: viewHierarchy()
filter(hierarchy.aggregate()).firstOrNull()
}?.toUiElementOrNull()

return if (element == null) {
null
} else {
if (viewHierarchy != null) {
hierarchy = ViewHierarchy(element.treeNode)
}
return FindElementResult(element, hierarchy)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ data class ElementSelector(
val selected: Boolean? = null,
val checked: Boolean? = null,
val focused: Boolean? = null,
val childOf: ElementSelector? = null
) {

data class SizeSelector(
Expand All @@ -58,6 +59,7 @@ data class ElementSelector(
containsChild = containsChild?.evaluateScripts(jsEngine),
containsDescendants = containsDescendants?.map { it.evaluateScripts(jsEngine) },
index = index?.evaluateScripts(jsEngine),
childOf = childOf?.evaluateScripts(jsEngine)
)
}

Expand Down Expand Up @@ -116,6 +118,10 @@ data class ElementSelector(
descriptions.add("Index: ${it.toDoubleOrNull()?.toInt() ?: it}")
}

childOf?.let {
descriptions.add("Child of: ${it.description()}")
}

val combined = descriptions.joinToString(", ")

return if (optional) {
Expand Down
40 changes: 38 additions & 2 deletions maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt
Original file line number Diff line number Diff line change
Expand Up @@ -840,21 +840,57 @@ class Orchestra(
lookupTimeoutMs
}
)

val (description, filterFunc) = buildFilter(
selector,
deviceInfo(),
)
if (selector.childOf != null) {
val parentViewHierarchy = findElementViewHierarchy(
selector.childOf,
timeout
)
return maestro.findElementWithTimeout(
timeout,
filterFunc,
parentViewHierarchy
) ?: throw MaestroException.ElementNotFound(
"Element not found: $description",
parentViewHierarchy.root,
)
}


return maestro.findElementWithTimeout(
timeoutMs = timeout,
filter = filterFunc,
filter = filterFunc
) ?: throw MaestroException.ElementNotFound(
"Element not found: $description",
maestro.viewHierarchy().root,
)
}

private fun findElementViewHierarchy(
selector: ElementSelector?,
timeout: Long
): ViewHierarchy {
if (selector == null) {
return maestro.viewHierarchy()
}
val parentViewHierarchy = findElementViewHierarchy(selector.childOf, timeout);
val (description, filterFunc) = buildFilter(
selector,
deviceInfo(),
)
return maestro.findElementWithTimeout(
timeout,
filterFunc,
parentViewHierarchy
)?.hierarchy ?: throw MaestroException.ElementNotFound(
"Element not found: $description",
parentViewHierarchy.root,
)
}

private fun deviceInfo() = deviceInfo
?: maestro.deviceInfo().also { deviceInfo = it }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ data class YamlElementSelector(
val focused: Boolean? = null,
val repeat: Int? = null,
val delay: Int? = null,
val waitToSettleTimeoutMs: Int? = null
val waitToSettleTimeoutMs: Int? = null,
val childOf: YamlElementSelectorUnion? = null
) : YamlElementSelectorUnion
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ data class YamlFluentCommand(
checked = selector.checked,
focused = selector.focused,
optional = selector.optional ?: false,
childOf = selector.childOf?.let { toElementSelector(it) }
)
}

Expand Down
48 changes: 48 additions & 0 deletions maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3051,6 +3051,54 @@ class IntegrationTest {
driver.assertEventCount(Event.Tap(Point(50, 50)), expectedCount = 1)
}

@Test
fun `Case 114 - child of selector`() {
// Given
val commands = readCommands("114_child_of_selector")

val driver = driver {
element {
id = "id1"
bounds = Bounds(0, 0, 200, 600)

element {
bounds = Bounds(0, 0, 200, 200)
text = "parent_id_1"
element {
text = "child_id"
bounds = Bounds(0, 0, 100, 200)
}
}
element {
bounds = Bounds(0, 200, 200, 400)
text = "parent_id_2"
element {
text = "child_id"
bounds = Bounds(0, 200, 100, 400)
}
}
element {
bounds = Bounds(0, 400, 200, 600)
text = "parent_id_3"
element {
text = "child_id_1"
bounds = Bounds(0, 400, 100, 600)
}
}
}
}

// When
Maestro(driver).use {
orchestra(it).runFlow(commands)
}

// Then
// No test failures
driver.assertNoInteraction()

}

private fun orchestra(
maestro: Maestro,
) = Orchestra(
Expand Down
10 changes: 10 additions & 0 deletions maestro-test/src/test/resources/114_child_of_selector.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
appId: com.example.app
---
- assertVisible:
text: "child_id"
childOf:
text: "parent_id_1"
- assertNotVisible:
text: "child_id"
childOf:
text: "parent_id_3"

0 comments on commit 4eca23f

Please sign in to comment.