コンテンツにスキップ

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-testidElement.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 steps are rewritten in place.

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 steps are rewritten in place.

required
resolve Callable[[str], Component]

Maps a component name to its Component (e.g. by loading a shared file).

required
max_depth int

The deepest use nesting allowed before giving up on a runaway chain.

25

Raises:

Type Description
ValueError

A required param is missing, an unknown param is passed, a ${params.*} token references an undeclared param, a reference cycle is detected, or nesting exceeds max_depth.

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 dataFile reference into a list of {column: value} rows.

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 use: component or dataFile reference to resolve.

required

Returns:

Type Description
Path

The resolved real path, ready to read.

Raises:

Type Description
ValueError

The ref is absolute, escapes the root via .., or symlinks outside it.

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 — _parse_yaml_named normalizes a yaml.YAMLError into a ValueError naming the offending file (the scenario or a referenced component), so its callers' except (OSError, ValueError) guard a malformed file as cleanly as a structurally-invalid one (BE-0150) — or a ref resolves outside root (contained_ref).

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 load_expanded_scenarios — an unreadable or invalid file, or a ref that escapes the suite root.

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 include keeps everything.

required
exclude list[str]

A scenario carrying any of these tags is dropped. exclude wins over include.

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).