puppeteer/docs/guides/page-interactions.md
2024-06-03 11:07:40 +02:00

14 KiB

Page interactions

Puppeteer allows you interact with the pages in various ways.

Locators

Locators is a new, experimental API that combines the functionalities of waiting and actions. With additional precondition checks, it enables automatic retries for failed actions, resulting in more reliable and less flaky automation scripts.

:::note

Locators API is experimental and we will not follow semver for breaking changes in the Locators API.

:::

Use cases

Waiting for an element

await page.locator('button').wait();

The following preconditions are automatically checked:

  • Waits for the element to become visible or hidden.

Waiting for a function

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();

Clicking an element

await page.locator('button').click();

The following preconditions are automatically checked:

  • 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.

Clicking an element matching a criteria

await page
  .locator('button')
  .filter(button => !button.disabled)
  .click();

The following preconditions are automatically checked:

  • 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

await page.locator('input').fill('value');

Automatically detects the input type and choose an appropriate way to fill it out with the provided value.

The following preconditions are automatically checked:

  • 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.

Retrieving an element property

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

Hover over an element

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

The following preconditions are automatically checked:

  • 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

await page.locator('div').scroll({
  scrollLeft: 10,
  scrollTop: 20,
});

The following preconditions are automatically checked:

  • 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.

Configuring locators

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

await page
  .locator('button')
  .setEnsureElementIsInTheViewport(false)
  .setTimeout(0)
  .setVisibility(null)
  .setWaitForEnabled(false)
  .setWaitForStableBoundingBox(false)
  .click();

Getting locator events

Currently, locators support a single event that notifies you when the locator is about to perform the action:

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.

Query Selectors

Queries are the primary mechanism for interacting with the DOM on your site. For example, a typical workflow goes like:

// Import puppeteer
import puppeteer from 'puppeteer';

(async () => {
  // 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();
})();

P Selectors

Puppeteer uses a superset of the CSS selector syntax for querying. We call this syntax P selectors and it's supercharged with extra capabilities such as deep combinators and text selection.

:::caution

Although P selectors look like real CSS selectors (we intentionally designed it this way), they should not be used for actually CSS styling. They are designed only for Puppeteer.

:::

:::note

P selectors only work on the first "depth" of selectors; for example, :is(div >>> a) will not work.

:::

>>> and >>>> combinators

The >>> and >>>> are called deep descendent and deep combinators respectively. Both combinators have the effect of going into shadow hosts with >>> going into every shadow host under a node and >>>> going into the immediate one (if the node is a shadow host; otherwise, it's a no-op).

:::note

A common question is when should >>>> be chosen over >>> considering the flexibility of >>>. A similar question can be asked about > and a space; choose > if you do not need to query all elements under a given node and a space otherwise. This answer extends to >>>> (>) and >>> (space) naturally.

:::

Example

Suppose we have the markup

<custom-element>
  <template shadowrootmode="open">
    <slot></slot>
  </template>
  <custom-element>
    <template shadowrootmode="open">
      <slot></slot>
    </template>
    <custom-element>
      <template shadowrootmode="open">
        <slot></slot>
      </template>
      <h2>Light content</h2>
    </custom-element>
  </custom-element>
</custom-element>

Note: <template shadowrootmode="open"> is not supported on Firefox. You can read more about it here.

Then custom-element >>> h2 will return h2, but custom-element >>>> h2 will return nothing since the inner h2 is in a deeper shadow root.

P-elements

P elements are pseudo-elements with a -p vendor prefix. It allows you to enhance your selectors with Puppeteer-specific query engines such as XPath, text queries, and ARIA.

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).

Example
const element = await page.waitForSelector('div ::-p-text(My name is Jun)');
// You can also use escapes.
const element = await page.waitForSelector(
  ':scope >>> ::-p-text(My name is Jun \\(pronounced like "June"\\))'
);
// or quotes
const element = await page.waitForSelector(
  'div >>>> ::-p-text("My name is Jun (pronounced like \\"June\\")"):hover'
);
XPath selectors (-p-xpath)

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

Example
const element = await page.waitForSelector('::-p-xpath(h2)');
ARIA selectors (-p-aria)

ARIA selectors can be used to find elements with a given ARIA label. These labels are computed using Chrome's internal representation.

Example
const node = await page.waitForSelector('::-p-aria(Submit)');
const node = await page.waitForSelector(
  '::-p-aria([name="Click me"][role="button"])'
);

Custom selectors

Puppeteer provides users the ability to add their own query selectors to Puppeteer using Puppeteer.registerCustomQueryHandler. This is useful for creating custom selectors based on framework objects or other vendor-specific objects.

Custom Selectors

You can register a custom query handler that allows you to create custom selectors. For example, define a query handler for getById selectors:

Puppeteer.registerCustomQueryHandler('getById', {
  queryOne: (elementOrDocument, selector) => {
    return elementOrDocument.querySelector(`[id="${CSS.escape(selector)}"]`);
  },
  // Note: for demonstation perpose only `id` should be page unique
  queryAll: (elementOrDocument, selector) => {
    return elementOrDocument.querySelectorAll(`[id="${CSS.escape(selector)}"]`);
  },
});

You can now use it as following:

const node = await page.waitForSelector('::-p-getById(elementId)');
// OR used in conjunction with other selectors
const moreSpecificNode = await page.waitForSelector(
  '.side-bar ::-p-getById(elementId)'
);
Custom framework components selector

:::caution

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

:::

Find Vue components by name by using Vue internals for querying:

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;
  },
});

Query the Vue component as following:

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

Web Components create their own tag so you can query them by the tag name:

const element = await page.$('my-web-component');

Extend HTMLElementTagNameMap to define types for custom tags. This allows Puppeteer to infer the return type for the ElementHandle:

declare global {
  interface HTMLElementTagNameMap {
    'my-web-component': MyWebComponent;
  }
}

Query Selectors (legacy)

:::caution

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

:::

Queries are the primary mechanism for interacting with the DOM on your site. For example, a typical workflow goes like:

// Import puppeteer
import puppeteer from 'puppeteer';

(async () => {
  // 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();
})();

CSS

CSS selectors follow the CSS spec of the browser being automated. We provide some basic type deduction for CSS selectors (such as HTMLInputElement for input), but any selector that contains no type information (such as .class-name) will need to be coerced manually using TypeScript's as coercion mechanism.

Example

// Automatic
const element = await page.waitForSelector('div > input');
// Manual
const element = (await page.waitForSelector(
  'div > .class-name-for-input'
)) as HTMLInputElement;

Built-in selectors

Built-in selectors are Puppeteer's own class of selectors for doing things CSS cannot. Every built-in selector starts with a prefix .../ to assist Puppeteer in distinguishing between CSS selectors and a built-in.

Text selectors (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).

Example
// Note we usually need type coercion since the type cannot be deduced, but for text selectors, `instanceof` checks may be better for runtime validation.
const element = await page.waitForSelector('text/My name is Jun');

XPath selectors (xpath/)

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

Example
// There is not type deduction for XPaths.
const node = await page.waitForSelector('xpath/h2');

ARIA selectors (aria/)

ARIA selectors can be used to find elements with a given ARIA label. These labels are computed using Chrome's internal representation.

Example
const node = await page.waitForSelector('aria/Button name');

Pierce selectors (pierce/)

Pierce selectors will run the querySelector* API on the document and all shadow roots to find an element.

:::danger

Selectors will not partially pierce through shadow roots. See the examples below.

:::

Example

Suppose the HTML is

<div>
  <custom-element>
    <div></div>
  </custom-element>
</div>

Then

// This will be two elements because of the outer and inner div.
expect((await page.$$('pierce/div')).length).toBe(2);

// Partial piercing doesn't work.
expect((await page.$$('pierce/div div')).length).toBe(0);

Custom selectors

Puppeteer provides users the ability to add their own query selectors to Puppeteer using Puppeteer.registerCustomQueryHandler. This is useful for creating custom selectors based on framework objects or other vendor-specific objects.