Is there any way or Puppeteer API we can wait element to disappear or remove from Dom and then continue the execution?
E.g I have a loading animation I want to wait until this loading animation remove from Dom.
waitForSelector has a hidden option which also check if the element is in the DOM:
await page.waitForSelector('div', {hidden: true});
Try this
await page.waitForFunction(() => !document.querySelector(querySelector));
If you're waiting for API response, maybe it's better to rely on
await page.waitForResponse(response => response.url() === myUrl && response.status() === 200);
You can use page.waitForFunction with a conditional statement.
await page.waitForFunction('document.querySelector("#myElement") === null')
https://pptr.dev/#?product=Puppeteer&version=v5.2.1&show=api-pagewaitforfunctionpagefunction-options-args
Related
I'm testing an application that uses iframes, I wanted to know if with cypress it is possible to wait for an iframe to load, because I need to ensure that the iframe has been fully loaded to perform the test.
I tried using a cy.wait(), it even worked, however, setting a time on it makes my test very slow, since there are several use cases in this iframe
The package cypress-iframe has some built-in routines to wait for the iframe to load.
From the source, it waits for the URL, ready state and load event:
const hasNavigated = fullOpts.url
? () => typeof fullOpts.url === 'string'
? contentWindow.location.toString().includes(fullOpts.url)
: fullOpts.url?.test(contentWindow.location.toString())
: () => contentWindow.location.toString() !== 'about:blank'
while (!hasNavigated()) {
await sleep(100)
}
if (contentWindow.document.readyState === 'complete') {
return $frame
}
await new Promise(resolve => {
Cypress.$(contentWindow).on('load', resolve)
})
return $frame
You can use that package directly without needing to implement your own wait strategy, but note the docs are a little out of date if you are using Cypress with version greater than 10.
I need to check whether all elements of a class are not present in the DOM. Say, I want all the elements with the class .loading to not present in the DOM. I know I can do this:
browser.wait(EC.stalenessOf($$('.loading')), 5000);
My question is whether this code will wait for all the loading class to go away or just the first one? If it waits for only the first one, how will I make it work for all of them? Thanks in advance :)
yes, this should wait until ALL elements matching the locator are not present
But for future, when in doubt, you can write your function instead of using ExtectedConditions library. In this case, you could do
let loading = $$('.loading');
await browser.wait(
async () => (await loading.count()) === 0,
5000,
`message on failure`
);
In fact, this is what I'm using to handle multiple loading animations ;-)
I have been working on a Puppeteer system to help me handle my automation testing. However, my page would prompt a random pop-up to notify customers of on-going promotions - This prevents my test from proceeding. My initial thought was to run a endless loop in the background during the test to waitForSelector and click if it exists. However, I was thinking that this approach doesn't sound too smart, and I couldn't find anything suitable in the API.
Has anyone encountered the similar problem and has come out with a brilliant solution?
If the popup is always going to appear towards the beginning of the session, you can use page.waitForSelector():
await page.waitForSelector('#popup', {visible: true});
await page.click('#popup'); // Close Popup
Alternatively, if the element is dynamically added to the page and might not appear, you can use the MutationObserver interface to watch for the element to be added to the DOM tree and click it:
await page.evaluate(() => {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
for (let i = 0; i < mutation.addedNodes.length; i++) {
if (mutation.addedNodes[i].id === 'popup' && window.getComputedStyle(mutation.addedNodes[i]).display !== 'none') {
mutation.addedNodes[i].click(); // Close Popup
}
}
});
});
observer.observe(document, {subtree: true});
});
I'm trying to test a website using Puppeteer. Unfortunately, I'm having trouble clicking elements in a toolbar. The toolbar is using a CSS transition to gracefully slide into the page. My code is failing because I'm clicking where the element will appear while the page is still animating. I'm using a timeout as a workaround, but there's got to be a more elegant solution. Here's an example:
await page.click("#showMeetings"); //Toolbar slides in
await page.waitFor(3000); //Can I do this without a timeout?
await page.click("#mtgfind"); //Click button in toolbar
I think I need to wait on the transitionend event, but I'm unsure of how to do that in Puppeteer. Any help would be appreciated.
In case of Grant solution, you shouldn't forget to remove event listener and to wait for it. You can try this solution, it works for me. I had similar problem.
async waitForTransitionEnd(element) {
await page.evaluate((element) => {
return new Promise((resolve) => {
const transition = document.querySelector(element);
const onEnd = function () {
transition.removeEventListener('transitionend', onEnd);
resolve();
};
transition.addEventListener('transitionend', onEnd);
});
}, element);
}
And call it:
await page.click('#showMeetings');
await waitForTransitionEnd('#elementWithTransition');
await page.click("#mtgfind");
I came up with a fairly dumb solution. I looked up how long the transition was supposed to take (in this case 0.2 seconds) and just modified the waitFor statement to wait that long. Final code:
await page.click("#showMeetings"); //Toolbar slides in
await page.waitFor(200);
await page.click("#mtgfind"); //Click button in toolbar
This wouldn't work if the timing was variable, but the website reuses the same transition everywhere, so it's fine for my use case.
You can use page.evaluate() to and the transitionend event to accomplish your goal:
await page.evaluate(() => {
const show_meetings = document.getElementById('showMeetings');
const mtg_find = document.getElementById('mtgfind');
mtg_find.addEventListener('transitionend', event => {
mtg_find.click();
}, false);
show_meetings.click();
});
I want to loop through an array containing URLs and push an element into another array.
This is the code i used:
for (var i=0; i < links.length ; i++){
await page.goto(links[i], { timeout: 0, waitUntil: ['domcontentloaded'] });
await page.waitFor(20000);
var values = await page.evaluate(
() => [...document.querySelectorAll('.XYZ')]
.map(element => element.getAttribute('src'))
); //get the elements location
media.push(values); // push to array
);
console.log(media);
}
This code works. However, notice the third line is an await page.waitFor(20000);.
I am using this, to wait so that the page has loaded.
If I omit this line, sometimes, the variable called values is undefined.
I experimented with other time delay values and the lower it gets, the more chance it has of being undefined.
What is the proper way to loop through the array without wasting unneccesary time with big delays?
Shouldn't this process be automatic since I am using waitUntil: ['domcontentloaded'] in the page.goto() method?
Considering you are using the evaluate method to retrieve all elements with class name XYZ from the UI, why not use page.waitForSelector() to ask puppeteer to wait for the last loaded element using that class to be loaded in the UI?
By doing so you will know that all the elements you are interested in will have loaded before your evaluate method is triggered.
That will be much more efficient than asking it to wait for a hardcoded amount of 20 seconds each time. You want to avoid using hardcoded wait calls at all times during automation.