跳到主要内容
版本:23.11.1

页面交互

Puppeteer 允许通过鼠标、触摸事件和键盘输入与页面上的元素进行交互。通常,您首先使用 CSS 选择器查询 DOM 元素,然后在选定的元素上调用一个操作。所有接受选择器的 Puppeteer API 默认都接受 CSS 选择器。此外,Puppeteer 还提供了自定义选择器语法,允许使用 XPath、文本、辅助功能属性查找元素,并访问 Shadow DOM,而无需执行 JavaScript。

如果想在不首先选择元素的情况下发出鼠标或键盘事件,请使用 page.mousepage.keyboardpage.touchscreen API。本指南的其余部分概述了如何选择 DOM 元素并在其上调用操作。

定位器

定位器是选择元素并与之交互的推荐方法。定位器封装了有关如何选择元素的信息,并允许 Puppeteer 自动等待元素出现在 DOM 中并处于适合操作的正确状态。您始终使用 page.locator()frame.locator() 函数实例化定位器。如果定位器 API 没有提供您需要的功能,您仍然可以使用较低级别的 API,例如 page.waitForSelector()ElementHandle

使用定位器单击元素

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

定位器在单击之前会自动检查以下内容

  • 确保元素在视口中。
  • 等待元素变为可见或隐藏。
  • 等待元素变为启用状态。
  • 等待元素在两个连续的动画帧中具有稳定的边界框。

填写输入框

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

自动检测输入类型,并选择适当的方式使用提供的值填充它。例如,它将填充 <select> 元素以及 <input> 元素。

定位器在键入输入框之前会自动检查以下内容

  • 确保元素在视口中。
  • 等待元素变为可见或隐藏。
  • 等待元素变为启用状态。
  • 等待元素在两个连续的动画帧中具有稳定的边界框。

悬停在元素上

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

定位器在悬停之前会自动检查以下内容

  • 确保元素在视口中。
  • 等待元素变为可见或隐藏。
  • 等待元素在两个连续的动画帧中具有稳定的边界框。

滚动元素

[.scroll()] 函数使用鼠标滚轮事件来滚动元素。

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

定位器在悬停之前会自动检查以下内容

  • 确保元素在视口中。
  • 等待元素变为可见或隐藏。
  • 等待元素在两个连续的动画帧中具有稳定的边界框。

等待元素可见

有时您只需要等待元素可见。

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

定位器在返回之前会自动检查以下内容

  • 等待元素变为可见或隐藏。

等待一个函数

有时,等待表示为 JavaScript 函数的任意条件很有用。在这种情况下,可以使用函数而不是选择器来定义定位器。以下示例等待 MutationObserver 检测到页面上出现 HTMLCanvasElement 元素。您还可以在函数定位器上调用其他定位器函数,例如 .click().fill()

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

在定位器上应用过滤器

以下示例展示了如何向表示为 JavaScript 函数的定位器添加额外的条件。仅当按钮元素的 innerText 为 'My button' 时,才会单击该按钮元素。

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

从定位器返回值

map 函数允许将元素映射到 JavaScript 值。在这种情况下,调用 wait() 将返回反序列化的 JavaScript 值。

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

从定位器返回 ElementHandles

waitHandle 函数允许返回 ElementHandle。如果对于您需要的操作没有相应的定位器 API,则此方法可能很有用。

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

配置定位器

可以配置定位器以调整预条件和其他选项

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

定位器超时

默认情况下,定位器从页面继承超时设置。但是,可以根据每个定位器设置超时。如果在指定的时间段内未找到元素或未满足预条件,则会抛出 TimeoutError

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

获取定位器事件

目前,定位器支持 单个事件,当定位器即将执行操作时,该事件会通知您,表明已满足预条件

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

此事件可用于日志记录/调试或其他目的。如果定位器重试操作,该事件可能会触发多次。

waitForSelector

waitForSelector 是比定位器低级的 API,允许等待元素在 DOM 中可用。如果操作失败,它不会自动重试,并且需要手动释放生成的 ElementHandle 以防止内存泄漏。该方法存在于 Page、Frame 和 ElementHandle 实例上。

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

由于向后兼容的原因,一些页面级 API,例如 page.click(selector)page.type(selector)page.hover(selector) 是使用 waitForSelector 实现的。

不等待的查询

有时,您知道元素已经在页面上。在这种情况下,Puppeteer 提供了多种方法来查找与选择器匹配的一个或多个元素。这些方法存在于 Page、Frame 和 ElementHandle 实例上。

  • page.$() 返回与选择器匹配的单个元素。
  • page.$$() 返回与选择器匹配的所有元素。
  • page.$eval() 返回在与选择器匹配的第一个元素上运行 JavaScript 函数的结果。
  • page.$$eval() 返回在与选择器匹配的每个元素上运行 JavaScript 函数的结果。

选择器

Puppeteer 在每个接受选择器的 API 中都接受 CSS 选择器。此外,您可以选择使用其他选择器语法来执行 CSS 选择器所能提供的更多操作。

非 CSS 选择器

Puppeteer 使用自定义的 伪元素扩展了 CSS 语法,这些伪元素定义了如何使用非 CSS 选择器选择元素。Puppeteer 支持的伪元素以 -p 供应商前缀开头。

XPath 选择器 (-p-xpath)

XPath 选择器将使用浏览器原生的 Document.evaluate 查询元素。

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

文本选择器 (-p-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 选择器 (-p-aria)

ARIA 选择器可用于查找具有计算出的可访问名称和角色的元素。这些标签是使用浏览器内部可访问性树的表示计算的。这意味着在运行查询之前会解析诸如 labeledby 之类的 ARIA 关系。如果您不想依赖任何特定的 DOM 结构或 DOM 属性,则 ARIA 选择器很有用。

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

穿透选择器 (pierce/)

穿透选择器是一种返回文档中所有阴影根中与提供的 CSS 选择器匹配的所有元素的选择器。我们建议使用深度组合器,因为它们在组合不同的选择器时提供了更大的灵活性。pierce/ 仅在带前缀的表示法中可用。

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

在 Shadow DOM 中查询元素

CSS 选择器不允许下降到 Shadow DOM 中,因此,Puppeteer 向 CSS 选择器语法添加了两个组合器,允许在shadow DOM内进行搜索。

>>> 组合器

>>> 称为深度后代组合器。它类似于 CSS 的后代组合器(用单个空格字符   表示,例如 div button),它选择父元素下任意深度的匹配元素。例如,my-custom-element >>> button 将选择 my-custom-element (shadow host) 的 shadow DOM 内的所有 button 元素。

注意

深度组合器仅在 CSS 选择器的第一个“深度”和打开的阴影根上起作用; 例如,:is(div > > a) 将不起作用。

>>>> 组合器

>>>> 称为深度子组合器。它类似于 CSS 的子组合器(用 > 表示,例如 div > button),并且它选择父元素直接阴影根下的匹配元素(如果该元素有)。例如,my-custom-element >>>> button 将选择 my-custom-element (shadow host) 直接阴影根中的所有 button 元素。

自定义选择器

您还可以使用Puppeteer.registerCustomQueryHandler添加自己的伪元素。这对于基于框架对象或应用程序创建自定义选择器非常有用。

例如,您可以使用 react-component 伪元素编写所有选择器,并实现自定义逻辑来解析提供的 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)}"]`);
},
});

在您的应用程序中,您现在可以编写如下选择器。

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

另一个示例展示了如何定义一个自定义查询处理程序来定位 vue 组件

注意

在依赖库或框架的内部 API 时要小心。它们可能随时更改。

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

如下所示搜索给定的视图组件

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

带前缀的选择器语法

注意

虽然我们维护带有前缀的选择器,但建议的方式是使用上面记录的选择器语法。

还支持以下旧语法 (${nonCssSelectorName}/${nonCssSelector}),该语法允许一次运行单个非 CSS 选择器。请注意,此语法不允许组合多个选择器。

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