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.
Related
I am trying to ensure my unit test case covers the scenario of a promise never being resolved, and I am having some issues.
A code block is worth a thousand words, so..
it('returns my resource', function() {
var myUser = {name:'Bob'}
$httpBackend.expectGET('/myresource').respond(200,myUser);
myService.getMyUser()
.then(function(data) {
expect(data).toEqual(myUser);
},fail);
});
This is all well and good, and tests that the response is as expected and also fails the test should the promise be rejected.
However, should the promise never be resolved at all, the test passes and I would like it to fail. I am caching this request in myService like so:
var cachedUser;
function getMyUser(forceUpdate) {
var deferred = $q.defer();
if (cachedUser && !forceUpdate) {
deferred.resolve(cachedUser);
} else {
$http.get('/myresource')
.then(function(data) {
cachedUser = data;
deferred.resolve(data);
},function(error) {
deferred.reject(error);
});
}
return deferred.promise;
}
Now in the above scenario, if one were to remove the line "deferred.resolve(data)" from within the $http.get, the test would still pass. This is because the callback function containing the expectation for data.toEqual(myUser) is never run. However the test should fail because removing that line breaks the purpose of this function.
I have tried within the test code to make the positive callback a spy and expect the spy toHaveBeenCalled, but that seems to run before the promise is resolved and fails even though I can see that the spy's function ran via a console log.
I have also tried within the unit test putting the promise in a variable and then expect(promise.$$state.status).toEqual(1), but again it appears that this expectation is run before the promise is resolved and so the test fails when it should pass.
Please be sure you understand the problem before answering, post a comment if I have not been clear.
The issue turned out to be that when dealing with $httpBackend, you must flush() the backend for requests to go through. I was doing this in an afterEach() block around every test, and so when trying to test the state of the promise from within the test block the promise had not actually been resolved yet.
By moving $httpBackend.flush() to within the unit test, the promise returned did indeed update its status from 0 to 1.
Here is the final unit test code:
it('returns my resource', function() {
var myUser = {name:'Bob'}
var promise;
$httpBackend.expectGET('/myresource').respond(200,myUser);
promise = myService.getMyUser()
.then(function(data) {
expect(data).toEqual(myUser);
},fail);
$httpBackend.flush();
expect(promise.$$state.status).toEqual(1);
});
I am working on trying to get some end to end tests implemented for an AngularJS site, and I am having a bit of an issue getting past a call to the IsPresent() method on the ElementFinder returned by a call to the element method.
Sadly I am governed by rules from my employer preventing me from posting ANY of our code on StackOverflow, but essentially this is what I am doing...
describe('Some feature', function () {
it('Some Scenario', function () {
browser.get(browser.baseUrl + '/#/somePage');
var ee = element(by.css('.test-comment'));
expect(ee.isPresent()).toBeTruthy();
});
});
If I comment out the call to the expect() method, then the test executes and passes without issue. If I leave it in, I get :
Failed: Timed out waiting for Protractor to synchronize with the page
after 20 seconds. Please see
https://github.com/angular/protractor/blob/master/docs/fa q.md. The
following tasks were pending:
- $timeout: function (){n=null}
This doesn't make any sense to me - the IsPresent() method returns a promise, which is resolved by the expect method, or at least that is what I would expect to happen.
Any clues?
Try the browser.isElementPresent() instead:
expect(browser.isElementPresent(ee)).toBeTruthy();
If you are curious about the differences, please see:
In protractor, browser.isElementPresent vs element.isPresent vs element.isElementPresent
Also, you may introduce the "presence of" explicit wait before making the expectation:
var EC = protractor.ExpectedConditions;
var ee = element(by.css('.test-comment'));
browser.wait(EC.presenceOf(ee), 5000);
expect(ee.isPresent()).toBeTruthy();
.isDisplayed() might be of help to you as well.
See What is the difference between the isPresent and isDisplayed methods for more info.
All my UNIT tests, not E2E tests, that do an explicit rootScope.digest() or httpBackend.flush() to flush the asynchronous callback, experience the error:
How to avoid the 'Error: Unexpected request: GET'
No more request expected
I reckon it is because httpBackend calls the ui-router template. I don't know why it wants to do so. I'm not asking for this. I only want it to call my mocked json service.
This error forces me to have the following statement in each it() block:
$httpBackend.whenGET(/\.html$/).respond('');
There must be a neater way.
Specially if the test has no use of the $httpBackend in the first place:
it('should return the list of searched users', function() {
// Always use this statement so as to avoid the error from the $http service making a request to the application main page
$httpBackend.whenGET(/\.html$/).respond('');
var users = null;
UserService.search('TOTO', 1, 10, 'asc', function(data) {
users = data.content;
});
$rootScope.$digest();
expect(users).toEqual(RESTService.allUsers.content);
});
The test passes but it looks hackish. Or noobish :-)
EDIT: Another test:
it('should return the list of users', function () {
// Always use this statement so as to avoid the error from the $http service making a request to the application main page
$httpBackend.whenGET(/\.html$/).respond('');
// Create a mock request and response
$httpBackend.expectGET(ENV.NITRO_PROJECT_REST_URL + '/users/1').respond(mockedUser);
// Exercise the service
var user = null;
RESTService.User.get({userId: 1}).$promise.then(function(data) {
user = data;
});
// Synchronise
$httpBackend.flush();
// Check for the callback data
expect(user.firstname).toEqual(mockedUser.firstname);
expect(user.lastname).toEqual(mockedUser.lastname);
});
This is obviously by design, your tests should be checking that HTTP calls are being made and that they're requesting the correct URL. Instead of checking whether requests are made to /\.html$/ why not instead check whether requests are made to the correct endpoints? Whether that be a directives partial or an API call.
If you insist on throwing away what could be a useful test, you could move your whenGET() to a beforeEach().
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.
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();
};