Puppeteer: proper selection of inner text - javascript

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;
});

Related

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.

Clicking on the last element of multiple selectors

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.

Javascript DOM .querySelector() null issue

I am writing code in plain JavaScript, there are lot of scenarios where I will use querySelector() method, I ran into issue multiple times like
"Uncaught TypeError: Cannot read property 'classList' of null" for the following code,
document.querySelector('.tab.active').classList.remove('active');
/** Tab not available at the time**/
In Jquery $('.tab.active').removeClass('active'); will run only if the element is available without throwing error.
I want to achieve similar behavior in JavaScript. Please provide your ideas.
I am not willing to write three lines of code for every DOM operation I am doing, looking for one line code like Jquery.
var activeTab = document.querySelector('.tab.active');
if(activeTab !== 'null' ){
activeTab.classList.remove('active');
}
Explicitly checking for the existence of the element in your code as you're doing originally is surely the clearest way you could do it, but if you really don't want to, you could create your own function that, if no element is found, returns an object with methods that don't do anything. For example:
const customQS = selector => (
document.querySelector(selector)
|| {
classList: {
remove: () => void 0
}
}
);
customQS('.tab.active').classList.remove('active');
console.log('done, no error');
Of course, with that method, you'd have to create properties for each DOM method you'd want to use. A more robust option would be to actually create an element and return it, which would be more expensive, but the element will be garbage collected right afterward:
const customQS = selector => (
document.querySelector(selector)
|| document.createElement('div')
);
customQS('.tab.active').classList.remove('active');
console.log('done, no error');

Uncaught TypeError: Cannot read property 'value' of null, need some direction

I have been trying to figure out this particular problem in my developer tools, but I've had no luck thus far. I have an error on one of my js files that says
Uncaught TypeError: Cannot read property 'value' of null
The following error refers to the 1st variable of dt_version below. The particular thing is if I comment out the first line of code. I get the same error on the following variables of offload1 and offload2. The variable is a number that I am trying to get passed over. I run this function on my body when the page loads...onload=updatetotal();
function updatetotal() {
var dt_version = document.getElementById("dt_version").value-0;
var offload1 = document.getElementById("capacity_offload1").value-0;
var offload2 = document.getElementById("capacity_offload2").value-0;
var offload3 = document.getElementById("capacity_offload3").value-0;
}
If a run an if statement looking for document.getElementByID("dt_version");...it defaults to false..so its not being carried over though on the previous page, I can see its input fine with the value in it. What am I missing here guys?
This error means that the id dt_version does not exist. Check your html to make sure it is there:
var dt = document.getElementById("dt_version");
if (dt){
// do your stuff
}else {
console.log("dt does not exist")
}
Another cause for this error may be- as you are calling the javascript function on page load there is a possible chance that your control is not yet completely rendered to the page. A simple solution is just move that control to the beginning of the page. If it doesn't work then an reliable solution is, call the function inside jquery $(document).ready().

Categories

Resources