Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Pass the selector variable directly to the Puppeteer method that accepts a selector: await page.$(selector) or await page.$eval(selector, callback). Do not wrap the variable in quotes, or Puppeteer will look for the literal text of the variable name. In $eval, the first argument is the selector and the second is the function that receives the matched element.

Pass the selector variable directly

A CSS selector is a JavaScript string. If a function receives that string as a parameter, pass the parameter as the selector argument to Puppeteer:

const selector = '.result';
const element = await page.$(selector);

That asks Puppeteer to find an element matching .result. By contrast, page.$('selector') passes the literal string selector as the query; it does not substitute the value stored in a variable named selector.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This distinction is especially useful when a selector comes from a helper’s argument, a configuration value, or a choice made earlier in the program. The variable is already the runtime value you want to pass—there is no special syntax for “parameterizing” the selector.

Choose the Puppeteer method for the result you need

These methods accept a selector in different ways and have different no-match behavior. The examples below follow the Puppeteer API documentation; Page.$eval and Page.evaluate are documented in version 25.12.0. Check the documentation for the Puppeteer version installed in your project if you rely on version-specific behavior.

Method What to pass What happens if nothing matches Useful when
page.$(selector) The selector as the first argument Resolves to null You want an element handle and may treat the element as optional.
page.$eval(selector, callback) Selector first, callback second Throws if there is no match You want to run a one-off operation on the first match.
page.waitForSelector(selector, options) Selector first, optional wait settings second Waits for the selector, then throws if it does not appear before the timeout The element may appear after the page begins loading.
page.evaluate(callback, selector) A callback first, then the selector as an additional argument Depends on what the callback does with the query result The DOM query belongs inside page-context code.

Use page.$ when you need a handle

const selector = '.result';
const element = await page.$(selector);

if (element === null) {
  console.log('No matching element');
} else {
  console.log('Found a matching element');
  await element.dispose();
}

page.$ returns an ElementHandle for the first match, or null if none is found. A handle is useful when you need to perform further operations on that element. Dispose of handles you no longer need so they do not remain attached to browser-side objects unnecessarily.

Use page.$eval for a one-off extraction or operation

async function readText(page, selector) {
  return page.$eval(selector, element => element.textContent);
}

const title = await readText(page, '.result-title');

The helper forwards its selector parameter as the first argument to page.$eval. Puppeteer finds the first matching element and supplies that element to the callback as element. The callback runs in the page context, so the matched DOM element is available there. If there is no match, $eval throws rather than returning null.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

textContent can include text from descendants, including content that is not visually rendered. If you need a different notion of visible text, choose and verify the appropriate DOM property or interaction for your use case rather than assuming textContent reflects exactly what a person sees.

Use page.waitForSelector when the element may appear later

const selector = '.result';
const element = await page.waitForSelector(selector, {
  visible: true,
  timeout: 10_000,
});

if (element === null) {
  // With hidden-state waits, a null result can indicate the element is absent.
} else {
  try {
    console.log('The matching element is present and visible');
  } finally {
    await element.dispose();
  }
}

waitForSelector is intended for a selector that may not match immediately. Its documented default timeout is 30,000 milliseconds; set timeout when your workflow needs a different limit. The documented options include visible, hidden, timeout, and signal. Review the Puppeteer waitForSelector API reference for the behavior of the options in your installed version. The method returns a handle, so dispose of it when you are done. If your next step is an interaction, Puppeteer’s locator guide describes locators that wait automatically for element presence and an appropriate state: Page interactions.

Understand the different argument positions

In selector-taking methods, Puppeteer receives the selector as an API argument. In page.evaluate, the first argument is instead a function, and values after that function are passed into it:

const selector = '.result';

// Puppeteer selects the element and supplies it to the callback.
const textFromEval = await page.$eval(
  selector,
  element => element.textContent,
);

// The selector is an argument to the function running in the page.
const textFromEvaluate = await page.evaluate(
  sel => document.querySelector(sel)?.textContent,
  selector,
);

Both forms pass the variable’s value. The choice is about where the query happens: $eval asks Puppeteer to select an element and then invokes the callback with it; evaluate passes the selector into your page-context function, where the function calls document.querySelector. The Page.$eval API reference and Page.evaluate API reference document the respective argument patterns.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For $eval, any additional values go after the callback and become additional callback arguments. They do not replace the selector or the element argument:

const selector = '.result';
const suffix = ' — checked';

const label = await page.$eval(
  selector,
  (element, extraText) => element.textContent + extraText,
  suffix,
);

Here Puppeteer supplies the matched element as the callback’s first parameter and suffix as the next one. Name the callback parameters to reflect those roles to make the ordering easier to maintain.

Forward a selector through reusable functions

A helper can receive a selector and forward it unchanged. Pass the page explicitly or close over it in the scope where the helper is defined:

async function findElement(page, selector) {
  return page.$(selector);
}

async function getText(page, selector) {
  return page.$eval(selector, element => element.textContent);
}

const selector = '#account-name';
const handle = await findElement(page, selector);

if (handle) {
  try {
    console.log(await handle.evaluate(element => element.textContent));
  } finally {
    await handle.dispose();
  }
}

const accountName = await getText(page, selector);

Each caller decides how to handle a missing match. The first helper returns null when there is none; the second rejects with the no-match error from $eval. If absence is expected, use a method and control flow that represent that expectation instead of catching every error and treating it as a missing element.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common mistakes and how to fix them

Passing the function where the selector belongs

This reverses the arguments and is not the $eval signature:

// Wrong: the selector must come first.
await page.$eval(element => element.textContent, selector);

// Correct:
await page.$eval(selector, element => element.textContent);

When debugging, check the API signature before changing the callback. For $eval, the selector is argument one, the callback is argument two, and optional callback arguments follow them.

Quoting the variable name

const selector = '.result';

// Wrong for a variable: searches for the literal selector text "selector".
await page.$('selector');

// Correct: passes the value '.result'.
await page.$(selector);

Quotes are appropriate when you are writing the selector string itself, such as page.$('.result'). They are not needed around a variable reference.

Assuming the callback receives the selector

In page.$eval(selector, callback), Puppeteer passes the matched element to the callback. It does not pass the selector there automatically. In page.evaluate(callback, selector), the selector is an extra argument to the callback, so the callback must declare a parameter for it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Using a selector before it can match

A correct selector can still return no result if the page has not rendered the target element. Use a wait when the element is expected to appear later, and choose a timeout that fits the operation. For a locator-driven interaction, consider Puppeteer’s locator approach rather than adding a separate wait without checking the interaction’s own waiting behavior.

Calling a non-CSS selector CSS

Puppeteer supports CSS selectors and additional selector syntax, including text, accessibility role/name, and XPath forms. The examples in this article such as '.result' and '#account-name' are CSS. Do not label a text, role, or XPath query as CSS just because it is passed to a selector-taking API. The Page class API reference and interaction guide describe Puppeteer’s selector and interaction options.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is to save a screenshot or PDF rather than manipulate a live page in Puppeteer, ScreenshotNeo provides a website screenshot API and MCP server. It does not replace Puppeteer’s selector-parameter technique; it is an alternative when the deliverable you need is a capture. One GET request can return a PNG, JPEG, WebP, or PDF. The examples below use the documented endpoint and parameter format; see the ScreenshotNeo documentation for available options.

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 and consent banners, newsletter popups, and chat widgets are removed before the capture; each of those steps can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients.
  • The free plan includes 1,000 screenshots a month with no card required; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to try it with 1,000 screenshots a month and no card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot by symptom

Symptom Likely cause What to check
The query appears to search for the word selector. The variable name was quoted, so its name became a literal string. Pass selector, not 'selector', to the method.
$eval reports that no element matched. The selector currently matches nothing, or the element has not appeared yet. Confirm the selector and page state. If the element is expected later, wait for it; if it is optional, use $ and check for null.
page.$ returns null. No element matched when the query ran. Check selector spelling and timing; choose an explicit wait if the page renders the target asynchronously.
The callback sees an element where you expected a selector string. That is the normal $eval callback argument: Puppeteer supplies the matched element. Use page.evaluate(callback, selector) if your callback should receive the selector and perform its own DOM query.
A wait times out despite a valid selector. The element did not reach the requested state within the timeout, or the query is not the right selector type. Inspect the rendered page, confirm whether visibility or hidden-state options are appropriate, and adjust the timeout only if the page genuinely needs longer.

How to decide in practice

  1. Need a handle and the element might not exist? Call page.$(selector) and branch on null.
  2. Need a value from an element that should already exist? Call page.$eval(selector, callback) and account for its no-match error.
  3. Expect the element to appear asynchronously? Wait for the selector or use a locator suited to the interaction.
  4. Need the query to run inside your page function? Pass the selector after the callback in page.evaluate(callback, selector).

For the exact argument details and behavior, consult the official references for $eval, evaluate, waitForSelector, and the Page class.

Frequently Asked Questions

Can I pass a CSS selector received from a command-line argument or config file?

Yes. Once it is a JavaScript string, pass that value as the selector argument. Validate inputs from untrusted sources if your application permits arbitrary selectors.

Does $eval return every matching element?

No. It selects the first match. For collection work, choose an API or page-context query designed to handle multiple elements.

Should I use waitForSelector before every $eval?

No. Wait only when the page flow makes the target’s later appearance part of the expected behavior; otherwise handle an immediate missing match directly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.