How to click on element with text in Puppeteer
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')
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) astext()
returns a list with two texts (Start
andEnd
), butcontains
will only check the first one -
//button[contains(text(), 'Middle')]
will return no results astext()
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.
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.
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();
});