How can I get an element by xpath? - puppeteer

I need to find any element in DOM by xpath. I already tried the following:
let el = await page.$x('//*[#id="readium-right-panel"]/ul/li[1]');
The returning error is:
TypeError: page.$x is not a function

Looks like your puppeteer version may be outdated
page.$x()
is new to 1.0.0

the problem most probably might be because of the older puppeteer version. You might want to check the puppeteer version in your package.json file.
SMALL NOTE: npm i https://github.com/GoogleChrome/puppeteer/ does not upgrade puppeteer.
if(puppeteer version >1.0.0)
###try this,###
suppose that //*[#id="ng-app"] is the global prefix then u add it before the Xpath variable. I have used interpolation for this.
await page.waitForXPath(`//*[#id="ng-app"]/${Xpath}`, { visible: true });//waiting for the xPath element to be visible
const elementToClick = await page.$x(`//*[#id="ng-app"]/${Xpath}`);
await elementToClick[0].click();
this is an example to click the element extracted of course.
in your case it will be
await page.waitForXPath('//*[#id="readium-right-panel"]/ul/li[1]');
let el =await page.$x(`//*[#id="readium-right-panel"]/ul/li[1]`);
await el[0].click();
source:the API DOCS Of Puppeteer

Following should work
let el = await page.xpath('//*[#id="readium-right-panel"]/ul/li[1]');

Related

Why is puppeteer $(<selector>) not working?

I am using pupteer to select a field input with the name of website. When i select it and then use the click method i keep getting an error message that says "website_input.click is not a function "
But when i try to do this
page.type("input[name='website']","test");
it works fine which i find very weird. Below is the code i am using. Any help would be really appreciated
const website_input = page.$("input[name='website']");
await website_input.click({clickCount: 3});
await website_input.press('Backspace');
The problem might be related to promises. You should await it and then work with the element:
const website_input = await page.$("input[name='website']");
await website_input.click({clickCount: 3});
await website_input.press('Backspace');
That's because page.$() method returns Promise<ElementHandle<T> | null>, not just element handle. You can check the docs.

Unable to locate an element with puppeteer

I'm trying to do a basic search on FB marketplace with puppeteer(and it was working for me before) but fails recently.
The whole thing fails when it gets to "location" link on marketplace page. to change the location i need to click on it, but puppeteer Errors out saying:
Error: Node is either not visible or not an HTMLElement
If i try to get the boundingBox of the element it returns null
const browser = await puppeteer.launch();
const page = await browser.newPage();
const resp = await page.goto('https://www.facebook.com/marketplace', { waitUntil: 'networkidle2' })
const withinLink = await page.waitForXPath('//span[contains(.,"Within")]', { timeout: 4000 })
console.log(await withinLink.boundingBox()) //returns null
await withinLink.click() //errors out
If i take a screenshot of the page right before i locate an element it is clearly there and i am able to locate in in chrome console using the same xPath manually.
It just doesn't seem to work in puppeteer
Something clearly changed on FB. Maybe they started to use some AI technology to detect scraping?
I don't think facebook changed in headless browser detection lately, but it seems you haven't taken into account that const withinLink = await page.waitForXPath('//span[contains(.,"Within")]', { timeout: 4000 }) returns an array, even if there is only one matching elment to contains(.,"Within").
That should work if you add [0] index to the elementHandles:
const withinLink = await page.waitForXPath('//span[contains(.,"Within")]')
console.log(await withinLink[0].boundingBox())
await withinLink[0].click()
Note: Timeout is not mandatory in waitForXPath, but I'd suggest to rather use domcontentloaded instead of networkidle2 in page.goto if you don't need all analytics/tracking events to achive the desired results, it just slows down your script execution.
Note 2: Honestly, I don't have such element on my fb platform, maybe it is market dependent. But it works with any other XPath selectors with specific content.

How do I get actual config in puppeteer?

I want to conditionally execute some code based on the headless config attribute in puppeteer (passed in the .launch function).
e.g. : when I use the .type function, if it is running with headless: true, I don't want any delay. Else, add some { delay: 200 }.
How can I retrieve the headless value from the config?
Edit (thanks to #AndreyLushnikov comment)
You can figure out if puppeteer runs (non-)headless at runtime by checking browser.process() spawnargs for --headless switch with which Chromium were (or not) launched:
const headless = browser.process().spawnargs.includes("--headless") ? true : false;
console.log("Headless? " + headless);
With the latest puppeteer version to date (1.7.0), this is how I retrieved the config :
const client = await page.target().createCDPSession();
const response = await client.send('Browser.getBrowserCommandLine');
page.headless = response.arguments.includes('--headless');
See this github issue for more information

puppeteer form submit: evaluate is not a function

the form exists in the page and i am sure.
const form = await page.$('#my-form');
await form.evaluate(form => form.submit());
I get this error:
TypeError: form.evaluate is not a function
EDIT 2019: As mentioned by Kyle, the latest puppeteer have .evaluate method on elementHandle. It's been two years after all.
const tweetHandle = await page.$('.tweet .retweets');
expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
You can try it this way,
await page.evaluate(() => {
const element = document.querySelector("#my-form")
element.submit()
});
ElementHandle does not have a .evaluate function property. Check the docs.
For new comers, if you encounter this problem. You're probably using puppeteer 1.19 or lower and need to update npm update puppeteer. Use the API of your version (see links at the top of the page by version).

Puppeteer Element Handle loses context when navigating

What I'm trying to do:
I'm trying to get a screenshot of every element example in my storybooks project. The way I'm trying to do this is by clicking on the element and then taking the screenshot, clicking on the next one, screenshot etc.
Here is the attached code:
test('no visual regression for button', async () => {
const selector = 'a[href*="?selectedKind=Buttons&selectedStory="]';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:8080');
let examples = await page.$$(selector);
await examples.map( async(example) => {
await example.click();
const screen = await page.screenshot();
expect(screen).toMatchImageSnapshot();
});
await browser.close();
});
But when I run this code I get the following error:
Protocol error (Runtime.callFunctionOn): Target closed.
at Session._onClosed (../../node_modules/puppeteer/lib/Connection.js:209:23)
at Connection._onClose (../../node_modules/puppeteer/lib/Connection.js:116:15)
at Connection.dispose (../../node_modules/puppeteer/lib/Connection.js:121:10)
at Browser.close (../../node_modules/puppeteer/lib/Browser.js:60:22)
at Object.<anonymous>.test (__tests__/visual.spec.js:21:17)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:169:7)
I believe it is because the element loses its context or something similar and I don't know what methods to use to get around this. Could you provide a deeper explanation or a possible solution? I don't find the API docs helpful at all.
ElementHandle.dispose() is called once page navigation occurs as garbage collection as stated here in the docs. So when you call element.click() it navigates and the rest of the elements no longer point to anything.