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;
Related
I am currently developing a set of tests using Protractor to test an AngularJS app that my company are developing. When I currently run through my tests, I am getting a failure on one of them, but can't figure out why... The error message says:
A Jasmine spec timed out. Resetting the WebDriver Control Flow.
The error message is shown at the end of the execution of the first of the following two test scripts, and the next thing to be shown in the console is the console.log() statement from the start of the second test:
it('should navigate to the Charts page', function() {
console.log("Start Charts page test");
browser.waitForAngularEnabled(false);
browser.actions().mouseMove(chartsMenuBtn).perform();
chartsMenuBtn.click();
browser.waitForAngularEnabled(true);
browser.call(closeDlg);
expect(browser.getCurrentUrl()).toBe(VM + '/#/charts');
});
/*Test that 'Export' menu item works correctly */
it('should navigate to the Export page', function() {
console.log("Start Export page test");
browser.waitForAngularEnabled(true); //false);
browser.actions().mouseMove(exportMenuBtn).perform();
exportMenuBtn.click().then(function(){
console.log("End Export page test (then call)");
expect(browser.getCurrentUrl()).toBe(VM + '/#/export');
});
console.log("End Export page test");
});
Inside the first of these two test scripts, I am calling the function closeDlg() using the line: browser.call(closeDlg);- this function is defined in the same spec.js file as where these tests are written with:
function closeDlg(){
browser.waitForAngularEnabled(false);
console.log("closeDlg() function called ")
var EC = protractor.ExpectedConditions;
var chartConfigForm = element(by.className('class="ui-tab-container ui-tab-vertical'));
var closeDlgBtn = element(by.buttonText('Cancel'));
browser.call(function(){
console.log("About to click closeDlgBtn ");
});
closeDlgBtn.click();
browser.call(function(){
console.log("just clicked closeDlgBtn ");
});
browser.call(function(){
console.log("End of closeDlg() function ");
});
}
and all of the console.log() statements from this function are displayed in the console, indicating that all of the code inside this function executes as expected.
This appear to suggest that the timeout is occurring on the expect(browser.getCurrentUrl()).toBe(VM + '/#/charts'); line, but I can see in the browser where the test scripts are being run that this is the current URL at the time that this line is executed.
After the line
End of closeDlg() function
is printed to the console from the closeDlg() function, that's when the timeout happens, which suggests that it's the line:
expect(browser.getCurrentUrl()).toBe(VM + '/#/charts');
that's causing the script to timeout, since that is the next line that will be executed.
I have tried setting browser.waitforAngularEnabled(true); & browser.waitForAngularEnabled(false); in various places within the test script, but nothing seems to have resolved or made a difference to the timeout error at all.
Does anyone have any suggestions why I'm getting this? How can I fix it?
You should check YouTube for some Protractor Tutorials as they introduce nicely the meaning of Promises and how Protractor works.
Further check these answers here to learn about some Protractor specific issues:
How Protractor works and how to debug timeouts
A bit about then() 1
A bit more about then() 2
After all here my suggestion to your code:
beforeAll(function(done){
browser.waitForAngularEnabled(false); //in case it doesn't work without this line.
var EC = protractor.ExpectedConditions;
});
it('should navigate to the Charts page', function(done) {
// var chartsMenuBtn = ?? //this button-definition is missing in your example.
var closeDlgBtn = element(by.buttonText('Cancel'));
chartsMenuBtn.click();
//browser.wait(EC.visibilityOf(closeDlgBtn),5000); //try first without, but it might be needed.
closeDlgBtn.click(); //no call to a function. Just click the button.
expect(browser.getCurrentUrl()).toBe(VM + '/#/charts');
done();
});
/*Test that 'Export' menu item works correctly */
it('should navigate to the Export page', function(done) {
// var exportMenuBtn = ?? //this button-definition is missing in your example.
exportMenuBtn.click();
//browser.waitForAngular(); //a help-line, if you need to tell Protractor, that he must wait for all promises to be resolved first.
expect(browser.getCurrentUrl()).toBe(VM + '/#/export');
done();
});
The issue here was actually caused by the second test- the line:
browser.waitForAngularEnabled(true);
should have been:
browser.waitForAngularEnabled(false);
inside the Export page test. Once I changed this, the timeout issue was resolved, and my test script passed as expected.
I have an application that is using Angular Growl v2 and in my protractor test I want to ensure that the growl event happens and that it contains the right text. So I have the following check:
var expectGrowlMessage = function(text) {
var growl = element(by.css('.growl-message'));
# Protractor stops here until growl div is removed
browser.wait(EC.presenceOf(growl), 3004, 'Waiting for growl to appear');
expect(growl.getText()).toContain(text);
browser.wait(EC.not(EC.presenceOf(growl)), 7002, 'Waiting for growl message to disappear');
};
What I can tell (via console.log) is happening is that protractor will enter the expectGrowlMessage and then stop before the first browser.wait. In the browser, I can see the growl message so the first wait should succeed. Once the growl element is removed, THAT is when protractor proceeds to the first wait check, which will obviously fail.
I have tried browser.driver.wait and browser.waitForAngular() both of which don't seem to work.
Any suggestions on what Protractor is doing and how to get it to work??
Try being specific about what order you want to do things, by use of "then":
var expectGrowlMessage = function(text, timeout = 3004) {
var growl = element(by.css('.growl-message'));
var EC = protractor.ExpectedConditions;
browser.wait(EC.presenceOf(growl), timeout).then(function(){
expect(growl.getText()).toContain(text);
});
});
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".
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 ().
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.