Scenario schema¶
The scenario model (steps, assertions, preconditions) and the loaders that parse a *.yaml,
expand its component / data references, and serialize it back.
bajutsu.scenario
¶
Scenario spec — the structure normalized from natural language.
The package keeps the data shapes (models), parsing (load), compile-time expansion
(expand), tag selection (select), and serialization (serialize) in separate modules so a
change to one rarely touches the others (BE-0043). The full public API is re-exported here, so
from bajutsu.scenario import Scenario, load_scenarios, ... keeps working unchanged.
Assertion
¶
Bases: _Model
One machine check. Exactly one kind may be set.
Background
¶
Bases: _Model
Send the app to the background, as pressing the Home button does.
Backgrounds without terminating (SpringBoard is brought to the front), so the app's state
survives for a later foreground.
CaptureRule
¶
Bases: _Model
A capturePolicy rule — capture the artifacts in capture when its on trigger fires.
ClearClipboard
¶
Bases: _Model
Clear the Simulator's pasteboard.
ClearKeychain
¶
Bases: _Model
Reset the Simulator's keychain (saved passwords, certificates).
ClearStatusBar
¶
Bases: _Model
Remove any status bar overrides (restore the live status bar).
ClipboardMatch
¶
Bases: _Model
clipboard: verify what the app copied to the pasteboard — exactly one of equals / matches.
Read off the device (simctl pbpaste), so it needs the per-device control channel and is
unavailable on the fake driver / in parallel runs.
Component
¶
Bases: _Model
A reusable, parameterized sequence of steps.
params are the names a caller must supply via use: { with: {...} }; the steps reference them
as ${params.<name>}.
CountMatch
¶
Bases: _Model
count: exactly one of equals / atLeast / atMost.
CountOp
¶
Bases: _Model
A count comparison with no element selector — exactly one of equals / atLeast / atMost.
The element-free counterpart to CountMatch, for aggregating over the network timeline (e.g. an
event's multiplicity) rather than over screen elements.
DismissAlerts
¶
Bases: _Model
Per-scenario control of the system-alert guard.
The vision-based dismissal of OS prompts (e.g. iOS "Save Password?", a permission request) that
idb cannot see or tap. The guard is ON by default and fires only when a step (or expect) is blocked: it
screenshots, asks the locator where to tap, taps the prompt away, and retries once.
Two on-disk forms (the bare boolean is shorthand for { enabled: <bool> }):
dismissAlerts: false — disable the guard for this scenario
dismissAlerts: { instruction: "..." } — keep it on, but tap the named button
(e.g. "tap Allow" to grant a prompt)
Drag
¶
Bases: _Model
drag action — a real pointer drag of an element (on) in a direction (BE-0227).
Where swipe's directional form scrolls (revealing off-screen content), drag grabs the
element and moves it — a resize divider, a slider thumb, a reorder handle, a map inside a canvas:
any control you drag rather than scroll. amount sets how far to travel as a fraction of the
screen (0 < amount ≤ 1); omitted, a small default distance is used. It matters on the web
backend, where a directional swipe is a wheel scroll (which does not move a grabbed element)
but a drag is a genuine pointer drag (move → down → move → up). On iOS / Android a real OS
drag both scrolls and moves handles, so swipe's directional form and drag coincide there.
Email
¶
Bases: _Model
email — poll a mailbox until a matching message arrives, extract a value into ${vars.*}.
match selects the awaited message, extract pulls the value, and timeout (seconds, required)
bounds the poll — a condition wait, never a fixed sleep (BE-0046). The mailbox endpoint lives in
config (targets.<name>.mailbox), so the scenario stays app-agnostic and credential-free.
EmailExtract
¶
Bases: _Model
Pull a value from the matched message body into ${vars.<var>} via a regex.
bodyMatches is a regex; its first capturing group (or the whole match, if it has none) is the
value written to var. A matched message whose body the regex does not hit fails the step.
EmailMatch
¶
Bases: _Model
Which message email waits for: recipient and/or subject, AND-ed. At least one is required.
EventMatch
¶
Bases: _EndpointMatch
An analytics / telemetry event the app sent (BE-0048).
Matched over the captured request timeline by endpoint (url / urlMatches / path / pathMatches /
method, AND-ed, same meaning as RequestMatch) and structured request-body fields (body: each
given key must be present in the JSON request body and equal — compared as text — the given
value). count is the expected multiplicity (default: at least one). At least one of an endpoint
criterion or body is required, so an event always pins something.
ExcludeRegion
¶
Bases: _Model
A rectangular region to ignore during visual comparison (e.g. status bar, clock).
Coordinates are in screenshot pixels — a fixed box, stable only while the layout is.
Use SelectorRegion to mask by element instead, which survives reflow and resolution changes.
Exists
¶
Bases: _Model
exists: { <selector>, negate? } (selector inline, optional negate).
Extract
¶
Bases: _Model
Capture a UI element's property into a runtime variable (vars.*).
ForEach
¶
Bases: _Model
Iterate over elements matching a selector.
Each element's identifier is stored as vars.<as> and the nested steps are executed.
Foreground
¶
Bases: _Model
Resume a backgrounded app to the foreground (simctl launch, without terminating it).
The other half of background. It adds no settle sleep — any wait after resuming is the
scenario's own condition wait.
GoldenMatch
¶
Bases: _Model
golden: compare the live element tree against a recorded golden file (BE-0006).
The path is resolved against the golden context's base directory. The comparison is
field-level per BE-0006 rules: exact on identity/state, set-equal on traits, tolerant
(sanity only) on frame geometry.
Gone
¶
Bases: _Model
until: { gone: <Selector> } — wait until a selector no longer matches any element.
HttpRequest
¶
Bases: _Model
Issue an HTTP request (for test-data setup, webhook triggers, API calls).
The response status is checked against status (if given); a mismatch
fails the step. saveBody stores the response body text as
vars.<saveBody> for subsequent ${vars.*} interpolation.
If
¶
Bases: _Model
Conditional execution.
Evaluate an assertion as the condition, then run then steps if it passes or else steps
otherwise.
LongPress
¶
Bases: _Model
longPress action — press and hold a selector for duration seconds.
Manual
¶
Bases: _Model
manual action — a human-takeover marker recorded during record (BE-0185).
Emitted when a blocker is an operation the AI cannot perform (a CAPTCHA, a biometric prompt,
a repeatedly unresolvable gesture): the human operated the device live and record recorded
this marker of the observed transition, not the raw gesture. It has no deterministic run-time
equivalent, so at run time it fails loudly with label — never a silent pass, never a hang
(directives 1 and 2). bypass, when set, names the deterministic bridge an author can wire to
make the step replayable (a test-build flag, a device-control / device-state primitive,
BE-0035 / BE-0052); None means no such bridge exists (a real CAPTCHA).
Mock
¶
Bases: _Model
A deterministic network stub.
When an outgoing request matches match, BajutsuKit returns respond instead of hitting the
network (so tests don't depend on a live server). match reuses the request matcher's
request-side fields (method / url / urlMatches / path / pathMatches / bodyMatches); status /
count do not apply here.
MockResponse
¶
Bases: _Model
The canned response a mock returns (defaults to an empty 200).
Network
¶
Bases: _Model
Per-scenario network settings.
filter scopes which observed requests are interleaved into the report's Steps timeline.
NetworkFilter
¶
Bases: _Model
Which observed requests to interleave into the report's Steps timeline.
With domains set, only exchanges whose URL host matches one of them — exactly or as a
parent suffix (example.com matches api.example.com) — appear in Steps; empty /
unset shows every captured exchange. The Network tab always lists them all.
OverrideStatusBar
¶
Bases: _Model
Override the Simulator's status bar for deterministic screenshots.
All fields are optional; only the provided fields are overridden.
Pinch
¶
Bases: _Model
Two-finger magnify. scale > 1 zooms in, 0 < scale < 1 zooms out.
Preconditions
¶
Bases: _Model
Per-test environment setup.
Push
¶
Bases: _Model
Deliver a simulated push notification (simctl push) to the app under test.
Carries this APNs payload, e.g. {"aps": {"alert": "..."}}.
Redact
¶
Bases: _Model
Redaction config — element labels, network headers, and JSON fields to scrub from evidence.
Each list names items that are zeroed out before evidence is written to the report.
A standard set of credential-bearing headers (authorization, cookie, set-cookie, …)
is masked by default (BE-0130); unmask_headers is the explicit, visible opt-out that
releases a specific default — turning off protection is never the mere absence of redact:.
Relaunch
¶
Bases: _Model
relaunch action — restart the app process, optionally overriding its launch env/args.
RequestMatch
¶
Bases: _EndpointMatch
Network-traffic matcher, shared by the request assertion and until: { request: ... }.
The fields (method / url / urlMatches / path / pathMatches / status / bodyMatches) are AND-ed;
count is how many exchanges matched — exact for the assertion, a lower bound for the wait.
The endpoint can be pinned by url (exact full URL) or urlMatches (regex/substring; query
strings live here), or just the path; bodyMatches checks the request body. At least one
match field is required.
ResponseSchemaMatch
¶
Bases: _Model
Validate a captured response body against a stored JSON Schema (BE-0048).
request selects the exchange whose response is checked (reusing the request matcher); schema
is the schema file, resolved against the app's schemas dir. schema_path carries the value (the
field is aliased schema to avoid shadowing pydantic's own schema attribute).
Rotate
¶
Bases: _Model
Two-finger rotation. radians > 0 rotates clockwise.
Scenario
¶
Bases: _Model
One scenario.
ScenarioFile
¶
Bases: _Model
A scenario file: an optional file-level description plus the scenarios it defines.
Two on-disk forms are accepted: the bare list of scenarios (no file description), or a
{description: "...", scenarios: [...]} mapping.
SelectOption
¶
Bases: _Model
selectOption action — set a native <select> to the option with the given value.
Web-only: a <select> lives in the DOM but has no native counterpart on iOS / Android, so those
backends refuse it (UnsupportedAction). option matches an option's value (not its visible
label), mirroring the value assertion — which reads the <select>'s current value — so a
picked option is directly assertable. A <select>'s dropdown is not in the DOM, so this is how a
web <select> (e.g. the BE-0191 theme picker) is switched deterministically rather than by a
coordinate click.
Selector
¶
Bases: _Model
How to address an element. Provided fields are combined with AND.
id / idMatches accept a single value or a list of candidates; a list matches an element whose
identifier equals (or glob-matches) any candidate — an OR. This lets one shared scenario carry
every platform's form of an id (id: [stable.refresh, stable_refresh]), so it runs unchanged
where the native id syntax differs — e.g. Android's android:id, which allows neither . nor
-, surfaces stable.refresh as stable_refresh (BE-0221). Ambiguity is unchanged: 2+ matching
elements on screen still fail fast.
List the canonical (dotted SPEC) form first. OR matching is order-independent, but single-id
consumers take the first candidate as the representative — first_id(), audit.referenced_ids
coverage bucketing (namespace_of splits on .), and the XCUITest / Playwright codegen emitters.
Leading with the dotted SPEC id keeps their output correct; an underscore-first list would still
resolve at runtime but skew coverage and generate the non-portable id.
as_selector()
¶
Convert to the TypedDict consumed by base.resolve_unique.
first_id()
¶
The primary id candidate (the first when id is a list), or None (BE-0221).
For single-id consumers — triage rename suggestions, capturePolicy on: matching, the
WebView host — that want one representative id rather than the whole OR set. Selectors list
the canonical (dotted SPEC) form first (see the class docstring), so "first" is the portable id.
SelectorRegion
¶
Bases: _Model
An element to ignore during visual comparison, addressed by selector (BE-0171).
Resolved to the element's frame at evaluation time and masked exactly as an ExcludeRegion
rectangle is. Robust where a pixel box is not: it follows the element across reflow, resolution,
and locale changes. A selector matching nothing is a masking no-op (nothing on screen to hide);
an ambiguous one fails, like every other selector resolution (prime directive 2).
SetClipboard
¶
Bases: _Model
Seed the Simulator's pasteboard with text (simctl pbcopy), for paste flows.
SetLocation
¶
Bases: _Model
Override the simulated device's GPS location (simctl location set).
Step
¶
Bases: _Model
One action plus optional modifiers (capture / name / extract).
Swipe
¶
Bases: _Model
swipe action — by direction on an element (on), or between two points (from/to).
amount (only with on/direction) sets how far to travel as a fraction of the screen
(0 < amount ≤ 1): ~0.2 nudges, ~0.5 scrolls half a screen, ~0.9 nearly a full one. Omitted, a
small default distance is used — so the caller can dial the scroll to the instruction.
TapPoint
¶
Bases: _Model
tapPoint action — tap a screen location by normalized coordinates (0..1), not a selector.
The bottom rung of the stability ladder (DESIGN §5), for a control the accessibility tree does
not expose as an addressable element — most notably a tab-bar tab on an app with no accessibility
ids, which idb collapses into one opaque group. record's agent locates it in the screenshot
and emits its center here; run replays it against the current screen size. x/y are fractions
of the app window (top-left origin), so the tap survives a resolution change a raw-pixel tap would
not — but it is still coordinate-based and unverifiable by selector, so prefer a real selector
whenever the element is addressable.
TextMatch
¶
Bases: _Model
value / label: exactly one of equals / contains / matches.
Totp
¶
Bases: _Model
totp — generate an RFC 6238 time-based one-time password into ${vars.*} (BE-0046).
secret is the shared base32 key (commonly ${secrets.*}); the current code is written to
into.var for a later type / assert to consume. Local and deterministic — no LLM, no
network, no scripting escape hatch.
Trigger
¶
Bases: _Model
A capturePolicy trigger that fires its CaptureRule when a condition holds.
The condition is exactly one of action / event / result; idMatches narrows an
action trigger to a specific element ID.
TypeText
¶
Bases: _Model
type action — enter text, optionally into a selector and optionally submitting after.
Use
¶
Bases: _Model
Invoke a reusable component, substituting its declared params with with.
The use step is expanded away (replaced by the component's steps) before the run, so it is a
compile-time macro, not a runtime action — determinism is unaffected.
VarTarget
¶
Bases: _Model
into: { var: <name> } — the ${vars.<name>} slot a step writes its produced value to.
VisualMatch
¶
Bases: _Model
Visual regression assertion — compare a screenshot to a baseline image.
By default the whole screen is compared. element (BE-0171) scopes the comparison to one
element's frame: the screenshot is cropped to it and the baseline is that crop, so unrelated
on-screen changes no longer churn the baseline.
Wait
¶
Bases: _Model
wait step — block until a selector appears (for) or a condition holds (until).
Bounded by timeout; always a condition wait, never a fixed sleep.
WaitRequest
¶
Bases: _Model
until: { request: <RequestMatch> } — wait until a matching network exchange has been observed.
Requires the run's network collector to be active.
Web
¶
Bases: _Model
Enter the web context: resolve a native WebView host, then run inner steps against its DOM.
The within selector resolves natively to exactly one WKWebView element; inner steps
address the normalized DOM (data-testid → Element.identifier), not the native a11y tree.
apply_setups(scenarios, default_setup, resolve)
¶
Prepend each scenario's reusable setup prelude, in place.
A scenario's setup precondition (falling back to the app/config default) names a reusable
prelude; those steps run before the scenario's own, so a shared login / navigation flow is
written once and reused. The same reference is resolved at most once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scenarios
|
list[Scenario]
|
The scenarios to prepend setups to; their |
required |
default_setup
|
str | None
|
The setup reference used when a scenario declares none. None means none. |
required |
resolve
|
Callable[[str], list[Step]]
|
Maps a setup reference to its list of steps (e.g. by loading a shared file). |
required |
expand_components(scenarios, resolve, max_depth=25)
¶
Replace every use step with the referenced component's steps, recursively and in place.
Pure compile-time expansion: a component may itself use another, and after this no use
steps remain, so the run loop is unaffected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scenarios
|
list[Scenario]
|
The scenarios to expand; their |
required |
resolve
|
Callable[[str], Component]
|
Maps a component name to its |
required |
max_depth
|
int
|
The deepest |
25
|
Raises:
| Type | Description |
|---|---|
ValueError
|
A required param is missing, an unknown param is passed, a |
expand_data(scenarios, resolve_csv)
¶
Expand each data-driven scenario into one scenario per data row.
${row.<col>} tokens are substituted per row. A scenario with neither data nor dataFile
passes through unchanged. Each derived scenario keeps the original's preconditions (erase
default intact), so every row runs in its own clean environment — isolation is preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scenarios
|
list[Scenario]
|
The scenarios to expand. |
required |
resolve_csv
|
Callable[[str], list[dict[str, str]]]
|
Loads a |
required |
Returns:
| Type | Description |
|---|---|
list[Scenario]
|
The scenarios with every data-driven one replaced by its per-row instances, in order. |
read_csv(text)
¶
Parse CSV text into a list of {column: value} row dicts (header row required).
load_component(text)
¶
Parse a YAML string (a single component mapping) into a validated Component.
load_scenario_file(text)
¶
Parse a scenario file: a list of scenarios, or a {description, scenarios} mapping.
Raises:
| Type | Description |
|---|---|
ValueError
|
The top level is neither a list nor a mapping (§6.1), or the file declares a schema version newer than this bajutsu supports (BE-0119). |
load_scenarios(text)
¶
Parse a scenario file into validated Scenario objects (any file-level description dropped).
contained_ref(root, base, ref)
¶
Resolve a scenario ref (base / ref) and require its real path to stay within root.
The one containment choke point for a scenario's component / data refs (BE-0174): every
device-free resolver routes ref resolution through here, so a scenario cannot make the loader
read a file outside its suite. resolve follows symlinks, so an out-of-root link is caught the
same as a .. chain or an absolute path — the three ways a ref leaves the tree. On rejection the
error names only the offending ref, never the target's contents, so the check happens before
the read and closes the leak as well as the read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
The suite root the ref must stay within (the scenarios dir the load started from). |
required |
base
|
Path
|
The directory refs resolve against (the referring scenario file's directory). |
required |
ref
|
str
|
The |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The resolved real path, ready to read. |
Raises:
| Type | Description |
|---|---|
ValueError
|
The ref is absolute, escapes the root via |
load_expanded_scenarios(path, root=None)
¶
Load a scenario file and expand its components + data rows, resolving refs relative to the file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The scenario file to load. |
required |
root
|
Path | None
|
The suite root every component / data ref must stay within (BE-0174). Defaults to the scenario file's own directory, so a single-file load confines refs to that directory; a suite loader passes the shared scenarios dir instead. |
None
|
Raises:
| Type | Description |
|---|---|
OSError
|
The scenario file or a referenced component / CSV cannot be read. |
ValueError
|
The content is invalid, the YAML does not parse — |
load_scenarios_dir(scenarios_dir)
¶
Every expanded scenario in scenarios_dir's *.yaml files, sorted by filename.
The device-free suite loader shared by coverage on the CLI and in the serve Web UI, so both read
a target's suite identically. The scenarios dir is the containment root passed to each file's
load, so a scenario's refs stay inside the suite (BE-0174).
Raises:
| Type | Description |
|---|---|
(OSError, ValueError)
|
as |
select_scenarios(scenarios, include, exclude)
¶
Filter scenarios by tag, preserving order.
Pure metadata filtering — never mutates or reorders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scenarios
|
list[Scenario]
|
The scenarios to filter. |
required |
include
|
list[str]
|
A scenario is kept only if it carries at least one of these tags; an empty
|
required |
exclude
|
list[str]
|
A scenario carrying any of these tags is dropped. |
required |
Returns:
| Type | Description |
|---|---|
list[Scenario]
|
The kept scenarios, in their original order. |
dump_block(items)
¶
Serialize models as a - … YAML sequence block — one pruned, alias-keyed item each.
Alias keying matches scenario_dict, but a scoped block also drops default-valued fields
(exclude_defaults), so a single spliced step / assertion stays as terse as the author wrote it
rather than sprouting submit: false and other model defaults. Used by the Author editor's
scoped round-trip edits (BE-0261) to re-serialize just the changed step / expect block.
dump_mocks(mocks)
¶
Serialize a scenario's mocks to the compact JSON BajutsuKit reads from BAJUTSU_MOCKS.
Alias keys, omitting unset fields.
dump_scenario_file(scenarios, description=None)
¶
Serialize a scenario file.
With a file-level description, emits the {description, scenarios} mapping form; otherwise the
bare list (round-trips through load_scenario_file).
dump_scenarios(scenarios)
¶
Serialize scenarios back to YAML (round-trips through load_scenarios).
redact_totp_secrets(scenario)
¶
A copy of scenario with literal totp.secret seeds masked, for on-disk evidence (BE-0152).
The executed scenario is snapshotted into the run's artifacts; a literal base32 seed there is
durable credential material, so it is replaced with a placeholder before the snapshot is
written. A ${secrets.*} reference is left intact (its resolved value never reaches the
snapshot — BE-0032). Round-trips through the model so the result stays a valid scenario.
scenario_dict(scenario)
¶
A pruned, alias-keyed dict of one scenario (for the rich report view).