Skip to main content
python_script_action lets you run arbitrary Python code against the live Playwright page object—covering anything that standard interaction actions cannot express.

Overview

  • Use when: Built-in actions are insufficient (complex DOM manipulation, custom scrolling, dispatching browser events, reading element properties).
  • Execution: The runner execs your script, finds an async function named code_fn, and calls it with the current Playwright page.
  • No return value: The value returned by code_fn is discarded. To extract data with custom code, use extraction_action.python_script. To emit a file, add a ctx argument and call ctx.save_download().

Properties

execution_code supports the same {variable[0]} / {index} substitution as other actions, so a python_script_action inside a for_loop_node can read {index} directly.

Script Contract

Your script must define an async function named code_fn that accepts a single page argument (a Playwright Page):
The function is called as await code_fn(page). Any return value is ignored.

JSON Example

Common Patterns

Scroll to the bottom of the page

Wait for a custom JavaScript condition

Dispatch a custom browser event

Interact with a shadow DOM element

execution_code is evaluated with exec(). Only run scripts from trusted sources.

Python Script Extraction

To extract data from the page using custom code, use python_script inside an extraction_action instead. The contract is different: the function receives (axtree, browser) and must return a dict containing the extracted values.

Properties

Script Contract

The returned dict is stored as OutputData and, if output_variable_names is set, the specified keys are promoted to generated_variables for use in later nodes.
All keys in output_variable_names must exist in extraction_format, or schema validation will fail.
Put import statements inside code_fn, not at the top of the script. The script is exec’d with separate globals and locals, so a module-level import lands somewhere code_fn’s body cannot see it and you get NameError at runtime.

The Script Context (ctx)

Both script types can request an extra ctx argument by naming it in the signature. Add ctx (or context) and you get it; omit it and nothing changes.
Opting in is by parameter name, not position — an existing third parameter called something else will never receive the context.

ctx.save_download() — emit a file as a task download

Previously only click_element / select_option with expect_download could produce a downloadable file, so scripts that had already built the bytes had to push them back into the page as a Blob, inject an <a download> anchor, and add a second node to click it. ctx.save_download() removes that round trip.
Returns the final Path, which may differ from filename after sanitizing or de-duplication. Raises if the resulting file is empty or missing; nothing partial is left behind. The file is uploaded with the task’s other downloads and its metadata is returned as downloads_with_metadata — identical to the expect_download path, because it uses the same download registry. See Downloads & Files.
Unlike expect_download, filename is required — there is no browser-supplied name to fall back on, and defaulting to a UUID would only hide mistakes.

ctx.state — share data between script nodes

Each script node is exec’d with fresh globals, so nothing survives between nodes by default. ctx.state is a plain dict scoped to the run — use it instead of stashing work lists on window, which costs a JS round trip per read and is lost on navigation.

Other members

ctx.log tags each line with the current step index (e.g. [python_script step=12] ...) so lines from different nodes or loop iterations can be told apart, and writes it via the standard logger — pass level="warning" or level="error" for anything more severe than routine progress. These lines land in optexity.log, viewable in the dashboard’s task logs “Logs” panel.

Action vs Extraction — Which to Use