Testing the contents of a temporary element with protractor - angularjs

I'm trying to test the login page on my site using protractor.
If you log in incorrectly, the site displays a "toast" message that pops up for 5 seconds, then disappears (using $timeout).
I'm using the following test:
describe('[login]', ()->
it('should show a toast with an error if the password is wrong', ()->
username = element(select.model("user.username"))
password = element(select.model("user.password"))
loginButton = $('button[type=\'submit\']')
toast = $('.toaster')
# Verify that the toast isn't visible yet
expect(toast.isDisplayed()).toBe(false)
username.sendKeys("admin")
password.sendKeys("wrongpassword")
loginButton.click().then(()->
# Verify that toast appears and contains an error
toastMessage = $('.toast-message')
expect(toast.isDisplayed()).toBe(true)
expect(toastMessage.getText()).toBe("Invalid password")
)
)
)
The relevant markup (jade) is below:
.toaster(ng-show="messages.length")
.toast-message(ng-repeat="message in messages") {{message.body}}
The problem is the toastMessage test is failing (it can't find the element). It seems to be waiting for the toast to disappear and then running the test.
I've also tried putting the toastMessage test outside the then() callback (I think this is pretty much redundant anyway), but I get the exact same behaviour.
My best guess is that protractor sees that there's a $timeout running, and waits for it to finish before running the next test (ref protractor control flow). How would I get around this and make sure the test runs during the timeout?
Update:
Following the suggestion below, I used browser.wait() to wait for the toast to be visible, then tried to run the test when the promise resolved. It didn't work.
console.log "clicking button"
loginButton.click()
browser.wait((()-> toast.isDisplayed()),20000, "never visible").then(()->
console.log "looking for message"
toastMessage = $('.toaster')
expect(toastMessage.getText()).toBe("Invalid password")
)
The console.log statements let me see what's going on. This is the series of events, the [] are what I see happening in the browser.
clicking button
[toast appears]
[5 sec pass]
[toast disappears]
looking for message
[test fails]
For added clarity on what is going on with the toaster: I have a service which essentially holds an array of messages. The toast directive is always on the page (template is the jade above), and watches the messages in the toast service. If there is a new message, it runs the following code:
scope.messages.push(newMessage)
# set a timeout to remove it afterwards.
$timeout(
()->
scope.messages.splice(0,1)
,
5000
)
This pushes the message into the messages array on the scope for 5 seconds, which is what makes the toast appear (via ng-show="messages.length").
Why is protractor waiting for the toast's $timeout to expire before moving on to the tests?

I hacked around this using the below code block. I had a notification bar from a 3rd party node package (ng-notifications-bar) that used $timeout instead of $interval, but needed to expect that the error text was a certain value. I put used a short sleep() to allow the notification bar animation to appear, switched ignoreSynchronization to true so Protractor wouldn't wait for the $timeout to end, set my expect(), and switched the ignoreSynchronization back to false so Protractor can continue the test within regular AngularJS cadence. I know the sleeps aren't ideal, but they are very short.
browser.sleep(500);
browser.ignoreSynchronization = true;
expect(page.notification.getText()).toContain('The card was declined.');
browser.sleep(500);
browser.ignoreSynchronization = false;

It turns out that this is known behaviour for protractor. I think it should be a bug, but at the moment the issue is closed.
The workaround is to use $interval instead of $timeout, setting the third argument to 1 so it only gets called once.

you should wait for your toast displayed then do other steps
browser.wait(function() {
return $('.toaster').isDisplayed();
}, 20000);

In case anyone is still interested, this code works for me with no hacks to $timeout or $interval or Toast. The idea is to use the promises of click() and wait() to turn on and off synchronization. Click whatever to get to the page with the toast message, and immediately turn off sync, wait for the toast message, then dismiss it and then turn back on sync (INSIDE the promise).
element(by.id('createFoo')).click().then(function () {
browser.wait(EC.stalenessOf(element(by.id('createFoo'))), TIMEOUT);
browser.ignoreSynchronization = true;
browser.wait(EC.visibilityOf(element(by.id('toastClose'))), TIMEOUT).then(function () {
element(by.id('toastClose')).click();
browser.ignoreSynchronization = false;
})
});

I hope this can help who has some trouble with protractor, jasmine, angular and ngToast.
I create a CommonPage to handle Toast in every pages without duplicate code.
For example:
var CommonPage = require('./pages/common-page');
var commonPage = new CommonPage();
decribe('Test toast', function(){
it('should add new product', function () {
browser.setLocation("/products/new").then(function () {
element(by.model("product.name")).sendKeys("Some name");
var btnSave = element(by.css("div.head a.btn-save"));
browser.wait(EC.elementToBeClickable(btnSave, 5000));
btnSave.click().then(function () {
// this function use a callback to notify
// me when Toast appears
commonPage.successAlert(function (toast) {
expect(toast.isDisplayed()).toBe(true);
});
});
});
})
});
And this is my CommonPage:
var _toastAlert = function (type, cb) {
var toast = null;
switch (type) {
case "success":
toast = $('ul.ng-toast__list div.alert-success');
break;
case "danger":
toast = $('ul.ng-toast__list div.alert-danger');
break;
}
if (!toast) {
throw new Error("Unable to determine the correct toast's type");
}
browser.ignoreSynchronization = true;
browser.sleep(500);
browser.wait(EC.presenceOf(toast), 10000).then(function () {
cb(toast);
toast.click();
browser.ignoreSynchronization = false;
})
}
var CommonPage = function () {
this.successAlert = function (cb) {
_toastAlert("success", cb);
};
this.dangerAlert = function(cb) {
_toastAlert("danger", cb);
}
}
module.exports = CommonPage;

Chris-Traynor's answer worked for me but i've got an update.
ignoreSynchronization is now deprecated.
For those using angular and protractor to test this, the below works nicely for me.
$(locators.button).click();
await browser.waitForAngularEnabled(false);
const isDisplayed = await $(locators.notification).isPresent();
await browser.waitForAngularEnabled(true);
expect(isDisplayed).toEqual(true);
I've simplified this to make it easier to see, I would normally place this inside a method to make the locators dynamic.

Related

Two identical Protractor tests in a row, second one fails

I use Protractor 4.0.14 on an Angular 1.6.3 website.
I execute two identical tests. The first one will succeed but not the second one. Any idea ?
it('Should return a toast error on inferior amount', function () {
$$('#purposeList input.amount').first().clear().sendKeys('4999');
$$('button[type="submit"]').get(0).click();
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear();
$$('button.toast-close-button').each(function (item) {
item.click();
});
expect($('button.toast-close-button').isPresent()).toBe(false);
});
it('Should return a toast error on inferior amount BIS', function () {
$$('#purposeList input.amount').first().clear().sendKeys('4999');
$$('button[type="submit"]').get(0).click();
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear();
$$('button.toast-close-button').each(function (item) {
item.click();
});
expect($('button.toast-close-button').isPresent()).toBe(false);
});
The field is provided with a wrong amount so it throws a toast-error when I submit. When I do it manually, I get the toast-error. When doing it with protractor, only the first of the identical tests will pass. On the other, it looks like the toast is never launch or closed really quickly.
I tried sleeps, ignoreSynchronisation with browser wait. I tried to replace the $timeout by $interval in the service dealing with error toasts (although other $timeout could be present). It didn't change anything.
It is actually the first expect of the second it that is failing and there is no actual help from the error, it's just an expect at false instead of true :
Message:
Expected false to be true.
Edit :
So #LostJon, by handling every promises, do you mean like this :
it('Should return a toast error on inferior than authorized purpose amount on step 1 BIS', function () {
$$('#purposeList input.amount').first().clear().then(function() {
$$('#purposeList input.amount').first().sendKeys('4999').then(function () {
$$('button[type="submit"]').get(0).click().then(function () {
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear().then(function () {
$$('button.toast-close-button').each(function (item) {
item.click();
}).then(function () {
expect($('button.toast-close-button').isPresent()).toBe(false);
});
});
});
});
});
});
I tried it, same result.
Edit :
Apparently it was an ignoreSynchronization = true that was inside the wait instead of before. I didn't figure it out before because I also set browser.manage().timeouts().implicitlyWait(2000), so without synchronization, we were still waiting for the elements but it was not accurate.
so, the below line returns a promise.
$$('button[type="submit"]').get(0).click();
Your code is running synchronously, when you should be handling the promise resolve from click(). In fact, your line that calls clear() also returns a promise, so that needs to be handled as well.
It was an ignoreSynchronization = true that was inside the wait instead of before. I didn't figure it out before because I also set browser.manage().timeouts().implicitlyWait(2000), so without synchronization, we were still waiting for the elements but it was not accurate.

Protractor - click causing angular to freeze

I'm having trouble with a protractor test.
Overview of the test
Click button to show a form
Fill out the form
Click another button to save the form - this should call a function in one of my controllers that makes an http call and then reloads some models on the page.
When clicking the final "save" button, everything seems to freeze and the attached ng-click function never seems to get called. I don't see any errors in the console when I use browser.pause. I can see that the button is in fact clicked, but at that point, nothing seems to happen.
Final button definition:
this.addConfigFilterLineButton = element(by.css('[ng-click="cflCtrl.create();"]'));
Function that fills out the form:
this.addNewConfigFilterLine = function (cb) {
var self = this;
var deferred = protractor.promise.defer();
browser.executeScript('window.scrollTo(0,10000);')
.then(function(){
self.newConfigFilterLineButton.click();
// code that fills out the form
self.addConfigFilterLineButton.click();
browser.waitForAngular()
.then(function(){
deferred.fulfill();
});
});
return deferred.promise;
};
Spec
it('should allow creating a new ConfigFilterLine', function (done) {
var length;
settingsPage.configFilterLines.count()
.then(function(count){
length = count;
return settingsPage.addNewConfigFilterLine();
})
.then(function(){
expect(settingsPage.configFilterLines.count()).to.eventually.equal(length+1);
done();
});
});
I've tried with browser.waitForAngular and without it, and it doesn't seem to matter. When the button is clicked, nothing happens.
Any ideas would be helpful. Let me know if there's more info I can provide.
Instead of using
this.addConfigFilterLineButton = element(by.css('[ng-click="cflCtrl.create();"]'));
try something more like this:
this.addConfigFilterLineButton = element(by.id('id-of-final-button'));
My guess is that Protractor isn't correctly finding "addConfigFilterLineButton".

afterEach not waiting to complete before continuing

I use afterEach to logout, but my tests try to login before afterEach completes logging out.
I'm trying to avoid the use of sleep statements (really slows things down).
How can I get a test to wait for the previous beforeEach to finish?
code snippets:
afterEach(function() {
AccountBlock.logout().then(function(){
browser.sleep(2000); // I want to get rid of this
});
});
the logout() method (from the AccountBlock object):
this.logout = function() {
var logoutLink = this.logoutLink;
var userName = this.userName;
return logoutLink.isDisplayed().then(function(isDisplayed){
if (!isDisplayed) {
return userName.click().then (function() {
return logoutLink.click();
}); // open the account menu before logout
} else { // account menu already displayed
return logoutLink.click();
}
});
}
this.userName = element(by.css('a[ng-bind="userName"]'));
this.logoutLink = element(by.css('a[ng-click^="logout"]'));
browser.wait is definitely the way to go but rather than calling on elements directly to ask if they are present or displayed protractor has built-in functionality for just this called ExpectedConditions. You can use this let protractor force the browser to wait until it has met it's own expected conditions.
Below I am finding the element, using expected conditions to detect the presence of the element then using the not operator to detect when the element is no longer present. This should force browser.wait to pause future promises until that condition is true.
afterEach(function(){
var ec = protractor.ExpectedConditions;
var logout = element(by.css('a[ng-click^="logout"]'));
var logoutLink = ec.presenceOf(logout);
var logoutLinkNotThereAnymore = ec.not(logoutLink);
//do logout
browser.wait(logoutLinkNotThereAnymore, 10000, "Waited 10 seconds");
});
beforeEach(function(){
//do login
});
I use browser.wait() to avoid sleep(), in your login block of code you can do this
var doLogin = function(){
browser.wait(element(by.id('yourLoginBoxId')).isDisplayed); //this will wait until the html with the form to login is in your DOM
//your code to login here
};
note that isDisplayed doesn't have the ().

get text of element during angular $timeout in protractor

Note: using MEAN - Mocha, Express, Angular, and Node for the app
I am learning how to do e2e test and I built a little app to test against. One thing it does is whenever you perform an action it displays a message for X seconds and then goes away. I'm using angulars $timeout.
$scope.displayMessage = function(messageIn, borderColor){
var timeoutTime = 5000; //clear the message after x seconds
$scope.borderType = "1px solid " + borderColor;
$scope.messages = messageIn;
$scope.visibility = true; //reveal the div
$timeout.cancel($scope.timeout); //cancel any previous timers
$scope.timeout = $timeout(function(){ //Set and start new timer.
$scope.visibility = false;
$scope.messages = "";
}, timeoutTime);
};
Now I'm trying to test this. Currently I have
it("should display message", function(done){
var button = element(by.id("getStudents"));
console.log("clicking button");
var messageElem = element(by.binding("messages"));
button.click().then(function () {
console.log("button clicked");
messageElem.getText().then(function(text){
console.log("The text should equal: " + text);
});
//expect(messageElem.getText()).toBe("Students retrieved");
console.log("test executed");
});
done();
});
But it seems to be waiting until after the timeout to do the messageElem.getText().then() at which point the div has been cleared. The "test executed" log will print during the timeout but not the "getText()" function. I'm guessing this has something to do with angulars life cycle?
Oh and here's the HTML (in Jade format)
div(ng-model="messages",ng-show = "visibility", style = "clear:both;padding-top:3em;border:{{borderType}};text-align:center;")
h3
{{messages}}
ignore my style, I was too lazy to make a css file for this test app :D
Try using $interval instead of $timeout.
Protractor attempts to wait until the page is completely loaded before
performing any action (such as finding an element or sending a command
to an element).
Read here. Details here.
I hacked around this using the below code block. I had a notification bar from a 3rd party node package (ng-notifications-bar) that used $timeout instead of $interval, but needed to expect that the error text was a certain value. I put used a short sleep() to allow the notification bar animation to appear, switched ignoreSynchronization to true so Protractor wouldn't wait for the $timeout to end, set my expect(), and switched the ignoreSynchronization back to false so Protractor can continue the test within regular AngularJS cadence. I know the sleeps aren't ideal, but they are very short.
browser.sleep(500);
browser.ignoreSynchronization = true;
expect(page.notification.getText()).toContain('The card was declined.');
browser.sleep(500);
browser.ignoreSynchronization = false;

Protractor (WebDriverJS) cannot switchTo window. nameOrHandle is not defined

I have gone around this as many ways as I can think, and still can't get this to work. I am trying to switch to a popUp as a way to automate a login. (Protractor is just a wrapper on WebDriverJS that adds some AngularJS functionality.) The webDriver goes to my main page, clicks the login button and waits for the loggin popup.
So far I have:
var ptor = protractor.getInstance();
beforeEach(function() {
var handlesDone = false;
ptor = protractor.getInstance();
ptor.get('#/');
runs(function() {
return ptor.findElement(protractor.By.className('btn')).click();
});
waits(3000);
runs(function() {
return ptor.getAllWindowHandles().then(function(handles) {
popUpHandle = handles[1];
parentHandle = handles[0];
return handlesDone = true;
});
});
waitsFor(function() {
return handlesDone;
});
});
So far so good, next I want to make sure that I have, in fact, a window handle for my pop-up:
describe('login', function() {
it('should switch to popUp\'s handle', function() {
expect(popUpHandle).toBeDefined();
finally, I try to switch to this window:
ptor.switchTo().window(popUpHandle).getWindowHandle().then(function(handle) {
expect(handle).toEqual(popUpHandle);
});
});
});
Yet no matter what I have tried, I keep getting the following error:
login
should switch to popUp's handle
Failures:
1) login should switch to popUp's handle
Message:
ReferenceError: nameOrHandle is not defined
Stacktrace:
ReferenceError: nameOrHandle is not defined
at webdriver.WebDriver.TargetLocator.window (.../node_modules/protractor/node_modules/selenium-webdriver/lib/webdriver/webdriver.js:1385:32)
at null.<anonymous> (.../test/e2e/e2e-spec.js:40:21)
at ...node_modules/protractor/jasminewd/index.js:54:12
at webdriver.promise.ControlFlow.runInNewFrame_ (.../node_modules/protractor/node_modules/selenium-webdriver/lib/webdriver/promise.js:1438:20)
at webdriver.promise.ControlFlow.runEventLoop_ (.../node_modules/protractor/node_modules/selenium-webdriver/lib/webdriver/promise.js:1303:8)
at Timer.exports.setInterval.timer.ontimeout (timers.js:234:14)
==== async task ====
Finished in 5.388 seconds
1 test, 2 assertions, 1 failure
as you can see, I have 2 assertions:
that popUpHandle is defined
that the handle after the switchTo is the same as the popUpHandle
I have tested that I have 2 handles total. I have tested that they are both strings. I have tested that they are different from each other. In this example I test that popUpHandle is defined. All those test pass. Yet no mater what I do, when I try to plug a handle into the .switchTo().window() method I get the same "nameOrHandle is not defined".
I'm stumped. There is so little documentation on WebDriverjs I can't even be sure that switchTo is implemented. Does anyone know what is going on here?
Thanks.
Add ptor.ignoreSynchronization = true or browser.ignoreSynchronization = true just before you switchTo() the other window. (Use browser.ignoreSynchronization=true for the new implementation where browser replaces protractor.getInstance(). Refer this link)

Resources