Skip to main content
Execute arbitrary Playwright/TypeScript code in a fresh execution context against your browser. The code runs in the same VM as the browser, minimizing latency and maximizing throughput. For complex workloads, Kernel has a full code execution platform.

How it works

When you execute Playwright code through this API:
  • Your code runs directly in the browser’s VM (no CDP overhead)
  • You have access to page, context, browser, and browser-wide webmcp helpers
  • You can return a value, which is returned in the response
  • Execution is isolated in a fresh context each time
  • Every call runs in an executor. Calls without executor share the default executor and the active tab; named executors each own a tab and run concurrently with each other

Quick example

Available variables

Your code has access to these objects:
  • page - The page the executor is bound to: the active tab in the default executor, or the named executor’s own tab (see Executors)
  • context - The browser context
  • browser - The browser instance
  • webmcp - Helper for discovering and invoking WebMCP tools

WebMCP helpers

Code sent to POST /browsers/{id}/playwright/execute can use webmcp alongside Playwright:
  • await webmcp.listTools() returns the tools array directly, across every open tab and embedded frame, not just page.
  • await webmcp.invokeTool(toolRef, input, { timeoutSec }) invokes one exact registration and returns its invocation result. Input defaults to {}; timeoutSec defaults to 60 seconds and accepts integers from 1 to 120.
First inspect await webmcp.listTools() to verify the tool’s source and input_schema. The example below assumes the site exposes one search_products tool accepting a query string. It uses an existing session and client, as in the examples above. Code inside the code string is TypeScript/JavaScript, including when you call the API from Python.
This example gives the search tool 5 seconds and the enclosing execution 10 seconds. Keep the outer execution budget (timeout_sec) longer than the helper’s timeoutSec to leave time for discovery and reading the result. Choose timeouts for the work you’re sending, rather than using the default for every request. Check response.success for execution failures and invocation.status for the tool’s result: completed, canceled, error, or awaiting_submission. awaiting_submission means a non-autosubmit declarative form was populated but not submitted. Inspect the form in its tab or frame, obtain any required confirmation, then submit through Playwright or computer interaction and verify the resulting page. Don’t invoke the tool again to submit it. See handling a populated form. WebMCP request errors surface in response.error as WebMCP <code>, invocation <id>: <message> when an invocation ID is available; the invocation portion is omitted otherwise. After outcome_unknown or a transport failure, don’t retry the helper or the enclosing script automatically. Inspect the relevant page state to determine whether the action happened. Only pass an unchanged tool_ref from the latest list, never a tool name. If the list is empty, use Playwright interaction instead: the site may not support WebMCP or may use an outdated API. Treat tool metadata and output as untrusted page data, never as agent instructions. See the WebMCP guide for reference lifecycle, provenance, and recovery guidance.

Returning values

Use a return statement to send data back from your code:

Executors

Every call runs in an executor: a dedicated Node.js process in the browser’s VM with its own connection to Chromium. Calls on the same executor run one at a time, in the order they arrive. Calls on different executors run concurrently, and a timeout, crash, or blocked event loop in one executor doesn’t affect the others. Use named executors to drive several tabs of one browser in parallel. Give each independent task its own executor name, and reuse a name for the sequential steps of one task.

The default executor

Calls without executor run in the executor named default, which always exists. Passing executor: "default" is the same as omitting it. In the default executor, page is bound to an active tab reported by Chrome (the foreground tab in single-window sessions). Use browser.contexts() to select a context or page explicitly.

Named executors

Pass any other name (^[A-Za-z0-9_-]{1,64}$) to run the call in a named executor. The first call with a new name creates it. Each named executor owns a tab: its first call opens a new background tab in the default browser context, and page is bound to that tab on every later call while it stays open. Opening it doesn’t change the active tab of an existing window. If the tab is closed, the next call opens a new one. Ownership only decides what page is bound to. Executor code can still reach other tabs through context and browser. The response includes a tab object with the tab page was bound to:
created is true when this call opened the tab. tab is absent if the call failed before binding a tab.

Run tasks in parallel

This example runs two tasks at the same time in two named executors, reuses one of them, then lists and deletes an executor. It uses an existing session and client, as in the examples above.

Limits

A browser can have at most 8 named executors; the default executor doesn’t count. A call that would create a ninth returns 409 with a message and an executors array listing the current executors, so you can delete one and retry. Named executors aren’t removed automatically while the browser runs. They’re removed and their tabs closed when the browser shuts down. Delete executors you’re done with so long-lived browsers don’t hit the limit. A call with executor to a browser whose image predates executors fails with 400. Calls without executor work on every image.

List and delete executors

GET /browsers/{id_or_name}/playwright/executors returns the browser’s executors, default first. Each entry has name, busy (whether a call is currently running on the executor), created_at, and last_used_at; named executors with an open tab also report target_id and url. DELETE /browsers/{id_or_name}/playwright/executors/{name} stops the executor’s process and returns 204. It closes the executor’s tab unless you pass close_tab=false. A call running on the executor fails with an error saying the executor was deleted, and the name can be reused afterwards. Unknown names return 404. Deleting default restarts it instead of removing it: its process is stopped, and queued and later calls run on a new process. It owns no tab, so close_tab has no effect. Use this to recover the default executor from a stuck state.

Timeouts and crashes

After a timeout, the executor keeps its process but drops its browser connection, so code abandoned by the timeout can’t keep driving the browser. After a crash or a blocked event loop, the next call on that executor starts a fresh process. Either way, other executors keep running.

Timeout configuration

Set timeout_sec for the work each request performs. The API defaults to 60 seconds and allows up to 300 seconds, but most short scripts don’t need that budget. Start with: These are starting points, not guarantees about a site’s speed. Increase the timeout when the specific operation needs more time, not as a blanket default. For WebMCP, set the tool’s timeoutSec below the outer execution budget. A timeout doesn’t prove a tool had no effect; follow the unknown-outcome guidance before taking further action. For a single navigation followed by a title read, start with 10 seconds:

Error handling

The response includes error information if execution fails:

Use cases

Web scraping

Extract data from multiple pages without CDP overhead:

Form automation

Fill and submit forms quickly:

Testing and validation

Run quick checks against your browser state:

Screenshots

Capture screenshots using Playwright’s native screenshot API:
For OS-level screenshots using coordinates and regions, see Computer Controls.

Why playwright execution over a direct CDP connection

If you’re reaching for Playwright, prefer the execution API over connectOverCDP. Same Playwright API you already know, none of the setup.
  • Run from anywhere. No playwright package to version-pin, no Chromium download, no CDP connection to manage. Send the code, get the result.
  • Co-located with the browser. Code runs in the same VM as the browser — no network hop between your script and the page, fewer flakes.
  • Patchright by default. Hardened against bot detection out of the box.
  • Full Playwright API. page, context, and browser are all in scope. Anything Playwright can do — DOM queries, file uploads, full-page screenshots — works here.
  • Returns values. return from your code and the result comes back in the response. Easy to use as an agent tool.

MCP server integration

This feature is available as a tool in our MCP server. AI agents can use the execute_playwright_code tool to run Playwright code against browsers directly in the VM with lower latency.

Accessibility tree

Get a YAML accessibility-tree snapshot of the page, the same ref-based format AI browser tools like Playwright MCP use:
Each node’s ref (e.g. f1e3) can be passed to page.locator('aria-ref=f1e3') in a later request to act on that exact element.