Selectors
UiSelector (selector) is used to select widgets on the screen by various conditions, and then perform actions such as click/long click on those widgets. Before diving in, here is a quick introduction to widgets and UI structure.
Most app UIs are composed of widgets. For example, an image area is an image widget (ImageView), and a text area is a text widget (TextView). Layout containers decide where widgets are placed. For example, in a linear layout (LinearLayout) widgets are stacked horizontally or vertically; a list layout (AbsListView) displays items as a list.
Widgets have many properties, including text, desc (content description), className, id, etc. We usually locate a widget by its properties. For example, to click the "Send" button in a chat app, we can locate it by text="Send":
var sendButton = text("Send").findOne();
sendButton.click();In this example, text("Send") is a condition (text equals "Send"), and findOne() finds a widget that matches that condition. Then we can call sendButton.click() to click it.
Locating by text is often effective for buttons and text widgets. However, if a widget is an image (for example, the search icon in the top-right of the Auto.js main screen), it has no text. In that case you need other properties. How do you inspect them? Enable the floating window and accessibility service, then tap the blue icon (Layout Inspector) to see something like:
Then tap the search icon, and you can see its properties:
Notice its desc (Content-Description) is "Search", so we can locate it by desc and click it:
desc("Search").findOne().click();You may notice the widget has many other properties as well, such as checked, className, clickable, etc. Why not use those? Because many other widgets share the same values. For example, many widgets have checked=false. If you use checked(false) as the condition, you will match many widgets and cannot uniquely identify the search icon. Therefore, selector conditions should usually uniquely identify the target widget. In this example, no other widget has desc="Search".
Also, for this search icon, the id is unique, so you can also do id("action_search").findOne().click(). If a widget has an id, it is often unique, except in cases like:
- Obfuscated IDs: names may be identical or change between versions
- List items: e.g. contact list rows, etc.
Although id is convenient, it is not always the best choice. For example, in some apps (like WeChat or NetEase Cloud Music), widget IDs may change with updates, making the same script incompatible across versions.
Other commonly used properties include:
className: widget class name (type). For example, text widgets are"android.widget.TextView", image widgets are"android.widget.ImageView".packageName: app package name the widget belongs to. For example, widgets in Auto.js Pro have package name"org.autojs.autojspro".bounds: widget bounds on the screen.drawingOrder: drawing order inside its parent.indexInParent: index position within its parent.clickable: whether it is clickable.longClickable: whether it is long-clickable.checkable: whether it can be checked.checked: whether it is checked.scrollable: whether it is scrollable.selected: whether it is selected.editable: whether it is editable.visibleToUser: whether it is visible to the user.enabled: whether it is enabled.depth: layout depth.
Sometimes one property is not enough to uniquely identify a widget. In that case, combine properties via chaining, for example className("ImageView").depth(10).findOne().click().
Usually these techniques solve most cases. If they don't, you can try the "Generate code" feature in the Layout Inspector to get sample selector code. Next is operating on the selected widget, such as:
click(): click the widget (requiresclickable=true)longClick(): long-click the widget (requireslongClickable=true)setText(): set text (for editable widgets)scrollForward(),scrollBackward(): scroll a widget (e.g. lists; requiresscrollable=true)exists(): check whether the widget existswaitFor(): wait for the widget to appear
These cover most widget operations. For example, you can easily write a "spam" script (example only; do not test in other people's group chats):
while(true){
className("EditText").findOne().setText("spam...");
text("Send").findOne().click();
}The code above can also be written as:
while(true){
className("EditText").setText("spam...");
text("Send").click();
}If you operate without calling findOne() first, the selector will find all widgets that match the conditions and apply the operation to them.
Another common operation is scrolling. First, locate the widget to scroll. For example, to scroll the QQ message list, find an AbsListView in the layout hierarchy; that widget is the message list:
Long-press to view widget details. Notice scrollable=true and its id is "recent_chat_list". Then you can scroll it like this:
id("recent_chat_list").className("AbsListView").findOne().scrollForward();scrollForward() means scrolling forward (including scrolling down and scrolling right).
That’s it for the quick intro. For more details, see the documentation below and the advanced selector section.
selector()
- Returns {UiSelector}
Create a new selector. In most cases you don't need this function, because you can create selectors directly with condition helpers.
Due to legacy reasons, this API should not have been designed this way (global functions like id() and text() should have been By.id() and By.text()), but it is kept for backward compatibility.
This design pollutes the global namespace. In the future, there may be an option to remove these global helpers and use By.* instead.
UiSelector.algorithm(algorithm)
[v4.1.0 Added]
algorithm{string} Search algorithm. Supported values:DFSDepth-first search (default)BFSBreadth-first search
Specify the search algorithm for the selector. Example:
log(selector().text("Text").algorithm("BFS").find());Breadth-first search can be faster when the target widget is at a deeper level, or when the layout depth is not large.
UiSelector.text(str)
str{string} Widget text- Returns {UiSelector} The selector itself (for chaining)
Add the condition “text equals str” to the current selector.
The text property is the visible text on a text widget. For example, the "WeChat" label at the top-left in WeChat.
UiSelector.textContains(str)
str{string} Substring to contain
Add the condition “text contains str”.
This is useful. For example, a widget like “Everyone is searching ...” can be located via textContains("Everyone is searching").findOne().
UiSelector.textStartsWith(prefix)
prefix{string} Prefix
Add the condition “text starts with prefix”.
Also useful. For example, to find scripts whose names start with "QQ" in the Auto.js script list: textStartsWith("QQ").find().
UiSelector.textEndsWith(suffix)
suffix{string} Suffix
Add the condition “text ends with suffix”.
UiSelector.textMatches(reg)
reg{string} | {Regex} Regular expression to match
Add the condition “text matches reg”.
For regular expressions, see: Regular Expression - RUNOOB.
Note: if the regex is provided as a string, you must escape backslashes with \\ (Java-style), e.g. textMatches("\\d+") matches multi-digit numbers. If you use JavaScript regex syntax, you don't need that, e.g. textMatches(/\d+/). When using string form, do not wrap it with leading and trailing / like textMatches("/\\d+/"), otherwise the starting and ending / will be ignored.
UiSelector.desc(str)
str{string} Content description text- Returns {UiSelector} The selector itself (for chaining)
Add the condition “desc equals str”.
The desc (Content-Description) describes a widget. For example, the magnifier icon in the top-right of NetEase Cloud Music may have description “Search”. You can inspect it via the floating window as well.
desc is also a powerful property for locating widgets.
UiSelector.descContains(str)
str{string} Substring to contain
Add the condition “desc contains str”.
UiSelector.descStartsWith(prefix)
prefix{string} Prefix
Add the condition “desc starts with prefix”.
UiSelector.descEndsWith(suffix)
suffix{string} Suffix
Add the condition “desc ends with suffix”.
UiSelector.descMatches(reg)
reg{string} | {Regex} Regular expression to match
Add the condition “desc matches reg”.
For regular expressions, see: Regular Expression - RUNOOB.
Same note as textMatches: if the regex is a string, escape \ as \\. Do not wrap the string with leading/trailing /.
UiSelector.id(resId)
resId{string} Widget id, usually in the form"package:id/xxx", e.g."com.tencent.mm:id/send_btn". Package name can be omitted; it will be completed using the currently active app package. For example,id("send_btn")is equivalent toid("com.tencent.mobileqq:id/send_btn").
Add the condition “id equals resId”.
Widget id is often the best way to uniquely identify a widget. To view a widget's id, enable the floating window and use the UI inspector; tap the widget to inspect it. If the id is null, the widget has no id. Also, in lists you may see multiple widgets sharing the same id (e.g. WeChat contact list avatars). In such cases, id alone cannot uniquely identify a widget.
In QQ you may often see many widgets with id "name", and in WeChat widget ids may change across versions. For those apps, locating by id can be unreliable.
UiSelector.idContains(str)
str{string} Substring that the id should contain
Add the condition “id contains str”. Rarely used.
UiSelector.idStartsWith(prefix)
prefix{string} Id prefix
Add the condition “id starts with prefix”. Rarely used.
UiSelector.idEndsWith(suffix)
suffix{string} Id suffix
Add the condition “id ends with suffix”. Rarely used.
UiSelector.idMatches(reg)
reg{Regex | string} Regular expression that the id should match
Add the condition “id matches reg”.
Same note as above: if the regex is a string, escape \ as \\. Do not wrap the string with leading/trailing /.
idMatches("[a-zA-Z]+")UiSelector.className(str)
str{string} Class name- Returns {UiSelector} The selector itself (for chaining)
Add the condition “className equals str”.
className indicates the widget type. For example, a text widget class name is android.widget.TextView.
If the class name starts with "android.widget.", you can omit that prefix. For example, you can use className("TextView").
Common widget class names:
android.widget.TextViewText widgetandroid.widget.ImageViewImage widgetandroid.widget.ButtonButton widgetandroid.widget.EditTextInput widgetandroid.widget.AbsListViewList widgetandroid.widget.LinearLayoutLinear layoutandroid.widget.FrameLayoutFrame layoutandroid.widget.RelativeLayoutRelative layoutandroid.support.v7.widget.RecyclerViewUsually a list widget as well
UiSelector.classNameContains(str)
str{string} Substring to contain
Add the condition “className contains str”.
UiSelector.classNameStartsWith(prefix)
prefix{string} Prefix
Add the condition “className starts with prefix”.
UiSelector.classNameEndsWith(suffix)
suffix{string} Suffix
Add the condition “className ends with suffix”.
UiSelector.classNameMatches(reg)
reg{string} | {Regex} Regular expression to match
Add the condition “className matches reg”.
For regular expressions, see: Regular Expression - RUNOOB.
Note: if the regex is provided as a string, you must escape backslashes with \\ (Java-style), e.g. textMatches("\\d+") matches multi-digit numbers. If you use JavaScript regex syntax, you don't need that, e.g. textMatches(/\d+/). When using string form, do not wrap it with leading and trailing / like textMatches("/\\d+/"), otherwise the starting and ending / will be ignored.
UiSelector.packageName(str)
str{string} Package name- Returns {UiSelector} The selector itself (for chaining)
Add the condition “packageName equals str”.
packageName is the package name of the app that the widget belongs to. For example, Auto.js Pro's package is "org.autojs.autojspro", so widgets on Auto.js Pro screens have packageName="org.autojs.autojspro".
To get an app's package name, you can call app.getPackageName(), for example toast(app.getPackageName("WeChat")).
UiSelector.packageNameContains(str)
str{string} Substring to contain
Add the condition “packageName contains str”.
UiSelector.packageNameStartsWith(prefix)
prefix{string} Prefix
Add the condition “packageName starts with prefix”.
UiSelector.packageNameEndsWith(suffix)
suffix{string} Suffix
Add the condition “packageName ends with suffix”.
UiSelector.packageNameMatches(reg)
reg{string} | {Regex} Regular expression to match
Add the condition “packageName matches reg”.
For regular expressions, see: Regular Expression - RUNOOB.
UiSelector.bounds(left, top, right, bottom)
left{number} Distance from the widget's left edge to the screen's left edgetop{number} Distance from the widget's top edge to the screen's top edgeright{number} Distance from the widget's right edge to the screen's left edgebottom{number} Distance from the widget's bottom edge to the screen's top edge
The bounds of a widget is the rectangle it occupies on the screen. You can locate a widget by its bounds. While this can be accurate on static pages, it does not adapt to different screen resolutions, and it works poorly on dynamic pages such as lists. Therefore this selector is not recommended.
The four numbers must be exact; otherwise the widget won't match. For example, to click the plus icon at the top-right of QQ main screen, inspect its bounds via layout analysis:
If the bounds are (951, 67, 1080, 196), then bounds(951, 67, 1080, 196).clickable().click() clicks it.
UiSelector.boundsInside(left, top, right, bottom)
left{number} Distance from region left edge to the screen's left edgetop{number} Distance from region top edge to the screen's top edgeright{number} Distance from region right edge to the screen's left edgebottom{number} Distance from region bottom edge to the screen's top edge
Add the condition “bounds is inside the region defined by left, top, right, bottom”.
This is used to restrict the search to a region. For example, to find a TextView in the upper half of the screen:
var w = className("TextView").boundsInside(0, 0, device.width, device.height / 2).findOne();
log(w.text());Here device.width is the screen width and device.height is the screen height.
UiSelector.boundsContains(left, top, right, bottom)
left{number} Distance from region left edge to the screen's left edgetop{number} Distance from region top edge to the screen's top edgeright{number} Distance from region right edge to the screen's left edgebottom{number} Distance from region bottom edge to the screen's top edge
Add the condition “bounds contains the region defined by left, top, right, bottom”.
This can be used to require that a widget's bounds contains a given area. For example, given a point (500, 300), to find a clickable widget at that point:
var w = boundsContains(500, 300, 500, 300).clickable().findOne();
w.click();UiSelector.drawingOrder(order)
order{number} Drawing order within its parent
Add the condition “drawingOrder equals order”.
drawingOrder is the drawing order inside a parent, and can be used to distinguish widgets at the same level.
Only available on Android 7.0+.
UiSelector.clickable([b = true])
b{Boolean} Whether the widget is clickable
Add a condition on whether a widget is clickable. Note that not all widgets with clickable=false are truly not clickable; it depends on implementation. For many custom widgets (e.g. class android.view.View), clickable may be false but the widget can still be clicked.
You can omit b to mean “clickable widgets”. For example, className("ImageView").clickable() matches clickable image widgets, while className("ImageView").clickable(false) matches non-clickable ones.
UiSelector.longClickable([b = true])
b{Boolean} Whether the widget is long-clickable
Add a condition on whether a widget is long-clickable.
UiSelector.checkable([b = true])
b{Boolean} Whether the widget is checkable
Add a condition on whether a widget is checkable. “Checkable” usually refers to checkboxes, for example the checkbox in the top-left when selecting multiple photos.
UiSelector.checked([b = true])
b{Boolean} Whether the widget is checked
Add a condition on whether a widget is checked. Useful for filtering checkboxes, switches, etc.
UiSelector.selected([b = true])
b{Boolean} Whether the widget is selected
Add a condition on whether a widget is selected. For example, in QQ chat, when you tap the “emoji” button and your emoji panel shows up, the emoji button is in selected state (selected=true).
UiSelector.enabled([b = true])
b{Boolean} Whether the widget is enabled
Add a condition on whether a widget is enabled. Most widgets are enabled (enabled=true). Disabled widgets are usually gray and not clickable.
UiSelector.scrollable([b = true])
b{Boolean} Whether the widget is scrollable
Add a condition on whether a widget is scrollable (vertical or horizontal).
You can use this to find a scrollable widget and scroll the UI. For example, to scroll the Auto.js script list:
className("android.support.v7.widget.RecyclerView").scrollable().findOne().scrollForward();
// Or: classNameEndsWith("RecyclerView").scrollable().findOne().scrollForward();UiSelector.editable([b = true])
b{Boolean} Whether the widget is editable
Add a condition on whether a widget is editable. Typically editable widgets are input fields (EditText), but not every EditText is editable.
UiSelector.multiLine([b = true])
b{Boolean} Whether a text/input widget is multi-line
Add a condition on whether a text/input widget is multi-line.
UiSelector.focusable([b = true])
b{Boolean} Whether the widget can take focus
Add a condition on whether a widget can take focus.
UiSelector.focused([b = true])
b{Boolean} Whether the widget is currently focused
Add a condition on whether a widget is focused.
UiSelector.contextClickable([b = true])
b{Boolean} Whether the widget supports context click
Add a condition on whether a widget supports context click.
UiSelector.dismissable([b = true])
b{Boolean} Whether the widget is dismissable
Add a condition on whether a widget can be dismissed.
UiSelector.contentInvalid([b = true])
b{Boolean} Whether the widget content is invalid
Add a condition on content validity.
UiSelector.password([b = true])
b{Boolean} Whether the widget is a password field
Add a condition on whether the widget is a password input.
UiSelector.visibleToUser([b = true])
b{Boolean} Whether the widget is visible to the user
Add a condition on whether the widget is visible. Commonly used to exclude invisible nodes.
UiSelector.depth(d)
d{number} Layout depth
Add a condition on layout depth. Useful for narrowing the search scope in complex layouts.
UiSelector.indexInParent(index)
index{number} Index within parent
Add a condition on the widget's index within its parent.
UiSelector.row(row)
row{number} Row index
Add a condition on row index.
UiSelector.rowCount(count)
count{number} Row count
Add a condition on row count.
UiSelector.rowSpan(span)
span{number} Row span
Add a condition on row span.
UiSelector.column(column)
column{number} Column index
Add a condition on column index.
UiSelector.columnCount(count)
count{number} Column count
Add a condition on column count.
UiSelector.columnSpan(span)
span{number} Column span
Add a condition on column span.
UiSelector.findOne()
- Returns {UiObject}
Search the screen using the current selector conditions until a matching widget appears, then return that widget. If it is not found, it will retry when the screen content changes, until it is found.
Note: if the described widget never appears, this function blocks until it does. Therefore it does not return null.
This function should have been named untilFindOne(), but it cannot be changed due to legacy reasons. If you want to search only once instead of waiting, use findOnce().
If multiple widgets match, findOne() uses depth-first search (DFS) and returns the first widget found by that algorithm. The traversal order can matter sometimes.
UiSelector.findOne(timeout)
timeout{number} Search timeout in milliseconds- Returns {UiObject}
Search the screen using the current selector conditions until a matching widget appears and return it. If no matching widget is found within timeout milliseconds, stop searching and return null.
This is like findOne() without arguments, but with a time limit.
Example:
// Launch Auto.js
launchApp("Auto.js");
// Find the log icon within 6 seconds
var w = id("action_log").findOne(6000);
// Click if found
if(w != null){
w.click();
}else{
// Otherwise show a hint
toast("Log icon not found");
}UiSelector.findOnce()
- Returns {UiObject}
Search the screen once using the current selector conditions. Return the matching widget if found; otherwise return null.
UiSelector.findOnce(i)
i{number} Index
Search the screen once using the current selector conditions, and return the ((i + 1))-th matching widget. If no widget is found, or the number of matches is (\lt i), return null.
The match order is determined by the DFS traversal order.
UiSelector.find()
- Returns {UiCollection}
Search the screen once using the current selector conditions, and return a collection of all matching widgets. This search runs only once and is not guaranteed to find anything, so the returned collection may be empty.
Unlike findOne() / findOnce() which return a single widget, find() returns a widget collection that you can operate on.
You can use empty() to check whether the collection is empty. Example:
var c = className("AbsListView").find();
if(!c.empty()){
toast("Found");
}else{
toast("Not found");
}UiSelector.untilFind()
- Returns {UiCollection}
Search the screen using the current selector conditions until at least one match is found, then return a collection of all matching widgets.
Unlike find(), this function never returns an empty collection. However, if no matching widget ever appears, it blocks indefinitely.
UiSelector.exists()
- Returns {Boolean}
Check whether there exists a widget matching the selector conditions. For example, to perform an action when a certain text appears:
if (text("Some text").exists()) {
// do something
}UiSelector.waitFor()
Wait until a widget matching the selector conditions appears. This function blocks until the widget is found.
For example, to wait for a text widget containing "hahaha":
textContains("hahaha").waitFor();UiSelector.filter(f)
f{Function} Filter function. Parameter isUiObject, return value is boolean.
Add a custom filter to the selector.
For example, to find all TextView widgets whose text length is 10:
var uc = className("TextView").filter(function(w){
return w.text().length == 10;
});