-
-
Save tokland/d3bae3b6d3c1576d8700405829bbdb52 to your computer and use it in GitHub Desktop.
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, run()
executes an example of usage, what else do you need?
this is a great saviour!
couple of points:
-
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.
-
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}`);
};
@svamja where is element
being used?
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}`);
};
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);
...
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', "'", '', '')`
);
});
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.
How I can use it?