Skip to main content
Use for_loop_node to repeat actions for each value in a list, or for each element matching a Playwright locator on the page—processing search results, downloading multiple files, or clicking through table rows. Exactly one of variable_name or locator is required.

Structure

Variable loop

Locator loop

At loop start the runtime waits up to locator_timeout for the first match to attach, then waits until the match count stays unchanged for 1 second (so slowly streaming rows are not missed), snapshots that count once, and iterates 0 .. count-1. An empty match set runs zero iterations with a warning (same as an empty list variable — the run does not fail).

Properties

The Index Variable

Variable loops

Inside a variable loop, {variable[index]} references the current iteration’s value. The token index comes from index_variable_name (default "index"):

Locator loops

Locator loops use the same shape as variable loops: {locator[index]} is the current match (like {order_ids[index]}), and bare {index} is the numeric index:
With index_variable_name: "row": There is no {index_of(locator)} counterpart to {index_of(<variable>)}. It would carry no per-loop name, so in nested locator loops the outer loop would bind the inner loop’s occurrences. Use the bare {<index_variable_name>} form, which is scoped per level.

Using Multiple Variables in a Loop

You can iterate over multiple parameters in parallel by passing a comma-separated list to variable_name. Important: The first in variable_name is the main loop variable that controls:
  • How many iterations run (loop length)
  • Which index is used for each step
All additional names in the list are synced to the same index. In other words:
  • variable_name: "primary_var,secondary_var" → loop length comes from primary_var
  • {primary_var[index]} and {secondary_var[index]} both use the same index on each iteration
For each iteration, {variable[index]} is expanded for every name in the list:
If your parameters are:
  • primary_var = ["Value A1", "Value A2"]
  • secondary_var = ["Value B1", "Value B2"]
Then:
  • Iteration 1 uses {primary_var[index]}"Value A1" and {secondary_var[index]}"Value B1"
  • Iteration 2 uses {primary_var[index]}"Value A2" and {secondary_var[index]}"Value B2"

Waiting for Matches

Locator loops only. count() does not auto-wait, and the sleep after an action returns as soon as the page has loaded — which is immediately for an SPA that re-renders without navigating. Counting straight away would see zero rows and skip the loop body silently, so a locator loop:
  1. Waits up to locator_timeout (default 5s) for the first match to attach
  2. Then waits until the match count stays unchanged for 1 second, so rows that appear shortly after the first paint are included
Raise locator_timeout for slow grids; set it to 0 to skip the first-match wait when rows are already on the page (the 1s stability wait still applies).

Storing One Value Per Iteration

output_variable_name is expanded like any other placeholder, so template it to give each iteration its own variable:
Without the placeholder every iteration overwrites the same variable and only the last row survives. Extracted values are appended to the task’s output data on every iteration either way — template the name only when you need to reference a specific row’s value later.

Nested Loops

for_loop_node can contain another for_loop_node. Give each loop a distinct index_variable_name so the outer index stays usable inside the inner loop:
The inner loop’s list must already exist when that loop starts (for example, extract items inside the outer iteration before the inner for_loop_node). An inner loop’s locator is itself expanded by the outer loop, so a locator loop can be scoped to the outer iteration — "locator": "{locator[row]}.locator(\"td\")" inside a loop over tr elements iterates that row’s cells. The inner count is taken from the already-scoped locator.

Data Sources

Loop variables can come from: For dynamic iteration, extract values first:

Reset Nodes

Actions that run after each iteration to return the browser to a known state. Essential for loops that navigate away from the starting page. Reset nodes also receive the current iteration’s placeholder bindings.

Reset Strategy Recommendations

Avoid go_back for reset nodes. If an error occurs mid-loop, the browser may be on an unexpected page.

Error Handling

Use "continue" when some items may fail but you want to process as many as possible (e.g., downloading files where some may be missing).

Common Patterns

Click Every Matching Element

Download Multiple Files

Download One File Per Matched Row

When the rows are on the page but their identifiers aren’t known in advance, loop the locator and use the index for the filename. {row} works in any field, not just command:
Three matched rows download invoice_0.pdf, invoice_1.pdf, invoice_2.pdf. Keep the index in download_filename — a constant name makes every iteration overwrite the previous file.

Process Items and Return

Limitations

  • Variable loops require the list to be populated before loop execution
  • Locator loops wait for a stable count() (unchanged for 1s) once at start; DOM changes mid-loop do not change how many iterations run. Set max_iterations as a guard and re-check state inside the loop rather than relying on the initial count
  • Use {locator[index]} for the current match and bare {index} for the numeric index
  • When nesting locator loops, give each a distinct index_variable_name and reference {locator[row]} / {locator[cell]} so each level keeps its own match token
  • Locator loops that mutate the matched set (remove/add rows) should use reset_nodes and/or prefer extracting a stable list into a variable loop
  • Deep nesting is allowed; keep index_variable_name distinct per nesting level when you need both indexes