My test steps to test a logout flow are,
1) click a logout button
2) wait for url change to login.html
3) wait for login page text fields to be loaded.
Code looks like,
//wait for logout menu/button
browser.wait(function(){
return element(by.buttonText('Log out')).isPresent()
})
element(by.buttonText('Log out')).click()
//wait for url to change to login.html
browser.wait(function(){
return browser.getCurrentUrl().then(function(url){
return url.indexOf("login") != -1
})
})
//wait for login page text boxes
browser.wait(function(){
return element(by.css('[type=text]')).isPresent()
})
This makes my code lengthy since, i am wrapping each action inside a browser.wait call.
Is there a way i can avoid browser.wait. I tried adding,
browser.manage().timeouts().implicitlyWait(5000)
But then, i get No element found using locator: by.buttonText("Log out") error.
Protractor has inbuilt ExpectedConditions checks, you don't have to write a custom function yourself all the time. Here's how to wait for an element to be visible -
var EC = protractor.ExpectedConditions;
browser.wait(EC.presenceOf(element(by.buttonText('Log out'))), 10000); //Checks only if element is present in DOM
browser.wait(EC.visibilityOf(element(by.buttonText('Log out'))), 10000); //Checks if element is present in DOM and visible to user on page
You can also create a custom function as shown in another answer. Hope this helps.
If you are doing the same thing more than once, it tends to help to make a helper function for it. Add to your helper file something like:
this.waiterFunc = function(element){
browser.wait(function() {
return element.isPresent();
})
};
then in your main class you can do things like:
helper.waiterFunc(element(by.buttonText('Log out')));
Im not sure about avoiding browser.wait as its an integral step in the process of waiting for the elements to be accessible at a particular time. If you dont wait, then you are going to start getting nasty "element not clickable at time x" errors
Related
No, but really! I know this generic question has been asked thousands of times, but there is something more specific that looks feasible to me and thus I want to know how to achieve it
The problem
I'm testing an angular app with protractor. Inside the app I want to verify that when I click a link I'm being redirected to the right page (non-angular). The problem is that until I reach the page I'm verifying, the url changes 3 times or so (there are multiple redirections happening), so I can't make a waiting function wait until the page is loaded completely
What I tried/what won't work for me
I'm against browser.sleep() for more than 1000 ms!
browser.waitForAngular() as this is not an angular page
ExpectedConditions.urlIs() the url is the variable I'm asserting
ExpectedConditions.presenseOf() the page maybe changing so I can't rely on elements inside
browser.executeScript("return window.document.readyState") returns compete immediately, though the page is still loading (I was certain this is what I need, but that didn't work either)
I tried even adding a functions that waits for innerHtml of the whole page not change for at least 3 sec, but it fails because at times there is a pause of more than 3 sec between redirects are happening. Everything above 3 sec isn't a reasonable timeout
The question
What I noticed is when the browser is loading the page, Reload this page button changes its state to Stop loading this page (X icon) until I'm redirected to the final page (screenshots below). So the question is is there a way to make protractor point to the same condition that chrome uses to choose which icon is displayed?
vs
And if not exactly the same, but how do I make protractor hang until the page is fully loaded
Important to consider
Obviously there are a lot of dirty solutions that I can do like explicit waits. But I'm coming back to this question every once in a while, so I'm not interested in these dirty solutions that work 70% of the time for a specific cause
P.S. I figured that the button changes the icon on document.load() event. But I can't figure out what should I write in the console in order for that script to log a message when I refresh they page
Have you tried
await browser.wait(async () =>
await browser.driver.executeScript('return document.readyState;') === 'loading', 2000)
.then(() => true, () => true);
await browser.wait(async () =>
await browser.driver.executeScript('return document.readyState;') === 'complete', 5000)
I have the following snippet for just the problem, I wait for an element to be displayed:
ElementFinder.prototype.secureIsDisplayed = function () {
return browser
.wait(EC.visibilityOf(this), actionTimeout)
.then(() => this.isDisplayed())
.catch(err => {
throw new Error(`Expected ElementFinder ${this.locator()} to be displayed. ${err.toString()}`);
});
};
where actionTimeout is a const int and EC = protractor.ExpectedConditions you will need to import the file and call it like so:
import './asd.ts'
element(by.css('.myClass')). secureIsDisplayed()
I if you can find the last element that loads, or just get an element of a redirect it can do wonders.
Cheers.
Have you tried...
browser.executeScript("return window.jQuery.active").equals(0)
I'm writing automation tests for a website using Mocha + SeleniumServer + wd.js + chai-as-promised.
The website uses JavaScript for the front-end which seems to refresh the elements on the page when certain action is performed. i.e. Upon selecting an element in a grid, the "next" button is enabled to allow user to move on to the next page. It seems that this changes the reference to the button element resulting in the StaleElementReference error.
describe('1st step', function () {
it('should select an element is grid', function () {
return browser
.waitForElementByCss('#grid', wd.asserters.isDisplayed, 20000)
.elementByCss('#grid .elementToBeSelected')
.click()
.sleep(1000)
.hasElementByCss('#grid elementToBeSelected.active')
.should.eventually.be.true;
});
it('should proceed next step', function () {
return browser
.waitForElementByCss('.btnGrid .btn.nextBtn:not(.disabled)', wd.asserters.isDisplayed, 20000)
.elementByCss('.btnGrid .btn.nextBtn:not(.disabled)')
.click()//Error thrown here
.sleep(2000)
.url()
.should.eventually.become('http://www.somewebsite.com/nextpage');
});
});
With my limited experience with JavaScript, I have tried all that i could think off, but to no avail. So is there anyway I can avoid this StaleElementReference error? Also, the error is only sometimes thrown during execution.
You might want to read some more on the Stale Element Reference exception. From what you are describing, it sounds like you get a reference to an element, do something on the page which then changes/removes the referenced element. When you do something with the variable reference you get this error. The solution really depends on the code you are using to do your tests and your framework for accessing elements. In general, you need to be aware of when you perform an action that changes the page and refetch the element before you access it. You could always refetch an element before you access it, you could refetch all elements that are affected by a page change, and so on...
You code probably looks something like this
WebElement e = driver.findElement(...); // get the element
// do something that changes the page which, in turn, changes e above
e.click(); // throws the StaleElementReference exception
What you probably want is something more like one of these...
Don't fetch the element until you need it
// do something that changes the page which, in turn, changes e above
WebElement e = driver.findElement(...); // get the element
e.click(); // throws the StaleElementReference exception
...or fetch it again right before you need it...
WebElement e = driver.findElement(...); // get the element
// do something that changes the page which, in turn, changes e above
e = driver.findElement(...); // get the element
e.click(); // throws the StaleElementReference exception
I would prefer the first fix... just fetch what you need when you need it. That should be the most efficient way to solve this problem. The second fix might have performance issues because you might be refetching a bunch of elements over and over and either never using them or refetching them 10 times only to reference the element once at the end.
I have the following code in a javascript file:
if(dojo.byId('WC_selectedColorNumber') == null && this.defaultColor != null)
{
dijit.byId('WC_color_selection').domNode.style.display = 'block';
dojo.html.set(dojo.query(".message__button .add"), "Add product with only base color " + this.defaultColor + "?");
var userResponse = true;
dojo.connect(WC_add_color_yes, "onclick", function(evt){
userResponse = true;
});
dojo.connect(WC_add_color_no, "onclick", function(evt){
userResponse = false;
});
//var userResponse = confirm("Add product with only base color " + this.defaultColor + "?");
//I WANT TO WAIT HERE FOR THE RESPONSE
if(userResponse == false) //if user clicks Cancel or 'no', display a message and leave the function.
{
alert("Remember to select a color before adding to cart."); //should be a tooltip/popup (not javascript alert) with the same message
return; //return so item doesn't get added to cart
}
}
Firstly, the logic behind this code is correct and it works perfectly well when using javascript confirm's.
As of now, everything comes up and displays correctly, and clicking the buttons perform the correct actions (if I put a console.log in the onclick dojo events, they do indeed print to the console when I click the buttons). However, the program doesn't wait for the responses and continues beyond the dojo.connect methods before it sees the user's input.
I need it to wait until either the yes or no button have been pressed, but I cannot figure out how to do it. I've tried using a
while(userResponse == null);
but a) it's generally a terrible idea and b) it didn't work anyways.
How can I make my code wait until the user has clicked one of the two buttons?
If you can make a jsfiddle I'd be able to help you more, I think, but your dojo.connect calls shouldn't be inside a logic flow like this. Instead, set up your connects on widget startup, and have them act generically.
In your example code, it looks to me like saying "Yes" means "Use default color", and "No" means "User must specify color". So...
startup: function () {
this.inherited(arguments);
dojo.connect(WC_add_color_yes, "onclick", dojo.hitch(this, function(evt){
this.useDefaultColor();
}));
dojo.connect(WC_add_color_no, "onclick", dojo.hitch(this, function(evt){
this.displayColorPicker();
}));
}
And then... only display those two buttons (or the dialog they're hopefully in) when applicable.
There is no "wait" or "sleep" function in javascript and each invocation of javascript code executes to completion (it does not get interrupted in mid execution by a response to some other event). You have correcly identified the historical execeptions that overcome this - global alert and confirm functions execute in browser native code and wait on user input.
Because of this your code will have to be restructured in some way, e.g. an event handler for "add to cart" validates the color choice and calls a function to really add it to the cart if valid. If it is not valid it modifies the DOM to present user with some buttons. The handler for the "yes" option would likewise call the same function to really add it to the cart.
Specific code is outside the scope of this answer - there must be many methods in page and code design to achieve the desired result. For example only: breaking up the sequential code and putting it in separate event handlers, coding using Promise objects defined in EC6 but not supported in MSIE, or perhaps even providing an option of "none - base color only" in the color selection logic.
FYI the dojo 1.10 toolkit documentation reports support for Dojo Promises but I leave research to determine its suitability with you.
I'm on my third day working with Protractor and I'm constantly hitting bric walls in regards to waiting around for pages to load and elements to appear. This test case in particular has grown ugly and I would like to solve the issues without having to rely on sleeps.
I am currently "outside of the land of AngularJS"
it('it should reflect in both the field and the title when the personnel name is changed', function() {
var inputField, personnelHeader, personnelName;
personnelName = element(By.css(".overlay.editnameoverlay")).click();
personnelHeader = element(By.id("personnel_name_header"));
inputField = element(By.css("input[name='newvalue']"));
inputField.clear();
inputField.sendKeys("Test 123");
element(By.css("input[name='ok_button']")).click();
// browser.driver.sleep(2000); This test only works with this sleep added
browser.wait(function() {
console.log("Waiting for header to change...");
return personnelHeader.getText().then(function(text) {
return text === "Test 123";
});
}, 5000);
return expect(personnelHeader.getText()).toBe(personnelName.getText());
});
So the test here changes the name in an input field. submits it and waits for the changes to become reflected in the header of the modal. The problem is that without the browser.driver.sleep(2000) I get an error saying
Stacktrace:
StaleElementReferenceError: stale element reference: element is not attached to the page document
How do I go about solving this in this particular case?
From the documentation for Expect Conditions:
var EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to contain the text 'foo'.
browser.wait(EC.textToBePresentInElement($('#abc'), 'foo'), 5000);
When you use Protractor to test for non-angular pages you're on your own regarding waiting for elements to be ready for interaction.
StaleElementReferenceError is probably the most useless selenium error, it happens when the element got removed from the DOM but is still cached somehow, I also suffered this problem when started with Protractor and even tried to convince it should be automatically retried Protractor-side.
The solution for me is to always explicitly wait for an element to appear on the page using a custom function waitReady() that browser.wait for elements ready, i.e: waits for the element to be ready for interaction:
expect($('#login_field').waitReady()).toBeTruthy();
First integrate this snippet in your code: https://gist.github.com/elgalu/2939aad2b2e31418c1bb
Not only the custom waitReady() waits for the element but it also swallows any unrelated useless webdriver error like StaleElementReferenceError and will simply retry up until finding the element or it will timeout.
So waitReady() each element before interacting, i.e. before clear() or sendKeys() or click() ...
// TODO: Move to Page Objects module
var personnelNameElm = $(".overlay.editnameoverlay");
var personnelHeaderElm = $("#personnel_name_header");
var inputFieldElm = $("input[name='newvalue']");
var okBtnElm = $("input[name='ok_button']");
it('it should reflect in both the field and the title when the ' +
'personnel name is changed', function() {
expect(personnelNameElm.waitReady()).toBeTruthy();
personnelNameElm.click();
expect(inputFieldElm.waitReady()).toBeTruthy();
inputFieldElm.clear().sendKeys("Test 123");
expect(okBtnElm.waitReady()).toBeTruthy();
okBtnElm.click();
browser.wait(function() {
console.log("Waiting for header to change...");
// Using waitReady() before getText() avoids Stale element errors
return personnelHeaderElm.waitReady().then(function() {
return personnelHeaderElm.getText().then(function(text) {
return text === "Test 123";
});
});
}, 5000);
expect(personnelHeaderElm.getText()).toEqual(personnelNameElm.getText());
});
In this instance, I load a single paypal page, in which I am prompted to login. Once I login, the page changes, through the use of other javascripts on paypal's end. The address does not change on this transition, nor does the source code in any material way. I am trying to find a way to have my script wait long enough after the first click to be able to get the element that loads after. I thought I could do this fairly simple using the following:
document.getElementById("submitLogin").click();
window.onload = function() {
document.getElementById("continue").click();
};
When the script is executed, the first button is clicked, the page transitions, but it won't click the second button that loads. My javascript console does not report any errors, suggesting that it is able to "get" the element. Not sure why it won't click it though.
If nothing else, you could always poll for the existence of the "continue" element at some interval:
function clickContinue() {
var button = document.getElementById("continue");
return button ? button.click() : setTimeout(clickContinue, 100);
}
document.getElementById("submitLogin").click();
clickContinue();
If you go this route, you'll probably want to include a failsafe so it doesn't run too long, in case something unexpected happens. Something like this should work:
clickContinue.interval = 100; // Look for "continue" button every 0.1 second
clickContinue.ttl = 10000; // Approximate time to live: 10 seconds ~ 10,000 ms
clickContinue.tries = clickContinue.ttl / clickContinue.interval | 0;
function clickContinue() {
var button = document.getElementById("continue"),
interval = clickContinue.interval;
return button ? button.click() :
clickContinue.tries-- && setTimeout(clickContinue, interval);
}
// ...
Take a look at PayPal's API docs and see if they provide a way to set up a callback to handle this, though. This polling technique should probably only be used as a last resort.