How to find Child Element in a Parent WebElement in Selenium webdriver - selenium-webdriver

I thought WebElement.findElement() only searched the DOM below the element,
but it apparently searches the whole DOM. I have something like this in HTML:
<table>
<tbody>
<tr>
<td>Some Text</td>
</tr>
<tr class="network">
<td><input ...></td>
<tr>
<td>Some More Text</td>
</tr>
<tr class="network">
<td><input ...></td>
</tbody>
</table>
And selenium code as below:
List<WebElement> tableRows =
radioTable.findElements(By.CssSelector("tbody > tr"));
for (WebElement row : tableRows) {
String s = row.getAttribute("class");
if (s.equals("network")) {
currentGroup.add(row.findElement(By.CssSelector(".network")));
}
However the row.findElement() call searches from the top of the DOM and
finds the same initial each time.
It would be very useful if WebElement.findElement() just searched under the
current element.

Well we can get the index of row element and using nth-child selector we could then select the child element as so.
List <WebElement> tableRows =
radioTable.findElements(By.CssSelector("tbody > tr"));
tableRows.forEach((row, index) => {
String s = row.getAttribute("class");
if (s.equals("network")) {
currentGroup.add(row.findElement(By.CssSelector(`.network:nth-child(${index + 1}) .network`)));
}
})

Related

How do you get the td values in each iteration of tr?

This is the table below that I need to iterate.
<tbody>
<tr>
<td>1</td>
<td>Thomas Hill</td>
<td>4</td
</tr>
<tr>
<td>2</td>
<td>Greg Hill</td>
<td>39</td
</tr>
<tr>
<td>3</td>
<td>Jane Hill</td>
<td>31</td
</tr>
</tbody>
I am trying get the first td each and code it like this.
let values = []
cy.get('tbody > tr')
.find('td')
.each(($el, $index) => {
cy.wrap($el)
.invoke('text')
.then(num => {
if($index!==0)
values.push(num)
})
})
Upon searching it, the arrays has no value. I want to get value of the first TD only in each row.
You say that you try to invoke the first Td value, of each row, but you are invoking a command that gets each Td in the code. Here is my suggestion
let values = []
cy.get('tbody > tr')
.each(($el) => {
cy.wrap($el)
.find('td')
.first()
.invoke('text')
.then(num => {
values.push(num)
})
})

display element (span tag ) after last ocurence of repeated item inside ng-repeat

I want to display something like this inside an ng-repeat: display a span tag saying the total purchase right after the last purchase of each person.( I do not want to display the total after each purchase). I have an array of object like this :
let group=[{name:'Brandon Pack',city:'NY',purchase:25,accepted:true},
{name:'Josh Vilet',city:'Memphis',purchase:30,accepted:true},
{name:'Brandon Pack',city:'NY',purchase:62,accepted:true},
{name:'Patrick Whiteside',city:'NY',purchase:50,accepted:false},
{name:'Josh Vilet',city:'Memphis',purchase:50,accepted:true}]
I can get the total, my problem is with the view that I don't want to display the total only after the last ocurence for that person
First of all, you can create a function in your controller in order to get the total amount:
$scope.getTotal = function () {
var sum = 0;
$scope.group.forEach(function(customer){
sum += customer.purchase;
});
return sum;
}
By the way, you can't declare variables with whitespaces in their name, like "let group", better call it "letGroup".
Then, you just have to create your table in Html:
<table>
<thead>
<tr>
<th>Name</th>
<th>City</th>
<th>Purchase</th>
<th>Accepted</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="o in group">
<td ng-bind="o.name"></td>
<td ng-bind="o.city"></td>
<td ng-bind="o.purchase"></td>
<td ng-bind="o.accepted ? 'Yes': 'No'"></td>
</tr>
<tr>
<td></td>
<td></td>
<td>Total</td>
<td ng-bind="getTotal()"></td>
</tr>
</tbody>
</table>

Angular ng-repeat-start inside a different "level" tag that ng-repeat-end. Is it a bug?

I ran into this situation where I needed to repeat an HTML code block with ng-repeat-start / ng-repeat-end but had angular complaining that:
Error: [$compile:uterdir] Unterminated attribute, found 'ng-repeat-start' but no matching 'ng-repeat-end' found.
I found a workaround which it might be useful to someone that will run into this situation. I would also like to know what you think. Is it a bug? I should have avoided that approach? I am using Angular v1.3.5 and the code is like that:
<table>
<thead>
...
<tr ng-repeat-start="a in b">
...
</tr>
...
</thead>
<tbody>
...
</tbody>
<thead>
...
<tr ng-repeat-end>
...
</tr>
...
</thead>
</table>
The problem seems to be that the ng-repeat-end is not within the same HTML tag block (here the first thead that ng-repeat-start is inside).
I found a workaround with the following code:
<table>
<thead>
...
</thead>
<thead ng-repeat-start="a in b">
<tr>
...
</tr>
...
</thead>
<tbody>
...
</tbody>
<thead>
...
</thead>
<thead ng-repeat-end>
<tr>
...
</tr>
...
</thead>
</table>
This way I am bringing the ng-repeat-start and ng-repeat-end within the same tag, the table.
ngRepeat is a multiElement directive.
If you look through the $compile source, you can see that angularJs will loop through next sibling nodes:
/**
* Given a node with an directive-start it collects all of the siblings until it finds
* directive-end.
* #param node
* #param attrStart
* #param attrEnd
* #returns {*}
*/
function groupScan(node, attrStart, attrEnd) {
var nodes = [];
var depth = 0;
if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
do {
if (!node) {
throw $compileMinErr('uterdir',
"Unterminated attribute, found '{0}' but no matching '{1}' found.",
attrStart, attrEnd);
}
if (node.nodeType == NODE_TYPE_ELEMENT) {
if (node.hasAttribute(attrStart)) depth++;
if (node.hasAttribute(attrEnd)) depth--;
}
nodes.push(node);
node = node.nextSibling;
} while (depth > 0);
} else {
nodes.push(node);
}
return jqLite(nodes);
}
On a personnal account, trying to use this with non-siblings elements feels very unnatural to the treeish nature of HTML.

$broadcast appending dynamic data (rows) at wrong place using ng-scrollbar

I have used ng-scrollbar like this :
<tbody ng-scrollbar rebuild-on="rebuild:me" class="scrollme" id ="tempID">
and in ng table , included the broadcast
getData: function($defer, params) {
$scope.$broadcast('rebuild:me');
Now the ng-repeat is populating the tr in thead after first th instead of appending/loading it correctly in tbody
how to correctly display my results?
did you try this
getData: function($defer, params) {
$defer.promise.then(function(){
$scope.$broadcast('rebuild:me');
};
...
right after updating table
Edit 1
tbody is replacing by div and thus how layout became messy,check plunk
Edit 2
Here some workaround about this,need some editing,i will update later
Edit 3(final)
Finally get working example of ngtable and ngscrollbar together,but its not most beautiful solution but still:
1.
you need separate thead from rest of table into new table which locate right above main table
<thead>
<tr >
<th class="sortable" ng-class="{
'nameasc':'sort-asc' ,
'namedesc':'sort-desc'
}[sortTableParam]"
ng-click="SortBy('name')">
<div>name</div>
</th>
<th class="text-center sortable" ng-class="{
'ageasc':'sort-asc' ,
'agedesc':'sort-desc'
}[sortTableParam]"
ng-click="SortBy('age')">
<div>Age</div>
</th>
</tr>
</thead>
</table>
2.
second - hide thead in main table and wrap table into div tag,which will be processing by directive:
<div style="height:300px" ng-scrollbar rebuild-on="rebuild:me" class="scrollme">
<table ng-table="tableParams" show-filter="true" class="table">
<thead><tr><th ></th><th ></th></tr></thead>
<tbody >
<tr ng-repeat="user in $data">
<td data-title="'Name'" filter="{ 'name': 'text' }" sortable="name">
{{user.name}}
</td>
<td data-title="'Age'" sortable="age">
{{user.age}}
</td>
</tr>
</tbody>
</table>
</div>
3.
Clicks from header will trigger reloading data with sorting option,by binding SortBy function
var sorting = $scope.tableParams.$params.sorting;
var dir = sorting != null ? (sorting[param.toString()] == 'asc' ? 'desc' : 'asc') : 'asc';
var sort={};
sort[param.toString()]=dir;
$scope.tableParams.sorting(sort);
//edit classes in header for rows imaging(check plunk)
updatesorting(param);
};

AngularJS - ng-repeat not working with a map-entry named length and value 0

I would like to show Events with Subevents I got from an API as JSON
[
{
"class":"de.ff.prg.EventItem",
"id":27667,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":0,
"runningorder":0,
"screening":{"class":"Screening","id":27529},
"title_eng":"'71",
"title_ger":"'71",
"venue":{"class":"Venue","id":1}},
{"class":"de.ff.prg.EventItem",
"id":27676,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":5,
"runningorder":0,
"screening":null,
"title_eng":"NEW",
"title_ger":"NEW",
"venue":{"class":"Venue","id":8}
}
]
In order to display the fields of the items in rows and not in columns, I have nested two tables with ng-repeat so that I get a table of tables.
<!--Items-->
<table>
<thead>
<td colspan="6" style="background-color: #b9da73">
<button class="btnAdd" ng-click="addAndEditEventItem()">Add Item</button>
</td>
</thead>
<tbody>
<tr ng-repeat="item in eventItems">
<h1>{{eventItems.length}}</h1>
<th>
<table>
<thead>
<td colspan="2" style="background-color: #c0da86">{{item.runningorder}}</td>
<td colspan="4" style="background-color: #c0da86">
<button class="btnAdd" ng-click="deleteEventItem(item.id)">Delete Item</button>
</td>
</thead>
<tbody>
{{item}}
<tr ng-repeat="(key,value) in item">
<th colspan="2" style="background-color: #ceeca1">{{key}}</th>
<th colspan="4" style="font-weight: normal;">{{value}} </th>
</tr>
</tbody>
</table>
</th>
</tr>
</tbody>
</table>
Up until now this was no problem, but somewhere along the way I have lost the possibility to display the body of the first sub-table (all other rows render fine). I have inserted {{item}} before the body tag and it shows the missing data, so it's there all right.
Any ideas? Or do you need to see the other code to tell? I have no clue...
Here is a Fiddle
Interesting case.
Empirically, I found that this is because of
"length":0
Just looked into angular source code and found that it transforms object's properties in repeat to an array, and then takes it's length property to iterate through it.
...ngRepeatDirective...
if (isArrayLike(collection)) {
collectionKeys = collection;
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
// if object, extract keys, sort them and use to determine order of iteration over obj props
collectionKeys = [];
for (key in collection) {
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
collectionKeys.push(key);
}
}
collectionKeys.sort();
}
arrayLength = collectionKeys.length; <<<--- HERE
// locate existing items
length = nextBlockOrder.length = collectionKeys.length; <<<--- AND HERE
So nothing really happens.
Seems like a bug actually. I've posted an issue.
Just try then iterating through your array and change name of this property. Like:
for(var i = 0; i < $scope.eventItems.length; i++){
$scope.eventItems[i].itemLength = $scope.eventItems[i].length;
delete $scope.eventItems[i].length;
}

Resources