I have an angular application that has a login page, login.html and index page, index.html. I use requireJs to reduce page load time and angularjs in both the pages are manually bootstrapped as,
angular.bootstrap(document, ['loginApp']) //for login.html
angular.bootstrap(document, ['mainApp']) //for index.html
Now, i enter the credentials, press submit in login.html page and on success, index.html gets loaded with a delay. Now, i used login button click promise as,
element(by.name('normalLogin')).click().then(
function(){
element(by.xpath('//a[@title="Venues"]')).click() //this crashes because there is no element with matching xpath
})
Then i tried,
element(by.name('normalLogin')).click()
browser.wait(function() {
return browser.driver.isElementPresent(by.xpath('//a[@title="Venues"]'))
})
var venueLink = by.xpath('//a[@title="Venues"]')
browser.driver.isElementPresent(venueLink).then(function(){
console.log("tenant login process successful")
element(venueLink).click()
})
This also throws the exception,
Error: Failed: invalid selector: Unable to locate an element with the xpath expression //a[@title="Venues"] because of the following error:
TypeError: Failed to execute 'createNSResolver' on 'Document': parameter 1 is not of type 'Node'.
Protractor is not waiting for the complete page load after login button click causing this issue. How can i instruct protractor to wait until a new angular app gets bootstrapped/loaded completely?
stack overflow discussion on the same - http://stackoverflow.com/questions/31498207/protractor-button-click-calls-the-callback-sooner-than-desired
@madhavan020985 Have you tried:
element(by.name('normalLogin')).click()
browser.wait(function() {
return browser.driver.isElementPresent(by.xpath('//a[@title="Venues"]'))
}).then(function(){
var venueLink = by.xpath('//a[@title="Venues"]')
browser.driver.isElementPresent(venueLink).then(function(){
console.log("tenant login process successful")
element(venueLink).click()
})
});
??
Protractor works asynchronously, so
var venueLink = by.xpath('//a[@title="Venues"]')
will be executed while
browser.wait(function() {
return browser.driver.isElementPresent(by.xpath('//a[@title="Venues"]'))
})
Have you set the ignoreSynchronization flag?
Could you try to click twice please?
I would like to check if you get the same issue as mine https://github.com/angular/protractor/issues/2360
the error unable to locate the element is happening inconsistently, may be fifteen per cent of the times, i can say. @sjelin , the inconsistency in error makes it difficult to test with the ignoreSynchronization flag. so, i am putting my investigation hold for now. i don't know, if you want me to close this....
Temporary fix to your problem is that put "browser.sleep(5000)" after log-in button click instruction. Browser will sleep for 5 seconds mean time index.html page will be loading completely.
Hope this will help you to fix.
Ok, sorry, I didn't read your issue in enough detail. I think what's going on is that because navigation is being trigged by a button press, instead of browser.get, Protractor doesn't know to wait. Does that sound right to you? We've been getting a lot of issues related to this problem lately.
@sjelin, i too mixed up certain things here... there are two problems, i am facing in fact... yes, there are two angular apps bootstrapped... login app is via browser.get, whereas index app is loaded via button click... fifteen per cent of the times, login app crashes with the message "Angular could not be found on the page ". I think, setting ignoreSynchronization (as sjelin suggested ) possibly can solve this... but my bug was raised for another issue, which is when login app is loaded successfully and you submit login button to go to index page... the callback func argument to loginButton.click.then is triggered before the index app is loaded completely..
Yes, the callback would be invoked as soon as the click finishes.
I'm seeing similar behavior in my tests. It's completely maddening because it will sometimes work. I'm testing a non-angular app, so it might be worse for me. browser.waits don't always wait. It seems to occur more often when using sendKeys. I'm also seeing the next test fail before the previous test's expect finish. Are expects blocking?
The ONLY thing that seems to work is a strategically placed browser.sleep (say for a few hundred ms). This seems to block just enough to right the flow.
It's very hard to debug, and to say what's happening with any certainty, because of the inconsistent behavior.
I believe that the logic for waiting has improved with version 2.4.0. Please try that, and open a new issue with any specific problems - this one is getting too crowded to have anything actionable for us.
@juliemr I'm seeing similar behaviour intermittently with 2.4.0. Can you clarify if it is _expected_ to have to explicitly wait after a command has triggered a view change, before carrying on with commands to elements on the _new_ page.
For example:
// we start on the 'landing' page
it('should go to the dashboard', function () {
var goToLoginButton = element(by.linkText('LOGIN'));
goToLoginButton.click(); // triggers a route change
// should I wait here?
// until an element from the login page is available?
// or does element() or sendKeys() wait internally?
var username = element(by.model('vm.username'));
var password = element(by.model('vm.password'));
var loginButton = element(by.buttonText('LOG IN'));
username.sendKeys('someusername');
password.sendKeys('somepassword');
loginButton.click();
expect(browser.getCurrentUrl()).toContain('#/dashboard');
});
Thanks.
@jskrepnek It is indeed expected to have to wait if a command other than get or refresh causes a page reload. Currently, there is no way for Protractor to know if a click event would cause a page reload, so it's unable to do the same waiting that occurs with protractor.get.
This is clearly causing confusion, so we should find some good place in the docs to mention it. Does anyone from this thread have suggestions on where in our docs they would look for help with this issue?
See https://github.com/angular/protractor/issues/2567, opened to track adding documentation.
I'll think about a good place to clarify the docs and comment on #2567.
To be clear, when you say "cause a page reload", do you mean to include in that simple route changes as well as full-blown, back-to-the-server reloads?
Thanks for the help!
'Cause a page reload' meaning only a full-blown reload, not just route changes.
Precisely, anything that causes angular to bootstrap again.
Okay, so: given angular doesn't bootstrap on a route change. So in that specific case of a route change, I shouldn't have to wait for an element that is present on the new view?
Take my example above. Based on what you're saying, I shouldn't have to wait before sending keys to inputs on the navigated-to view/route.
Thanks for clarifying.
_Update_: It's clear that it's important to include waits for route changes (i.e.: not full-blown reloads) as well.
I am also very interested in a response to this. We are waiting after every click even when it is just a route change. It would be nice to get rid of those waits.
Just a route change is causing issues for me too, when the new route is slow to load/render. I checked by adding some logging (visible through console plugin) to the app main module .run functions, that it's really really just a route change, the app isn't created from scratch in a full page reload.
After changing route, first few expectations/actions that find an element fail, with the element not being there, or having a wrong, uninitialized value. I take screenshots for failed specs, sometimes it even shows a not-fully rendered page, e.g. one that has blank space where interpolated Angular variables are supposed to appear! So it definitely has started running stuff without waiting for angular to settle, if it gets as far as taking the screenshot with the page still loading/rendering.
A short-ish browser.sleep works around the issue, as for others. The required time is proportionate to the amount of time it takes to load and render the new route, in this case 500-100ms.
My route is loaded with ui-router $state.go, from a simple synchronous ng-click handler. No setTimeout, event handlers without $scope.$apply, non-$http requests etc usual culprits going on anywhere in this app. Angular sync always on here (browser.ignoreSynchronization = false).
Using protractor 2.5.1 with chromedriver banging Chrome 47, on Windows 10. Node 0.12.6.
FWIW, We still have these issues with Protractor 4, Chrome 53, Node 4.x etc.
Have settled for some strategically placed browser.waits for certain elements to be displayed with the right content etc whenever this is an issue. More reliable than any short-ish sleep and longer sleeps would slow down the tests more. It's only needed when a really big view/DOM change occurs, luckily not between smaller operations.
if it's an actual <a> tag, i've opted to manually get the href attribute from the element, then just call browser.get(), since browser.get() seems to resolve at the correct time, but .click() resolves too early
linkElement.getAttribute('href')
.then(function(linkUrl){
browser.get(linkUrl);
});
I hate using .then() when protractor is supposed to chain up all the commands properly but eh, it's a workaround for click() resolving too early
I also use .then(), but I use it everywhere. That is the only way (plus browser.sleep when required) to make my protractor test less un-deterministic.
instead use:
it('should get linkUrl', async function () {
let linkUrl = await linkElement.getAttribute('href');
browser.get(linkUrl);
});
I was able to work around this issue without need to use hardcoded fixed waiting intervals. The idea is to add some custom property to the window and wait till it disappears on cold page reload.
async initWaitForPageReload(): Promise<void> {
await browser.executeScript(`window.$waitForReload = true;`);
}
async waitForPageReload(): Promise<void> {
await browser.wait(
async () => {
// wait till property disappears and angular is initialized
const reloaded = await browser.executeScript(`return !window.$waitForReload && window.getAngularTestability`);
if (reloaded) {
return true;
}
await browser.sleep(50); // this checker function will be called every 50ms until it returns true or timed out
return false;
},
30000,
`The page didn't reload in specified time`,
);
await browser.waitForAngular(); // wait till angular is finished initializing (http requests and animations)
}
It's important to call init function before action that will cause cold page reload. So in your tests you do something like this:
await this.initWaitForPageReload();
await this.getElement(By.css(...).click(); // this will cause cold page reload
await this.waitForPageReload();
// here the page is reloaded and is ready to be interacted with using standard element() functions
Most helpful comment
Just a route change is causing issues for me too, when the new route is slow to load/render. I checked by adding some logging (visible through console plugin) to the app main module .run functions, that it's really really just a route change, the app isn't created from scratch in a full page reload.
After changing route, first few expectations/actions that find an element fail, with the element not being there, or having a wrong, uninitialized value. I take screenshots for failed specs, sometimes it even shows a not-fully rendered page, e.g. one that has blank space where interpolated Angular variables are supposed to appear! So it definitely has started running stuff without waiting for angular to settle, if it gets as far as taking the screenshot with the page still loading/rendering.
A short-ish browser.sleep works around the issue, as for others. The required time is proportionate to the amount of time it takes to load and render the new route, in this case 500-100ms.
My route is loaded with ui-router $state.go, from a simple synchronous ng-click handler. No setTimeout, event handlers without $scope.$apply, non-$http requests etc usual culprits going on anywhere in this app. Angular sync always on here (browser.ignoreSynchronization = false).
Using protractor 2.5.1 with chromedriver banging Chrome 47, on Windows 10. Node 0.12.6.