beforeEach (to prevent test pollution) not working as expected - angularjs

I've been doing the AngularJS tutorial and I came into a weird issue with beforeEach for one of the end to end tests. For context, I am currently in the Experiments section at step 3. The test code is here:
'use strict';
/* http://docs.angularjs.org/guide/dev_guide.e2e-testing */
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
});
it('should filter the phone list as user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
});
it('should display current filter value within an element with id "status"', function() {
var statusElement = element(by.id('status'));
expect(statusElement.getText()).toMatch(/Current filter:\s*$/);
element(by.model('query')).sendKeys('nexus');
expect(statusElement.getText()).toMatch(/Current filter: nexus\s*$/);
});
});
The beforeEach block should reload the page before each spec execution, but it seems it is not, since when I run this using protractor, the last text inserted into the query element in the first spec ('motorola') still exists when the second spec is executed, causing that second spec to fail.
Now, when I move the beforeEach to the outer describe block, all the specs pass successfully. Any ideas?

glepretre is correct. As structured in the above example your tests look like this
describe
describe
before
it
it
Because of this nesting the before runs only once, before the first it. Looking at what each it block is testing, I think they are both meant to be testing the Phone List View, in which case the correct nesting is as follows:
describe
describe
before
it
it
However, if the index page is not specific to the Phone List View but contains the whole app, then it should probably be loaded in the top-level describe block, making this the most correct approach:
describe
before
describe
it
it

Related

Jasmine create specs that depend on a variable list size

I have a beforeAll function, which goes to my webpage and populates a list based on elements from the page. The amount of these elements vary depending on what the users do in the UI.
let variableList = []
beforeAll(() => {populate a list with elements found in the page}
Then, I want to do the same test on all elements of my list, I don't want to write a spec for each element (as these elements may change, and its a very long list).
I don't want to use the list within one unique spec, as it would take me too long to run that spec. I rather have one spec for each component of my list.
variableList.map(element => {
it('should run a spec for each list element', function(()=>{
//Using element to perform the tests
})
})
When running this code, the beforeAll function will not populate the variableList. The list is used without being populated.
Any ideas on how I could have a better approach to this?
Thanks in advance!
In Jasmine, code that occurs inside a beforeEach(), afterEach(), beforeAll(), afterAll(), or it() function is only executed when the tests actually run. All other code (including in describe() functions) happens when the test runner starts up. I suggest doing all the work inside a single async test:
it("tests all the elements", done => {
getElementFromOtherPage().then( elements => {
element.forEach( runTestOnElement );
done();
});
});
Not very elegant but should work correctly.

finding it difficult to test AngularJS UI Grid with protractor

I am pretty much new to protractor and testing AngularJS is my first time. I have to test a table which is populated with Angular grid. These values don't have any unique id's to identify them. After doing some research, i found we need to use gridTestUtils.spec.js. I have tried this in my spec file as below:
var gridTestUtils = require('./gridTestUtils.spec.js');
describe('get row count of grid', function(){
it('get the row count of grid1', function(){
gridTestUtils.expectRowCount('grid1',8);
});
});
This is the only spec file and there is no pageobject file. But this code doesn't seem to run. it says pageObject is not defined. Can you please let me know how to proceed from here and i am not an expert. please answer in simple and detailed manner as it will be easy for me to understand.
Thank you for your help.
Thanks,
Mallesh
gridTestUtils definitely feels like it may be overkill. Have you thought about testing the page with lower level calls to protractor directly? It's not a lot of code.
describe('get row count of grid', function () {
before(function () {
browser.get('http://localhost:9000/orwhatever');
});
it('get the row count of grid1', function(){
expect(element.all(by.repeater('row in rows')).count()).to.eventually.equal(8);
});
});
Replace the url with the url of your app, and replace the repeater string with the same string that is in ng-repeat in your DOM. If you're not using a repeater, just use the $$() function to grab all elements by css-selector instead.

Using element.find() by class in Angular Unit testing

When running this test, I keep getting the error Expected undefined to be true.
it('should have the right classes', function() {
// this doesnt work
expect(element.find('.exampleClass').hasClass('ng-hide')).toBe(true);
// but this works
expect(element.find('span').hasClass('ng-hide')).toBe(true);
});
How can I use jqlite to find an element by id and class?
That is because angular-jqlite (very lightweight library when compared to jquery itself) find is limited to look up by tag names only. You can use element.querySelector(All) and wrap it in angular.element. i.e
var elm = element[0];
expect(angular.element(elm.querySelector('.exampleClass')).hasClass('ng-hide')).toBe(true);
//Or even
expect(elm.querySelector('.exampleClass.ng-hide')).toBeDefined();
See documentation
find() - Limited to lookups by tag name

End-to-End testing with Jasmine

I am trying to perform some end-to-end tests of an application written with AngularJS. Currently, I have the following tests setup in e2e.tests.js:
describe('MyApp', function() {
beforeEach(function() {
browser().navigateTo('../index.html');
});
it('should be true', function() {
expect(true).toBe(true);
});
});
Oddly, this basic test fails. The test itself runs. However the results are:
203ms browser navigate to '../index.html'
5ms expect undefined toBe true
http://localhost:81/tests/e2e.tests.js:10:9
expected true but was undefined
Considering "true" is hard-coded, I would expect it to pass this test. I have no idea how true can be undefined. What am I missing?
The thing about Angular's E2E test framework is that looks like Jasmine, but it is not Jasmine. The E2E test code is actually all asynchronous, but it is written in a clever way so that the calls look normal. Most of the calls create asynchronous tasks and Future objects that are tested later. The Future object is kind of like a promise, but it's a little different. It has a value property that it sets when it's ready, and then it calls a done function to move to the next step. In E2E tests, the expect function takes Future objects, not values. You're seeing undefined because expect is testing against future.value, which in this case is true.value, which is undefined.
Try using one of the available selectors that return futures and then test the result. Something like this:
expect(element("html").text()).toMatch("Our App");
The Future objects are not well documented, but you should be able to create a Future manually like this:
var trueFuture = angular.scenario.Future(
"a true value", // name that is displayed in the results
function(callback) { // "behavior" function
callback(null, true); // supposed to call callback with (error, result)
});
expect(trueFuture).toEqual(true);
If you look in the ng-scenario source code, you can see the place where the matcher tests future.value in the angular.scenario.matcher function.
I too have faced the similar problem and here's what I found.
You must be using ng-scenario as your framework with jasmine in config file.
The fact is that expect function in ng-scenario doesn't take any var value or Boolean value. It only takes functions like
expect(browser().location().path()).toContain('some/string')
or some other ng-scenario function like
expect(element('some element').html()).toContain('some String');
Any variable value or Boolean in expect function is undefined.
If you want to use Boolean(true/false) or you want your test to be passed then you have to remove 'ng-scenario' from your framework section of config file.
Try It with only jasmine!

AngularJS: Accessing $scope objects in e2e tests

I am building a Math tutoring application and would like to test my UI using angular's e2e testing suite.
Currently I am working on a Fraction page that generates a random fraction, displays a series of shaded and unshaded boxes and asks the user to input the fraction formed by the shading.
Using an e2e test, I would like to test how the UI responds to both correct and incorrect input; however, since the fraction is randomized on page load, I do not know what 'correct' input is from inside the test.
The easiest way for me to get the correct answers to input would be to gain access to the Fraction object, located at $scope.problemObject for the controller, and call its API functions .getNumerator() and .getDenominator(). However, I have not found a way to get to this object from within my tests.
Relevant lines from my controller are:
$scope.problemObject = Fraction.random();
// This produces an object with two relevant
// functions (getNumerator() & getDenominator())
What I Have Tried
binding()
Initially I thought binding() would do what I needed, however all calls to binding('problemObject') or binding('problemObject.getNumerator()' and the like issue an error saying that the binding cannot be found. I suspect that this is because $scope.problemObject and the return value of $scope.problemObject.getNumerator() are not directly bound to the UI.
angular.element().scope()
Executing angular.element('#problem').scope().problemObject from the console on the page that I am testing works perfectly; however, trying the same line from within my test issues the following error: 'selectors not implemented'.
I have also tried a few variations:
element('#problem').scope().problemObject: Error: 'Object # has no method 'scope''
angular.element(element('#problem')).scope().problemObject: Error: 'Cannot read property 'problemObject' of undefined'
I guess 'element' in e2e test and 'angular.element' are different objects.
You may want to try reading the value from the view.
if it is input field.
var value = element('#problem').val();
Otherwise, something like:
var value = element('#problem').text();
(Looking into scope object from e2e is kind of cheating in my opinion.)
Edit
I totally misunderstood the question and construct of the web page.
Sorry for the confusion.
What it has to validate is the input fields against numbers of the shaded and non-shaded boxes ('td' elems in this example).
var total = element('td').count()
, fraction = element('td.shaded').count();
Idea is same, it is trying to get the numbers from the view, not from $scope.
Turns out the problem lies in the scope being stored in jQuery's data. Since jQuery stores the data in a hashtable as $.cache global, once we are outside of the frame that the test webpage is running in, we no longer have access to them. The way I solved it is by accessing the jQuery inside the iframe's window (conveniently given in the $window parameter).
Below is what I come up with to access the scope. You can do scope('#myElement', 'foo.bar') to query $scope.foo.bar.
angular.scenario.dsl('scope', function() {
return function(selector, entry) {
return this.addFutureAction('find scope variable for \'' + selector + '\'',
function($window, $document, done) {
var $ = $window.$; // jQuery inside the iframe
var elem = $(selector);
if (!elem.length) {
return done('No element matched \'' + selector + '\'.');
}
var entries = entry.split('.');
var prop = elem.scope();
for (var i in entries) {
prop = prop[entries[i]];
}
done(null, prop);
});
};
});

Resources