I created a basic test with protractor to click on a element and on the new page check if given element exists, but Protractor does not seem to wait and it runs the assertion just after the click but before the new page loads. The element I am looking for is available on both pages, so protractor sees the element on the old page, before the new page loads. Can someone please tell me what am I doing wrong?
it('should check when new page is loaded', function () {
button.click().then(function (){
return expect(newElement).to.exist;
});
First of all ,add "getPageTimeout" variable to your Protractor configuration file.
If you are not using it already.This is to set global page timeout based on your average page load time in your application.
conf.js
getPageTimeout: 120000,//change it based on your app response time
If it does not help even then verify the page title of next page(assuming its different from previous page) before checking the actual element you are looking for.
it('should check when new page is loaded', function () {
button.click().then(function (){
browser.getCurrentUrl();
browser.getTitle().then(function (title) {
expect(title).toEqual('Next Page Title');
});
return expect(newElement.isDisplayed()).toBeTruthy();
});
Even if that does not help you may use expected conditions.There are several predefined conditions to explicitly wait for. In case you want to wait for an element to become present:
var EC = protractor.ExpectedConditions;
var e = element(by.id('xyz'));
browser.wait(EC.presenceOf(e), 10000);
expect(e.isPresent()).toBeTruthy();
You can use below method in Configuration file
jasmineNodeOpts: {
showColors: true,
includeStackTrace: true,
defaultTimeoutInterval: 1440000
},
And In Spec file you can customise the wait time according to page and object visibility
browser.sleep(20000);
I hope above method will work fine in all the cases if you customise the wait time Properly.
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 trying to click on '0 - Bootstrapping' under Tutorial after going to https://docs.angularjs.org/tutorial, but Protractor is not waiting for the page to fully load, so I get an Error
Failed: Cannot read property 'click' of undefined
I tried manually waiting for the page to load using
browser.driver.sleep(10000);
but I still get the same Error.
conf.js
exports.config = {
// seleniumAddress: 'http://localhost:4444/wd/hub',
getPageTimeout : 12000000,
capabilities: {
'browserName': 'chrome'
},
specs: ['todo-spec.js'],
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 1440000,
}
};
todo-spec.js
describe('angularjs homepage', function () {
it('should greet the named user', function () {
var dropdown = element.all(by.css('.dropdown')).first(),
dropdownToggle = dropdown.element(by.css('.dropdown-toggle')),
dropdownMenu = dropdown.element(by.css('.dropdown-menu')),
menuItem = dropdownMenu.all(by.tagName('li')).first();
browser.get('http://www.angularjs.org');
dropdownToggle.click();
menuItem.click();
expect(browser.getCurrentUrl()).toEqual('https://docs.angularjs.org/tutorial');
//waiting 10 seconds for the page to fully load
browser.driver.sleep(10000);
browser.waitForAngular();
});
it('', async function () {
/* tried waiting for the element to be present
var EC = protractor.ExpectedConditions;
var e = element.all(by.css('.nav-index-listing'))[0];
browser.wait(EC.presenceOf(e), 10000);
Failed: Cannot read property 'isPresent' of undefined
*/
//clicking '0 - Bootstrapping' under Tutorial
element.all(by.css('.nav-index-listing'))[0].click();
expect(element(by.cssContainingText('.pln', 'npm install')).isEnabled()).toBe(true);
// Failed: Cannot read property 'click' of undefined
});
});
Edit
Replaced
element.all(by.css('.nav-index-listing'))[0].click();
expect(element(by.cssContainingText('.pln', 'npm install')).isEnabled()).toBe(true);
by
element.all(by.css('.nav-index-listing')).get(0).click();
browser.wait(protractor.ExpectedConditions.urlIs("https://docs.angularjs.org/tutorial/step_00"), 5000);
expect(browser.getCurrentUrl()).toEqual('https://docs.angularjs.org/tutorial/step_00');
expect(element(by.cssContainingText('.pln', 'npm install')).isEnabled()).toBe(true);
And got an Error
Failed: Wait timed out after 5001ms
If I remove browser.wait I get
Expected 'https://docs.angularjs.org/tutorial' to equal 'https://docs.angularjs.org/tutorial/step_00'.
First of all browser.driver.sleep(10000) is not required. For page load, you can use protractor.ExpectedConditions. It will reduce unnecessary waiting for 10 seconds, if the page is already loaded.
Secondly, there is a minor mistake in your code which is the root cause of your problem.
Change this line
element.all(by.css('.nav-index-listing'))[0].click();
To this
element.all(by.css('.nav-index-listing')).get(0).click();
UPDATE:
element.all(by.css('.nav-index-listing')) will only give you li elements which will pass the test. But clicking it, will not lead you to the desired page.
Change it to:
element.all(by.css('.nav-index-listing a')).get(0).click();
In this case, [0] will not work since the returned value is not an array. It is of type ElementArrayFinder. So, that's why .get(0) is required.
It will be better if you check for below conditions too, for your test.
browser.wait(EC.urlIs("https://docs.angularjs.org/tutorial/step_00"), 5000);
expect(browser.getCurrentUrl()).toEqual('https://docs.angularjs.org/tutorial/step_00');
These conditions checks whether your current URL is https://docs.angularjs.org/tutorial/step_00 or not.
Try using waitForAngualarEnabled(true) instead of expected wait which makes the protractor to wait until all the angular elements are loaded and add await in every line of your test.
I am currently testing a non Angular js website with protractor. My code is as follows :
describe("Login ", function () {
it("test", co.wrap(function* () {
browser.ignoreSynchronization = true;
yield browser.get("URL");
var elmOK = element(by.css('a[href="#partner"]'))
yield elmOK.click();
expect(browser.getCurrentUrl()).toContain('login');
}));
});
Test Scenario : My test opens the URL mentioned and selects the link with href=#partner. A login page which should pop up. But when I run the test and the link is clicked the login page doesn't popp up.
Please tell me what I am doing wrong?
Protractor appears synchronous because it waits for angular to be stable prior to moving to the next action. So if the above was an angular page, it would:
load the page, then Protractor would wait for angular (wait for the page to be stable)
tell selenium webdriver to find the element and click on it.
In a non-angular page, setting the ignore synchronization set to true in the right direction. What you need to do is to come up with your own sleep between getting the page and clicking the element. After you click on the element, you'll also need to wait for the next window to pop up, change focus to the next window to see if the url contains login.
browser.ignoreSynchronization = true;
browser.get("URL");
// let the page load
browser.sleep(2000);
element(by.css('a[href="#partner"]')).click().then(() => {
// may have to add sleep in for the page to load
browser.sleep(2000);
// switch to the next window for the popup
browser.driver.getAllWindowHandles().then((windowHandles) => {
let popupLoginHandle = windowHandles[1];
browser.driver.switchTo().window(popupLoginHandle).then(() => {
// check to see if the pop up has a url that contains 'login'
expect(browser.getCurrentUrl()).toContain('login');
});
});
});
Putting Hard coded waits is not an good practice.
If you are using jasmine for reporting , you may put the following code inside conf.js:
jasmineNodeOpts: {
defaultTimeoutInterval: 600000,
},
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());
});
I am new to Protractor. I think I have this down when dealing with an Angular page, but can't figure it out for a non-Angular page. Any help would be appreciated.
describe('Search', function() {
it('should click Search button and wait for results', function() {
browser.driver.findElement(by.id('search')).click();
});
});
Testing non-angular pages with Protractor can be tricky regarding waiting for stuff.
I suggest you upgrade Protractor to latest (1.5.0 as of now), use a custom function waitReady() that browser.wait for elements ready and rewrite your test like below. Note you can put everything within 1 spec if you like so.
// TODO: use page objects
var searchBtnElm = $('#search'); // use element(by.id('search')) if you prefer
it('waits for the elements present and visible (non-angular)', function() {
expect(searchBtnElm.waitReady()).toBeTruthy();
});
it('should click Search button', function() {
searchBtnElm.click();
});
it('wait for more results', function() {
// keep using waitReady() before interacting with the elements
// and before performing expectations on them
});
More details of why waitReady here.
Note: remember to set ignore synchronization for testing a non-angular page:
browser.ignoreSynchronization = true;
You can set it before browser.get the non-angular page.
I've suggested setting a high implicit wait in the past, e.g.
browser.manage().timeouts().implicitlyWait(5000);
That hack allows to you avoid waitReady and keep using the standard
expect(searchBtnElm.isPresent()).toBeTruthy();
But has an ugly disadvantage when testing for elements NOT present, i.e. when testing for absent or non visible elements in which case it will wait 5 seconds (5000ms) in vane, e.g. when doing
expect(someNonExistingElm.isPresent()).toBeFalsy();
Figured this out. I simply added the code below, after the click method:
describe('Search', function() {
it('should click Search button and wait for results', function() {
browser.driver.findElement(by.id('search')).click();
dvr.wait(function() {
return dvr.isElementPresent(by.xpath(
'/html/body/div/div[4]/div/div[2]/div/div/div/span'));
}, 20000);
});
});
Another Neat approach is to use "Expected Conditions" inside browser.wait - something like this:
var EC = protractor.ExpectedConditions;
var search = element(by.id('search'))
browser.wait(EC.visibilityOf(search), 2000).then(function(){
search.click()
})
You can get more details here: https://angular.github.io/protractor/#/api?view=ExpectedConditions
In protractor there are two types terms for on the page. isPresent ask if the element is exists on the page. isDisplayed asks if the element is visible. If you are waiting for a page to load you need to wait for isDisplayed, but that will error if it is not present, so wait for isPresent first. I use a function to wait for an element.
function waitForElement(el, waitTimeoutMilliseconds){
return browser.wait(function() { return el.isPresent(); }, waitTimeoutMilliseconds)
.then(function(){
return browser.wait(function() { return el.isDisplayed(); }, waitTimeoutMilliseconds);
});
}
Then just call that function in your test.
describe('Search', function() {
it('should click Search button and wait for results', function() {
var el = element(by.id('search'));
waitForElement(el, 5000);
el.click();
});
});