Selenium Events and Waiting - javascript

I'm working with Selenium on automating some testing for our company website but running in to issues.
TestItemFromSearch: (driver, part, qty) => {
Search.SearchItem(driver, part);
driver.findElement(By.id('enterQty')).findElement(By.tagName('input')).sendKeys(qty);
driver.findElement(By.linkText('Personalize')).click();
//This directs to a new page.
driver.wait(() => {
}, 1000).then(() => {
}, //TODO: This sucks. Fix it.
Login.Login(driver, Logins.username, Logins.password));
driver.findElement(By.className('selectDesignButton')).click();
}
Some times, when Selenium is directed to a new page, I ave to use the wait. No matter what condition I put in, it doesn't really find that condition - it fails. I have to hijack the Reject method to put in a desired action (like Login.Login).
I may have to do this again in the same test since it's going to run through multiple pages.. that's going to result in ugly, ugly, unreadable code.
How can I get Selenium to PROPERLY wait? If I do the following:
driver.wait(()=>{},1000)
it just sits forever. If I put in a return statement, it instantly fails and doesn't even wait the second.

You need to use an explicit (rather than implicit) wait. There are multiple ways to do this:
WebDriverWait wait = new WebDriverWait(driver, timeOut);
wait.until(ExpectedConditions.elementToBeClickable(locator));
Or you can use a different form of explicit wait, which is a fluentWait:
public WebElement fluentWait(final By locator) {
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(30, TimeUnit.SECONDS)
.pollingEvery(5, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class);
WebElement foo = wait.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(locator);
}
}); return foo; };;
Either will work, I personally prefer a fluentWait because you can specify:
Frequency with which FluentWait has to check the conditions defined.
Ignore specific types of exception waiting such as
NoSuchElementExceptions while searching for an element on the page.
Maximum amount of time to wait for a condition
Try this:
driver.wait(function() {
return driver.findElement(locator).isDisplayed();
}, timeout);

You should be using the selenium driver's WaitForElementPresent or WaitForElementVisible functionalities prior to interacting with elements on the page. The selenium driver will watch the DOM and verify the presence or rendering, respectively, of each element. After those checks pass first, it should be safe to interact with the page for further testing.

maybe this helps it is a "async function () "
var nameButton = By.linkText('Personalize')
await driver.wait(function () {
return until.elementIsVisible(nameButton);
}, 20000).then(() => {console.log("element visible")})
button = await button.findElement(nameButton).then(()=>{ console.log("found element")})
await button.click().then(()=>{ console.log("I clicked the button")})

Related

Puppeteer, distinguish between a redirect or a new html element (without timeouts)

TL;DR: using puppeteer, after triggering a button click, which one is the best way to understand what is happening to a page, knowing that either a redirect / history push could happen (and the url change, in a set of known ones, but not necessarily through redirect but also through push into history object) or a dialog might appear (with a known id)?
I'm trying to write a scraper using Puppeteer (very first experience with it, never used before) to navigate a website with the final goal of retrieving a text code, with the challenge that the path to get there is not always the same, and the code might actually not be given.
In the first page - full of ads, therefore slow as well -, I do something like this to wait for the "get code" button to appear (snippet 1):
// ... code to get the page instance ...
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
while(true) {
// Puppeteer won't complain if I don't await for page reload (to avoid the ads),
// as long as I await for the container div before doing anything else.
page.reload(); // No await
await page.waitForSelector("#code-container");
const hasCode = await page.evaluate(() => {
// I cannot click on it already because I realised it could
// cause a "Execution context was destroyed" error
return document.querySelector('#get-code-button') != null;
});
if(!hasCode) {
await sleep(10000);
}
}
// out of the loop, "#get-code-button" exists
And then I click on it (snippet 2):
// For some reason, this method is more reliable than using
// await page.click('#get-code-button').
await page.evaluate(async () => {
document.querySelector('#get-code-button').click()
});
// ... at this point the real troubles begin ...
Now, after the snippet above, a few scenarios might happen:
A dialog might appear, with the "reveal code" button in it (happy days)
A redirect might happen (url change, but it could be either a redirect either a push in the history object), with ads. After clicking on the div with id "continue-without-ads" (to simplify), I end up in one of the next redirects.
A redirect might happen (as above, url change, but it could be either a redirect either a push in the history object), with the "reveal code" button in it (happy days)
A redirect might happen (same as above), with basically written "error: code not available". If I go back from this page, the "get code" button should stay in place, so I could skip snippet 1 and go straight for snippet 2.
Question is, how can I detect in which scenario am I, and act timely (e.g. without waiting for the waitForSelector timeout to happen if I want to check for element to be there)?
As well, is the idea of using page.goBack() to get to the initial link and make another attempt a stupid one (to avoid waiting for the "get-code-button" to appear again, since the page should now be cached in Chrome)?
I want to avoid the headache of myself mashing the refresh button, clicking the "get-code-button" once it appears and go back to retry until I get the code.
I found an escamotage, but I don't think it's the easiest way to achieve what I wanted, neither the most correct ...
My solution is to have two "aggregators" of waiters: (1) one for selectors (a list of IDs, but any selector is just fine), (2) one for page url changes (a list of urls which are gonna trigger the promise if navigated to). Both this aggregators accepts as input a list of string (in one case selectors, in the other urls), and returns the first one to succeed.
The code to check what changed in the page after the click:
/**
* #param page the page to monitor for changes
* #param urls the list of urls that should trigger the redirect monitor
* #param selectors the list of selectors that should trigger the page change
* #param triggerPromise the promise that triggers the events (e.g. mouse click on a button)
* #returns the url or the selector that resulted as a change
*/
async function waitForWinner(page: Page, urls: string[], selectors: string[], triggerPromise: Promise<any>) {
// waitForUrlChange takes in input a list of urls, and returns the first
// one to succeed
const urlChangeMonitor = waitForUrlChange(page, urls);
// hasSelectors takes in input the list of selectors, and returns
// the first that succeeds
const selectorsPromise = hasSelectors(page, selectors);
const results = await Promise.all([
triggerPromise,
Promise.race([ urlChangeMonitor.promise, selectorsPromise ])
]);
urlChangeMonitor.clear();
const winner = results[1];
// This check is quite stupid, but it works for me:
const isRedirect = !winner.startsWith("#");
const isSelector = winner.startsWith("#");
// ... other custom logic here
// Simplification:
return { winner, isRedirect, isSelector }
}
The hasSelectors is a bit trivial, and it's full of custom logic in my case (when there are cookies it accepts them and then keeps going again), the most interesting part is the one to wait for url change.
In my case I realised there is no redirect, thus I suppose it's a push in the history object. Regardless, this method succeeds in listening for url changes in the page:
const unboundResolve = (url: string) => logger.error("Resolved too early, error.");
const unboundReject = () => logger.error("Rejected too early, error.");
export function waitForUrlChange(page: Page, urls: string[], timeout=60000) {
if (urls.length === 0) {
throw Error("Cannot have 0 lenght array of urls.");
}
const deferred = {
resolve: unboundResolve,
reject: unboundReject
};
const promise: Promise<string> = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
let promiseDone = false;
const checkForUrl = (frame: Frame) => {
const isRoot = frame.parentFrame() === null;
if (isRoot) {
// Frame might change, but page doesn't always change.
// Regardless, in this way I can detect url changes which
// occurs without redirect.
const currentUrl = page.url();
for(let u of urls) {
if (~currentUrl.indexOf(u)) {
// Resolve only once
if(!promiseDone) {
promiseDone = true;
deferred.resolve(currentUrl);
} else {
logger.warn(`Found another redirect of interest, but it's too late now.`);
}
clear();
break;
}
}
}
};
const clear = () => {
if (!promiseDone) {
deferred.reject();
promiseDone = true;
}
// Calling it multiple times doesn't make a difference
page.off("framenavigated", checkForUrl);
}
// If url doesn't change in one minute, call it a day
setTimeout(clear, timeout);
page.on("framenavigated", checkForUrl);
// Provide a way to turn off the listener from outside
return { clear, promise };
}
Main idea is to register to framenavigated event and listen for the url to contain one of the inputs (not the best, but works for me). Rather than directly returning a promise, I wrap it into an object which gives the possibility to clear the listener from outside, to keep things tidy.
The approach I presented has vast room for improvement (e.g. rather than having strings being passed around, I could add metadata and then the metadata is returned as well, to avoid very naive checks like .startsWith("#"), or the check for url could be a pattern or a callback), but it works and shows the main idea behind.

Using for loop and if else in cypress

I am in a situation where I need to use for loop and If else block in cypress
Scenario:
Once I login to an application, I need to read an element's text which is rounded in the below screenshot.
This element will appear within 20-90 seconds after I log in, when I refreshed the screen. so I need to write something like this, wait for element, if it appears reads the text and returns the value, if not wait for 10 seconds reload the page and do the process again.
function waitAndreload() {
for (let i = 0; i < 10; i++) {
cy.get("#ele").then(ele => {
if (ele.text()) {
return ele.text();
} else {
cy.wait(10000);
cy.reload();
}
});
}
}
How to write this in cypress, as cypress won't support if-else or for loops
Below is a technical solution, but first is want to explain what I believe is a better solution.
Adding to #dwelle comment, it seems like what you're trying to do is not a best-practice in terms of the the design of the test. Tests should be designed to be deterministic and should control all relevant inputs that may affect the expected result.
More specifically, is this text something that a real user should see and use, or only something that the developers put for debugging or testing purposes? If it's for a real user, then what determines if it should appear or not? (Is it purely random?, If so, see below) if it's for testing or debugging purposes, talk to the developers and come up with a better solution in which you can control whether this text appears or not directly. If it's something that the user should see and it's not random, then consider what conditions should be met in order for the text to appear, and design the test in a way that makes this condition true, either by controlling the actual necessary preconditions or by using mocks to simulate that condition. Again, I recommend that you consult with the developers to help you find the best approach.
In case it is "purely" random, then ask the developers to provide a way for you to specify the seed of the random generator, and then you'll be able to control it too.
As promised, in case you still want the technical solution for the specific problem, without redesigning the test, then there trick is to use recursion. Something like this:
function getEnvironment() {
function getEnvironmentInternal(retires) {
if (retires == 0)
throw "text didn't appear after the specified retires";
return ele.text().then(text => {
if(text)
return cy.wrap(text);
cy.wait(10000);
cy.reload();
return getEnvironmentInternal(retires-1);
});
)};
return getEnvironmentInternal(10);
}
// usage:
getEnvironment().then(text => {
// do something with text...
}
I wrote my own helper command for checking repeatedly until condition is fulfilled.
/**
* Waits until call to cb() resolves to something truthy.
*
* #param message {string} Error message on timeout
* #param cb {() => Cypress Command "promise"}
* Callback for checking if condition is met should return cypress command cy.xxxxxx.then()
* which resolves to undefined if polling should continue. Throwing an error aborts before
* waiting for timeout to complete.
*/
Cypress.Commands.add('waitFor', (message, cb, errorReporterCb = null, timeoutMs = 5000) => {
const startTime = new Date().getTime();
const giveupTime = startTime + timeoutMs;
const startTimeout = 5;
const ctx = {};
const errorReporter =
errorReporterCb ||
(err => {
throw err;
});
function checkCb(timeout) {
const currentTime = new Date().getTime();
if (currentTime > giveupTime) {
const err = new Error(`Timeout while waiting for (${currentTime - startTime}ms): ${message}`);
errorReporter(err, ctx);
} else {
cy.wait(timeout);
return cb(ctx).then(result => {
if (result === undefined || result === false) {
return checkCb(timeout * 2); // always wait twice as long as the last time
} else {
return result;
}
});
}
}
return checkCb(startTimeout);
});
With this you can implement polling loop like:
cy.waitFor(
'reload page until #ele contain text',
() => cy.reload().get("#ele").then(ele => ele.text() ? ele.text() : undefined),
null, 60000);
I would say using a for loop for something like this and refreshing is an anti-pattern. It looks like you're waiting for the text to show up in the element, not the element itself.
If so, can you stub the response to the server so it comes back right away? If that doesn't work, just do a cy.wait('#<whatever you aliased your response as>') until the call is completed
So it seems you just want to wait that element is appeared and then take text value.
So something like cy.get('#ele', {timeout: 60000}).should('exist').invoke('text').then(text => ...work with text value)
Assertions in cypress have built-in retry mechanism, so if it fails before timeout expire - it will retry previous command.
You can't use while/for loops with cypress because of the async nature of cypress. Cypress doesn't wait for everything to complete in the loop before starting the loop again. You can however do recursive functions instead and that waits for everything to complete before it hits the method/function again.
Here is a simple example to explain this. You could check to see if a button is visible, if it is visible you click it, then check again to see if it is still visible, and if it is visible you click it again, but if it isn't visible it won't click it. This will repeat, the button will continue to be clicked until the button is no longer visible. Basically the method/function is called over and over until the conditional is no longer met, which accomplishes the same thing as a loop, but actually works with cypress.
clickVisibleButton = () => {
cy.get( 'body' ).then( $mainContainer => {
const isVisible = $mainContainer.find( '#idOfElement' ).is( ':visible' );
if ( isVisible ) {
cy.get( '#idOfElement' ).click();
this.clickVisibleButton();
}
} );
}
Then obviously call the this.clickVisibleButton() in your test. I'm using typescript and this method is setup in a class, but you could do this as a regular function as well.

How to introduce delay between tests in protractor

I am exploring protractor tool with cucumber and test is executing super fast.. in order to know if really elements are getting clicked or not, I am using sleep() method but failing. I am also using another method wait() with expected conditions which is also failing.. In fact, I understood click() method on the link element itself is failing.. That is, unable to click on element which I desired, however when I print on console element is printing all its attributes and methods.
please find the code snippet as below;
When(/^I click on "(.*?)" link$/, (callback) => {
console.log("Clicking... ");
browser.wait(EC.visibilityOf(login.confirmInstructions), 5*1000, "Waiting for Confirmation link...");
var confirmLink = login.confirmInstructions;
var isClickable = EC.elementToBeClickable(confirmLink);
browser.wait(isClickable, 10*1000, "Element clickable");
confirmLink.click();
browser.sleep(10*10000);
login.confirmInstructions.click();
//browser.wait(validateText(element(by.binding('myvar'))), 5000, "");
//browser.wait(EC.presenceOf(confirmation.confirmScreen), 60*1000);
console.log("waited");
return callback;
});
What I am missing here.?
I'm not sure I understand the question but if you just want to see if your "confirmInstructions" is clicked, you should use a debugger and set a breakpoint before the method
I understood, the best way to wait for web elements in protractor is to use, wait() rather than sleep. However, I was looking either of the way (by using wait / sleep) to slow down the test execution, as it is useful while implementing test scenarios in order to recognize web elements. Finally, following method for now I am using it.. but still if there is a better way to handle please put your comments..
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
and calling sleep from my function as: await sleep(2000);
For now, I am able to move forward with writing tests.. I am also sure there is a better way in protractor API, yet to find it's implementation.

How to apply wait time when Your page javascript reloads the element

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.

Why is Protractor running every line of code immediately?

Why is Protractor running every line of code immediately?
So I have a webpage that is not written in angular. That I need my selenium based automation to hit. I have used selenium webdriver-js code to hit it. Example below. Once you login, you are taken to a page with 3 questions. The order of the questions are randomized each time you login. So you may never get the same questions in the same order each time you login.
Question 1) What is your name?
Question 2) What time is it?
Question 3) Wy are you here?
The answers to each question are the last word of the question.
Answer 1) name
Answer 2) it
Answer 3) here
So what I was thinking an easy way to solve this problem is to create an if conditional statement
var foo = browser.driver.findElement(By.id('question1')).getText();
if(foo == What is your name?) {
browser.driver.findElement(By.id('answer1')).sendKeys('name');
}
else {
blah
}
and so forth... etc...
But the problem I am running into is that Protractor immediately runs the if statement before it gets to that point. In the example below, the console immediately prints out the console log 'this sucks', because it runs through the if statement immediately without going through the first steps to get to the page and then checking.
this.foo_test = function() {
console.log('starting foo test');
browser.driver.get('http://my-test-url.com/');
browser.sleep(3000);
browser.driver.findElement(By.id('login')).click();
browser.sleep(3000);
browser.driver.findElement(By.id('user')).sendKeys('user');
browser.driver.findElement(By.id('login_button')).click();
browser.sleep(3000);
console.log('getting variable');
var foo = browser.driver.findElement(By.id('question1')).getText();
console.log(foo);
if (foo == 'What was the name of your first pet?') {
console.log('this is cool');
}
else{
console.log('this sucks');
}
};
Protractor builds on WebdriverJS, which uses an implicit-promise-queuing style of programming. See:
https://github.com/angular/protractor/blob/master/docs/control-flow.md
What that means is that each statement in a protractor test should be read as enqueuing a promise, not as actually executing. So for example, the line:
browser.driver.findElement(By.id('question1')).getText()
Does not return text, but returns a promise to return text. You must pass this promise to the other promise-expecting APIs, or provide a direct handler with .then().
The expect call you see in Protractor tests has been modified to wait for a promise to resolve. So something like:
expect(name.getText()).toEqual('Jane Doe');
Is actually enqueuing a promise to compare the result of the promise on the left to the value on the right.
I don't know much about Protractor specifically but this sounds like an issue of not recognizing asynchronous code. If the first assignment statement is asynchronous, then the rest of the code will run without waiting for it to complete. Hence, the values you expect will not be there when you try to test for them in the IF statement.
Your best bet is to run the rest of the code in a callback or promise .
It appears your page is still loading even though selenium thinks it is complete. This happens alot with dynamic/asynchronous pages.
browser.sleep() is not really appropriate, you never really know how long you need to wait for.
You can investigate the class WebDriverWait, which allows you to wait for an element to appear, or timeout.
Wait<WebDriver> wait = new WebDriverWait(driver, 50); // timeout is 50 secs
wait.until(new Function<WebDriver, Boolean>() {
public Boolean apply(WebDriver driver) {
return (driver.findElement(By.id('question1'))).isDisplayed();
}
});
You can also try running some javascript to check the document ready status :
Wait<WebDriver> wait = new WebDriverWait(driver, 60); // timeout is 60 secs
wait.until(new Function<WebDriver, Boolean>() {
public Boolean apply(WebDriver driver) {
String docReady = "";
Boolean rc = true;
if (null != ((RemoteWebDriver)driver).getSessionId()) {
docReady = String.valueOf(((JavascriptExecutor) driver).executeScript("return document.readyState"));
rc = docReady.equals("complete");
}
return rc;
}
});

Categories