Skip to content

Commit

Permalink
Support ancestor search in Selector (#49)
Browse files Browse the repository at this point in the history
Example Usage:

```python
ad.ui(res='android:id/text')
    .ancestor(res='android:id/notification_headerless_view_column')
    .child(res='android:id/title')
    .info
```
  • Loading branch information
ko1in1u committed May 30, 2024
1 parent 70fb9e3 commit 46e2e3b
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Snippet UiAutomator Release History

## 1.1.2:
* Support ancestor search in Selector

## 1.1.1:
* Migrate -jre flavor of Guava to -android flavor
* Fix the type of percent in scrollUntilFinished and scrollUntil
Expand Down
8 changes: 8 additions & 0 deletions docs/selector.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ ad.ui(text='Example', enabled=True, depth=3)
Selector supports to narrow down the search scope to objects that are related to
itself.

* Ancestor

Find the ancestor directly above present Selector.

```python
ad.ui(...).ancestor(...)
```

* Child

Find the child directly under present Selector.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
Expand All @@ -43,7 +42,7 @@
*/
public class Selector {
private static final ImmutableSet<String> SUB_SELECTORS =
ImmutableSet.of("child", "parent", "sibling", "bottom", "left", "right", "top");
ImmutableSet.of("ancestor", "child", "parent", "sibling", "bottom", "left", "right", "top");
private final UiDevice uiDevice = UiAutomator.getUiDevice();
private final ImmutableMap<String, IBySelector> bySelectorMap = BySelectorMap.create();
private final JSONObject selector;
Expand All @@ -52,6 +51,26 @@ public Selector(JSONObject selector) {
this.selector = selector;
}

private boolean isCriteriaMatch(UiObject2 uiObject2, BySelector bySelector) {
for (UiObject2 matchedObject : uiDevice.findObjects(bySelector)) {
if (matchedObject.equals(uiObject2)) {
return true;
}
}
return false;
}

private @Nullable UiObject2 findAncestorObject(UiObject2 uiObject2, BySelector bySelector) {
UiObject2 parentUiObject2 = uiObject2.getParent();
while (parentUiObject2 != null) {
if (isCriteriaMatch(parentUiObject2, bySelector)) {
return parentUiObject2;
}
parentUiObject2 = parentUiObject2.getParent();
}
return null;
}

private @Nullable BySelector getBySelector(JSONObject selector) throws SelectorException {
Log.d(String.format("Receive selector: %s", selector));
BySelector bySelector = null;
Expand Down Expand Up @@ -84,6 +103,9 @@ public Selector(JSONObject selector) {

List<UiObject2> uiObject2List = new ArrayList<>();
switch (type) {
case "ancestor":
uiObject2List.add(findAncestorObject(baseUiObject2, bySelector));
break;
case "child":
uiObject2List = baseUiObject2.findObjects(bySelector);
break;
Expand Down Expand Up @@ -123,7 +145,10 @@ public Selector(JSONObject selector) {
UiObject2 matchedUiObject2 = null;

try {
if (selector.has("child")) {
if (selector.has("ancestor")) {
matchedUiObject2 =
getUiObject2(selector.getJSONObject("ancestor"), uiObject2, "ancestor");
} else if (selector.has("child")) {
matchedUiObject2 = getUiObject2(selector.getJSONObject("child"), uiObject2, "child");
} else if (selector.has("parent")) {
matchedUiObject2 = getUiObject2(selector.getJSONObject("parent"), uiObject2, "parent");
Expand Down
Binary file modified snippet_uiautomator/android/app/uiautomator.apk
Binary file not shown.
11 changes: 10 additions & 1 deletion snippet_uiautomator/byselector.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,16 @@ class BySelector:
https://developer.android.com/reference/androidx/test/uiautomator/BySelector
"""

SUBSELECTOR = ('child', 'parent', 'sibling', 'bottom', 'left', 'right', 'top')
SUBSELECTOR = (
'ancestor',
'child',
'parent',
'sibling',
'bottom',
'left',
'right',
'top',
)

def __init__(self, **kwargs) -> None:
"""Converts the keyword arguments to selector type."""
Expand Down
4 changes: 4 additions & 0 deletions snippet_uiautomator/uiobject2.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,10 @@ def parent(self) -> UiObject2:
"""Finds this object's parent, or null if it has no parent."""
return self._create_instance('parent')

def ancestor(self, **kwargs) -> UiObject2:
"""Finds this object's ancestor, or null if it has no matched ancestor."""
return self._create_instance('ancestor', **kwargs)

def child(self, **kwargs) -> UiObject2:
"""Finds the child object directly under this object."""
return self._create_instance('child', **kwargs)
Expand Down

0 comments on commit 46e2e3b

Please sign in to comment.