Selectors
Purpose
Define the NodeMatcher contract used across execution payloads, explain how CLI selector flags map into that contract, and document the mutual-exclusion rules that prevent ambiguous selector input.
Sources
- Contract shape:
apps/node/src/contracts/selectors.ts - Shared matcher limits:
apps/node/src/contracts/limits.ts - Execution validation:
apps/node/src/domain/executions/validateExecution.ts - CLI selector parsing:
apps/node/src/cli/selectorFlags.ts read-valuelabel selector handling:apps/node/src/cli/registry.ts
NodeMatcher Contract
The raw selector object shared by matcher, container, expectedNode, and labelMatcher is:
{
"resourceId": "optional string",
"role": "optional string",
"textEquals": "optional string",
"textContains": "optional string",
"contentDescEquals": "optional string",
"contentDescContains": "optional string"
}
Meaning of each field:
| Field | Match behavior |
|---|---|
resourceId |
exact Android resource ID match |
role |
exact accessibility role match |
textEquals |
exact visible-text match |
textContains |
substring visible-text match |
contentDescEquals |
exact content-description match |
contentDescContains |
substring content-description match |
Rules enforced by Node:
- a matcher may contain one field or several fields
- multiple fields combine into one object, so the runtime receives all of them together
- empty matcher objects are invalid
- each matcher string value must be at most
512characters (LIMITS.MAX_MATCHER_VALUE_LENGTH) - blank strings are rejected before or during validation depending on how the matcher was built
- selector objects are strict in execution validation, so unknown keys are rejected
- common input aliases are normalized before that strict validation runs, including
id/resource_id,text,text_contains,content_desc,description, andaccessibility_label - when the shared CLI parser sees no selector flags at all, it returns an empty matcher object and the command decides whether selectors are required for that command
Accepted raw JSON matcher-field aliases:
| Alias | Canonical field |
|---|---|
id, resource_id |
resourceId |
text |
textEquals |
text_contains |
textContains |
content_desc, description, accessibility_label |
contentDescEquals |
content_desc_equals |
contentDescEquals |
content_desc_contains, description_contains, accessibility_label_contains |
contentDescContains |
Concrete payload example:
{
"matcher": {
"role": "button",
"textContains": "Settings"
}
}
Success condition for that selector object:
- the object uses only the six supported matcher keys
- at least one value is non-empty
Where Selectors Appear
NodeMatcher is reused in several action parameters:
| Action parameter | Meaning |
|---|---|
params.matcher |
primary target node for actions such as click, read_text, enter_text, wait_for_node, scroll_until, and scroll_and_click |
params.container |
optional ancestor or scrollable container constraint |
params.expectedNode |
navigation target for wait_for_navigation |
params.labelMatcher |
label node for read_key_value_pair |
Example execution fragment:
{
"id": "read-1",
"type": "read_text",
"params": {
"matcher": { "textEquals": "Battery" },
"container": { "resourceId": "android:id/list" }
}
}
CLI Selector Forms
For most commands, the CLI offers two equivalent ways to build a NodeMatcher:
- Shorthand flags such as
--text,--text-contains,--id,--desc,--desc-contains, and--role - Raw JSON via
--selector '<json>'
Agent-friendly CLI aliases accepted for shorthand selectors:
--resource-id->--id--content-desc->--desc--content-desc-contains->--desc-contains--container-resource-id->--container-id--container-content-desc->--container-desc--container-content-desc-contains->--container-desc-contains
Container selectors follow the same pattern:
- Shorthand flags such as
--container-text,--container-id, and--container-role - Raw JSON via
--container-selector '<json>'
The parser resolves shorthand flags into the same NodeMatcher object used by raw JSON. For example:
clawperator wait --text "Done" --role button
becomes the matcher:
{
"textEquals": "Done",
"role": "button"
}
Selector Flags
| Flag | Description | Notes |
|---|---|---|
--selector |
Raw NodeMatcher JSON for an element. | Mutually exclusive with shorthand element selector flags. |
--text |
Match an element by exact text. | May be combined with other shorthand selector flags. |
--text-contains |
Match an element by partial text. | May be combined with other shorthand selector flags. |
--id |
Match an element by resource id. | May be combined with other shorthand selector flags. |
--desc |
Match an element by exact content description. | May be combined with other shorthand selector flags. |
--desc-contains |
Match an element by partial content description. | May be combined with other shorthand selector flags. |
--role |
Match an element by accessibility role. | May be combined with other shorthand selector flags. |
--container-selector |
Raw NodeMatcher JSON for a container. | Mutually exclusive with raw selector JSON on the same matcher. |
--container-text |
Match a container by exact text. | Mutually exclusive with raw selector JSON on the same matcher. |
--container-text-contains |
Match a container by partial text. | Mutually exclusive with raw selector JSON on the same matcher. |
--container-id |
Match a container by resource id. | Mutually exclusive with raw selector JSON on the same matcher. |
--container-desc |
Match a container by exact content description. | Mutually exclusive with raw selector JSON on the same matcher. |
--container-desc-contains |
Match a container by partial content description. | Mutually exclusive with raw selector JSON on the same matcher. |
--container-role |
Match a container by accessibility role. | Mutually exclusive with raw selector JSON on the same matcher. |
Mutual Exclusion And Validation Rules
Element selector rules:
--selectoris mutually exclusive with all shorthand element selector flags- duplicate value flags such as repeating
--textor--idare rejected --selectormust be valid JSON--selectormust parse to a JSON object, not an array or scalar- blank values such as
--text ""or--selector ""are rejected click --coordinate <x> <y>is mutually exclusive with every element selector flagclick --coordinate ... --focusis invalid because coordinate clicks do not supportclickType = "focus"
Container selector rules:
--container-selectoris mutually exclusive with all--container-*shorthand flags- duplicate container flags such as repeating
--container-idare rejected --container-selectormust be valid JSON--container-selectormust parse to a JSON object- blank values such as
--container-text ""are rejected - if no container flags are present, Node omits
params.container
Validation examples:
Valid:
clawperator read --text "Price" --container-id "android:id/list" --json
Invalid:
clawperator read --text "Price" --selector '{"textEquals":"Price"}'
Why invalid:
- the CLI parser rejects mixing
--selectorwith shorthand element flags
Structured validation example:
{
"code": "EXECUTION_VALIDATION_FAILED",
"message": "use --selector OR the simple flags, not both"
}
Command-Specific Notes
Most commands
click, read, wait, scroll-until, scroll-and-click, and wait-for-nav all use the shared selector parser from selectorFlags.ts. That means they share the same shorthand-to-JSON mapping and the same mutual-exclusion rules.
Required-vs-optional behavior is decided by the command after parsing:
click,read, andwaitrequire an element selector unlessclickis using--coordinatewait-for-navaccepts either--appor a selector, but still requires at least one of themscrollhas no target selector and uses only optional container selector flagsscroll-untilandscroll-and-clickrequire a target selector
type
type is slightly different because --text means “text to enter”, not “textEquals selector”. For element targeting, type uses:
--id--desc--desc-contains--role--text-contains--selector
Example:
clawperator type "hello world" --role textfield
read-value
read-value does not use the general selector parser. It builds labelMatcher from three dedicated flags:
| CLI flag | labelMatcher field |
|---|---|
--label |
textEquals |
--label-id |
resourceId |
--label-desc |
contentDescEquals |
Accepted CLI aliases for those read-value label flags:
--textand--label-text->--label--idand--resource-id->--label-id--descand--content-desc->--label-desc
At least one of those flags is required.
Blank label values are rejected, and if you provide none of the three label flags the command returns a usage error before execution is built.
Raw JSON aliases for read_key_value_pair.params.labelMatcher follow the same NodeMatcher alias table shown above. Raw payload aliases label_matcher and label_selector are normalized to labelMatcher before validation.
Concrete execution fragment:
{
"id": "read-value-1",
"type": "read_key_value_pair",
"params": {
"labelMatcher": {
"textEquals": "Battery"
}
}
}
Container Matching Semantics
Container selectors narrow an action to a matched ancestor or scrollable region instead of searching the full screen.
Current uses:
readattaches the matcher asparams.containerscrollattaches the matcher asparams.containerscroll_untilattaches the matcher asparams.containerscroll_and_clickattaches the matcher asparams.container
If no container selector is provided:
read_textsearches without container scoping- scroll actions let Android choose the relevant on-screen scrollable container
Example:
clawperator scroll-until --text "About phone" --container-id "android:id/list"
becomes:
{
"type": "scroll_until",
"params": {
"matcher": { "textEquals": "About phone" },
"container": { "resourceId": "android:id/list" }
}
}
JSON Examples
Exact text:
{ "textEquals": "Wi-Fi" }
Partial text plus role:
{ "textContains": "Sign", "role": "button" }
Raw container selector:
{ "resourceId": "android:id/list" }
Navigation target:
{
"expectedPackage": "com.android.settings",
"expectedNode": { "textEquals": "Settings" }
}
CLI Examples
clawperator click --text "Wi-Fi"
clawperator read --selector '{"resourceId":"android:id/title"}' --json
clawperator wait --text-contains "Done" --timeout 10000 --json
clawperator scroll-until --text "About phone" --container-id "android:id/list"
clawperator read-value --label "Battery" --json