1

I'm using for first times Puppeteer and I have this code to click on a certain element:

await page.waitForSelector('.item-table > .grid-item > .grid-item-container > .grid-table-container > .grid-option-table:nth-child(1) > .grid-option:nth-child(1) > .grid-option-selectable > div');
await page.click('.item-table > .grid-item > .grid-item-container > .grid-table-container > .grid-option-table:nth-child(1) > .grid-option:nth-child(1) > .grid-option-selectable > div');

Since I have a lot of .item-table elements on the page I would like to make it to click on that has a certain text in one of its descendants (I don't know the level of a descendant). I have searched for a solution in the documentation and even among SO questions, but I cannot find anything helpful.

I tried to add > :contains("Foo bar") but maybe it is the wrong way to do it. In fact, it doesn't work.

await page.waitForSelector('.item-table > :contains("Foo Bar") > .grid-item > .grid-item-container > .grid-table-container > .grid-option-table:nth-child(1) > .grid-option:nth-child(1) > .grid-option-selectable > div');
await page.click('.item-table > :contains("Foo Bar") > .grid-item-container > .grid-table-container > .grid-option-table:nth-child(1) > .grid-option:nth-child(1) > .grid-option-selectable > div');

So, how to do it with Puppeteer?

EDIT: Here is the markup I'm trying to scrape:

<div class="item-table"></div>
<div class="item-table"></div>
<div class="item-table"></div>
<div class="item-table"></div>
<div class="item-table"></div>
<div class="item-table"></div>
<div class="item-table">
    <div class="grid-item">
        <div class="grid-item-container">
            <div class="grid-table-container>
                <div class="grid-option-header">
                    <div class="grid-option-caption">
                        <div class="grid-option-name">
                            Foo Bar
                            <span>some other text</span>
                        </div>
                    </div>
                </div>
                <div class="grid-option-table">
                    <div class="grid-option">
                        <div class="grid-option-selectable">
                            <div></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="item-table"></div>
<div class="item-table"></div>

So, I want to click on div that is in grid-option-selectable div, within the item-table div that contains Foo Bar among its descendants.

smartmouse
  • 13,912
  • 34
  • 100
  • 166

1 Answers1

1

I'm going to make a slight adjustment to your markup just to help validate that the code works:

<!-- ... same ... -->
<div class="grid-option-selectable">
    <div>You got me!</div>
</div>
<!-- ... same ... -->

It's also not a bad idea to add some same-structure siblings with non-matching text to make sure we aren't producing false positives.

The strategy we'll try is the one described in this answer (this answer in the same thread offers alternatives and this separate thread is related): query using xpath on the target parent mutually shared between the target text and clickable element by descendant text, then query that parent for the clickable element.

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch(/*{dumpio: true}*/);
  const page = await browser.newPage();
  await page.goto("http://localhost:8000");
  const xp = `
    //div[@class="item-table" 
          and descendant::*[contains(text(), "Foo Bar")]]
    //div[@class="grid-option-selectable"]
  `;
  const [el] = await page.$x(xp);
  console.log(await page.evaluate(el => el.innerText, el));
  await el.click();
  await browser.close();
})();

Output:

You got me!

Note that I'm using a precise class match, but if you may have multiple classes on your target element, you'll need to loosen your query with contains. So you could write the xpath expression like:

//div[contains(@class, "item-table")
      and .//*[contains(text(), "Foo Bar")]]
//div[contains(@class, "grid-option-selectable")]
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • It doesn't work. The reason could be that "You got me!" text has a `span` as sibling? – smartmouse Nov 20 '20 at 09:36
  • I did get the button using `cheerio` in a forEach loop. Any way to pass the selected element to Puppeteer to have it clicked? – smartmouse Nov 20 '20 at 10:10
  • Your actual markup must be different than what you've shown because this code works on what you posted. If you have a selector working with Cheerio, why not use the same selector in Puppeteer? Furthermore, if Cheerio works and the markup isn't created dynamically, why even use Puppeteer at all? Please update your post to show the actual markup you're scraping and explain whether it's dynamic or not. Thanks. – ggorlen Nov 20 '20 at 15:11