How to click on element with text in Puppeteer

Puppeteer

Puppeteer Problem Overview


Is there any method (didn't find in API) or solution to click on element with text?

For example I have html:

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

And I want to click on element in which text is wrapped (click on button inside .elements), like:

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

Puppeteer Solutions


Solution 1 - Puppeteer

Short answer

This XPath expression will query a button which contains the text "Button text":

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

To also respect the <div class="elements"> surrounding the buttons, use the following code:

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

Explanation

To explain why using the text node (text()) is wrong in some cases, let's look at an example:

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

First, let's check the results when using contains(text(), 'Text'):

  • //button[contains(text(), 'Start')] will return both two nodes (as expected)
  • //button[contains(text(), 'End')] will only return one nodes (the first) as text() returns a list with two texts (Start and End), but contains will only check the first one
  • //button[contains(text(), 'Middle')] will return no results as text() does not include the text of child nodes

Here are the XPath expressions for contains(., 'Text'), which works on the element itself including its child nodes:

  • //button[contains(., 'Start')] will return both two buttons
  • //button[contains(., 'End')] will again return both two buttons
  • //button[contains(., 'Middle')] will return one (the last button)

So in most cases, it makes more sense to use the . instead of text() in an XPath expression.

Solution 2 - Puppeteer

You may use a XPath selector with 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");
}

Check out clickByText in this gist for a complete example. It takes care of escaping quotes, which is a bit tricky with XPath expressions.

Solution 3 - Puppeteer

You can also use page.evaluate() to click elements obtained from document.querySelectorAll() that have been filtered by text content:

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

Alternatively, you can use page.evaluate() to click an element based on its text content using document.evaluate() and a corresponding XPath expression:

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

Solution 4 - Puppeteer

made quick solution to be able to use advanced css selectors like ":contains(text)"

so using this library you can just

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

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

Solution 5 - Puppeteer

The solution is

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

Solution 6 - Puppeteer

Here is my solution:

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

 

Solution 7 - Puppeteer

Since OP's use case appears to be an exact match on the target string "Button text", <button>Button text</button>, text() seems like the correct method rather than the less-precise contains().

Although Thomas makes a good argument for contains when there are sub-elements, avoiding false negatives, using text() avoids a false positive when the button is, say, <button>Button text and more stuff</button>, which seems just as likely a scenario. It's useful to have both tools on hand so you can pick the more appropriate one on a case-by-case basis.

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

Note that many other answers missed the .elements parent class requirement.

Also, it's often handy to use waitForXPath which waits for, then returns, the element matching the XPath or throws if it's not found within the specified timeout:

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

Solution 8 - Puppeteer

There is no supported css selector syntax for text selector or a combinator option, my work around for this would be:

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

Solution 9 - Puppeteer

With puppeteer 12.0.1, the following works for me:

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

Solution 10 - Puppeteer

I had to:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAleksandr GolubovskijView Question on Stackoverflow
Solution 1 - PuppeteerThomas DondorfView Answer on Stackoverflow
Solution 2 - PuppeteertoklandView Answer on Stackoverflow
Solution 3 - PuppeteerGrant MillerView Answer on Stackoverflow
Solution 4 - Puppeteeruser3493381View Answer on Stackoverflow
Solution 5 - PuppeteerAlex AView Answer on Stackoverflow
Solution 6 - PuppeteerKamenView Answer on Stackoverflow
Solution 7 - PuppeteerggorlenView Answer on Stackoverflow
Solution 8 - PuppeteerEkeuweiView Answer on Stackoverflow
Solution 9 - Puppeteerchinsoon12View Answer on Stackoverflow
Solution 10 - PuppeteerAlferd NobelView Answer on Stackoverflow