Protractor find element in repeater by binding? - angularjs

I am trying to write a simple test that matches a binding in a repeater.
I have it working when I search by a CSS class, however I am "not allowed" to do that in our code. I can't use HTML tags as a locator, either. I can only find by attributes or direct binding.
I have tried many different ways including (but get errors or no result):
var productPageUrl = element.all(by.repeater('product in products').row(0).column('{{product.productPageUrl}}'));
Not sure if it makes a difference, but in the application the HTML template is included by ng-repeat.
This works (but cannot use):
products.then(function(prods) {
prods[0].findElement(by.className('homepage-panel-link')).getAttribute('href').then(function(href){
expect(href).toMatch('/products/1');
});
});
The HTML template being repeated:
<div data-ng-repeat="product in products">
<div data-property-name="productItem-{{$index}}">
</div>
</div>
Is there anyway of simply testing the binding product.productPageUrl??? From the code above that works, it seems a hell of a long way to go around just to get that value.

It seems like you're just looking for the locator by.binding? http://angular.github.io/protractor/#/api?view=ProtractorBy.prototype.binding
i.e.
var productPageUrl = element(by.binding('product.productPageUrl'));
expect(productPageUrl.getAttribute('href')).toMatch('/products/1');
or if you have many that match:
var productPageUrls = element.all(by.binding('product.productPageUrl'));
expect(productPageUrls.getAttribute('href').get(0)).toMatch('/products/1');
or
expect(productPageUrls.getAttribute('href')).toMatch(['/products/1', '/products/2', ...]);

This is my problem too and I can not find any protractor feature to solve that so this is my suggest solution. :) This solution bases on protractor can get element by ng-bind and get value attribute of input. (I have no idea why getText() input not work :D)
element(by.binding('mainImageUrl')).getAttribute('value')
.then(function(text){
expect(text.toMatch(/img\/phones\/nexus-s.0.jpg/));
});
..
<a href="{{product.productPageUrl}}"
class="homepage-panel-link" data-property-name="productPageUrl"></a>
<input type="hidden" ng-bind="product.productPageUrl"
value= "{{product.productPageUrl}}" >
..
in javascript:
element.all(by.repeater('product in products').row(0)
.column('{{product.productPageUrl}}'))
.getAttribute('value').then(function(value){
//matching value
});

Related

Protractor locator chaining fails to find an element text

I try to find an element located under another element, and protractor seems to fail getting the right value.
Here's an elaborated description.
I have the following HTML:
<div ng-repeat="stageRow in stageModel" debugId="stage-row">
<div>
<span debugId="stage-name-text">{{ stageRow.name }}</span>
</div>
<div>
<span debugId="stage-count-text">{{ stageRow.count }}</span>
</div>
</div>
I then use the following protractor code to find a row:
var allStages = element.all(by.css('[debugId="stage-row"]'));
var stage01 = allStages.get(0);
I then try to chain a locator to find a certain cell:
var count01 = stage01.$('[debugId="stage-count-text"]');
Then I make an expectation:
expect(count01.getText()).to.eventually.equal('1')
And here is what I get:
+ expected - actual
+"1"
-""
Even though I clearly have "1" as the stage's count.
I know this because:
I see it with my own eyes :)
I stop in debug mode right
before the expectation, and inspect the HTML to see that indeed the
text of that span is "1".
Then I try this:
I put a printout of the HTML before the expectation:
stage01.getOuterHtml().then(function(result) {
console.log(result);
});
And I see the expected HTML:
<div ng-repeat="stageRow in stagingModel" class="ng-scope" debugid="stage-row">
<div>
<span debugid="stage-name-text" class="ng-binding">nuziba</span>
</div>
<div>
<span debugid="stage-count-text" class="ng-binding">1</span>
</div>
</div>
And right after that, I manually try this:
stage01.element(by.css('[debugId="stage-count-text"]')).getText().then(function(count) {
console.log(count);
});
And I get "" at the printout...
I then tried to locate the elements using a different method - with by.repeater instead of by.css. Got the same results.
What happens here?
Clearly, the HTML contains the correct markup.
Why is protractor failing to extract it properly?
For the record, I'm using protractor version 1.4.0,
and the runner is mocha 1.18.2, rather than Jasmine.
Many thanks in advance,
Daniel
You need at least protractor 1.7 to use the expected conditions the current version is 2.0 so you should be able to do something like this if you're still having issues.
Here you can have protractor wait till the element has the text '1' in it and then the expect won't check until that condition is met
var count01HasText = EC.textToBePresentInElement(count01, '1');
browser.wait(count01HasText, 5000,);
expect(count01.getText()).toEqual('1');

AngularJS + Twitter Popover: Content Iteration

I'm using twitter bootstrap with a popover and got a AngularJS scoped variable to appear correctly. The below works.
(data-content="{{notifications[0].user}} shared {{notifications[0].user_two}}'s records")
When I add the following
(data-content="<b>{{notifications[0].user}} shared {{notifications[0].user_two}}'s records</b>")
No errors show up, but all of the {{}} no longer render.
So I tried this as a test of sorts
(data-content="<div ng-repeat='item in notifications'>test {{item}} <br/><hr/></div>")
Much like the last example, I see the "test" but not the {{item}}. And the "test" only show s up once, even though the notifications had three elements. When I look at the DOM there's this
<div class="popover-content">
<div ng-repeat="item in notifications">you <br><hr></div>
</div>
I've also tried just creating a directive to iterate through the array and make the output I want, but my attempt to set data-content equal to a directive have been failures. The examples I've found elsewhere I'm confident would work, but I just wanted to confirm before I begin implementing something like this (http://tech.pro/tutorial/1360/bootstrap-popover-using-angularjs-compile-service) or (Html file as content in Bootstrap popover in AngularJS directive) that I'm not missing a straightforward fix to the problem I outlined above that would not require me creating a directive.
Edit:
Plunkr Url http://plnkr.co/edit/VZwax4X6WUxSpUTYUqIA?p=preview
html might be breaking it, try marking it as trusted html using $sce
How do you use $sce.trustAsHtml(string) to replicate ng-bind-html-unsafe in Angular 1.2+
$scope.html = '<ul><li>render me please</li></ul>';
$scope.trustedHtml = $sce.trustAsHtml($scope.html);
<button ... data-content="trustedHtml" ...> </button>

Directive for input element with error assistance span elements

I am trying to write a directive to do input validation for ip addresses.
I see a lot of examples on the web similar to this boiler plate code:
<label>IP Address 1:</label>
<input ng-model="formData.ip1" required name="ip1" type="text"
placeholder='xxx.xxx.xxx.xxx'
ng-pattern = "/^(\d{1,3}\.){3}(\d{1,3})$/">
<span ng-show="myForm.ip1.$error.required" style="color:red"> * </span>
<span ng-show="myForm.ip1.$dirty && myForm.ip1.$invalid" style="color:red">
This is an invalid IP.</span>
Since this needs to be in dozens of places, and since the validation rules and the way errors are indicated will likely change, I would like to use a directive such as this:
<label>IP Address A:</label>
<input ng-model="formData.ipa" required my-ip-validator>
The myIpValidator directive would add attributes (like ng-pattern) and extra elements (like spans)
I've tried several approaches. My latest was a directive with compile such as from here Add directives from directive in AngularJS
I started a plunker (see update below)
I couldn't figure out how to get the form name for use in ng-show. Plus, the resulting input element didnt have necessary classes added later by angular, such as ng-pristine, ng-invalid, etc.
How can I do this? I'm open to either fixing the problems with this directive or a totally different approach.
Update:
The plunker above was wrong. It was old. I'm working on an update which I'll post soon.
Update 2:
I figured out something that works. Plunker at http://plnkr.co/edit/efnfMWprVH91hQnzYkX7?p=preview
I was missing passing $compile to the function. And, the other part of it was getting the form name from some DOM inspection. I'll now clean it up and improve it. But if anyone has other suggestions, I'm open to learning. This was my first directive.
I'm answering my own question. Yes, this can be done. I was close with the compile approach. I just needed to fix some minor things. Plunker at http://plnkr.co/edit/efnfMWprVH91hQnzYkX7?p=preview
It wasn't working before because I wasn't passing $compile in to the directive. And, I didn't know how to get form and input names.
Here's how I did that:
var inputname = element.attr("name"),
foundForm = false,
ancestor = element.parent();
while (!foundForm && ancestor) {
if ( angular.uppercase(ancestor.prop("tagName")) == "FORM" ) {
foundForm = true;
var formname = ancestor.attr("name");
} else {
ancestor = ancestor.parent();
}
}

How to prepopulate ngModel in AngularJS

I have no idea how to pre-populate an ng-model in this circumstance that I have to use ngBind. I tried ng-init, but it's not working.
<h6 ng-show="isOwner" ng-bind="currentMagazine.magazine_name"
contenteditable ng-model="currentMagazine.magazine_name"
ng-change="update()"></h6>
I have a seperate directive that binds contenteditable attributes to ngModelController.
The problem now is whenever I update the model, ng-bind will jump out and refresh the div element, resulting in the cursur going back to the beginning of the text, which makes it impossible for any user to type.
I tried ng-init in a fashion like this:
<div ng-init="magazineName = currentMagazine.magazine_name">
<h6 ng-show="isOwner"
contenteditable ng-model="magazineName"
ng-change="update()"></h6>
</div>
It's not working. If I don't use ng-bind, then no text will show up.
Also I notice it might be related to this problem, when I type with space or delete key, they are escaped into HTML entities...so you get result like this:
Hopefully both of my problems can be solved! Thank you (it's a very frustrating day)!
In your app.js do this:
$scope.magazineName = $scope.currentMagazine.magazine_name;
In your HTML do something like this:
<input
ng-show="isOwner"
contenteditable
ng-change="update()"/>
or
<h6 ng-show="isOwner"
contenteditable
ng-model="currentMagazine.magazine_name"
ng-change="update()">
{{currentMagazine.magazine_name}}
</h6>
... though perhaps not, it's a bit difficult to gauge without seeing it... please make a jsfiddle or plunkr if you'd like to get more eyes on it.
If you're just trying to make some large text that is still editable and bound to the model it may be easier to just style an input for your needs.
Decided to play with contenteditbale since it's chance for me to learn something too... I can't seem to recreate the issue though:
http://jsfiddle.net/VSJQX/
I saw after doing this it wasn't updating the model, found another SO post that resolves that and included the changes here:
http://jsfiddle.net/VSJQX/2/

Is there a way to make AngularJS work with HTML-first?

Is there a way to have a HTML-view with pre-populated values from the server, and then get AngularJS to read those values into it's $scope?
I'm thinking of a scenario where the HTML is like this:
<div ng-controller="TestController">
<div ng-bind="title">Test Title</div>
<div ng-bind="itemCount">33</div>
<div ng-repeat="item in items">
<div ng-bind="item.title">Item 1 Title</div>
</div>
</div>
<button ng-click="update()">Update</button>
And the JavaScript is like this:
function TestController($scope) {
$scope.update = function() {
console.log($scope.title); // Should log "Test Title"
};
}
The thought behind this is to let the server render HTML that search engines can index, but have a JavaScript-model-representation of the content for manipulation through JS.
While ng-init is one solution, it requires you to explicitly set the value. So here is an alternative solution.
http://plnkr.co/edit/pq8yR9zVOHFI6IRU3Pvn?p=preview
Note : This solution wont work for ng-repeat. Control flow directives cant be used with this. But for simple extraction of information from ng-bind this works pretty well. All that you need to do is add the default directive ( code in plunk ) to wherever you are doing the bind and it will extract the text content and push it to the scope variable.
EDIT (solution with ng-repeat):
So, I was thinking of a way to make ng-repeat also work the same way. But getting ng-repeat to work like this isnt an easy job ( see the code for proof :P ). I have finally found a solution - here you go :
http://plnkr.co/edit/GEWhCNVMeNVaq9JA2Xm2?p=preview
There are a couple of things you need to know before you use this. This hasnt been thoroughly tested. It only works for repeating over arrays ( not objects ). There could be cases that have not been covered. I am overriding ngRepeat itself which could have other consequences. When you loop through the items ( in your server side code ) dont forget to add default="true" on the first element and default on the rest of the elements.
Hope this helps.
Add ng-init to your elements with the value so that it will work the way you want.
http://docs.angularjs.org/api/ng.directive:ngInit
I think what you really want is to make your application searchable by serving static files in parallell. Read more about it here http://www.yearofmoo.com/2012/11/angularjs-and-seo.html

Resources