Clicking on the last element of multiple selectors - javascript

I have multiple .home elements and I want to click on the last one
Here is what i wrote:
await page.waitForSelector('.home');
const el = await page.$eval('.home', (elements) => elements[elements.length - 1]);
el.click();
But it does not work. Instead I get the following error:
TypeError: Cannot read property 'click' of undefined
at open_tab (C:\wamp64\www\home_robot\robot.js:43:12)
at process._tickCallback (internal/process/next_tick.js:68:

The easiest way is to use page.$$ to get all element handles of the .home elements and then you click on the last element in the array:
const elements = await page.$$('.home');
await elements[elements.length - 1].click();
Why your code is not working
You cannot use page.$eval to return an element handle because the data you return there will be serialized via JSON.stringify when sending it from the browser to your Node.js environment.
Quote from the docs linked above:
returns: Promise<Serializable> Promise which resolves to the return value of pageFunction
As a DOM element cannot be serialized, you cannot click on it in your Node.js script and you get the error instead. You have to use page.$ or page.$$ to get the element handles.

Related

Please i dont know where i get it wrong

const quoteText = document.querySelector("quote")
quoteBtn = document.querySelector("button")
// random quote function
function randomQuote() {
//Fetching random quote from API and parsing it into Js Object
fetch("https://api.quotable.io/random").then(res => res.json()).then(result=>{
console.log(result);
quoteText.innerText = result.content;
});
}
quoteBtn.addEventListener("click", randomQuote);
I expect it to next the quote as i am clicking on it, but rather it is displaying in the console as "Uncaught (in promise) TypeError: Cannot set properties of null (setting 'innerText')"
It looks like you are trying to fetch a random quote from an API and display it on a website when a button is clicked.
There's a syntax error in the code, the quoteBtn declaration is missing a var or const keyword, which should be fixed:
const quoteText = document.querySelector("quote");
const quoteBtn = document.querySelector("button");
Additionally, make sure that the elements with the quote and button class names actually exist in your HTML, otherwise quoteText and quoteBtn will be null and the code will throw an error when trying to add the click event listener.
If you use document.querySelector(), you need to use a class or an id, unless your target is a native html element. <quote> is not a native html element. So I guess you need to use:
const quoteText = document.querySelector(".quote")
Mind the . in document.querySelector(".quote")

Cant click element in puppeteer: TypeError: product_page.click is not a function

I'm trying to find an element by it's xpath and i guess that's working, but to click the element is not working. I tested out to click the element in Selenium Python, with the same xpath and that's working, so the xpath is not the problem. This is the part of the code, that is not working:
await page.goto('THEWEBSITE')
const product_page = await page.$x('//*[#id="container"]/article[6]/div/h1/a');
await product_page.click()
I'm getting this Error:
await product_page.click()
TypeError: product_page.click is not a function
page.$x() returns an array of element handles. Try this to get the first element by array destructuring:
await page.goto('THEWEBSITE');
const [product_page] = await page.$x('//*[#id="container"]/article[6]/div/h1/a');
await product_page.click();

testcafe selector does not find 'div' elements

I'm experiencing a very strange bug using testcafe. On one of my websites I'm not able to select div-elements but I'm still able to select other elements. So
await t.click(Selector("span").withAttribute('id', 'foo'));
await t.click(Selector("p").withAttribute('id', 'foo'));
await t.click(Selector("button").withAttribute('id', 'foo'));
await t.click(Selector("input").withAttribute('id', 'foo'));
works fine while
await t.click(Selector("div").withAttribute('id', 'foo'));
will throw the following error:
Function that specifies a selector can only return a DOM node, an
array of nodes, NodeList, HTMLCollection, null or undefined. Use
ClientFunction to return other values.
This only happens on one specific website of mine. What could this be? What could I have possibly done in my script to prohibit the testcafe selector to get "div" elements? Really out of ideas right now.
This issue can occur if you pass a function with an incorrect return value as a Selector argument:
test('Return non-DOM node', async () => {
await Selector(() => 'hey')();
});
However, the error should not occur if you use string as a parameter to specify your selector. Please check that you do not pass a function to your Selector object.
If your Selector is defined correctly and the issue still appears, please share the example (html page or public url to your site) and your full test code to demonstrate the issue.

Taking element screenshots in a loop. Detecting and skipping hidden web elements. Using puppeteer

I am taking screenshots of 'strip' elements on a webpage. Im having trouble detecting elements that are not currently displayed on the site.
I am taking screenshots of all the desktop elements on a website.
What I have tried is using await page.$$eval('section .strip', p => p.map((e) => e.getAttribute('display')))
Im also aware that I could use getcomputedstyles() but dont understand where to add this with regards to map().
let arr = await await page.$$('section .strip');
let naming = await page.$$eval('section .strip', p => p.map((e) => e.previousElementSibling.getAttribute('id')))
for(el in arr){
await arr[el].screenshot({path: './' +naming[el] + '.png'})
}
I expect a screenshot to be taken if the element is there and ignored if the element is visible (display: hidden).
What i am getting is, When using element.screenshot() Im getting and error of (node:3736) UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
No, it does not simply ignore the element if the element does not exist. It throws an error as you are experiencing.
Solution
As the code (see link about) is already checking if the element has a bounding box, you do not need to check this yourself. Instead, you can ignore the error by wrapping the expression in a try..catch like this:
for(el in arr){
try {
await arr[el].screenshot({path: './' +naming[el] + '.png'})
} catch (err) {
console.log(`No screenshot for ${naming[el]}: ${err.message}`);
}
}
This will try to take screenshot of all elements and log the elements for which this was not possible.

Puppeteer: proper selection of inner text

I want to grab a string that has a particular class name, lets say 'CL1'.
This is what is used to do and it worked:
(we are inside an asycn function)
var counter = await page.evaluate(() => {
return document.querySelector('.CL1').innerText;
});
Now, after some months, when i try to run the code i get this error:
Error: Evaluation failed: TypeError: Cannot read property 'innerText' of null
I did some debugging with some console.log() before and after the previous snippet of code and found out that this is the culprit.
I looked the code of the webpage and the particular class is inside.
But i found out two more classes with the same name.
All three of them are nested deep inside many classes.
So what is the proper way to selected the one i want, given i know the class hierarchy for the one i am interested in?
EDIT:
Since there are three class names with the same name, and i want to extract info from the first, can i use an array notation on the querySelector() to access the information from the first one?
EDIT2:
I run this:
return document.querySelector('.CL1').length;
and i got
Error: Evaluation failed: TypeError: Cannot read property 'length' of null
This gets even more confusing...
EDIT 3:
I trie the suggestion of Md Abu Taher and i saw that the snippet of code he provided did not return undefined. This means that the selector is visible to my code.
Then i run this snippet of code:
var counter = await page.evaluate(() => {
return document.querySelector('#react-root > section > main > div > header > section > ul > li:nth-child(1) > a > span').innerText;
});
And i got back the same error:
Error: Evaluation failed: TypeError: Cannot read property 'innerText' of null
The answer is divided in to parts. Getting right selector, and getting data.
1. Getting right Selector
Use inspect element
Right click on your desired element and click inspect element.
Then right click and click Copy > Copy selector
This will give you a unique selector for that specific element.
Use a selector tool
There are bunch of chrome extension that helps you find the right selector.
Selectorgadget
Get Unique CSS Selector
Copy Css Selector
2. Getting the data
Given your selector is .CL1, you need to do few things.
Wait for all Network events to finish
Basically on a navigation you can wait until network is idle.
await page.goto(url, {waitUntil: 'networkidle2'});
Wait for the element to appear in DOM.
Even if the network is idle, there might be redirect etc. Best choice is to wait until the element appears. The following will wait until the element is found and will throw an error otherwise.
await page.waitFor('.CL1');
Or, Check if element exists and return data only if it exists
If you do not want to throw an error or if the element appears randomly, you need to check it's existence and return data.
await page.evaluate(() => {
const element = document.querySelector('.CL1');
return element && element.innerText; // will return undefined if the element is not found
});
try to verify the element before
var x = document.getElementsByClassName("example");
OR
var x = document.getElementsById("example");
and then
var counter = await page.evaluate(() => {
return x.innerText;
});

Categories

Resources