smartmouse
smartmouse

Reputation: 14404

Puppeteer: how to click element that contains certain value in its descendants

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.

Upvotes: 1

Views: 692

Answers (1)

ggorlen
ggorlen

Reputation: 56885

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 el.evaluate(el => el.innerText));
  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")]

Since this answer was posted, page.$x has been removed from the library. See How can I get an element by xpath? for details.

Upvotes: 1

Related Questions