Skip to content

Instantly share code, notes, and snippets.

@tokland
Last active August 11, 2023 03:48
Show Gist options
  • Save tokland/d3bae3b6d3c1576d8700405829bbdb52 to your computer and use it in GitHub Desktop.
Save tokland/d3bae3b6d3c1576d8700405829bbdb52 to your computer and use it in GitHub Desktop.
Click link by text in Puppeteer
const puppeteer = require('puppeteer');
const escapeXpathString = str => {
const splitedQuotes = str.replace(/'/g, `', "'", '`);
return `concat('${splitedQuotes}', '')`;
};
const clickByText = async (page, text) => {
const escapedText = escapeXpathString(text);
const linkHandlers = await page.$x(`//a[contains(text(), ${escapedText})]`);
if (linkHandlers.length > 0) {
await linkHandlers[0].click();
} else {
throw new Error(`Link not found: ${text}`);
}
};
const run = async () => {
const browser = await puppeteer.launch({args: ['--no-sandbox'], headless: true});
const page = await browser.newPage();
await page.goto('https://en.wikipedia.org/wiki/List_of_The_Sandman_characters');
await clickByText(page, `Fiddler's Green`);
await page.waitForNavigation({waitUntil: 'load'});
console.log("Current page:", page.url());
return browser.close();
};
const logErrorAndExit = err => {
console.log(err);
process.exit();
};
run().catch(logErrorAndExit);
@AlonsoK28
Copy link

How I can use it?

@tokland
Copy link
Author

tokland commented Nov 12, 2018

@AlonsoK28, run() executes an example of usage, what else do you need?

@svamja
Copy link

svamja commented Nov 27, 2018

this is a great saviour!

couple of points:

  1. If there is icon (or, br tag) along with text, this doesnt work. Improvement suggested by https://stackoverflow.com/questions/3655549/xpath-containstext-some-string-doesnt-work-when-used-with-node-with-more.

  2. This does not take into account visibility.

I ended up with below function. Please incorporate so that others can benefit.. Thanks!

const clickByText = async function(page, text, element) {
    element = element || 'a';
    const escapedText = escapeXpathString(text);
    xpath = `//*[text()[contains(., ${escapedText})]]`;
    const elements = await page.$x(xpath);
    if(elements.length > 0) {
        for(i in elements) {
            e = elements[i];
            if(await e.isIntersectingViewport()) {
                await e.click();
                return;
            }
        }
    }
    else {
        console.log(xpath);
    }
    throw new Error(`Link not found: ${text}`);
};

@germanattanasio
Copy link

@svamja where is element being used?

@temitope
Copy link

temitope commented Jan 12, 2019

yeah i think maybe incorporating element into the Xpath may be what was intended. Though technically the asterik is not a bad fallback and element can be ignored.

const clickByText = async function(page, text, element) {
    const element = element || 'a';
    const escapedText = escapeXpathString(text);
    xpath = `//${element}[text()[contains(., ${escapedText})]]`;
    const elements = await page.$x(xpath);
    if(elements.length > 0) {
        for(i in elements) {
            e = elements[i];
            if(await e.isIntersectingViewport()) {
                await e.click();
                return;
            }
        }
    }
    else {
        console.log(xpath);
    }
    throw new Error(`Link not found: ${text}`);
};

@gpawlik
Copy link

gpawlik commented Nov 12, 2019

In my case I needed to add waitForXPath before, just like that:

...
xpath = `//${element}[text()[contains(., ${escapedText})]]`;
await page.waitForXPath(xpath);
const elements = await page.$x(xpath);
...

@Maxim-Mazurok
Copy link

This was easier for me to understand:

export const escapeXpathString = (str: string) => {
  const splitQuotes = str.split(`'`).join(`', "'", '`);
  return `concat('${splitQuotes}', '')`;
};

it("escapeXpathString works", () => {
  expect(escapeXpathString("test'lol'")).toEqual(
    `concat('test', "'", 'lol', "'", '', '')`
  );
});

@ggorlen
Copy link

ggorlen commented Apr 25, 2023

You don't need to use XPath for selecting by text any longer. There's page.click("::-p-text(hello world)"). See this Stack Overflow answer for more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment