Make Protractor wait for an alert box? - angularjs

I am writing E2E tests for a log in page. If the log in fails, an alert box pops up informing the user of the invalid user name or password. The log in itself is a call to a web service and the controller is handling the callback. When I use browser.switchTo().alert(); in Protractor, it happens before the callback finishes. Is there a way I can make Protractor wait for that alert box to pop up?

I solved similar task with the following statement in my Protractor test:
browser.wait(function() {
return browser.switchTo().alert().then(
function() { return true; },
function() { return false; }
);
});
In general, this code constantly tries to switch to an alert until success (when the alert is opened at last). Some more details:
"browser.wait" waits until called function returns true.
"browser.switchTo().alert()" tries to switch to an opened alert box and either has success, or fails.
Since "browser.switchTo().alert()" returns a promise, then the promise either resolved and the first function runs (returns true), or rejected and the second function runs (returns false).

You may use the ExpectedConditions library now, which makes the code more readable:
function openAndCloseAlert () {
var alertButton = $('#alertbutton');
alertButton.click();
// wait up to 1000ms for alert to pop up.
browser.wait(protractor.ExpectedConditions.alertIsPresent(), 1000);
var alertDialog = browser.switchTo().alert();
expect(alertDialog.getText()).toEqual('Hello');
alertDialog.accept();
};

Related

Protractor expectation that element is eventually present

Is there a way to have an expectation that an element is eventually on the page? e.g. a way for
browser.wait(protractor.ExpectedConditions.presenceOf(element(by.partialLinkText('Continue'))), 1000, 'Unable to find continue link');
to fail with an expectation error instead of a timeout? Essentially a way to have a isEventuallyPresent() instead of isPresent() in the line below
expect(element(by.partialLinkText('Continue')).isPresent()).toBe(true);
For reference, I'm using browser.ignoreSynchronization = true even though it's an Angular app, and using Jasmine (at least for now).
Using the facts that
browser.wait returns a promise that is resolved once the condition function returns truthy, or rejected if it times out.
If expect is passed a promise, it only runs the expectation when the promise is resolved
You can make a function that wraps a call to browser.wait
function eventual(expectedCondition) {
return browser.wait(expectedCondition, 2000).then(function() {
return true;
}, function() {
return false;
});
}
and then create an expectation
expect(eventual(protractor.ExpectedConditions.presenceOf(element(by.partialLinkText('Continue'))))).toBe(true);
Or, to make it work on any browser instance, you can monkey-patch the Protractor prototype
protractor.Protractor.prototype.eventual = function(expectedCondition) {
return this.wait(expectedCondition, 2000).then(function() {
return true;
}, function() {
return false;
});
}
and can be used as
expect(browser.eventual(protractor.ExpectedConditions.presenceOf(element(by.partialLinkText('Continue'))))).toBe(true);
To avoid timeouts, you must make sure that the timeout passed to browser.wait is less than the the Jasmine async test timeout, which is specified as jasmineNodeOpts: {defaultTimeoutInterval: timeout_in_millis} in the protractor configuration file
The presenceOf expected condition used with a browser.wait() would allow to have a single line in the test:
var EC = protractor.ExpectedConditions;
browser.wait(EC.presenceOf(element(by.partialLinkText('Continue'))), 1000, 'Unable to find continue link');
where EC is protractor.ExpectedConditions - I usually make it global in onPrepare() through the global namespace.
Note that in case of a failure, you would have a Timeout Error, but with the Unable to find continue link error description.
As a side note, it is important to provide a meaningful custom error description, as you've already did. If you want to enforce it, there is a eslint-plugin-protractor plugin to ESLint static code analysis tool that would warn you if there is a browser.wait() used without an explicit error description text.

What are the methods we can use to wait for an angular site to be loaded in order to test it with protractor?

What are the methods we can use to wait for an Angular site to be loaded in order to test it with protractor in order to avoid this error caused by jasmine : A Jasmine spec timed out. Resetting the WebDriver Control Flow ?
I'm able to make the login and go to home page that test is passed, but from the second test i have problems of jasmine.
I have configured this problem by adding this function into my config file :
onPrepare: function() {
return browser.getProcessedConfig().then(function(config) {
var browserName = config.capabilities.browserName;
browser.manage().timeouts().setScriptTimeout(60000);
});
});
You can use the browser object of Protractor to wait for angular.
As soon as you load your page add the following :
browser.waitForAngular();
This error means that your test took too much time and exceeded the default Jasmine spec timeout interval which is 30 seconds by default (It looks like you've configured the timeout to be 60 seconds). It can be configured in the jasmineNodeOpts object in your Protractor config:
jasmineNodeOpts: {defaultTimeoutInterval: timeout_in_millis},
The solution is usually use-case specific and it usually indicates there is an error in the test code. In order to fully understand what is going, we would need to see the code itself.
In your particular case, for starters, you should try moving the "ignore synchronization" and the browser.get() part into the beforeEach. Also, since you are turning the sync off, you need to wait for the element to be present on the page before interacting with it:
describe("my app", function () {
beforeEach(function () {
browser.ignoreSynchronization = true;
browser.get("...");
});
it("should make the login test", function () {
// ...
var EC = protractor.ExpectedConditions;
var username = element(by.model("credentials.username"));
browser.wait(EC.presenceOf(username), 10000);
username.sendKeys("RET02");
// ...
});
});
And, I am not sure if you really need to turn the synchronization off since this is an AngularJS page you are working with.
Can you wait for a url?
Let's assume that when you click on the login button your page is redirected to another url. So you can wait for the expected url. Example:
browser.driver.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
// Dashboard is the loaded url after login (in this example)
return /Dashboard/.test(url);
});
}, 60000);
This code waits for the page browser.baseUrl/Dashboard to be loaded, for 60 seconds

$timeout.flush during protractor test [duplicate]

I'm testing my angular application with Protractor.
Once the user is logged in to my app, I set a $timeout to do some job in one hour (so if the user was logged-in in 13:00, the $timeout will run at 14:00).
I keep getting these failures:
"Timed out waiting for Protractor to synchronize with the page after 20 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md. The following tasks were pending: - $timeout: function onTimeoutDone(){....."
I've read this timeouts page: https://github.com/angular/protractor/blob/master/docs/timeouts.md
so I understand Protractor waits till the page is fully loaded which means he's waiting for the $timeout to complete...
How can I make Protractor NOT wait for that $timeout?
I don't want to use:
browser.ignoreSynchronization = true;
Because then my tests will fail for other reasons (other angular components still needs the time to load...)
The solution will be to flush active timeouts (as #MBielski mentioned it in comments), but original flush method itself is available only in anuglar-mocks. To use angular-mocks directly you will have to include it on the page as a <script> tag and also you'll have to deal with all overrides it creates, it produces a lot of side effects. I was able to re-create flush without using angular-mocks by listening to any timeouts that get created and then reseting them on demand.
For example, if you have a timeout in your Angular app:
$timeout(function () {
alert('Hello World');
}, 10000); // say hello in 10 sec
The test will look like:
it('should reset timeouts', function () {
browser.addMockModule('e2eFlushTimeouts', function () {
angular
.module('e2eFlushTimeouts', [])
.run(function ($browser) {
// store all created timeouts
var timeouts = [];
// listen to all timeouts created by overriding
// a method responsible for that
var originalDefer = $browser.defer;
$browser.defer = function (fn, delay) {
// originally it returns timeout id
var timeoutId = originalDefer.apply($browser, arguments);
// store it to be able to remove it later
timeouts.push({ id: timeoutId, delay: delay });
// preserve original behavior
return timeoutId;
};
// compatibility with original method
$browser.defer.cancel = originalDefer.cancel;
// create a global method to flush timeouts greater than #delay
// call it using browser.executeScript()
window.e2eFlushTimeouts = function (delay) {
timeouts.forEach(function (timeout) {
if (timeout.delay >= delay) {
$browser.defer.cancel(timeout.id);
}
});
};
});
});
browser.get('example.com');
// do test stuff
browser.executeScript(function () {
// flush everything that has a delay more that 6 sec
window.e2eFlushTimeouts(6000);
});
expect(something).toBe(true);
});
It's kinda experimental, I am not sure if it will work for your case. This code can also be simplified by moving browser.addMockModule to a separate node.js module. Also there may be problems if you'd want to remove short timeouts (like 100ms), it can cancel currently running Angular processes, therefore the test will break.
The solution is to use interceptors and modify the http request which is getting timeout and set custom timeout to some milliseconds(your desired) to that http request so that after sometime long running http request will get closed(because of new timeout) and then you can test immediate response.
This is working well and promising.

Fixing waits in Protractor 2.0 with ExpectedCondition

Due to Protractor 2.0's breaking changes, I have to refactor my at() function. I use this on non-angular pages to both wait for the page to load, and return true, so it's expectable (if that's a word).
Because 2.0 no longer returns a promise from a locator, it's breaking my wait (wait fires immediately). Thus, I've turned to the new ExpectedConditions... which works fine. My question is, am I using this correctly, or is there a better solution?
Page object locator:
this.pageLoaded = $('div#splash[style="display: none;"]');
Works in Protractor 1.8.0 (breaks in 2.0.0):
this.at = function() {
var that = this;
return browser.wait(function() {
return browser.isElementPresent(that.pageLoaded);
});
};
My working solution for Protractor 2.0.0:
this.at = function() {
var that = this;
return browser.wait(function() {
return protractor.ExpectedConditions.presenceOf(that.pageLoaded);
});
};
And for example, I would call this like so:
expect(mainPage.at()).toBeTruthy();
It looks like there was another breaking change in the Protractor 2.0 changelog, referring to a problem in WebDriver 2.45 that makes the wait parameter required:
Due to changes in WebDriverJS, wait without a timeout will now default to waiting for 0 ms instead of waiting indefinitely.
Before:
browser.wait(fn); // would wait indefinitely
After:
browser.wait(fn, 8000) // to fix, add an explicit timeout
This will be reverted in the next version of WebDriverJS.
In our Protractor tests, we always refer to a common helper class var helper = require("..\helpers.scenario.js"). To fix this problem, we just wrapped browser.wait in the helper class and passed in a timeout.
wait: function(f) {
return browser.wait(f, 30000);
},
We then used find-replace to change from browser.wait to helper.wait.

AngularJs ui-router. Protractor test failing

I'm creating a SPA using AngularJs and the ui-router from Angular-UI. Now I'm trying to create the authentication logic.
$rootScope.$on("$stateChangeStart", function (event, toState) {
if(toState.authenticate && !MainService.isAuthenticated()) {
if($cookieStore.get('authToken')) {
MainService.loginWithToken($cookieStore.get('authToken'))
.then(function() {
$state.go(toState.name);
event.preventDefault();
});
}
$rootScope.requestPath = toState.name;
$state.go('public.login');
event.preventDefault();
}
if(toState.url == '/login' && MainService.isAuthenticated()) {
$state.go('private.main');
event.preventDefault();
}
});
On state change, this checks if state requires authentication and transfer to login state if necessary. Also if user is logged in it prevents from reaching the login state. Authentication is done by token stored in cookie.
This is my protractor test scenario:
describe('Routes', function() {
it('Should go to the selected path if user logged in', function() {
browser.get('/');
expect(browser.getLocationAbsUrl()).toMatch("/login");
browser.manage().addCookie("authToken", "aaa");
browser.manage().getCookie("authToken").then(function(cookie) {
expect(cookie.name).toBe('authToken');
expect(cookie.value).toBe('aaa');
});
browser.get('/');
expect(browser.getLocationAbsUrl()).toMatch("/main");
browser.get('/#/main');
expect(browser.getLocationAbsUrl()).toMatch("/main");
/* This part fails, because, when the user is logged in,
he should be transfered to main state, if he is trying to reach the
login page. In this failing case, the user is able to reach the
/login even if he is logged in. */
browser.get('/#/login');
expect(browser.getLocationAbsUrl()).toMatch("/main");
browser.manage().deleteCookie("authToken");
browser.get('/#/login');
expect(browser.getLocationAbsUrl()).toMatch("/login");
browser.get('/#/main');
expect(browser.getLocationAbsUrl()).toMatch("/login");
});
});
When I try to simulate the test behaviour myself, everything is ok, but when I run protractor:
Message:
Expected 'http://localhost/#/login' to match '/main'.
Stacktrace:
Error: Failed expectation
I bumped into another question which resolved this issue.
Basically, you wait for an element in the new page to appear instead of relying on protractor to wait for state/page finish loading. At the time of writing this answer, protractor is still unreliable on waiting page fully loaded for ui-router. Protractor waits for $timeout and $http to be done.
official doc
so if you are using websocket, it might not be covered (at least according my observation).
The api you need to use is browser.wait
browser.wait(function() {
return $('#test321').isPresent(); // keeps waiting until this statement resolves to true
},
timeToWaitInMilliseconds,
'message to log to console if element is not present after that time'
);
expect($('#test321').isPresent()).toBe(true);
You can find details in the following link
Protractor, AngularJS, Parse -- Protractor does not wait for Angular to resolve
You might need to wait for the page to get loaded:
browser.get('/#/main');
var ptor = protractor.getInstance();
ptor.waitForAngular();
expect(browser.getLocationAbsUrl()).toMatch("/main");
Note that in Angular version 1.3 browser.getLocationAbsUrl() returns only the relative path. See the issue in https://github.com/angular/protractor/issues/1436:
Using angular 1.3 in the app under test and protractor 1.3.1 browser.getLocationAbsUrl() returns a relative url instead of the absUrl due to using angular.getTestability().getLocation() instead of $location.absUrl(). It's probably as easy as adding a getLocationAbs() to $$testability, but that goes into architectural questions I don't have context for.

Resources