# Find and control app elements

UIHelper locates controls by their properties instead of hard-coded screen positions. Start with the inspector or MCP `observe_screen`, inspect the screenshot, and test the suggested selector before saving it in a script.

## Pin the app and check the target

The identifiers below are examples, not universal app identifiers.

```js
const bundleId = "com.example.app";
if (!openApp(bundleId)) throw new Error("Could not open target app");
const ui = UIHelper.forApp(bundleId);
const matches = ui.findElements({id: "searchField", visible: true, enabled: true});
if (matches.length !== 1) throw new Error("Expected one search field");
matches[0].setText("Hello");
```

`forApp()` does not launch an app. Without it, `UIHelper` uses the foreground app. Most actions require the target app to be foreground. If the helper is blacklisted, use the appropriate screen-based APIs or change the blacklist deliberately.

## Choose selectors

| Selector | Meaning |
| --- | --- |
| `{id: "search"}` | Exact accessibility identifier; `identifier` and `accessibilityId` are aliases |
| `{type: "Button", label: "Continue"}` | Type and exact label must both match |
| `{textContains: "Order #"}` | Substring match |
| `{label: "continue", caseInsensitive: true}` | Case-insensitive text match |
| `{visible: true, enabled: true}` | Require visible and enabled state |
| `{anyOf: [{id: "ready"}, {id: "error"}]}` | Either branch may match |
| `{allOf: [{type: "Button"}, {selected: true}]}` | All branches must match |

Other exact keys include `name`, `text`, `value`, `className` (alias `class`), `selected`, `accessible`, `focused`, `index`, `x`, `y`, `width`, `height` and `rect.width`-style geometry keys. Contains filters also exist for name, identifier, label and value. `name` uses the identifier, falling back to the label; a string selector is shorthand for `{name: string}`.

Types accept `Button` or `XCUIElementTypeButton`. `className` means the actual native class. Text filters are case-sensitive by default. Case-insensitive options do not change type, class or explicit predicates. `index` is a child position, not a durable identifier.

Predicates accept documented attributes and boolean comparisons, for example `{predicate: "label CONTAINS[c] 'search' AND visible == true"}`. Type comparisons inside predicates use full names. Arbitrary function expressions and ANY/ALL modifiers are unsupported. Prefer ordinary selectors when they express the same intent.

Groups accept 1..32 selectors, with at most 8 nesting levels and a combined 256-node/key budget. Keep them simple.

## Wait for state, then act once

```js
const ui = UIHelper.forApp("com.example.app");
ui.waitForElement({id: "continueButton", visible: true, enabled: true}, 5000).tap();
ui.waitForGone({id: "loading", visible: true}, 10000);
ui.waitForElement({id: "resultScreen", visible: true}, 5000);
```

Explicit waits default to 5000 ms and 100 ms between polls. Options accept `{timeout, interval, throwOnTimeout}`. `throwOnTimeout:false` returns null for an optional element wait, or false for waitForGone. Helper failures, stale roots, invalid selectors and cancellation still throw. A helper call can outlast the polling deadline.

`findElement()` returns the first match, not proof of uniqueness. `waitForAny()` selects by tree order, not the order of your selectors. `waitForAll()` returns matches in input order; the same element can satisfy several selectors.

## Scope repeated controls

```js
const panel = UIHelper.waitForElement({id: "profilePanel"}, 5000);
panel.waitForElement({id: "editButton", visible: true}, 3000).tap();
```

Element queries search descendants. Reacquire the parent after navigation or view recycling. `snapshot` is cached data from a query; `getAttributes()` fetches fresh fields together.

## Scrolling and text

`element.scroll("down")` changes a native scroll view’s position without simulating a finger. It may look instantaneous. Use `swipe()` if the test requires a touch gesture. Verify progress and limit attempts; reaching the end can return successfully without moving.

`setText()` replaces a supported field’s value. `typeText()` inserts text, optionally with `{interval:80}` milliseconds per Unicode code point. Check the existing value after an interruption to avoid duplicate input. Do not log passwords or secure-field contents.

## References and visibility

A JavaScript element reference belongs to the app process that created it. A navigation, restart or recycled view can invalidate it. MCP observation references such as `e4` also belong to a particular observation, nominally expire after 60 seconds, and may be evicted earlier. Saved scripts should use selectors instead.

Visibility checks cover frames, clipping and hidden state, not every overlapping window. A successful tap is not proof the intended transition happened. Inspect a fresh screen when uncertain, and use [known interruption handlers](/doc/recovery) rather than tapping through overlays.
