Protractor locator chaining fails to find an element text - angularjs

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

Related

How to make simple form more React-like? (DOM node goes missing)

I am converting a little HTML/JS form I did years ago, as a React.js learning exercise, and I have run into a stumbling block which I'd like to understand the reason for.
Basically, as it originally was, I had a select, where you could choose which form you wanted to display. The onchange handler for the select displayed the chosen form and hid the others. So my first step in doing a React makeover of this, which has worked fine, is to make the forms into React components. I am left with this as my HTML:
<body>
<div id="main">
<select id="calculations" onChange="handleSelect();">
<option value="" selected="selected">SELECT CALCULATION FORM</option>
<option value="1">Enter pace and distance to get a total time</option>
<option value="2">Enter distance and target time to get the required pace</option>
<option value="3">Enter pace and time to get the distance run</option>
<option value="4">Convert mph to mins/mile</option>
<option value="5">Convert mins/mile to mph</option>
</select>
<div id="calcForm"/>
</div>
</body>
This is the JS:
window.handleSelect=function(){
var selected=$("#calculations").val();
if(selected!=""){
React.render(React.createElement(CalcForm, {selected: selected, distances: distances}),document.getElementById('calcForm'));
} else {
$("#calcForm").html("");//clear it
}
};
I have been frustrated, though, in my attempts to finish the job off my making the main select into a React component. I tried creating a SelectCalc component and using it like this:
<body>
<div id="main">
<div id="selectCalc"/>
<div id="calcForm"/>
</div>
</body>
In my app.js I had this:
React.render(React.createElement(SelectCalc,null), document.getElementById('selectCalc'));
I made the handleSelect a function of the SelectCalc class, so that when an option was selected, it called this:
React.render(React.createElement(CalcForm, {selected: selected, distances: distances}),document.getElementById('calcForm'));
Here is where the problem came in, though. When done this way, when I select an option I get the error:
Error: Invariant Violation: _registerComponent(...): Target container is not a DOM element.
The target container in question is the 'calcForm' div, as identified in document.getElementById('calcForm'). For reasons which baffle me, this is null (I checked in the console). And yet it is clearly there in the HTML, and it was successfully targeted by my more primitive select change handler, prior to the React makeover.
So what is the issue? What am I misunderstanding here? Given that this is such a very simple example of React.js at work, how should I be doing it?
Fiddle of non-working version at https://jsfiddle.net/JohnMoore/7rp3n1oy/2/, I hope - first ever use of Fiddle (!), so I hope it shows the issue. Try running it and selecting one of the options and see the error in the console. There is also a preceding error, I notice, on JSFiddle: "SyntaxError: expected expression, got '<'". I have to say this doesn't make much sense to me, as I would have thought what was expected in the return from the render was, precisely, some HTML beginning with <. I don't know whether this is related or whether it's an artefact of the Fiddle environment.
i solved it by removing the divs. They will be create dynamically in the react component.
Here is the fiddle.
EDIT: The old one did not work because <div id="selectCalc" /> is "invalid" html. it will be replaced with <div id="selectCalc"> instead of <div id="selectCalc"></div>. And because there is a missing end-tag, the browser ignores everthing after the <div ... />. -> <div id="calcForm" /> won't appear in the DOM.
Here is a version with the corrected html tags, but you should prefer the other version.
Take a look at this for more information about in/valid html tags.

Protractor says that a checkbox is not visible, but it is. How to investigate and fix it?

So, I have this HTML inside an angular app:
<div class="panel">
<input class="eula" id="eula" type="checkbox" />
<label for="eula">I agree</label>
</div>
And this two expectations on the same spec (just to be sure that there's no other stuff running):
expect(element(by.css('.panel')).isDisplayed()).toBeTruthy();
expect(element(by.css('.eula')).isDisplayed()).toBeTruthy();
First one, is true. Second one is false. That's strange. It should be also true.
So, i try:
element( by.css( '.panel' ) ).getOuterHtml()
.then( function ( html ) {
console.log( html );
});
And i get:
<input class="eula" id="eula" type="checkbox">
From what I see, there's nothing hidding the checkbox so i don't understand why protractor says that is not visible. Is there another way to test this? Any idea on how to proceed to debug and fix it?
From protractor isDisplayed() documentation:
An expectation for checking that an element is present on the DOM of a page and visible. Visibility means that the element is not only displayed but also has a height and width that is greater than 0.
The input had a display: none property, and that was the problem.

AngularJS - e2e testing - Why does this by.css selector work?

So I'm starting to break into AngularJS from a JavaScript (& jQuery) background. I've been wading through the tutorial, and an loving the way it's set up. Currently I'm looking at lesson 10 and I can't figure out why a piece of the code works. I have tried googling and poking through the documentation...the protractor docs don't even seem to have anything for by.css and I couldn't figure out how to search for this very concisely :/ ... Apologies if I'm just missing something seriously obvious.
In the e2e test scenario there is this code:
it('should swap main image if a thumbnail image is clicked on', function() {
element(by.css('.phone-thumbs li:nth-child(3) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
element(by.css('.phone-thumbs li:nth-child(1) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
which acts on this page-html:
<ul class="phone-thumbs ng-scope">
<li ng-repeat="img in phone.images" class="ng-scope">
<img ng-src="img/phones/nexus-s.0.jpg" ng-click="setImage(img)" src="img/phones/nexus-s.0.jpg">
</li>
</ul>
produced by this ng-markup:
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}" ng-click="setImage(img)">
</li>
</ul>
I can't figure out why the element(by.css('img.phone'))... is functional. Based on the selector (and coming from jQuery) I would expect to see a 'phone' class on the images...but it's not there. Does the '.phone' reference something else?
I can see that removing the '.phone' portion gives '.....warning: more than one element found for locator By.cssSelector("img") - you may need to be more specific', so how is '.phone' providing that specificity?
Thanks,
Ben
You're just looking in the wrong place:
It is checking if clicking on a thumbnail
//app/partials/phone-detail.html (line 9):
<img ng-src="{{img}}" ng-click="setImage(img)">
makes THIS image:
//app/partials/phone-detail.html (line 1):
<img ng-src="{{mainImageUrl}}" class="phone">
change it's source. img.phone is exactly what you would expect.
The docs for by.css are here: https://github.com/angular/protractor/blob/master/docs/locators.md

Protractor find element in repeater by binding?

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

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