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 namedcode_fn, and calls it with the current Playwrightpage. - No return value: The value returned by
code_fnis discarded. To extract data with custom code, useextraction_action.python_script. To emit a file, add actxargument and callctx.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 namedcode_fn that accepts a single page argument (a Playwright Page):
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
Python Script Extraction
To extract data from the page using custom code, usepython_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
OutputData and, if output_variable_names is set, the specified keys are promoted to generated_variables for use in later nodes.
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.
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.