The current top answer for tokland only works on text nodes, and not on nodes with other elements inside.
Short answer
This XPath expression will request a button that contains the text "Button Text":
const [button] = await page.$x("//button[contains(., 'Button text')]"); if (button) { await button.click(); }
To also comply with the <div class="elements"> surrounding buttons, use the following code:
const [button] = await page.$x("//div[@class='elements']/button[contains(., 'Button text')]");
explanation
To explain why using a text node ( text() ) is incorrect 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 if it contains(text(), 'Text') :
//button[contains(text(), 'Start')] will return both two nodes (as expected)//button[contains(text(), 'End')] will return only one node (first), since text() returns a list with two texts ( Start and End ), but contains will only check the first//button[contains(text(), 'Middle')] will not return any results, as text() does not include the text of child nodes
Here are the XPath expressions for contains(., 'Text') that work with 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 (last button)
So in most cases it makes sense to use . instead of text() in an XPath expression.
Thomas Dondorf
source share