如何在 Puppeteer 中单击带有文本的元素

How to click on element with text in Puppeteer

有没有什么方法(在API中没有找到)或解决方案来点击带有文本的元素?

例如我有 html:

<div class="elements">
    <button>Button text</button>
    <a href=#>Href text</a>
    <div>Div text</div>
</div>

我想点击包含文本的元素(点击 .elements 内的按钮),例如:

Page.click('Button text', '.elements')

您可以将 XPath 选择器与 page.$x(expression):

一起使用
const linkHandlers = await page.$x("//a[contains(text(), 'Some text')]");

if (linkHandlers.length > 0) {
  await linkHandlers[0].click();
} else {
  throw new Error("Link not found");
}

查看 gist 中的 clickByText 以获得完整示例。它负责转义引号,这对于 XPath 表达式来说有点棘手。

这是我的解决方案:

let selector = 'a';
    await page.$$eval(selector, anchors => {
        anchors.map(anchor => {
            if(anchor.textContent == 'target text') {
                anchor.click();
                return
            }
        })
    });

解决方法是

(await page.$$eval(selector, a => a
            .filter(a => a.textContent === 'target text')
))[0].click()

您还可以使用page.evaluate() to click elements obtained from document.querySelectorAll() 已按文本内容过滤:

await page.evaluate(() => {
  [...document.querySelectorAll('.elements button')].find(element => element.textContent === 'Button text').click();
});

或者,您可以使用 page.evaluate() to click an element based on its text content using document.evaluate() 和相应的 XPath 表达式:

await page.evaluate(() => {
  const xpath = '//*[@class="elements"]//button[contains(text(), "Button text")]';
  const result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);

  result.iterateNext().click();
});

快速解决方案可以使用高级 css 选择器,例如“:contains(text)”

所以使用这个 library 你可以

const select = require ('puppeteer-select');

const element = await select(page).getElement('button:contains(Button text)');
await element.click()

简答

此 XPath 表达式将查询包含文本“按钮文本”的按钮:

const [button] = await page.$x("//button[contains(., 'Button text')]");
if (button) {
    await button.click();
}

为了尊重按钮周围的 <div class="elements">,请使用以下代码:

const [button] = await page.$x("//div[@class='elements']/button[contains(., 'Button text')]");

说明

为了解释为什么在某些情况下使用文本节点(text())是错误的,让我们看一个例子:

<div>
    <button>Start End</button>
    <button>Start <em>Middle</em> End</button>
</div>

首先,让我们检查一下使用contains(text(), 'Text')时的结果:

  • //button[contains(text(), 'Start')] 将 return 两个 两个 节点(如预期的那样)
  • //button[contains(text(), 'End')] 只会 return 一个 个节点(第一个)作为 text() returns 一个包含两个文本的列表( Start End), 但 contains 只会检查第一个
  • //button[contains(text(), 'Middle')]会return没有结果因为text()不包含子节点的文本

这里是 contains(., 'Text') 的 XPath 表达式,它作用于元素本身,包括它的子节点:

  • //button[contains(., 'Start')] 将 return 两个 两个 按钮
  • //button[contains(., 'End')] 将再次 return 两个 两个 按钮
  • //button[contains(., 'Middle')]将return一个(最后一个按钮)

因此在大多数情况下,在 XPath 表达式中使用 . 而不是 text() 更有意义。

文本选择器或组合器选项不支持 css 选择器语法,我的解决方法是:

await page.$$eval('selector', selectorMatched => {
    for(i in selectorMatched)
      if(selectorMatched[i].textContent === 'text string'){
          selectorMatched[i].click();
          break;//Remove this line (break statement) if you want to click on all matched elements otherwise the first element only is clicked  
        }
    });

我不得不:

await this.page.$eval(this.menuSelector, elem => elem.click());

由于 OP 的用例似乎与目标字符串完全匹配 "Button text"<button>Button text</button>text() 似乎是正确的方法而不是 less-precise contains().

虽然 对于 contains 当有 sub-elements 时,避免误报,使用 text() 避免误报,例如,当按钮是 <button>Button text and more stuff</button>,这似乎很可能是一种情况。手边同时拥有这两种工具很有用,因此您可以在 case-by-case 的基础上选择更合适的工具。

const xp = '//*[@class="elements"]//button[text()="Button text"]';
const [el] = await page.$x(xp);
await el?.click();

请注意,许多其他答案未满足 .elements 父 class 要求。

此外,使用 waitForXPath 等待,然后 returns,匹配 XPath 的元素通常很方便,如果在指定的超时时间内没有找到则抛出:

const xp = '//*[@class="elements"]//button[text()="Button text"]';
const el = await page.waitForXPath(xp);
await el?.click();

使用 puppeteer 12.0.1,以下对我有效:

await page.click("input[value='Opt1']"); //where value is an attribute of the element input
await page.waitForTimeout(1000);

await page.click("li[value='Nested choice 1']"); //where value is an attribute of the element li after clicking the previous option
await page.waitForTimeout(5000);