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.
Related
TypeError: elem[prop] is not a function
E2E testing in webdriveio. I want to click a button inside an iframe.
let iframe = browser.$('#fullmessage')
browser.pause(1000)
browser.switchToFrame(iframe)
browser.setTimeout({ implicit: 10000 })
let clickAgree = $('a[class="button is-success"]')
clickAgree.click()
browser.switchToParentFrame()
browser.pause(3000)
I was facing same error and when debug more using REPL found that the issue could be due to 2 reasons:
selector is returning array of elements and so it was not able to call the method used.
the method being called on element does not supports.
For example with following code:
$('.some_class').$$('input').getValue();
was getting error - Uncaught Error: elem[prop] is not a function. Using $('.auto_test_class').$$('input')[1].getValue(); works. But its better to use some Id or xpath.
Hope this might be useful for someone facing same issue :)
Hi i faced with the same problem, but in async. The reason is that you need
to await already defined element as parameter:
get iframe() { return $('.iframe'); }
await browser.switchToFrame(await this.iframe);
Because switchToFrame works only with element, not with promise.
Maybe for someone it will be useful.
I am trying to trigger a click event on a element but the click does not get triggered if i do not use it inside page.evaluate.
so, this works
await page.evaluate(async () => {
$('#myExport').click();
});
but, this does not work
await page.click('#myExport')
I am new to Node.js and any help in
1) clarifying as to whats the difference between these two approaches
2) when should one be preferred over the other would be really great.
3) Why does click work inside page.evaluate and not the other way
Thanks.
The evaluate function will execute the click() function defined on your selector in the browser context. The evaluate works the same way as if someone opened the browser console and typed a command.
The puppeter page.click() function will move the mouse over the center of the selector provided and then perform the mouse.down() followed by the mouse.up() actions. Bear in mind that if it doesn't find a matching selector, the page.click() will throw an error, and if it has to scroll the page, it will trigger an navigation and thus it may have unexpected behavior.
You can solve the problem using the following code, I expect.
await Promise.all([
page.waitForNavigation(waitOptions),
page.click(selector, clickOptions),
]);
Further information can be gathered on the api documentation:
How the evaluation on puppeteer works
How the page.click() works
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;
});
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.
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');