Access $scope object with protractor - angularjs

I got an object like:
$scope.project = {name: 'whatever', description: 'blabla', another: 'another'};
To debug this, I enter in repl mode and try to see what "project" has.
When I define project variable as below, and call it, it returns my object, but when I try to access its keys (project.name), I get undefined. If I do Object.keys(project) I am getting the page object methods like click, getAttribute, etc.
Any ideas on how can I have access to the original object keys?
View side:
<h1 id="foo">{{project.name}}</h1>
Test side:
var project = element(by.id('foo')).evaluate('project');

evaluate uses executeScript behind the scenes. It returns an ElementFinder which resolves to the object you are looking for:
var project;
element(by.id('foo')).evaluate('project').then(function(value) {
project = value;
});
The documentation says:
which resolves to the evaluated expression for each underlying
element. The result will be resolved as in
webdriver.WebDriver.executeScript. In summary - primitives will be
resolved as is, functions will be converted to string, and elements
will be returned as a WebElement.
Also, check out Accessing Angular inside Protractor Test
Edit: syntax error

Related

Angular2 Object Instantiation for Asynchronous Data

I was working on an angular2 project that gets an object asynchronously from the api using the new Observable framework.
this._accountsService.getAccountDetails(this.id)
.subscribe(
AccountDetails => this.accountDetails = AccountDetails,
err => this.errorMessage = <any>err,
() => console.log(this.accountDetails)
);
When we tried to access the property of that object using interpolation {{AccountDetails.UserName}} ,we got a property undefined error, because the object had not yet been received from the api. We fixed this by instantiating the object in the class
accountDetails: AccountDetails = new AccountDetails();
This works, however I noticed in Angular 1, a fake object would be created, if the object was undefined, so that there would be never be an an undefined error -- it would just show an empty value in the interpolated code. Is this a change in Angular2? Also, I noticed that when we used *ngFor in our code to iterate through the properties of an object, it would display an empty value (rather than trigger an object undefined error) if the object had not yet been received from the api. Is this something that occurs internally in the *ngFor directive?
With first piece of code you need to do following things,
1) Because it should be {{accountDetails.UserName}} (not {{AccountDetails.UserName}} ). NOTE : Please double check with U or u that I can't tell without knowing object's properties.
2) You could also use ?. like {{accountDetails?.UserName}}
I hope this will help.
Angular 2 is based off Typescript so there is stricter type checking hence the undefined error.
One thing you can do to avoid any errors is use an
*ngIf="AccountDetails.UserName"
condition on the parent tag (parent of the *ngFor loop). This way it only gets rendered once the information has been loaded from the API. You can have some loading screen with the opposite condition
*ngIf = "!AccountDetails.UserName"
so it displays till the data has been loaded and then gets swapped out once the data has been loaded.
You can remove the
accountDetails: AccountDetails = new AccountDetails();

Getting hold of loaded javascript objects in Protractor

In our application we load requirejs, which in return loads angularjs, and also other javascript modules. I am wondering if there any way to get hole of these LOADED modules (angularjs, javascript modules) in protractor test. Note, we want the instance that is loaded by the browser when running Protractor, we don't want to create instance by ourselves.
Any suggestion or example?
Thanks in advance.
Nick Tomlin's answer is what you can do if a module returns serializable data structure as a value. You call require and call with the module's value the callback that executeAsyncScript gives you to allow returning asynchronous values. This will work, for instance, if your module returns "foo" or { foo: 'bar' } or structures that are generally serializable.
However, it won't always work. Complex modules cannot be retrieved that way. Roughly speaking you should expect what you send through executeScript and executeAsyncScript and what they return to have the same limitations as JSON.stringify does. One major exception is that Selenium will wrap DOM objects returned from these calls into a structure that allows to identify them on the script side, and that allows passing them back to the browser. (Then again, there are limitations there too. This is why you get stale element exceptions, for instance.)
If you try to retrieve modules that export functions, you'll probably get something but it won't be complete. Try this, for instance:
browser.executeAsyncScript(function () {
arguments[0]({ foo: function () {}});
}).then(function (value) {
console.log(value);
});
The output I get is:
Object { foo: Object {} }
The function has been turned into an empty object.
I do not use angular with require.js, but i'm assuming you could access the require'd angular the same way you would in a module:
var pageAngular = browser.driver.executeAsyncScript(function () {
var callback = arguments[arguments.length - 1];
require(['angular'], function (angular) {
callback(angular);
})
});
The use of executeAsync is necessary here, since AMD modules are loaded asynchronously.
Do note that as #louis noted, the return of executeAsyncScript is going to be a serialized object, not the "live" instance of angular. If you need to interact with angular within the context of your page, you should do so within the callback torequire.
Something like this should do it:
var angular = browser.driver.executeScript("return window.angular;");

TypeError: Attempted to assign to readonly property. in Angularjs application on iOS8 Safari

Our Mobile App is getting "TypeError: Attempted to assign to readonly property." only on IOS 8 and the stack traces are not helpful and seem to be in Angular code.
This might be happening because of the "use strict" on top level of Angularjs code.
My questions are (1) why did it start happening only on IOS8? is this an IOS8 bug?
(2) or is this an angular bug surfaced on IOS8? (3) Or maybe we are violating strict mode rules but only IOS8 started to catch them! I am skeptical about the third option because strict mode is supported by other major browsers.
I have found one similar reported issue here.
Looks like this is an IOS8 bug
At least the guys at ember.js will temporarily strip the 'use strict' from their code until it's fixed.
Ember.js issue
SOLUTION -
Note:
angular.copy didn't work for me
lodash cloneDeep didn't work either.
removing "use strict"; didn't help - It just removed the error being logged in a try/catch arround the "erroneous" assignment to a readonly value
For us, the value that was being passed back from the SQL promise value was an array of objects.
The values that we were getting "read only" error on were strings, bools, and numbers.
console.log('this will log');
sqlReturnArray[0].name = "Bob"; // read only error here
console.log('wouldnt get to here to log');
I figured, well, if I can't assign values to properties on THOSE objects within the array, why can't I just copy over all the objects' values? Assignment of objects is by reference so just doing a loop over the array won't help. You need to loop over the keys and values.
This solved our problem
// where sqlReturnArray is the object given from the SQL promise
var newArrayOfObjects = [];
angular.forEach(sqlReturnArray, function (obj) {
var newObj = {};
angular.forEach(obj, function (val, key) {
newObj[key] = val;
});
newArrayOfObjects.push(newObj);
});
I just had this error and the fix was pretty simple.
My Code was-
$http.get('/api/v1/somedata').then(function(result){
$scope.data.somedata = result.data.list;
});
I get the TypeError in the assign. Because I haven't defined $scope.data.somedata anywhere and it got $scope.data undefined and trying to assign some property to it initiated the error.
I simply put $scope.data = {somedata : [] } in the top of the controller. I can also get rid of this error by changing $scope.data.somedata to $scope.somedata because any property of the $scope object that is undefined will end up to the $rootscope and won't give any error. I found this is pretty hard to debug my view-model. And also for this TypeError I can find out what exactly is wrong.
I had it with a form.
my issue was because of binding an SQL Return Object to an input.
after coping with angular.copy() problem resolved.
try to copy your return values and retry.
$scope.yourFunc= function(result) {
var item = angular.copy(result.rows.item(0));
...
};

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

underscore.js filter function

I'm attempting to learn backbone.js and (by extension) underscore.js, and I'm having some difficulty understanding some of the conventions. While writing a simpel search filter, I thought that something like below would work:
var search_string = new RegExp(query, "i");
var results = _.filter(this, function(data){
return search_string.test(data.get("title"));
}));
But, in fact, for this to work I need to change my filter function to the following:
var search_string = new RegExp(query, "i");
var results = _(this.filter(function(data){
return search_string.test(data.get("title"));
}));
Basically, I want to understand why the second example works, while the first doesn't. Based on the documentation (http://documentcloud.github.com/underscore/#filter) I thought that the former would have worked. Or maybe this just reflects some old jQuery habits of mine... Can anyone explain this for me?
I'd guess that you're using a browser with a native Array#filter implementation. Try these in your console and see what happens:
[].filter.call({ a: 'b' }, function(x) { console.log(x) });
[].filter.call([1, 2], function(x) { console.log(x) });
The first one won't do anything, the second will produce 1 and 2 as output (http://jsfiddle.net/ambiguous/tkRQ3/). The problem isn't that data is empty, the problem is that the native Array#filter doesn't know what to do when applied to non-Array object.
All of Underscore's methods (including filter) use the native implementations if available:
Delegates to the native filter method, if it exists.
So the Array-ish Underscore methods generally won't work as _.m(collection, ...) unless you're using a browser that doesn't provide native implementations.
A Backbone collection is a wrapper for an array of models, the models array is in c.models so you'd want to:
_.filter(this.models, function(data) { ... });
Backbone collections have several Underscore methods mixed in:
Backbone proxies to Underscore.js to provide 28 iteration functions on Backbone.Collection.
and one of those is filter. These proxies apply the Underscore method to the collection's model array so c.filter(...) is the same as _.filter(c.models, ...).
This mixing-in is probably what's confusing the "should I use the native method" checks that Underscore is doing:
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
You can use _.filter on a plain old object (_.filter({a:'b'}, ...)) and get sensible results but it fails when you _.filter(backbone_collection, ...) because collections already have Underscore methods.
Here's a simple demo to hopefully clarify things: http://jsfiddle.net/ambiguous/FHd3Y/1/
For the same reason that $('#element') works and $#element doesn't. _ is the global variable for the underscore object just like $ is the global variable for the jQuery object.
_() says look in the _ object. _filter says look for a method named _filter.

Resources