AngularJS: Accessing $scope objects in e2e tests - angularjs

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);
});
};
});

Related

Can't find id of Angular ui-grid element with Selenium/Protractor

I have some Protractor tests that are running on an Angular application that uses ui-grid. I have some working smoke tests for the application, but I am just starting to implement tests using https://github.com/angular-ui/ui-grid/blob/master/test/e2e/gridTestUtils.spec.js to test the ui-grid components. The problem I'm having is that I need the actual id of the grid element in order to use the getGrid function.
I'm able to successfully locate the element using element(by.css("[id$='-grid-container']")), but for some reasons my attempts to get the full id out of the element have failed. Here is what I am trying:
var grid = element(by.css("[id$='-grid-container']"));
grid.getAttribute('id').then(function(result) {
console.log(result);
var myGrid = gridTestUtils.getGrid(result.toString());
gridTestUtils.expectCellValueMatch(myGrid, 0, 0, 'Cox');
});
The console.log(result); is not logging anything. It doesn't SEEM necessarily related to ui-grid, it's just Selenium isn't finding the id for some reason. As far as I can tell I'm using getAttribute correctly; it works with this syntax in other tests, but maybe I'm missing something. Any ideas why this isn't working?
Edit because my comment is unreadable:
Thanks for the suggestions. However, I'm still just as confused because
var grid = element(by.css("[id$='-grid-container']"));
console.log(grid.toString());
grid.getAttribute('id').then(function(result) {
console.log('======'+result);
var myGrid = gridTestUtils.getGrid(result.toString());
gridTestUtils.expectCellValueMatch(myGrid, 0, 0, 'Cox');
});
gives a console output of
[object Object]
======
so it seems like the element is being found, which I had already checked, and the console.log inside the promise is being executed.
It's like it can't find the 'id', which according to the API documentation means there is no id on the element. But that is definitely not true.
Not sure on the semantics, but you could try this, just to make sure that you are getting the first element, if multiple:
element.all(by.css('[id$="-grid-container"]')).first().getAttribute('id').then(function(result) {
console.log(result);
var myGrid = gridTestUtils.getGrid(result.toString());
gridTestUtils.expectCellValueMatch(myGrid, 0, 0, 'Cox');
});
Your code looks correct.
However, if your console.log(result) doesn't log anything, this means you either didn't find the element or the moment you execute the getAttribute() the element is no longer present.
See in the API description, that getAttribute() always returns a value, if the element is present.
Maybe try to extend console.log('======='+result); to figure out, if that line of code gets executed (I'm pretty sure it's not executed). Also try console.log(grid.toString());, which should output [object Object], if the element is really found.
As for the ElementFinder, I'm used to use the ' and " just the other way around, so element(by.css('[id$="-grid-container"]')); or shorter $('[id$="-grid-container"]').
Let me know, if this helped and you could further determine the cause.
Round 2
Let's exclude a few things
change getAttribute('id') to getAttribute('outerHTML') to see,
if there is anything logged.
change (result) to (resultattr) to exclude, that result is else used by a plugin, who put result as a global variable.
change grid.getAttribute() to be element(by.css("[id$='-grid-container']")).getAttribute
What are the results of those actions?

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

protractor e2e testing Returning Promises from utility steps

I'm fairly new to protractor and promises in general. I've had a look around, and although there's many posts out there about returning promises, or the results from queued actions none of them make much sense to me, so i'm after a fairly easily described answer to what I hope is a simple question!
I am trying to write some protractor tests for my angularjs website.
I am using bootstrap and angular mainly, no other third party libraries, other than the occasional angular add-on such as toaster, and bootstrap modal.
I have several 'arrangement steps' before I get to the assertion part of my test. Let's say :
a) Person logs in
b) Person accesses options form ( which may or may
not be displayed already on the screen depending on some external
factors, but if it's not present they can open it with a button press
).
c) Person performs an action on the options form.
d) assert that the text box on the form now contains the correct value.
I can get my test to pass quite easily when the form is already on the screen, but the bit that's getting me stuck is step b) where I need to check first if the form is active and click a button if it's not pefore proceeding to step c.
I've tried to return the promise from isDisplayed like so :
//
// Is the user settings form active at the mo?
//
function _isUserSettingsFormActive()
{
var result = element(by.id(logoutFormID)).isDisplayed;
return result;
}
But if I call .then on _isUserSettingsFormActive() I get the following error :
[31mTypeError: undefined is not a function[0m
However if I output the results of _isUserSettinsFormActive() I see the below, so I know it's returning something :
function () {
return self.elementArrayFinder_[fnName].
apply(self.elementArrayFinder_, arguments).toElementFinder_();
}
All I want to do is check if an item exists and act on that before performing my assert.
It needs to be in a function, as this code will be used in many places throughout my test suit. It's not the 'expect' itself, more a step that may or may not need an action to set up the browser for my test to pass.
isDisplayed is a function, so it should be called like that:
function _isUserSettingsFormActive()
{
var result = element(by.id(logoutFormID)).isDisplayed();
return result;
}
Protractor does not work like the Java or C# bindings of Selenium would (it's funner but more work to achieve what would be simple actions in Java or C#). It would be safer to return a count promise if the options form is also not in the DOM but if it is in the DOM and just hidden you could use isDisplayed(). I wrote two examples below for both situation including clicking the button depending on the condition.
Option 1 (Not present in DOM and not displayed):
function _isUserSettingsFormActive() {
//$$('#logoutFormId') is the equivalent of element.all(by.id('logoutFormId'))
$$('#logoutFormId').count().then(function(num){
if(num < 1) {
element(by.id('openLogoutButton').click();
}
});
};
OR
Option 2 (Present in DOM but not displayed):
function _isUserSettingsFormActive() {
//$('#logoutFormId') is the equivalent of element(by.id('logoutFormId'))
$('#logoutFormId').isDisplayed().then(function(visible){
if(!visible) {
element(by.id('openLogoutButton').click();
}
});
};

Cannot access scope variable set using Mongoose query within controller

In my MEANJS app. I am setting the value of a scope variable to a query result, from my controller function
$scope.myVar = User.query();
console.dir($scope.myVar); //Returns all the documents from the DB correctly
console.log('User's name is : ' + $scope.myVar[0].name); //This comes as undefined
Somehow, in the very next line when I am trying to open the name field within the same controller function, it comes as undefined. Also, the entire result is being read absolutely correctly in my view file. So when I call
{{myVar.name}}
within my view file it outputs the name correctly. I cannot understand this behavior at all. This is my first time working with Angularjs, and I could have missed something basic, but I appreciate any help at this point.
Edit - Also the length of $scope.myVar is always 0 within the controller
Basically User.query() returns a promise object, so you need to update you $scope object inside promise success.
Code
User.query().$promise.then(function(res){
//you will get response here in data obj
$scope.myVar = res;
console.dir($scope.myVar);//Returns all the documents from the DB correctly
console.log('User's name is : ' + $scope.myVar[0].name); //This comes as undefined
});
EDIT: Found the answer. The callback functions in meanjs are in this format:
var myObj= User.query(function(response) {
console.log('Inside success response');
//Can access myObj values here easily
}, function(errResponse) {
console.log('Inside error response ' + errResponse);
});
I wasn't able to figure out how to access my $scope.myVar within the controller, but I completed a workaround by creating my own factory method to retrieve data the way I wanted it sorted. The problem of not being able to access these query results is really posing a problem in other parts as well. So if anyone has an answer, please do let me know.
For now posting the link I used to figure out how the MEAN.JS factory method needs to be plugged in. Hope this helps someone.
https://groups.google.com/forum/#!searchin/meanjs/query()/meanjs/4R7rIolH9bs/P1R4YlKgowUJ

Error appending Pagedown in AngularJS Directive

Based on this question, though I felt this warranted its own question: Google pagedown AngularJS directive
Following the example from this question, it seems to work, however I am running into issues when I try to append a directive to the page.
Here is the code I have in the linking function:
scope.editor_id = null;
if(attrs.id == null) {
scope.editor_id = nextID++;
} else {
scope.editor_id = attrs.id;
}
//append editor HTML
//has to be done here so that it is available to the editor when it is run below
var editor_html = $compile(
'<div id="wmd-button-bar-' + scope.editor_id + '"></div>' +
'<textarea class="wmd-input" id="wmd-input-' + scope.editor_id + '" ng-model="content"></textarea>'
)(scope);
element.find('.wmd-panel').append(editor_html);
var editor = new Markdown.Editor(editor_converter, "-" + scope.editor_id);
editor.run();
However, when I append one of these to the document, I get the following error:
TypeError: Cannot read property 'attachEvent' of null
This error tends to crop up when the wmd-input is not present in the HTML. however, I am adding it with the $compile function, and it works on page load, but not when it is appended. What am I doing wrong here?
UPDATE
I was able to reproduce your problem: http://plnkr.co/edit/R2eDKBreHmYBjPtU0JxD?p=preview
Why typeError: Cannot read property 'attachEvent' of null?
I was wrong with my previous assumption ( the composite linking function do returns the element)
The problem is with the way you use angular.element#find.
angular.element#find only search for child elements not on the whole document.
the DOM element with a .wmd-panel class is not a child of the current element.
This should work fine:
angular.element('.wmd-panel').append(editor_html);
Try doing compile like this:
$compile('template stuff goes here')(scope, function(cloned, scope){
element.append(cloned);
});
You may also have to define your editor inside the callback function because I'm not sure if it's asynchronous or not. You may also want to re-consider having your directive compile and append to itself like this. Why not just add more instances of the entire directive using something like ng-repeat?
Also, if you have multiple instances inside this one directive, you will lose reference to editor. Not sure what's going on outside this code so I can't really tell.

Resources