Protractor: problems with making scenarios - angularjs

Before that day I always made isolated small tests. But now I want run them in one scenario. And I have the strange error. Some tests can't work together.
For example.
First one:
beforeEach(function(){
browser.get('ng-components/examples/ps-grid-column-filter-range.html');
});
it('балун содержит текст', function () {
filter_field.click();
browser.actions().click(filter_field).perform();
browser.wait(function () {
return balloon_info.isPresent();
},5000).then(function () {
expect(balloon_text.getText()).toContain(balloon_contain_text);
expect(balloon_text.isDisplayed()).toBe(true);
}).thenCatch(function () {
expect(true).toBe(false);
});
console.log("ps-grid-column-filter-range_spec_1.1.с");
});
Second one:
beforeEach(function(){
browser.get('ng-components/examples/ps-grid-column-filter-range.html');
});
it('балун демонстрируется', function () {
filter_field.click();
browser.actions().click(filter_field).perform();
browser.wait(function () {
return balloon_info.isPresent();
},5000).then(function () {
expect(balloon_info.isDisplayed()).toBe(true);
}
,function (error) {
expect(false).toBe(true);
});
console.log("ps-grid-column-filter-range_spec_1.1.a");
});
When my tests isolated they working fine. But in group - they failing. What is my mistake? It is a problem with asynchronous?
Also, what is interesting is that some broken test hasn't method wait() in it.

browser.wait is non blocking so your second test probably runs while your first is still in progress. And since it is the same functionality they probably influence each others outcome.
Try putting them both in the same test and chain them with
.then()
You can make protractor blocking wait by following this example
Blocking wait

Related

How to get karma/jasmine unit tests working with a callback after a promise?

I am writing an app in AngularJS 1.5, JavaScript and Cordova.
I want to write a unit test that will check to see if some code was executed after a promise.
Here is my codepen: https://codepen.io/aubz/pen/yrxqxE
I am not sure why but the unit test keeps saying this error:
Expected spy attemptGeoClocking to have been called.
It's strange because the console log prints out so the function is actually being called.
it('if location services are on, proceed', function () {
spyOn(CordovaDiagnostics, 'getLocationServicesStatus').and.callFake(function () {
return Promise.resolve(true);
});
spyOn(Clocking, 'attemptGeoClocking').and.callFake(function () {});
Clocking.geolocationClocking();
expect(Clocking.attemptGeoClocking).toHaveBeenCalled();
});
function geolocationClocking() {
CordovaDiagnostics
.getLocationServicesStatus()
.then(attemptGeoClocking)
.catch(function () {});
}
function attemptGeoClocking() {
console.log(' here ');
}
Basically you're spying on the wrong functions. Let me rename a few things so it's more clear what you're doing:
function Clocking(CordovaDiagnostics) {
return {
geolocationClocking: geolocationClocking,
attemptGeoClockingOUTER: attemptGeoClockingINNER//private API
};
function geolocationClocking() {
CordovaDiagnostics
.getLocationServicesStatus()
.then(attemptGeoClockingINNER)
.catch(function () {});
}
function attemptGeoClockingINNER() {
console.log(' here ');
}
}
And in the test:
spyOn(Clocking, 'attemptGeoClockingOUTER').and.callFake(function () {
console.log('calling fake')
});
As you can see, your code is spying on the OUTER
but geolocationClocking is never calling the OUTER, it's using the INNER:
CordovaDiagnostics
.getLocationServicesStatus()
.then(attemptGeoClockingINNER)
You'll need to rework your code in such a way that it's using the same function internally as to the one you're stubbing in your test.
Here's a working codepen: https://codepen.io/anon/pen/xeyrqy?editors=1111
Note: I've also replaced Promise.resolve with $q.when and added $rootScope.$apply(), this is needed to resolve the promises.
Adding the changes I made here, in case the codepen would ever disappear:
I've changed the factory to a service (while not necessary, I prefer using services in this case):
myApp.service("Clocking", Clocking);
function Clocking(CordovaDiagnostics) {
this.geolocationClocking = function() {
CordovaDiagnostics
.getLocationServicesStatus()
.then(() => this.attemptGeoClocking())
.catch(function () {});
}
this.attemptGeoClocking = function() {
console.log(' here ');
}
}

How to test a method that uses async/await?

I've seen a lot of articles about how use async/await in your unit tests, but my need is the opposite.
How do you write a test for a method that uses async/await?
My spec is not able to reach any code after the 'await' line. Specifically, the spec fails in two ways.
1) HelloWorld.otherCall returns undefined instead of the return value I specify
2) HelloWorld.processResp never gets called
class HelloWorld {
async doSomething(reqObj) {
try {
const val = await this.otherCall(reqObj);
console.warn(val); // undefined
return this.processResp(val);
}
}
}
describe('HelloWorld test', function () {
let sut = new HelloWorld(); //gross simplification for demo purposes
describe('doSomething()', function () {
beforeEach(function mockInputs() {
this.resp = 'plz help - S.O.S.';
});
beforeEach(function createSpy() {
spyOn(sut, 'otherCall').and.returnValue( $q.resolve(this.resp) );
spyOn(sut, 'processResp');
});
it('should call otherCall() with proper arguments', function () {
//this test passes
});
it('should call processResp() with proper arguments', function () {
sut.doSomething({});
$rootScope.$apply(); //you need this to execute a promise chain..
expect(sut.processResp).toHaveBeenCalledWith(this.resp);
//Expected spy processResp to have been called with [ 'plz help SOS' ] but it was never called.
});
});
});
Running angular 1.5 and jasmine-core 2.6.
The .then of a promise is overloaded to handle either promises or values, and await is syntactic sugar for calling then.
So there is no reason your spy would be required to return a promise, or even a value. Returning at all, even if undefined, should trigger the await to fire, and kick off the rest of your async function.
I believe your problem is that you are not waiting for the doSomething promise to resolve before trying to test what it did. Something like this should get you more in the ballpark.
it('should call processResp() with proper arguments', async function () {
await sut.doSomething({});
// ...
});
Jasmine has Asynchronous Support. You can probably find a solution that way.
Personally, I think you should not test such methods at all.
Testing state means we're verifying that the code under test returns the right results.
Testing interactions means we're verifying that the code under test calls certain methods properly.
At most cases, testing state is better.
At your example,
async doSomething(reqObj) {
try {
const val = await this.otherCall(reqObj);
return this.processResp(val);
}
}
As long as otherCall & processResp are well covered by unit tests your good.
Do something should be covered by e2e tests.
you can read more about it at http://spectory.com/blog/Test%20Doubles%20For%20Dummies

Two identical Protractor tests in a row, second one fails

I use Protractor 4.0.14 on an Angular 1.6.3 website.
I execute two identical tests. The first one will succeed but not the second one. Any idea ?
it('Should return a toast error on inferior amount', function () {
$$('#purposeList input.amount').first().clear().sendKeys('4999');
$$('button[type="submit"]').get(0).click();
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear();
$$('button.toast-close-button').each(function (item) {
item.click();
});
expect($('button.toast-close-button').isPresent()).toBe(false);
});
it('Should return a toast error on inferior amount BIS', function () {
$$('#purposeList input.amount').first().clear().sendKeys('4999');
$$('button[type="submit"]').get(0).click();
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear();
$$('button.toast-close-button').each(function (item) {
item.click();
});
expect($('button.toast-close-button').isPresent()).toBe(false);
});
The field is provided with a wrong amount so it throws a toast-error when I submit. When I do it manually, I get the toast-error. When doing it with protractor, only the first of the identical tests will pass. On the other, it looks like the toast is never launch or closed really quickly.
I tried sleeps, ignoreSynchronisation with browser wait. I tried to replace the $timeout by $interval in the service dealing with error toasts (although other $timeout could be present). It didn't change anything.
It is actually the first expect of the second it that is failing and there is no actual help from the error, it's just an expect at false instead of true :
Message:
Expected false to be true.
Edit :
So #LostJon, by handling every promises, do you mean like this :
it('Should return a toast error on inferior than authorized purpose amount on step 1 BIS', function () {
$$('#purposeList input.amount').first().clear().then(function() {
$$('#purposeList input.amount').first().sendKeys('4999').then(function () {
$$('button[type="submit"]').get(0).click().then(function () {
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear().then(function () {
$$('button.toast-close-button').each(function (item) {
item.click();
}).then(function () {
expect($('button.toast-close-button').isPresent()).toBe(false);
});
});
});
});
});
});
I tried it, same result.
Edit :
Apparently it was an ignoreSynchronization = true that was inside the wait instead of before. I didn't figure it out before because I also set browser.manage().timeouts().implicitlyWait(2000), so without synchronization, we were still waiting for the elements but it was not accurate.
so, the below line returns a promise.
$$('button[type="submit"]').get(0).click();
Your code is running synchronously, when you should be handling the promise resolve from click(). In fact, your line that calls clear() also returns a promise, so that needs to be handled as well.
It was an ignoreSynchronization = true that was inside the wait instead of before. I didn't figure it out before because I also set browser.manage().timeouts().implicitlyWait(2000), so without synchronization, we were still waiting for the elements but it was not accurate.

How to test $interval with Jasmine in AngularJS

I have a service Home and inside it, I have selectInterval function like so:
Home.selectInterval = function () {
var interval = $interval(function () {
if (angular.element('.ui-select-toggle').length > 0) {
angular.element('.ui-select-toggle').bind('click', function () {
if (angular.element('.ui-select-choices-row').length > 0) {
angular.element('.ui-select-choices-group').mCustomScrollbar();
}
});
$interval.cancel(interval);
}
}, 50);
};
Right now I am testing it like this:
it('selectInterval() should be called.', function () {
Home.selectInterval();
$interval.flush(50);
$rootScope.$apply();
expect(Home.selectInterval).toHaveBeenCalled();
// need to test $interval as well
});
I want to test if function is called and also $interval worked fine. Right now It giving me this error.
Some of your tests did a full page reload!
I'm not sure if this test is what is causing your issue. Whenever I have seen that error it is because there is some code that is changing the url. Setting something like $window.location.href or $location.path('/new-path') will cause the phantomjs browser to do a page reload which there just isn't any support to handle right now.
If you find this is the issue, you just need to try to spy the method and it will never actually call it. That specifically works for $location.path('/new-path')

Testing window.postMessage directive

I'm having trouble testing my directive which enables cross-document messaging by registering a message handler:
.directive('messaging', function ($window, MyService) {
return {
link: function () {
angular.element($window).on('message', MyService.handleMessage);
}
};
})
All I want to unit test is that when this directive is compiled, and window.postMessage('message','*') is called, my message handler should be called:
http://jsfiddle.net/mhu23/L27wqn14/ (including jasmine test)
I'd appreciate your help!
Michael
Your are using original window API, you are not mocking it, so the method postMessage will keep it's asynchronous behavior. Knowing that, tests should be written in an asynchronous way. In JSFiddle you have Jasmine 1.3, so test should look kinda like this:
it('should ....', function () {
var done = false;
spyOn(MyService,'handleMessage').andCallFake(function () {
// set the flag, let Jasmine know when callback was called
done = true;
});
runs(function () {
// trigger async call
$window.postMessage('message','*');
});
waitsFor(function () {
// Jasmine waits until done becomes true i.e. when callback be called
return done;
});
runs(function () {
expect(MyService.handleMessage).toHaveBeenCalled();
});
});
Check the docs about testing async with Jasmine 1.3. And here is a working JSFiddle.
It would be a bit easier in Jasmine 2.x:
it('should ....', function (done) {
spyOn(MyService,'handleMessage').and.callFake(function () {
expect(MyService.handleMessage).toHaveBeenCalled();
done();
});
$window.postMessage('message','*');
});
Also, I have to mention, that you have to change how you add a listener from this
angular.element($window).on('message', MyService.handleMessage);
to that
angular.element($window).on('message', function (e) {
MyService.handleMessage(e);
});
because .on registers a function itself, it won't be used as a method attached to the MyService, so you won't be able to spy on it.

Resources