Driver & selectors¶
The common interface every backend satisfies, and the shared types a selector resolves against.
bajutsu.drivers.base
¶
Driver abstraction — the linchpin shared by every backend (idb / fake).
Frozen first because everything else depends on it: - common types Point / Element / Selector - the Driver Protocol (only the actuator performs actions) - selector resolution (the determinism core): a single action requires a unique match, and an ambiguous match (2+) raises AmbiguousSelector to rule out nondeterminism structurally.
Capability
¶
Capability names returned by Driver.capabilities().
Used to pick the actuator and resolve fallbacks. A backend with SEMANTIC_TAP actuates more stably (no coordinates involved).
Element
¶
Bases: TypedDict
A single on-screen element, normalized from idb output.
Trait
¶
Normalized accessibility traits used by state assertions.
Drivers normalize at least the following to these common tokens.
Selector
¶
Bases: TypedDict
How to address an element. Provided fields are combined with AND.
The stable selector is id (non-localized, data-derived). label /
labelMatches are auxiliary; index is a last resort (flaky).
Driver
¶
Bases: Protocol
Common interface for every backend.
Actions (tap/type/swipe/wait/query) are performed by the actuator only. On a backend without semantic tap (e.g. idb), the abstraction resolves the frame center via query() / resolve_unique() and taps by coordinates.
EvidenceProvider
¶
Bases: Protocol
A read-only evidence source from a non-actuator backend (BE-0020).
A multi-backend run keeps actuation on the one actuator and may consult another same-platform
backend read-only to fill an evidence gap the actuator lacks (e.g. idb has no native network,
so a second iOS actuator supplies it). The narrow surface — capabilities plus observation
methods only, never tap / type / swipe / wait / query — makes "the fallback never
actuates" a type-level fact rather than a convention.
BackendLifecycle
¶
Bases: Protocol
The full set of lifecycle hooks backends run around a single run (BE-0141).
A run launches, tears down, and resets a backend, but those steps are platform-shaped: the web
(Playwright) backend navigates / closes / resets a browser context, the XCUITest backend waits
for its on-device runner to answer, and idb needs none of them (its boot / erase / install
sequence lives outside the driver in simctl). The four hooks are therefore split disjointly
across backends — no single driver implements all four — so this is a typing umbrella for the
call sites, not a conformance target: the platform_lifecycle environments reach each hook through
cast(BackendLifecycle, driver) under the platform invariant that already scopes the driver,
which turns "the hook exists" into a mypy-checked fact (a renamed or dropped hook fails
make check instead of at runtime) without forcing idb to stub no-op methods. @runtime_checkable
mirrors EvidenceProvider, but a structural isinstance holds only for a class implementing the
whole set — which the concrete drivers, owning disjoint subsets, deliberately do not.
SelectorError
¶
Bases: Exception
Selector resolution failed.
UnsupportedAction
¶
Bases: Exception
The actuator backend cannot perform this action.
For example, a multi-touch gesture on idb, which is single-touch. The tool surfaces it as a step failure with a clear reason rather than letting it pass silently.
ManualStepRequired
¶
Bases: UnsupportedAction
A recorded manual takeover step has no deterministic run-time equivalent (BE-0185).
Raised at run time so a human-takeover marker (a CAPTCHA, a biometric prompt) fails loudly and
visibly with its label rather than a silent pass or a hang — the honest boundary for an operation
only a human can perform. A subclass of UnsupportedAction so the run loop surfaces it as a
clean, labeled step failure like any other action the environment cannot perform.
ElementNotFound
¶
Bases: SelectorError
No candidate matched. A wait times out; an immediate action fails.
AmbiguousSelector
¶
Bases: SelectorError
2+ candidates with no way to disambiguate; needs within or index.
Queryable
¶
Bases: Protocol
Just the current-screen read a wait needs — the query surface, not a full Driver.
default_wait_for reads one screen and matches; a shared read base like CoordinateTreeDriver
supplies exactly that without implementing the whole actuator surface, so typing the helper to
this narrow protocol lets both a full Driver and such a base delegate to it.
permission_capability(service)
¶
The per-service device-control token for a permission service (BE-0276).
One token per vocabulary entry rather than a single deviceControl.permissions token, so a
backend that honors only part of the vocabulary (iOS: everything but notifications) can
advertise exactly that subset and preflight names the unsupported service individually.
id_candidates(v)
¶
A single id/pattern or a list of OR candidates, normalized to a list (BE-0221).
validate_id_candidates(field, value)
¶
Reject a malformed id / idMatches OR-candidate list; a no-op for a string or None (BE-0221).
Shared by the scenario Selector model and config's readyWhen (a base.Selector) so a
candidate list is checked the same way wherever it is authored. A list must be non-empty with no
blank entry, and if it contains any dotted (SPEC-form) candidate, that candidate must lead:
single-id consumers — the resolver's representative pick, audit coverage bucketing
(namespace_of splits on .), the XCUITest / Playwright codegen emitters — take candidate[0], so
a dotted-but-not-first list resolves fine at runtime but silently skews them. Failing at load
beats debugging a skewed report. An all-underscore list (no dotted candidate) is accepted as-is.
Raises:
| Type | Description |
|---|---|
ValueError
|
the list is empty / has a blank entry, or a dotted candidate follows a non-dotted first one. |
matches(el, sel)
¶
Whether an element satisfies a selector's per-element conditions (all AND-ed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
el
|
Element
|
One element from a |
required |
sel
|
Selector
|
The selector to test. Only the per-element fields are checked here
( |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when every per-element field set on the selector matches the element. |
find_all(elements, sel)
¶
Every element matching the selector — backs idMatches resolution and count assertions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
list[Element]
|
One |
required |
sel
|
Selector
|
The selector to match. |
required |
Returns:
| Type | Description |
|---|---|
list[Element]
|
The matching elements, in |
deadline_ticks(timeout, poll_init, poll_max=None)
¶
Yield once per poll to a monotonic deadline, sleeping with capped backoff between ticks.
The one deadline/backoff skeleton the condition waits share (BE-0118, BE-0256): wait_until
here and the platform-lifecycle readiness waits (_await_ready / _await_boot) each run their
own check body on every yield and decide what to return, while this owns only the monotonic
deadline, the exponential backoff (poll_init doubling up to poll_max), and the
never-sleep-past-the-deadline sleep — a condition wait with no fixed up-front sleep, so a
timeout means the same real seconds regardless of the caller. A fixed interval is
poll_max is None (or equal to poll_init); the first yield fires before any sleep.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
Seconds from the first tick before the deadline passes. |
required |
poll_init
|
float
|
The first inter-tick sleep, doubling each tick. |
required |
poll_max
|
float | None
|
The backoff ceiling; a fixed |
None
|
wait_until(driver, sel, timeout, poll=0.2)
¶
Poll driver.wait_for(sel) against a monotonic deadline until it matches.
A condition wait with no fixed sleep, mirroring the orchestrator's discipline — it turns the
backend's single-shot wait_for into a timeout-honouring wait over deadline_ticks, so a
timeout means the same real seconds regardless of which backend drives.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
driver
|
Driver
|
The backend whose single-shot |
required |
sel
|
Selector
|
The selector to wait for. |
required |
timeout
|
float
|
Seconds to keep polling before giving up. |
required |
poll
|
float
|
Seconds slept between checks. |
0.2
|
Returns:
| Type | Description |
|---|---|
bool
|
True once the selector matches; False if |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
resolve_unique(elements, sel)
¶
Resolve a selector to exactly one element for a single action.
A single action requires a unique match, so an ambiguous selector fails rather than acting on "whatever matched first" — the determinism core (BE-0001).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elements
|
list[Element]
|
One |
required |
sel
|
Selector
|
The selector to resolve. |
required |
Returns:
| Type | Description |
|---|---|
Element
|
The one element the selector resolves to. |
Raises:
| Type | Description |
|---|---|
ElementNotFound
|
Nothing matched, or |
AmbiguousSelector
|
Two or more matched and no |
default_wait_for(driver, sel)
¶
The single-shot wait_for body every real backend delegates to (BE-0118, BE-0251).
Whether sel matches the driver's current screen, checked once — the shared wait_until
owns the deadline poll, so a backend never loops here. Hoisted into one definition so the
identical driver bodies can't silently diverge; a backend that can wait natively still overrides
wait_for rather than calling this.
Returns:
| Type | Description |
|---|---|
bool
|
True when at least one element matches the selector right now. |
frame_center(frame)
¶
The center point of an already-resolved element frame (BE-0251).
Takes the resolved (x, y, w, h) so it stays pure geometry — each backend keeps its own
selector-to-frame resolution and routes only the arithmetic through here.
gesture_anchor(frame)
¶
A two-finger gesture's center and finger half-distance for a resolved frame (BE-0251).
The half-distance is a quarter of the smaller side, so the two fingers (and a pinch-out up to ~2x) stay within the element's bounds rather than landing on a neighbour.
Returns:
| Type | Description |
|---|---|
tuple[float, float, float]
|
|