# How to Execute Custom JavaScript Code on a Page with Zendriver

> Execute custom JavaScript on a page using Zendriver. Learn to use Tab.evaluate() for arbitrary expressions or add_script_to_evaluate_on_load() for persistent script injection. Read now.

- Repository: [CDP Driver/zendriver](https://github.com/cdpdriver/zendriver)
- Tags: how-to-guide
- Published: 2026-02-27

---

**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`](https://github.com/cdpdriver/zendriver/blob/main/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`](https://github.com/cdpdriver/zendriver/blob/main/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.

```python
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.

```python

# 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`](https://github.com/cdpdriver/zendriver/blob/main/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.

```python

# 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`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/core/tab.py)** — Contains the high-level `Tab.evaluate()` coroutine that handles expression evaluation and result deserialization.
- **[`zendriver/cdp/runtime.py`](https://github.com/cdpdriver/zendriver/blob/main/zendriver/cdp/runtime.py)** — Provides the low-level CDP wrapper around `Runtime.evaluate`, handling the protocol-level message passing.
- **[`zendriver/cdp/page.py`](https://github.com/cdpdriver/zendriver/blob/main/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`](https://github.com/cdpdriver/zendriver/blob/main/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)})")`.