How to Execute Custom JavaScript Code on a Page with Zendriver

Use the Tab.evaluate() coroutine to run arbitrary JavaScript expressions in the context of the loaded page, or add_script_to_evaluate_on_load() to inject scripts that persist across navigations.

The cdpdriver/zendriver library provides a high-level Python API for browser automation that makes it straightforward to execute custom JavaScript code on a page. Whether you need to extract data from the DOM, modify page state, or inject utility functions, Zendriver exposes the Chrome DevTools Protocol (CDP) Runtime.evaluate method through convenient coroutines.

Using Tab.evaluate() to Execute JavaScript

The primary mechanism for executing JavaScript is the Tab.evaluate() method, defined in zendriver/core/tab.py around line 699. This coroutine accepts a JavaScript expression as a string, forwards it to the browser's runtime via CDP's Runtime.evaluate (implemented in zendriver/cdp/runtime.py around line 1106), and returns the result.

By default, evaluate() uses return_by_value=True, which serializes the result and returns it as a Python object via the value attribute of the cdp.runtime.EvaluateResult object.

import zendriver as zd
import asyncio

async def run_js():
    async with zd.Browser() as browser:
        tab = await browser.new_tab()
        await tab.goto("https://example.com")
        
        # Execute a simple expression

        title = await tab.evaluate("document.title")
        print("Page title:", title)
        
asyncio.run(run_js())

Returning Complex Objects

When you need to extract structured data from the page, wrap your expression in parentheses to return an object literal. The evaluate() method automatically handles the serialization of JavaScript objects into Python dictionaries.


# Return viewport dimensions as a dictionary

dimensions = await tab.evaluate(
    "({width: window.innerWidth, height: window.innerHeight})"
)
print("Viewport:", dimensions)

Injecting Persistent Scripts with add_script_to_evaluate_on_load

For scenarios requiring JavaScript to execute automatically on every page load or navigation, use the add_script_to_evaluate_on_load() method. This functionality is implemented in zendriver/cdp/page.py around line 2195 and maps to the CDP Page.addScriptToEvaluateOnLoad command.

This approach is ideal for injecting polyfills, global utility functions, or monitoring scripts that must persist across page navigations and reloads.


# Inject a script that runs on every load

await tab.page.add_script_to_evaluate_on_load(
    "window.myInjectedFlag = true;"
)

# Reload the page and verify the script executed

await tab.reload()
flag = await tab.evaluate("window.myInjectedFlag")
print("Injected flag present:", flag)  # → True

Core Implementation Details

Zendriver's JavaScript execution stack consists of three key modules that bridge Python coroutines to the Chrome DevTools Protocol:

  • zendriver/core/tab.py — Contains the high-level Tab.evaluate() coroutine that handles expression evaluation and result deserialization.
  • zendriver/cdp/runtime.py — Provides the low-level CDP wrapper around Runtime.evaluate, handling the protocol-level message passing.
  • zendriver/cdp/page.py — Implements page-level utilities including add_script_to_evaluate_on_load for persistent script injection.

All JavaScript execution is asynchronous and returns cdp.runtime.EvaluateResult objects, with the actual return value accessible via the .value property when return_by_value is enabled.

Summary

  • Use Tab.evaluate() to execute one-off JavaScript expressions and extract data from the DOM.
  • Wrap object literals in parentheses to return complex structures from evaluate() calls.
  • Use add_script_to_evaluate_on_load() to inject scripts that persist across page navigations and reloads.
  • All methods are asynchronous coroutines that return cdp.runtime.EvaluateResult objects with values accessible via the .value attribute.

Frequently Asked Questions

Can I execute JavaScript before the page loads?

Yes. Use add_script_to_evaluate_on_new_document() (available in zendriver/cdp/page.py) to inject scripts that execute immediately when a new document is created, before any of the page's own scripts run. This is useful for modifying navigator properties or injecting mocks before page scripts execute.

What is the difference between evaluate() and add_script_to_evaluate_on_load()?

evaluate() runs JavaScript immediately in the current page context and returns a result. add_script_to_evaluate_on_load() registers a script with the browser that automatically executes every time the page loads or reloads, but does not return a value to Python immediately.

How do I handle errors when executing JavaScript?

When JavaScript throws an exception, Tab.evaluate() propagates the error details through the cdp.runtime.EvaluateResult object. Check the exception_details property of the result object. If present, it contains the error message, line number, and stack trace from the browser's JavaScript engine.

Can I pass arguments to the JavaScript function?

While evaluate() accepts a string expression rather than a function with arguments, you can interpolate Python values into the string using f-strings or template strings. For complex data, serialize Python objects to JSON and embed them in the expression: await tab.evaluate(f"processData({json.dumps(python_dict)})").

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →