14

If I have some content in my page such as:

<a>Hi!</a>

How can I use Google's Puppeteer to automate the clicking of that element?

I need to be able to select it based on its contents alone, not id, class or attribute.

Is there something like $('a:contains("Hi!")') that I can leverage to select this element?

How can I do this with https://github.com/GoogleChrome/puppeteer

thanks

Bryce
  • 6,440
  • 8
  • 40
  • 64
Totty.js
  • 15,563
  • 31
  • 103
  • 175
  • Does this answer your question? [How to click on element with text in Puppeteer](https://stackoverflow.com/questions/47407791/how-to-click-on-element-with-text-in-puppeteer) – ggorlen Nov 01 '20 at 17:42

2 Answers2

21

Alternative approach using XPath

There is a much easier way to do it using an XPath expression:

const aElementsWithHi = await page.$x("//a[contains(., 'Hi!')]");
await aElementsWithHi[0].click();

Using page.$x, this code finds all a elements with the text Hi! inside. The result will be an array containing the matching a element handles. Using the elementHandle.click function, we can then click on the element.

Thomas Dondorf
  • 23,416
  • 6
  • 84
  • 105
9

First, we have to find element by text.

/**
 * findElemByText - Find an Element By Text
 *
 * @param  {String} str                case-insensitive string to search
 * @param  {String} selector = '*'     selector to search
 * @param  {String} leaf = 'outerHTML' leaf of the element
 * @return {Array}                     array of elements
 */
function findElemByText({str, selector = '*', leaf = 'outerHTML'}){
  // generate regex from string
  const regex = new RegExp(str, 'gmi');

  // search the element for specific word
  const matchOuterHTML = e => (regex.test(e[leaf]))

  // array of elements
  const elementArray = [...document.querySelectorAll(selector)];

  // return filtered element list
  return elementArray.filter(matchOuterHTML)
}

// usage
// findElemByText({str: 'Example', leaf: 'innerHTML', selector: 'title'});
// findElemByText({str: 'Example', selector: 'h1'});
// findElemByText({str: 'Example'});

Save it in same folder as your puppeteer script, name it script.js.

Now, we can use this in our puppeteer script. We can use ElementHandle, but for simplicity of understanding, I'll use .evaluate() function provided with puppeteer.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');

  // expose the function
  await page.addScriptTag({path: 'script.js'});

  // Find Element by Text and Click it
  await page.evaluate(() => {
   // click the first element 
   return findElemByText({str: 'More'})[0].click();
  });

  // Wait for navigation, Take Screenshot, Do other stuff
  await page.screenshot({path: 'screenshot.png'});
  await browser.close();
})();

Do not copy paste the code above, try to understand it and type them yourself. If the code above fails, try to find why it's failing.

Edo
  • 3,311
  • 1
  • 24
  • 25
Md. Abu Taher
  • 17,395
  • 5
  • 49
  • 73
  • 1
    Thanks :) I remember doing the same with selenium when I needed something "special". Would be nice to have this out of the box with Puppeteer. I guess would be nice to add jquery/css selector on the page and then use the `ElementHandle` to get all those features in one go. – Totty.js Dec 15 '17 at 10:10
  • Excellent answer to the question. I'd add that if possible, it's probably a better idea to add test-ids to the elements you're trying to match, as described here: https://ropig.com/blog/end-end-tests-dont-suck-puppeteer/. This ensures your tests don't break as soon as you update the text on the button/link. – Edo Feb 05 '18 at 09:35
  • It looks like the function returns an array starting at the outermost element to the innermost one, i.e. [body, div, p]. Does that sound about right? – Rob Gravelle Dec 13 '18 at 21:34