Skip to main content

Control Flow Actions

Loop over elements and conditionally execute actions.

loop

Iterate over matching DOM elements or repeat actions a fixed number of times.

Loop Over Elements

Use each with a CSS selector to iterate over every matching element. The special $this prefix targets elements within the current iteration's scope.

{
"loop": {
"each": ".product-card",
"do": [
{ "getText": "$this h3", "options": { "push": "titles" } },
{ "getText": "$this .price", "options": { "push": "prices" } }
]
}
}

Repeat N Times

Use repeat to execute actions a fixed number of times.

{
"loop": {
"repeat": 5,
"do": [
{ "scrollY": 800 },
{ "wait": 1000 }
]
}
}

$this Selector

Inside a loop with each, $this refers to the current element. Append a child selector to target descendants:

UsageMeaning
$thisThe current element itself
$this .child.child element within current element
$this > spanDirect span child of current element

Nested Actions in Loop

You can use most actions inside a loop. The following are not supported inside loops: screenshot, waitForResponse, loop (no nested loops).

if

Conditionally execute actions based on element existence or data comparison.

Simple Condition (Selector Exists)

If the condition is a string, it checks if a matching element exists:

{
"if": {
"condition": ".error-message",
"then": [
{ "screenshot": "viewport" }
],
"else": [
{ "click": "#continue" }
]
}
}

Object Condition (Property Comparison)

Use an object condition for more complex checks:

{
"if": {
"condition": {
"selector": ".item-count",
"property": "textContent",
"operator": ">",
"value": "0"
},
"then": [
{ "click": "#checkout" }
]
}
}

Check Cached Data

{
"if": {
"condition": {
"cached": "totalItems",
"operator": ">=",
"value": "10"
},
"then": [
{ "click": "#bulk-discount" }
]
}
}

Comparison Operators

OperatorDescription
==Equal
!=Not equal
>Greater than
<Less than
>=Greater than or equal
<=Less than or equal
containsText contains value
notContainsText does not contain value
matchesRegex match
emptyValue is empty/null
notEmptyValue is not empty/null

Nested Control Flow

You can nest if and loop inside each other:

{
"loop": {
"each": ".product",
"do": [
{
"if": {
"condition": {
"selector": "$this .in-stock",
"operator": "notEmpty"
},
"then": [
{ "click": "$this .add-to-cart" }
]
}
}
]
}
}

Example: Scrape Paginated Data

{
"actions": [
{ "openNewTab": "https://example.com/products?page=1" },
{
"loop": {
"repeat": 5,
"do": [
{ "waitForElement": ".product-card" },
{
"loop": {
"each": ".product-card",
"do": [
{ "getText": "$this .name", "options": { "push": "names" } },
{ "getText": "$this .price", "options": { "push": "prices" } }
]
}
},
{
"if": {
"condition": "a.next-page",
"then": [
{ "click": "a.next-page" },
{ "wait": 2000 }
]
}
}
]
}
},
{ "getSavedData": ["names", "prices"] }
]
}
note

The outer loop uses repeat: 5 — not nested, since the inner loop is inside an if block within the outer loop's do array. Nested loop is supported inside if branches but direct loop inside loop is not allowed.

Example: Conditional Screenshot

{
"actions": [
{ "openNewTab": "https://example.com" },
{ "waitForEvent": "networkIdle" },
{
"if": {
"condition": ".cookie-banner",
"then": [
{ "click": ".cookie-banner .accept" },
{ "wait": 500 }
]
}
},
{ "screenshot": "viewport" }
]
}

Example: Check Element Text

{
"actions": [
{ "openNewTab": "https://example.com/status" },
{ "waitForElement": "#status" },
{
"if": {
"condition": {
"selector": "#status",
"property": "textContent",
"operator": "contains",
"value": "Success"
},
"then": [
{ "click": "#proceed" }
],
"else": [
{ "screenshot": "viewport" }
]
}
}
]
}