Puppeteer:单击带有文本的元素

Ale*_*kij 55 puppeteer

是否有任何方法(在API中找不到)或解决方案点击带有文本的元素?

例如我有html:

<div class="elements">
    <button>Button text</button>
    <a href=#>Href text</a>
    <div>Div text</div>
</div>
Run Code Online (Sandbox Code Playgroud)

我想点击包含文字的元素(点击.elements里面的按钮),如:

Page.click('Button text', '.elements')
Run Code Online (Sandbox Code Playgroud)

有解决方案吗

tok*_*and 68

您可以将XPath选择器与页面一起使用.$ x(表达式):

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");
}
Run Code Online (Sandbox Code Playgroud)

请查看clickByText要点以获取完整示例.它负责转义引号,这对于XPath表达式来说有点棘手.

  • @RuneJeppesen 将 `//a[contains` 替换为 `//*[contains` 以选择任何元素,而不仅仅是锚点 (`a`) 元素。 (3认同)

Tho*_*orf 25

目前的顶级答案由tokland仅适用于文本节点,而不是与其他元素的内部节点。

简短答案

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

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

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

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

说明

为了说明为什么text()在某些情况下使用文本节点()是错误的,我们来看一个示例:

<div>
    <button>Start End</button>
    <button>Start <em>Middle</em> End</button>
</div>
Run Code Online (Sandbox Code Playgroud)

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

  • //button[contains(text(), 'Start')]将返回两个节点(按预期方式)
  • //button[contains(text(), 'End')]将仅返回一个节点(第一个),并text()返回包含两个文本(StartEnd)的列表,但contains仅检查第一个节点
  • //button[contains(text(), 'Middle')] 将不返回任何结果,因为text()不包括子节点的文本

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

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

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

  • @AndreaBisello您可以使用`//*[...]`代替。 (8认同)
  • 适用于各种元素的东西?我不知道文本是否在按钮、ap、div、span 等内部。 (2认同)

ggo*_*len 12

Puppeteer 19.7.1 添加了“p”(伪)选择器,因此text/已弃用::-p-text,取而代之的是选择子字符串。例如:

const el = await page.waitForSelector("::-p-text(Button text)");
Run Code Online (Sandbox Code Playgroud)

伪选择器可以与 CSS 选择器结合使用,例如

const el = await page.$(".elements button::-p-text(Button text)");
Run Code Online (Sandbox Code Playgroud)

在 Puppeteer >= 18.0.0 中,选择器有一个text/前缀,用于选择元素文本的子字符串:

const el = await page.waitForSelector("text/Button text");
Run Code Online (Sandbox Code Playgroud)

具体到 XPath,与 18.0.0 之前的 Puppeteer 最相关:

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

尽管托马斯对何时存在子元素、避免误报做出了很好的论证,但当按钮是 时,使用 可以避免误报,这似乎很可能是一种情况。手头同时拥有这两种工具非常有用,这样您就可以根据具体情况选择更合适的工具。containstext()<button>Button text and more stuff</button>

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

请注意,许多其他答案都忽略了.elements父类的要求。

另一个 XPath 函数是[normalize-space()="Button text"]“从字符串中去除前导和尾随空白,用单个空格替换空白字符序列”,并且对于某些情况可能很有用。

此外,使用waitForXPathwhich 等待然后返回与 XPath 匹配的元素通常很方便,如果在指定的超时内未找到则抛​​出异常:

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

另一种适用于所有环境的灵活方法是使用浏览器 JS 通过文本来.find().filter()移出您想要的元素:

// untrusted click (ignores visibility, sometimes useful):
await page.$$eval(".elements *", els =>
  els
    .find(el => el.textContent.trim().toLowerCase() === "button text")
    .click()
);

// trusted click:
const el = await page.evaluateHandle(() =>
  [...document.querySelectorAll(".elements *")]
    .find(el => el.textContent.trim().toLowerCase() === "button text")
);
await el.click();
Run Code Online (Sandbox Code Playgroud)

或者:

// untrusted clicks
const els = await page.$$eval(".elements *", els =>
  els
    .filter(el => el.textContent.trim().toLowerCase() === "button text")
    .forEach(el => el.click())
);

// trusted clicks (not ideal)
const els = await page.evaluateHandle(`
  [...document.querySelectorAll(".elements *")]
    .filter(el => el.textContent.trim().toLowerCase() === "button text")
`);
const length = await els.evaluate(els => els.length);

for (let i = 0; i < length; i++) {
  const el = await els.evaluateHandle((els, i) => els[i], i);
  await el.click();
}
Run Code Online (Sandbox Code Playgroud)

如果您需要等待此文本,您可以使用waitForFunction

const el = await page.waitForFunction(`
  [...document.querySelectorAll(".elements *")]
    .find(el => el.textContent.trim().toLowerCase() === "button text")
`);
await el.click();
Run Code Online (Sandbox Code Playgroud)

如果您正在操作的页面恰好有 jQuery(或者您自己导入它),则可以使用:containssizzle 伪选择器语法:

const el = await page.evaluateHandle(`
  $('.elements :contains("Button text")').first()
`);
Run Code Online (Sandbox Code Playgroud)


小智 8

做出了快速的解决方案,以能够使用高级的CSS选择器,例如“:contains(text)”

所以使用这个库,你可以

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

const element = await select(page).getElement('button:contains(Button text)');
await element.click()
Run Code Online (Sandbox Code Playgroud)


Gra*_*ler 7

您还page.evaluate()可以点击从document.querySelectorAll()文本内容中过滤出来的元素:

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

或者,您可以使用和相应的XPath表达式page.evaluate()来基于元素的文本内容单击元素document.evaluate()

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();
});
Run Code Online (Sandbox Code Playgroud)


小智 6

解决办法是

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

  • 考虑用“find(...)”替换“filter(...)[0]”。 (3认同)

小智 5

这是我的解决方案:

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