Protractor and unwanted waiting - angularjs

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);
});
});

Related

Testing AngularJS with Protractor- Jasmine spec appears to time out on 'expect' clause. Resetting the WebDriver Control Flow?

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.

Testing the contents of a temporary element with protractor

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.

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;

cant get my angularJS app to work with phantomJS

Using latest phantomJS (1.9.7) on Win8 on my AngularJS (v. 1.2.9) application, I'm not able to interact with my application.
I keep getting the message
InvalidElementStateError: {"errorMessage":"Element is not currently interactable and may not be manipulated".
When I call render(), I get an empty image;
If I add a timeout to my script before calling render(), I get a completely mangled view of my page:
for comparison, this is what it looks like in any other browser:
The script i've used to take the screen capture-
var page = require('webpage').create();
page.open('http://localhost/app/account#/login', function() {
setTimeout(function() {
page.render('login-phantom.png');
phantom.exit();
}, 1000);
});
has anyone else encountered anything like that?
what should I check for?
Timeout won't really help (unless you set it for a super long time) because network response speeds vary from time to time. The best option would be to have a variable that notifies when data loading (on the angular side) has completed e.g. $scope.dataStatus = 'ready'. Then you could add a data-status attribute to an element on the html and poll it from phantomjs side to see if data has loaded. You would do the polling inside page.evaluate on phantomjs.
Matsko has written a blog along those lines and some sample code
So your phantomjs code could look something like:
var page = require('webpage').create();
page.open('http://localhost/app/account#/login', function (status) {
var delay, checker = (function() {
var html = page.evaluate(function () {
var status = document.getElementById('status');
if(status.getAttribute('load-status') == 'ready') {
return document.getElementsByTagName('html')[0].outerHTML;
}
});
if(html) {
clearTimeout(delay);
page.render('login-phantom.png');
phantom.exit();
}
});
delay = setInterval(checker, 100);
});
While your html could be:
<html>
<head>...</head>
<body>
<div id="status" load-status="{{ dataStatus }}"></div>
</body>
</html>
Hope that helps

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