AutomatorsDocs
Authoring grammar

Repeating steps

Repeating a block with foreach, and repeating until the app settles with repeat: and when:.

Repeat over known values with foreach

A block that repeats with one value changing collapses into a foreach values matrix. Scalars are referenced with ${each}, mappings with ${each.<key>}; a whole-string token keeps its YAML type, so status: ${each.status} stays a number. Expansion happens at parse time - each iteration becomes an ordinary recorded step, so a foreach adds no runtime construct to the trace.

steps:
  - foreach:
      values: [mysql, mssql, oracle]
      steps:
        - assert_api:
            request: POST ${API}/connections/test
            body: { type: "${each}" }
            status: 500

Repeating until the app settles, and optional blocks (repeat: and when:)

foreach repeats a block as many times as you know when you write it. Sometimes you do not know: press a button until the label changes, dismiss a banner if it appeared. Those are repeat: and when:.

steps:
  - repeat:
      until: the "id:button" shows Enough
      max: 15
      steps:
        - Press the "id:button" button
  - when: the "id:b1" is not visible
    steps:
      - Press the "id:tech" button

repeat: is read again at every replay. Recording reads the condition against the live app before each pass and records the passes that actually ran, each step carrying the condition, the bound, and its pass number. Replay keeps the recorded pass that took the most branches as the body (a when: recovery inside the loop is only recorded in the passes that needed it): it reads the condition, runs the body while the condition does not hold, and stops when it holds or fails at max:, naming the bound. So the pass count is decided by the app under replay, not fixed by the recording: a list that has three items today and seven tomorrow is walked to its end both days. Passes beyond the first report with a .2, .3 suffix on the step id, and a loop that is already settled reports its body as skipped, by name.

until: is checked before the first pass, so a repeat: whose condition already holds runs zero times. max: is required: if the condition never holds within it, recording fails and names the bound, and so does replay. Each repeat: gets its own budget. Two consequences mirror when:. The body exists in the trace only if the loop ran at least one pass while recording, so record against an app that needs the loop. And only the outermost repeat: is re-decided at replay; a repeat: nested inside another is settled while recording, as part of the outer body.

when: is read again at every replay. Recording reads the condition against the live app and, if it holds, records the block's steps with the condition attached to each of them as a guard. Replay reads that guard again, once per block, before the block's first step: if it holds the block runs, and if not its steps are skipped, reported as SKIP with the condition named, and the flow continues. A skipped block is not a passed one; the report says which branch each run took.

Two consequences follow. A when: block is only in the trace if its condition held while recording, so record with the optional element present: the banner showing, the dialog open. If it was absent at record time there is nothing to replay when it appears later, and the step after it fails exactly as it did before. And the guard is read once per block, not once per step, so a block that removes the very thing its condition saw (dismissing the banner) still runs to its end.

Conditions read state; they never wait:

ConditionHolds when
page shows <text> / page does not show <text>the whole surface's text does or does not contain it
the "<target>" shows <text>that element's text contains it
the "<target>" is visible / is not visibleit is on screen, or is missing or hidden
the "<a>" is greater than the "<b>" / is less thanboth read as numbers, and the ordering holds

A missing element makes a positive shows false and a negative one true, the same reading replay takes. Anything else is refused by name.

The comparison is the one condition that weighs two readings against each other rather than a reading against a literal, and it is numeric: "9" is greater than "10" as text and smaller as a number, and a condition that quietly answered the text question would be worse than one that refuses. A side that does not read as a number fails the recording and is quoted back. It is also the one condition settled at record time only: it has no replay-time form yet, so a when: on it records the branch that held and replays it unconditionally, and a repeat: on it replays its recorded passes as they were.

Scope conditions tightly: page shows ERROR also matches a heading reading "Errors occur", so name the element instead.

On this page