Skip to main content
Version: 23.9.0

Page interactions

Puppeteer allows interacting with elements on the page through mouse, touch events and keyboard input. Usually you first query a DOM element using a CSS selector and then invoke an action on the selected element. All of Puppeteer APIs that accept a selector, accept a CSS selector by default. Additionally, Puppeteer offers custom selector syntax that allows finding elements using XPath, Text, Accessibility attributes and accessing Shadow DOM without the need to execute JavaScript.

If you want to emit mouse or keyboard events without selecting an element first, use the page.mouse, page.keyboard and page.touchscreen APIs. The rest of this guide, gives an overview on how to select DOM elements and invoke actions on them.

Locators

Locators is the recommended way to select an element and interact with it. Locators encapsulate the information on how to select an element and they allow Puppeteer to automatically wait for the element to be present in the DOM and to be in the right state for the action. You always instantiate a locator using the page.locator() or frame.locator() function. If the locator API doesn't offer a functionality you need, you can still use lower level APIs such as page.waitForSelector() or ElementHandle.

Clicking an element using locators

// 'button' is a CSS selector.
await page.locator('button').click();

The locator automatically checks the following before clicking:

  • Ensures the element is in the viewport.
  • Waits for the element to become visible or hidden.
  • Waits for the element to become enabled.
  • Waits for the element to have a stable bounding box over two consecutive animation frames.

Filling out an input

// 'input' is a CSS selector.
await page.locator('input').fill('value');

Automatically detects the input type and choose an appropriate way to fill it out with the provided value. For example, it will fill out <select> elements as well as <input> elements.

The locator automatically checks the following before typing into the input:

  • Ensures the element is in the viewport.
  • Waits for the element to become visible or hidden.
  • Waits for the element to become enabled.
  • Waits for the element to have a stable bounding box over two consecutive animation frames.

Hover over an element

await page.locator('div').hover();

The locator automatically checks the following before hovering:

  • Ensures the element is in the viewport.
  • Waits for the element to become visible or hidden.
  • Waits for the element to have a stable bounding box over two consecutive animation frames.

Scroll an element

The [.scroll()] functions uses mouse wheel events to scroll an element.

// Scroll the div element by 10px horizontally
// and by 20 px vertically.
await page.locator('div').scroll({
scrollLeft: 10,
scrollTop: 20,
});

The locator automatically checks the following before hovering:

  • Ensures the element is in the viewport.
  • Waits for the element to become visible or hidden.
  • Waits for the element to have a stable bounding box over two consecutive animation frames.

Waiting for element to be visible

Sometimes you only need to wait for the element to be visible.

// '.loading' is a CSS selector.
await page.locator('.loading').wait();

The locator automatically checks the following before returning:

  • Waits for the element to become visible or hidden.

Waiting for a function

Sometimes it is useful to wait for an arbitrary condition expressed as a JavaScript function. In this case, locator can be defined using a function instead of a selector. The following example waits until the MutationObserver detects a HTMLCanvasElement element appearing on the page. You can also call other locator functions such as .click() or .fill() on the function locator.

await page
.locator(() => {
let resolve!: (node: HTMLCanvasElement) => void;
const promise = new Promise(res => {
return (resolve = res);
});
const observer = new MutationObserver(records => {
for (const record of records) {
if (record.target instanceof HTMLCanvasElement) {
resolve(record.target);
}
}
});
observer.observe(document);
return promise;
})
.wait();

Applying filters on locators

The following example shows how to add extra conditions to the locator expressed as a JavaScript function. The button element will only be clicked if its innerText is 'My button'.

await page
.locator('button')
.filter(button => button.innerText === 'My button')
.click();

Returning values from a locator

The map function allows mapping an element to a JavaScript value. In this case, calling wait() will return the deserialized JavaScript value.

const enabled = await page
.locator('button')
.map(button => !button.disabled)
.wait();

Returning ElementHandles from a locator

The waitHandle function allows returning the ElementHandle. It might be useful if there is no corresponding locator API for the action you need.

const buttonHandle = await page.locator('button').waitHandle();
await buttonHandle.click();

Configuring locators

Locators can be configured to tune configure the preconditions and other options:

// Clicks on a button without waiting for any preconditions.
await page
.locator('button')
.setEnsureElementIsInTheViewport(false)
.setVisibility(null)
.setWaitForEnabled(false)
.setWaitForStableBoundingBox(false)
.click();

Locator timeouts

By default, locators inherit the timeout setting from the page. But it is possible to set the timeout on the per-locator basis. A TimeoutError will be thrown if the element is not found or the preconditions are not met within the specified time period.

// Time out after 3 sec.
await page.locator('button').setTimeout(3000).click();

Getting locator events

Currently, locators support a single event that notifies you when the locator is about to perform the action indicating that pre-conditions have been met:

let willClick = false;
await page
.locator('button')
.on(LocatorEvent.Action, () => {
willClick = true;
})
.click();

This event can be used for logging/debugging or other purposes. The event might fire multiple times if the locator retries the action.

waitForSelector

waitForSelector is a lower-level API compared to locators that allows waiting for an element to be available in DOM. It does not automatically retry the action if it fails and requires manually disposing the resulting ElementHandle to prevent memory leaks. The method exists on the Page, Frame and ElementHandle instances.

// Import puppeteer
import puppeteer from 'puppeteer';

// Launch the browser.
const browser = await puppeteer.launch();

// Create a page.
const page = await browser.newPage();

// Go to your site.
await page.goto('YOUR_SITE');

// Query for an element handle.
const element = await page.waitForSelector('div > .class-name');

// Do something with element...
await element.click(); // Just an example.

// Dispose of handle.
await element.dispose();

// Close browser.
await browser.close();

Some page level APIs such as page.click(selector), page.type(selector), page.hover(selector) are implemented using waitForSelector for backwards-compatibility reasons.

Querying without waiting

Sometimes you know that the elements are already on the page. In that case, Puppeteer offers multiple ways to find an element or multiple elements matching a selector. These methods exist on Page, Frame and ElementHandle instances.

  • page.$() returns a single element matching a selector.
  • page.$$() returns all elements matching a selector.
  • page.$eval() returns the result of running a JavaScript function on the first element matching a selector.
  • page.$$eval() returns the result of running a JavaScript function on each element matching a selector.

Selectors

Puppeteer accepts CSS selectors in every API that accepts a selector. Additionally, you can opt-in into using additional selector syntax to do more than CSS selectors offer.

Non-CSS selectors

Puppeteer extends the CSS syntax with custom pseudo-elements that define how to select an element using a non-CSS selector. The Puppeteer supported pseudo-elements are prefixed with a -p vendor prefix.

XPath selectors (-p-xpath)

XPath selectors will use the browser's native Document.evaluate to query for elements.

// Runs the `//h2` as the XPath expression.
const element = await page.waitForSelector('::-p-xpath(//h2)');

Text selectors (-p-text)

Text selectors will select "minimal" elements containing the given text, even within (open) shadow roots. Here, "minimum" means the deepest elements that contain a given text, but not their parents (which technically will also contain the given text).

// Click a button inside a div element that has Checkout as the inner text.
await page.locator('div ::-p-text(Checkout)').click();
// You need to escape CSS selector syntax such '(', ')' if it is part of the your search text ('Checkout (2 items)').
await page.locator(':scope >>> ::-p-text(Checkout \\(2 items\\))').click();
// or use quotes escaping any quotes that are part of the search text ('He said: "Hello"').
await page.locator(':scope >>> ::-p-text("He said: \\"Hello\\"")').click();

ARIA selectors (-p-aria)

ARIA selectors can be used to find elements using the computed accessible name and role. These labels are computed using the browsers internal representation of the accessibility tree. That means that ARIA relationships such as labeledby are resolved before the query is run. The ARIA selectors are useful if you do not want to depend on any particular DOM structure or DOM attributes.

await page.locator('::-p-aria(Submit)').click();
await page.locator('::-p-aria([name="Click me"][role="button"])').click();

Pierce selector (pierce/)

Pierce selector is a selector that returns all elements matching the provided CSS selector in all shadow roots in the document. We recommend using deep combinators instead because they offer more flexibility in combining difference selectors. pierce/ is only available in the prefixed notation.

await page.locator('pierce/div').click();
// Same query as the pierce/ one using deep combinators.
await page.locator('& >>> div').click();

Querying elements in Shadow DOM

CSS selectors do not allow descending into Shadow DOM, therefore, Puppeteer adds two combinators to the CSS selector syntax that allow searching inside shadow DOM.

The >>> combinator

The >>> is called the deep descendent combinator. It is analogous to the CSS's descendent combinator (denoted with a single space character  , for example, div button) and it selects matching elements under the parent element at any depth. For example, my-custom-element >>> button would select all button elements that are available inside shadow DOM of the my-custom-element (the shadow host).

note

Deep combinators only work on the first "depth" of CSS selectors and open shadow roots; for example, :is(div > > a) will not work.

The >>>> combinator

The >>>> is called the deep child combinator. It is analogous to the CSS's child combinator (denoted with >, for example, div > button) and it selects matching elements under the parent element's immediate shadow root, if the element has one. For example, my-custom-element >>>> button would select all button elements that are available inside the immediate shadow root of the my-custom-element (the shadow host).

Custom selectors

You can also add your own pseudo element using Puppeteer.registerCustomQueryHandler. This is useful for creating custom selectors based on framework objects or your application.

For example, you can write all your selectors using the react-component pseudo-element and implement a custom logic how to resolve the provided ID.

Puppeteer.registerCustomQueryHandler('react-component', {
queryOne: (elementOrDocument, selector) => {
// Dummy example just delegates to querySelector but you can find your
// React component because this callback runs in the page context.
return elementOrDocument.querySelector(`[id="${CSS.escape(selector)}"]`);
},
queryAll: (elementOrDocument, selector) => {
// Dummy example just delegates to querySelector but you can find your
// React component because this callback runs in the page context.
return elementOrDocument.querySelectorAll(`[id="${CSS.escape(selector)}"]`);
},
});

In your application you can now write selectors as following.

await page.locator('::-p-react-component(MyComponent)').click();
// OR used in conjunction with other selectors.
await page.locator('.side-bar ::-p-react-component(MyComponent)').click();

Another example shows how you can define a custom query handler for locating vue components:

caution

Be careful when relying on internal APIs of libraries or frameworks. They can change at any time.

Puppeteer.registerCustomQueryHandler('vue', {
queryOne: (element, name) => {
const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
do {
const currentNode = walker.currentNode;
if (
currentNode.__vnode?.ctx?.type?.name.toLowerCase() ===
name.toLocaleLowerCase()
) {
return currentNode;
}
} while (walker.nextNode());

return null;
},
});

Search for a given view component as following:

const element = await page.$('::-p-vue(MyComponent)');

Prefixed selector syntax

caution

While we maintain prefixed selectors, the recommended way is to use the selector syntax documented above.

The following legacy syntax (${nonCssSelectorName}/${nonCssSelector}) allows running a single non-CSS selector at a time is also supported. Note that this syntax does not allow combining multiple selectors.

// Same as ::-p-text("My text").
await page.locator('text/My text').click();
// Same as ::-p-xpath(//h2).
await page.locator('xpath///h2').click();
// Same as ::-p-aria(My label).
await page.locator('aria/My label').click();

await page.locator('pierce/div').click();