i'm using puppeteer and i need to keep refreshing the page until the requested element is live, "button" is the element i need.
i tried with the wait until but it is not working and gives me this error:
Error: Unknown value for options.waitUntil: JSHandle#node
This is what i tried
const [button] = await page.$x("//a[contains(., 'Denim')]");
if (button) {
await button.click();
}
await page.reload({ waitUntil: ["networkidle0", "domcontentloaded", button] });
The error is pretty explicit in this case. You are telling puppeteer to wait for these 3 things: ["networkidle0", "domcontentloaded", button]. The first 2 are acceptable options. The 3rd is not. button is a reference to a DOM element which you can use in your puppeteer code. (Or a JSHandle#node). As per the docs, this is not a viable option.
Just another point here: you don't share all your code so we must assume that this is happening in some sort of loop with some sort of timeout between calls. As is, this code will check for this button, click the button if found, and then reload the page exactly 1 time. Reload does not reload the page multiple times while searching for some feedback. It just reloads once. The waitUntil option just defines when the returned promise should resolve.
Good luck!
Related
I have a Puppeteer script that logs into a website and then is looking to click on 7 different "Add Time" buttons within an iFrame. Since the IDs change dynamically, I have it looking for the text of the button.
I have it logging in correctly and able to click sometimes, but it doesn't click all the buttons. It isn't consistent either, the last two buttons may click or the second button.
Here is the code:
let test = await frame.$$('[type="button"]')
test.forEach(async el => {
var text = await (await el.getProperty('textContent')).jsonValue()
if (text == "Add Time") {
await el.click();
}
});
As mentioned in the comments, the following pattern runs all of the clicks simultaneously:
test.forEach(async el => {...});
Most websites aren't usually designed such that 7 different clicks should occur at nearly the same time--it's inhuman. Often, there is some loading animation or pause before it makes sense for the user to click again.
Another issue with the above pattern is any code you have after this will be in a different promise chain and won't be able to wait for the results of these clicks. You'd have to use await Promise.all([test.map(async el => {...})]) to make that happen.
As OP confirmed in the comments, the typical solution is sequential iteration:
for (const el of test) {
...
}
This runs each click fully to completion, then the next one only after the last one has finished. Code after the for loop will execute after all iterations of the loop have completed.
See Crawling multiple URLs in a loop using Puppeteer and Using async/await with a forEach loop.
TestCafe 1.8.0,
Firefox 76.0 (any will do),
macOS 10.15.4.
My TestCafe tests (steps after useRole) are trying to execute even before useRole is completely done. Example:
import { Role } from 'testcafe';
const role = Role('http://example.com/login', async t => {
await t
.typeText('#login', 'username')
.typeText('#password', 'password')
.click('#sign-in'); // Redirects to http://example.com/session
});
fixture `My Fixture`;
test('My test', async t => {
await t
.navigateTo('http://example.com/')
.useRole(role);
.click()
.typeText('#search', 'query')
// Further tests.
.......
Role is being used -> user is redirected to: http://example.com/session at the end.
Once role execution is finished -> TestCafe goes back to My test AND it reloads http://example.com/ again.
This is a huge problem because between this one page reload for just a moment 'page is ready' and since TestCafe is working rapidly .click() is executed. Now page reloads so test execution stops. Once page loaded test brakes because it is trying to .typeText(...) without a .click().
Tried this solutions:
Wait until will work only first time. Second time when useRole used (from cache) code will execute even before this second page reload. .expect(getUrl()).eql('desiredurl', { timeout: 10000 })
As we all know hard wait .wait(3000) or slowing down tests .setTestSpeed(0.7) will work but it is not a good solution from code perspective. Tests still might fail from time to time and I need and want stability here.
With { preserveUrl: true } it would just reload http://example.com/session so it doesn't matter if this option is used. Reload is still happening.
Any ideas?
How can I let my test know to wait for exactly the same page reload without using any hardcoded waits, code sleep?
As far as I understand, the main issue is that the click action is executed on the wrong page for some reason. It does not wait until the page is completely reloaded. This behavior is unexpected. We would really appreciate it if you create a reproducible sample.
I agree that the use of wait or setTestSpeed is not a suitable solution.
I see that you tried to use assertions: .expect(getUrl()).eql('desiredurl', { timeout: 10000 }). I think this approach should work, but I cannot be sure because I couldn't reproduce the issue.
You can define your role with preserveUrl: true. Then, extract the useRole method as follows:
async function useRole (t, role) {
await t.useRole(role);
await t.expect(getUrl()).eql('http://example.com/session', {timeout: 10000 });
}
Now, you can use the new useRole(t, role) method, which will wait until the page is completely loaded.
Protractor hangs completely when trying to get any element property after logging in (idk if it's related to logging in or related just to switching pages).
it("Should get location of main container", async function() {
await LoginPage.validLogin();
// Works and logs in the dashboard
await browser.sleep(3000);
// Get the main container by class name
const container = await element(by.css(".main-container"));
// Logs properly the element functions (as expected)
console.log(container);
console.log(await container.getLocation()); // Hangs here
});
In this case, I'm trying to get the location of the main container element on the page. The first console.log fires and shows properly, while the second hangs completely, so I get the script timeout. Increasing the timeout time doesn't help at all...
I found online that misusing $timeout in AngularJS instead of using $interval may lead to this strange behaviour, but I really can't skim through the entire (very big!) project's codebase to change everything hoping that it just works, not to talk about the external libraries using $timeout.
I have SELENIUM_PROMISE_MANAGER = false; in my Protractor config so I disabled the built-in Control Flow in order to manually manage the promises using async/await, but even if I use the built-in Control Flow without using async/await I get the very same behaviour and error.
I'm using Jasmine as testing framework.
Maybe I'm missing something? Any help would be much appreciated, thanks!
This is caused by the fact that angular is not stable. Have a look at the link below. I found my answer there. When the page you are trying to test is open go to the browser dev tools and type in the console getAllAngularTestabilities(). There are a few properties here that indicate whether angular is ready to be tested. hasPendingMicrotasts needs to be false. hasPendingMacroTasks needs to be false. isStable needs to be true. I put a screenshot below. In my screenshot hasPendingMacrotasks is true and it must be false. So the page I looked at was not ready to be tested.
Failed: script timeout: result was not received in 11 seconds From: Task: Protractor.waitForAngular() - Locator: By(css selector, #my-btn)
Try something like this:
it("Should get location of main container", async function() {
await LoginPage.validLogin();
const container = await element(by.css(".main-container"));
await browser.wait(protractor.ExpectedConditions.presenceOf(container), 5000, 'Element taking too long to appear in the DOM');
await console.log(await container.getLocation());
});
I don't think that getLocation() exists in the Javascript bindings for selenium. I couldn't find it in the source code anyway. So that promise will never return which is why it hangs. But I the you can achieve basically the same thing with getRect():
it("Should get location of main container", async function() {
await LoginPage.validLogin();
const container = await element(by.css(".main-container"));
await browser.wait(protractor.ExpectedConditions.presenceOf(container), 5000, 'Element taking too long to appear in the DOM');
await console.log(await container.getRect());
});
I'm trying to get my script to go to a new page after successfully logging in, however, it attempts to go to the next page before login is complete.
const page = await browser.newPage();
page.goto('https://website/login');
page.once('load', async () => {
console.log('Page loaded!');
// Login script here
});
I can't figure out how to go to a new link after logging in though, my original solution was to just do;
// go to stats
await page.goto('https://www.website/stats');
However, since the page is already defined, this doesn't really wait for anything.
Is it possible to have a callback in the .click() function to go to a new page after?
The correct way to navigate via a click and wait for page load is to use Promise.all() and page.waitForNavigation(), like this:
await Promise.all([
page.waitForNavigation(),
page.click('#some-link')
]);
However, when you are navigating via page.goto(), the above is not necessary, because page.goto() waits for page load automatically.
In both cases, you can customize which event it waits for, using the waitUntil option, which defaults to the load event.
page.goto(url[, options])
url URL to navigate page to. The url should include scheme, e.g. https://.
options Navigation parameters which might have the following properties:
timeout Maximum navigation time in milliseconds, defaults to 30 seconds, pass 0 to disable timeout. The default value can be changed by using the page.setDefaultNavigationTimeout(timeout) method.
waitUntil When to consider navigation succeeded, defaults to load. Given an array of event strings, navigation is considered to be successful after all events have been fired. ...
Putting this together, an example of logging in would be:
const page = await browser.newPage();
await page.goto('https://website/login');
await page.type('input[type="email"]', 'foo#example.com');
await page.type('input[type="password"]', 'pass1234');
await Promise.all([
page.waitForNavigation(),
page.click('button')
]);
// Now we are on the home page (or wherever we end up after logging in)
Some other notes:
You may need to use page.waitForSelector() or other forms of waiting if the page load event fires before the input field is ready.
You could alternatively submit the login form by pressing the Enter key with the keyboard. This would still require the Promise.all() pattern, but means you don't need a selector for the submit button. You may need to .click() one of the input fields depending on how the page is implemented (e.g. if the page does not use autofocus).
Same as the solution below, however if i've understood correctly, you should have the click event first and then wait for navigation.
await Promise.all([
page.click('button'),
page.waitForNavigation(),
]);
Hi i am using selenium webdriver to automate my script and i have used wait.until condition in my script to click on the delivery bttn in below html page. The problem is selenium is finding my element but since the java script reloads the certain element. And the delivery bttn only becomes clickable after reloading. And my selenium script throws "stale element reference: element is not attached to the page document". What should i do to overcome this error.
WebElement delibttn=wait.until(ExpectedConditions.elementToBeClickable(By.xpath("(//button[#class='btn-to-cart nopricetohide btn btn-primary your-catalog-deliver btn-block btn-unpadded tocart-rounded'])[1]")));
delibttn.click();
WebElement contshopping=wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//a[#class='btn btn-link full-width mb-10']")));
Screenshot:
there are two ways to solve your problem.
1) Run the code with Async, that way you can 'await' a line of code, for example..
function async(test1){
await driver.findElement(By.id("");
driver.click();
});
or you can also do the following
2)
function (test1) {
let element = driver.findElement(By.id(elementId));
driver.wait(until.elementIsVisible(element), 10000).then(async () =>{
element.click();
});
This wait in number 2, is the one that i use in my code and it always works.
A very barbaric way of doing it would be to add a ridiculous wait time to check that it isn't something else showing an error similar to a wait problem
driver.sleep(10000);
or
thread.sleep(10000);
(the measurement is in milliseconds unless defined otherwise)
Please let me know if these solutions do not solve the problem.
as Jack suggested you could use async, but I always used an infinte while loop
Code i have given below is in python, but you can use the logic in java too
def wait_for_element():
val = True
while val:
web_elem = driver.find_element_by_id('id')
try:
web_elem.is_displayed()
except Exception as ex:
val = True
else:
val = False
i know infinite loop is not a better way than async, but if there are cases where you can't use async you can use this. Also keep in mind to put timeout for loop, otherwise you would looping infinitely when the page was unresponsive or has not loaded.
the reason it is still throwing this issue is because you are not handling your exceptions properly, this is a response to it still throwing stale element errors.
Add something like this to your project, if you look at the bottom of my code you will see that i have added exceptions to catch errors so it does not affect the code the way it is doing.
driver.findElement(By.id(buttonID)).then(pageElement => {
driver.wait(until.elementIsVisible(pageElement), 10000).then( () => {
pageElement.click();
next();
})
.catch(ex => {
console.log(ex.message, ex.stack)
});
}).catch(ex => {console.log(ex.message, ex.stack)});
This is the example of how i am using catches, however many promises you have in your function the more catches you will need, if you hover over an element in Visual Code / Studio you will be able to see if it throws a promise or not.