ScreenshotNeo

BlogHow-to

How to Execute a JavaScript Function Inside a Page with Pyppeteer

Use Pyppeteer’s page.evaluate() to run JavaScript in a page, pass arguments, inspect elements, and handle expressions reliably.

By the ScreenshotNeo team1 October 20267 min read

To execute JavaScript inside a page with Pyppeteer, navigate to the page and await page.evaluate() with a JavaScript function or expression as a string. The code runs in the browser page context, and a serializable return value comes back to Python.

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch()
    try:
        page = await browser.newPage()
        await page.goto('https://example.com', waitUntil='domcontentloaded')
        title = await page.evaluate('''() => document.title''')
        greeting = await page.evaluate('''(name) => `Hello, ${name}`''', 'Ada')
        print(title, greeting)
    finally:
        await browser.close()

asyncio.run(main())

evaluate() is asynchronous: call it from an async function and use await. The callback runs against the document and browser globals, not in Python. Its result is serialized back, so return plain values such as strings, numbers, booleans, arrays, or ordinary objects.

1. Install Pyppeteer and start a page

Install the package in the Python environment where the script will run:

python -m pip install pyppeteer

A complete minimal script launches Chromium, opens a page, evaluates JavaScript, and closes the browser even if an operation fails:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch()
    try:
        page = await browser.newPage()
        await page.goto('https://example.com', waitUntil='domcontentloaded')
        result = await page.evaluate('''() => ({
            title: document.title,
            url: location.href,
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        })''')
        print(result)
    finally:
        await browser.close()

asyncio.run(main())

waitUntil='domcontentloaded' waits for the document to be parsed, but not necessarily for every image, stylesheet, or asynchronous application request. Choose a later readiness condition or wait for a page-specific selector if the code depends on content added after initial parsing.

2. Evaluate functions and expressions

Pass a JavaScript arrow function as a string for a clear callback boundary. You can also pass an expression. Pyppeteer detects whether the supplied string is a function or expression; when an expression is misread, set force_expr=True.

# Function: evaluate a callback in the page
page_title = await page.evaluate('''() => document.title''')

# Expression: force expression interpretation if needed
body_text = await page.evaluate('document.body.textContent', force_expr=True)

# Object returned by a function
metrics = await page.evaluate('''() => ({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
    deviceScaleFactor: window.devicePixelRatio
})''')
print(metrics)

The extra parentheses in () => ({ ... }) make the braces an object expression returned by the arrow function. Without them, JavaScript can interpret braces as the function body.

3. Pass arguments into the page function

Supply callback inputs as additional positional arguments to evaluate(). Pyppeteer serializes these values and provides them to the function in order:

sum_value = await page.evaluate('''(a, b) => a + b''', 2, 3)
message = await page.evaluate('''(name, count) => `${name}: ${count}`''', 'Ada', 4)
print(sum_value, message)

Pass data as arguments instead of building JavaScript by concatenating strings. This keeps values separate from executable source and avoids quoting errors when inputs contain apostrophes, backslashes, or other special characters.

4. Evaluate JavaScript against a selected element

To inspect one matching node, get an element handle and pass it as an argument to the page callback:

element = await page.querySelector('h1')
if element is None:
    raise RuntimeError('No h1 element matched')

heading = await page.evaluate('''element => ({
    text: element.textContent,
    tag: element.tagName,
    className: element.className
})''', element)
print(heading)

For a selector-oriented shortcut, use querySelectorEval(selector, pageFunction, *args). It finds the matching element and passes it as the first argument. It raises an element error if nothing matches:

heading_text = await page.querySelectorEval(
    'h1',
    '(element) => element.textContent'
)
print(heading_text)

Use the explicit handle form when you want to check for a missing element or reuse the handle. Use querySelectorEval when a missing selector should be treated as an error.

5. Choose the right evaluation API

API Use it for Result and timing
evaluate() A one-off calculation or DOM read Returns a serialized JavaScript value immediately after execution.
evaluateHandle() An in-page object that you need to inspect or manipulate through the DevTools protocol Returns a JSHandle, rather than converting the result to a plain Python value.
evaluateOnNewDocument() Install code before page scripts run on navigation Registers code for page navigations and attached or navigated child frames.
waitForFunction() Wait until a browser-side condition becomes truthy Polls a predicate instead of performing a single evaluation.
querySelectorEval() Run a function for the element matched by a selector Passes the element as the callback’s first argument; errors if no element matches.

For example, wait for an application to expose a ready state before reading its content:

await page.waitForFunction("() => document.querySelector('[data-ready=""true""]')")
ready_text = await page.evaluate("() => document.querySelector('[data-ready=""true""]').textContent")

For code that must exist before the site’s own scripts execute, register it before navigating:

await page.evaluateOnNewDocument('''() => {
    window.captureStartedAt = Date.now();
}''')
await page.goto('https://example.com')

6. Return values and page context limits

  • Return serializable values from evaluate(). DOM nodes and complex browser objects are not ordinary Python data; use evaluateHandle() when retaining an in-page object is the goal.
  • The callback can access the current document and browser-side globals. It cannot directly use Python variables unless they are passed as callback arguments.
  • Run evaluation after the relevant page or element exists. Navigation completion does not guarantee that a single-page app has finished loading its data.
  • Keep work inside the callback finite. A long-running loop blocks page JavaScript execution and can make automation appear hung.
  • For a frame or iframe, ensure your operation targets the frame where the document or element lives; a selector in the top-level page does not automatically refer to a child frame.

7. Troubleshooting

Symptom Likely cause Fix
Syntax error or unexpected token The JavaScript string has invalid syntax, mismatched quotes, or object braces interpreted as a function body. Use a multiline triple-quoted Python string, check JavaScript syntax, and wrap returned object literals in parentheses.
Expression treated as a function Pyppeteer’s function/expression detection chose the wrong mode. Call page.evaluate(expression, force_expr=True).
Python receives None or no useful result The callback does not return a value, or the result is not serializable as a plain value. Use an explicit return in a block callback, return a plain object or primitive, or use evaluateHandle() for an in-page object.
Element lookup returns nothing The selector is wrong, the content has not appeared, or the element is in a frame. Check the selector, wait for the element or app-ready condition, and target the correct frame. Check for None before passing a handle.
Evaluation times out or the page appears frozen The page is busy, navigation is in progress, or the callback performs expensive or non-terminating work. Keep the callback short, wait for a specific readiness condition, and avoid synchronous heavy loops in page context.
Python says the coroutine was never awaited An async Pyppeteer call was made without await. Call it from an async function and await it. In an already-running notebook event loop, use top-level await instead of starting another loop.
Browser does not launch Chromium is unavailable or the host environment lacks a required system dependency or launch configuration. Review the launch error, install the required browser dependencies for that environment, and configure launch() for the installed browser when appropriate.

8. Performance, reliability, and cost

evaluate() avoids transferring a page’s full markup to Python when all you need is a small result such as a title or text value. Keep each evaluation focused and return only the data needed. For dynamic pages, waiting for a meaningful selector or predicate is more reliable than relying on an arbitrary delay; a delay can be too short on a slow run and waste time on a fast one.

Reuse a browser process for multiple pages in a batch when suitable for your workload, and close pages and the browser when finished. Handle navigation failures and missing selectors explicitly. The research sources publish no suitable benchmark or success-rate statistic for Pyppeteer evaluation, so performance depends on browser startup, page behavior, network conditions, and the work performed by the callback.

Pyppeteer runs a browser process, so account for the compute and operational cost of maintaining that runtime. The code above makes no claim about hosting cost or execution speed; those depend on where Chromium runs and how many pages you process.

9. Or skip the browser setup

If your goal is a screenshot rather than a custom DOM value, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its documented options include full-page and element capture, viewport and device presets, waiting controls, custom CSS and JavaScript, request blocking, headers and cookies, and more. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners are accepted and removed, and newsletter popups and chat widgets are removed before capture; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
  • An MCP server gives AI agents tools for screenshots, page information, and PDF capture.
  • The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo and get 1,000 free screenshots a month, with no card.

10. FAQ

Does JavaScript run in Python when I call evaluate()?

No. Pyppeteer sends the callback to the browser page context and converts its serializable result for Python.

Can I pass a Python function to evaluate()?

No. Pass JavaScript source as a string and pass Python data values as additional arguments.

When should I use evaluateHandle() instead?

Use it when you need a persistent handle to an in-page object rather than a plain serialized result.

How do I run code before a page loads?

Register it with evaluateOnNewDocument() before navigating. It also applies to child frames when they attach or navigate.