I am actually finishing the unit tests of my Angular app. I am currently working on E2E tests using Protractor and Jasmine. Unfortunately, I have the following problem:
I did loads of research online such as http://ramonvictor.github.io/protractor/slides/#/1 and I clearly never see the use of the "done" callback to launch tests.
This first test goes on the createUser page and makes sure that the user tab attribute is set to active. It passes ONLY if I use the done method, which I should not use.
'use strict';
var UserCreate = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
});
it('should set the user tab active', function(done) { // DONE callback
UserCreate.tabs.getAttribute('class').then(function(value) {
expect(value).toEqual('active');
done(); // calling callback
});
});
});
If I repeat the same test without using done(), the test passes even if this time, I want it to fail.
'use strict';
var UserCreate = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
});
it('should set the user tab active', function() {
UserCreate.tabs.getAttribute('class').then(function(value) {
expect(value).toEqual('activeWRONG');
});
});
});
It only fails if I use the done callback.
Here is my config file:
/* conf.js */
' use strict';
exports.config = {
rootElement: '#myApp',
directConnect: true,
seleniumAddress: 'http://localhost:4444/wd/hub',
capabilities: {
browserName: 'chrome',
shardTestFiles: true,
maxInstances: 1
},
framework: 'jasmine',
// specs: ['./*.spec.js'],
baseUrl: 'http://localhost:9001',
defaultTimeoutInterval: 0000,
jasmineNodeOpts: {
showColors: true,
},
suites: {
wip: './userCreate.spec.js',
all: './*spec.js'
},
onPrepare: function() {
browser.driver.get('http://localhost:9001/#/');
element(by.id('ld-link-login')).click();
browser.sleep(500);
element(by.model('username')).sendKeys('test');
element(by.model('password')).sendKeys('test');
element(by.id('nv-login-submit')).click();
return browser.driver.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
return /dashboard/.test(url);
});
}, 10000);
}
};
I am having asynchronous problems in deeper tests with the use of done everywhere, so I want to fix this before continuing my tests.
Thank you for your help.
Edit:
Protractor version: ./node_modules/.bin/protractor --version gives Version 3.2.2
userCreate.pageObjects :
'use strict';
module.exports = {
tabs: element(by.id('cc-tab-user'))
};
That's the expected behaviour. If you don't ask for the done() function, jasmine will consider your test as synchronous and finish without waiting for the promise to be resolved.
When you ask for it, your test became asynchronous, and will fail if done() hasn't been called before the timeout ( 5 seconds by default)
See: http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support for more info.
You have a problem in the way you are defining the Page Object leading into the problem with the promise resolution order. Follow the style guide and change it to:
var UserCreatePage = function() {
this.tabs = element(by.id('cc-tab-user'));
};
module.exports = UserCreatePage;
Usage:
'use strict';
var UserCreatePage = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
var userCreatePage;
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
userCreatePage = new UserCreatePage();
});
it('should set the user tab active', function() {
userCreatePage.tabs.getAttribute('class').then(function(value) {
expect(value).toEqual('activeWRONG');
});
});
});
This behavior can happen if your test creates a parallel TaskQueue in your test's Control Flow. For what done() is for and how to use it ( protractor, jasmine) I wrote a few examples of tests giving inconsistent results because of this.
To figure out if this is your problem, you can print out the control flow at various points in your test:
console.log(protractor.promise.controlFlow().getSchedule(false));
Your issue is, that you use the then() to pass value towards your expect-statement.
then() creates a new async-task, which is dropped as subtask. As result of this, your it first finishes with result = success and then your expect-statement gets executed.
It's documented in the Promise/ControlFlow documentation of Selenium.
As expect() already unwraps/resolves a promise, there is no need for then() in your case.
With your done it works, because you take control of the execution and prevent it to continue/finish before your then() is finished.
Try this:
'use strict';
var UserCreate = require('./page-objects/userCreate.pageObjects');
describe('on init', function () {
beforeEach(function() {
var rootUrl = browser.baseUrl + '/#/users/create';
browser.driver.get(rootUrl);
});
it('should set the user tab active', function() {
expect(UserCreate.tabs.getAttribute('class')).toEqual('activeWRONG');
});
});
It is because of the control flow which is not running properly in your code . The control flow is based on the concept of tasks and task queues. Tasks are functions that define the basic unit of work for the control flow to execute. Each task is scheduled via ControlFlow#execute(), which will return a ManagedPromise that will be resolved with the task's result.
The following should work.
let UserCreate = require('./pageobject.page.js');
describe('should navigate to url', function () {
beforeEach(function() {
url = `${browser.baseUrl}/#/users/create`;
browser.driver.get(url);
});
it('should set the user tab active', function() {
expect(UserCreate.tabs.getAttribute('class')).toEqual('activeWRONG');
});
});
Related
Trying to write a jasmine test for the below code...
refreshCacheIfNewVersionIsAvailable();
//Check if a new cache is available on page load and reload the page to refresh app cache to the newer version of files
function refreshCacheIfNewVersionIsAvailable() {
$window.addEventListener('load', function (e) {
$window.applicationCache.addEventListener('updateready', function (e) {
if ($window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Manifest changed. Now Browser downloadeds a new app cache.
alert(textService.versioning.newVersionMessage);
$window.location.reload(true);
} else {
// Manifest didn't change. Nothing new to server.
}
}, false);
}, false);
}
Your challenge
I assume the challenge you are facing is that you are unable to see how to test the code in the callback functions. You just have to realize that you have access to the callback function when you spy on addEventListener, after the spy is executed in your service under test (refreshCacheIfNewVersionIsAvailable). Since you can get a reference to it, you can execute it, just as if it was the function you were testing.
Sample solution
The following is untested, written off the top of my head, but something along the lines of what I would expect to write if I had to test that code.
describe('refreshCacheIfNewVersionIsAvailable()', function() {
beforeEach(function() {
spyOn($window, 'addEventListener');
});
it('should register a load event handler on the window', function() {
refreshCacheIfNewVersionIsAvailable();
expect($window.addEventListener.calls.count()).toBe(1);
var args = $window.addEventListener.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toBe('load');
expect(typeof args[1]).toBe('function');
expect(args[2]).toBe(false);
});
describe('load event', function() {
var loadFunction;
beforeEach(function() {
refreshCacheIfNewVersionIsAvailable();
var args = $window.addEventListener.calls.argsFor(0);
loadFunction = args[1];
spyOn($window.applicationCache, 'addEventListener');
});
it('should register an updateready event handler in the window application cache', function() {
loadFunction();
expect($window.applicationCache.addEventListener.calls.count()).toBe(1);
var args = $window.applicationCache.addEventListener.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toBe('updateReady');
expect(typeof args[1]).toBe('function');
expect(args[2]).toBe(false);
});
describe('updateready event', function() {
var updateReadyFunction;
beforeEach(function() {
loadFunction();
var args = $window.applicationCache.addEventListener.calls.argsFor(0);
updateReadyFunction = args[1];
});
it('should reload the window if the status is UPDATEREADY', function() {
// You get the point
});
});
});
});
We are using angular 1.2.x (we have to due to IE8). We are testing with Karma and Jasmine. I want to test the behavior of my modules, in case the server responds with an error. According to the angular documentation, I should just simply prepare the $httpBackend mock like this (exactly as I'd expect):
authRequestHandler = $httpBackend.when('GET', '/auth.py');
// Notice how you can change the response even after it was set
authRequestHandler.respond(401, '');
This is what I am doing in my test:
beforeEach(inject(function($injector) {
keepSessionAliveService = $injector.get('keepSessionAliveService');
$httpBackend = $injector.get('$httpBackend');
$interval = $injector.get('$interval');
}));
(...)
describe('rejected keep alive request', function() {
beforeEach(function() {
spyOn(authStorageMock, 'get');
spyOn(authStorageMock, 'set');
$httpBackend.when('POST', keepAliveUrl).respond(500, '');
keepSessionAliveService.start('sessionId');
$interval.flush(90*60*1001);
$httpBackend.flush();
});
it('should not add the session id to the storage', function() {
expect(authStorageMock.set).not.toHaveBeenCalled();
});
});
But the test fails, because the mock function is being called and I can see in the code coverage that it never runs into the error function I pass to the §promise.then as second argument.
Apparently I am doing something wrong here. Could it have to with the older angular version we're using?
Any help would be appreciated!
Something like this:
it("should receive an Ajax error", function() {
spyOn($, "ajax").andCallFake(function(e) {
e.error({});
});
var callbacks = {
displayErrorMessage : jasmine.createSpy()
};
sendRequest(callbacks, configuration);
expect(callbacks.displayErrorMessage).toHaveBeenCalled();
I have a setup of AngularJS application that uses RequireJS to download and register services on-demand. I also use Jasmine for testing. I am trying to test if a function is called in the callback of a require() call that is executed inside of a module definition. Look at the following file I have and want to test:
define(['app'], function(app) {
app.registerService('myService', function($injector) {
this.someMethod = function() {
require(['some-other-file'], function() {
var someOtherService = $injector.get('someOtherService');
console.log("first");
someOtherService.bla();
});
};
});
});
I want to test that when myService.someMethod() is called, someOtherService.bla() is also called. This is my test file:
define(['some-file', 'some-other-file'], function() {
//....
it('should test if someOtherService.bla() is called', function(done) {
inject(function($rootScope, myService, someOtherService) {
spyOn(someOtherService, 'bla');
myService.someMethod();
$rootScope.$digest();
setTimeout(function() {
console.log("second");
done();
}, 500);
expect(someOtherService.bla).toHaveBeenCalled();
});
});
});
The console output shows that the statements are executed in the right order:
"first"
"second"
But the test fails, because the spy method never gets called. Why is that and how can I fix it? I very much appreciate your help.
I am new to Protractor and AngularJS. I'm using Parse on the back end. Trying to do a very simple test:
describe('Test', function () {
beforeEach(function () {
browser.get('index.html#/example')
});
it('should have a button', function () {
expect(element(by.css('#test321')).isElementPresent()).toBe(true); //fails
}); ...
The test fails. The element is in template.html:
...
<body>
<button id="test321">stuff</button>
...
It is loaded by angular-route. This route also loads data from back end:
...
config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/example', {
templateUrl: 'template.html',
controller: 'templateCtrl',
resolve: {
data: 'dataService' //Data is loaded from Parse. This line causes the problem
}...
The problem is caused by the "data:" line above. If I take that line out, or have it return static result it works fine. Also if I move this element index.html it works as well.
This seems like a timing issue. However according to the documentation protractor (or specifically isElementPresent) waits for all resolutions before locating elements.
I'm stomped. Many thanks for any help.
Update: According to this this was solved in Protractor 1.2, but I'm on 1.4. Very strange.
Since it's a timing issue, a workaround can be to wait for the element to be present before you assert that it's present:
describe('Test', function () {
beforeEach(function () {
browser.get('index.html#/example')
});
it('should have a button', function () {
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);
}); ...
In Angular 2 I use the following in my protractor.conf.js file:
onPrepare: function() {
browser.ignoreSynchronization = false;
},
useAllAngular2AppRoots: true
isPresent() will return promises,so put your expect code inside the callback function and try to execute.
Try below code.hope it will work,
it('should have a button', function () {
$('#test321').isPresent().then(function(result){
expect(result).toBe(true);
});
});
The test currently looks like
describe('if authenticated', function() {
beforeEach(function() {
var ptor = protractor.getInstance();
ptor.waitForAngular();
browser.executeAsyncScript(function() {
var callback = arguments[arguments.length - 1];
var loginService = angular.injector(['ng', 'firebase', 'myApp.config', 'myApp.service.firebase', 'myApp.service.login']).get('loginService');
loginService.init();
loginService.login(browser.params.login.user, browser.params.login.password, callback);
callback(null, true);
});
});
it('should stay on account screen if authenticated', function() {
browser.get('/app/index.html#/account');
// expect(browser.window().hash()).toBe('/account');
expect(browser.getCurrentUrl()).toMatch(/\/account/);
});
});
There are two errors that I can't get around, either 'Error while waiting for Protractor to sync with the page: {}' or 'unknown error: angular is not defined', depending on the waitForAngular call.
How can I accomplish this so that the test passes?
Edit:
I also see 'UnknownError: javascript error: browser is not defined', if I run that test as part of the test suite (all other tests pass).
This is the only test that tries to inject the loginService in the beforeEach for an authenticated page. Getting access to angular in the afterEach seems to work as expected.
File config/protractor.conf.js
exports.config = {
// The address of a running selenium server.
seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
baseUrl: 'http://localhost:' + (process.env.HTTP_PORT || '8000'),
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
...
You can move the login to the onPrepare block of the config file.
onPrepare: function() {
// The require statement must be down here, since jasmine-reporters
// needs jasmine to be in the global and protractor does not guarantee
// this until inside the onPrepare function.
require('jasmine-reporters');
browser.ignoreSynchronization = true;
browser.driver.get("https://xyz.com");
browser.driver.findElement(by.id('loginPageUsername')).sendKeys('jdoe#email.com');
browser.driver.findElement(by.id('loginPagePassword')).sendKeys('mypass');
browser.driver.findElement(by.css('.login')).click();
jasmine.getEnv().addReporter(
new jasmine.JUnitXmlReporter('functional_results/', true, true));
}
,