Selenium Webdriver - Fetching Table Data - uitableview

I want to fetch data from tables in UI. I know about looping through rows and columns using "tr" and "td". But the one the table I have is something like this:
<table>
<tbody>
<tr><td>data</td><th>data</th><td>data</td><td>data</td></tr>
<tr><td>data</td><th>data</th><td>data</td><td>data</td></tr>
<tr><td>data</td><th>data</th><td>data</td><td>data</td></tr>
</tbody>
</table>
How can I make my code generic, so that the occurrence of "TH" in middle can be handled.
Currently, I am using this code :
// Grab the table
WebElement table = driver.findElement(By.id(searchResultsGrid));
// Now get all the TR elements from the table
List<WebElement> allRows = table.findElements(By.tagName("tr"));
// And iterate over them, getting the cells
for (WebElement row : allRows) {
List<WebElement> cells = row.findElements(By.tagName("td"));
for (WebElement cell : cells) {
// And so on
}
}

You could look for all children of tr element without differentiating between td and th. So instead of
List<WebElement> cells = row.findElements(By.tagName("td"));
I would use
List<WebElement> cells = row.findElements(By.xpath("./*"));

Mayby it's too late for the owner of this question, but helpful for other.
List<WebElement> cells = row.findElements(By.xpath(".//*[local-name(.)='th' or local-name(.)='td']"));

// Grab the table
WebElement table = driver.findElement(By.id("searchResultsGrid"));
// Now get all the TR elements from the table
List<WebElement> allRows = table.findElements(By.tagName("tr"));
// And iterate over them, getting the cells
for (WebElement row : allRows) {
List<WebElement> cells = row.findElements(By.tagName("td"));
for (WebElement cell : cells) {
System.out.println("content >> " + cell.getText());
}
}
using cell.getText() would simply work

You don't need to loop through elements. Instead, use a ByChained locator.
If you table looks like this:
<table>
<tbody>
<tr><th>Col1</th><th>Col2</th><th>Col3</th></tr>
<tr><td>data</td><td>data</td><td>data</td></tr>
<tr><td>data</td><td>data</td><td>data</td></tr>
<tr><td>data</td><td>data</td><td>data</td></tr>
</tbody>
</table>
The locate like this:
By tableBodyLocator = By.xpath(".//table/tbody");
By headerRowLocator = By.xpath(".//tr[position()=1]");
By dataRowsLocator = By.xpath(".//tr[not(position()=1)]");
By headerRowLocator = new ByChained(tableBodyLocator, headerRowLocator);
List<WebElement> weHeaders = driver.findElement(headerRowLocator)
.findElements(By.xpath(".//th");
List<WebElement> allRowData = driver.findElements(tableBodyLocator, dataRowsLocator);
WebElement row1Data = allRowData.get(0);
WebElement row2Data = allRowData.get(1);
etc.

yes it is working for c# with selenium...
IList<IWebElement> cells = row.findElements(By.xpath(".//*[local-name(.)='th' or local-name(.)='td']"));

IWebElement table = driver.FindElement(By.Id("id"));
List<IWebElement> allRows = new List<IWebElement> (table.FindElements(By.TagName("tr")));
foreach (var Row in allRows)
{
List<IWebElement> cells = new List<IWebElement>( Row.FindElements(By.TagName("td")));
foreach (var cel in cells)
{
string test = cel.Text;
}
}

The below code you can not only get the rows and columns of the table but also you can get the order in which they are in the Browser,this is mainly handy if you have a nested structures in the TD column as in your case.
public DataTable StoreHtmlTableToDataTable(IWebElement tblObj,bool isFirstRowHeader = true)
{
DataTable dataTbl = new DataTable();
int rowIndex = 0;
try
{
//_tblDataCollection = new List<TableDataCollection>();
var tblRows = ((IJavaScriptExecutor)DriverContext.Driver).ExecuteScript("return arguments[0].rows; ", tblObj);
if (tblRows != null)
{
//Iterate through each row of the table
foreach (IWebElement tr in (IEnumerable)tblRows)
{
int colIndx = 0;
DataRow dtRow = dataTbl.NewRow();
// Iterate through each cell of the table row
var tblCols = ((IJavaScriptExecutor)DriverContext.Driver).ExecuteScript("return arguments[0].cells; ", tr);
foreach (IWebElement td in (IEnumerable)tblCols)
{
//add the header row of the table as the datatable column hader row
if (rowIndex == 0)
{
dataTbl.Columns.Add("Col" + colIndx.ToString(), typeof(string));
}
dtRow["Col"+colIndx.ToString()] = td.Text;
//loop through any child or nested table structures if you want using the same approach for example links,radio buttons etc inside the cell
//Write Table to List : This part is not done yet
colIndx++;
}
dataTbl.Rows.Add(dtRow);
rowIndex++;
}
}
}
catch (Exception)
{
throw;
}
//if first row is the header row then assign it as a header of the datatable
if (isFirstRowHeader)
{
dataTbl = this.AssignDataTableHeader(dataTbl);
}
return dataTbl;
}

TableDriver (https://github.com/jkindwall/TableDriver.Java) supports cells with both td and th tags. Using TableDriver with your example table would look something like this:
Table table = Table.createWithNoHeaders(driver.findElement(By.id(searchResultsGrid), 0);
List<TableRow> allRows = table.getRows();
for (TableRow row : allRows) {
List<TableCell> cells = row.getCells();
for (TableCell cell : cells) {
// Access the cell's WebElement like this: cell.getElement()
}
}

Related

Data attribute for (Mottie) Tablesorter filter_selectSource

I have a dynamic table which can contain a status column that can contain a predefined list of status, for example:
0: closed
1: Open
2: Pending
3: ...
The status label is displayed in the table, but the id number is used for actual filtering. I successfully applied tablesorter filter-select to display a select filter, but it either display label (won't filter) or id (not pretty).
I could fix this using filter_selectSource inside javascript, but since my table is dynamic and displayed using Handlebar, I'm looking for an html solution using data attributes.
Is there a data attribute that could be used to set the filter select label/value, similar to how data-text can be used to define unparsed text? Or is there a way to define a custom parser for filter that would return a label/value combo as an array for example?
Based on Mottie reply and tablesorter.filter.getOptions source, I came up with this. Adding the filter-metaselect class to my column(s) th enables the data-value attribute in the cell td to be used as the select options. The parsed/unparsed text can still be used. Note that the child part of getOptions has been omitted since I'm not using feature at the moment.
Table Cell :
<td data-value="1">
Projet actif
</td>
Select option :
<option value="1" parsed="projet actif" data-function-name="1">Projet actif</option>
Javascript:
filter_selectSource: {
".filter-metaselect": function (table, column, onlyAvail) {
table = $( table )[0];
var rowIndex, tbodyIndex, len, row, cache, indx, child, childLen, colData,
c = table.config,
wo = c.widgetOptions,
arry = [];
for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
cache = c.cache[tbodyIndex];
len = c.cache[tbodyIndex].normalized.length;
// loop through the rows
for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
// get cached row from cache.row ( old ) or row data object
// ( new; last item in normalized array )
row = cache.row ?
cache.row[ rowIndex ] :
cache.normalized[ rowIndex ][ c.columns ].$row[0];
// check if has class filtered
if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) {
continue;
}
// Get the column data attributes
if (row.getElementsByTagName('td')[column].getAttribute('data-value')) {
colData = row.getElementsByTagName('td')[column].getAttribute('data-value');
} else {
colData = false;
}
// get non-normalized cell content
if ( wo.filter_useParsedData ||
c.parsers[column].parsed ||
c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) {
arry[ arry.length ] = {
value : (colData) ? colData : cache.normalized[ rowIndex ][ column ],
text : cache.normalized[ rowIndex ][ column ]
};
// child row parsed data
/* TODO */
} else {
arry[ arry.length ] = {
value : (colData) ? colData : cache.normalized[ rowIndex ][ c.columns ].raw[ column ],
text : cache.normalized[ rowIndex ][ c.columns ].raw[ column ]
};
// child row unparsed data
/* TODO */
}
}
}
// Remove duplicates in `arry` since using an array of objects
// won't do it automatically
var arr = {};
for ( var i=0, len=arry.length; i < len; i++ )
arr[arry[i]['text']] = arry[i];
arry = new Array();
for ( var key in arr )
arry.push(arr[key]);
return arry;
}
}
The filter_selectSource documentation has an example where this widget option calls the filter.getOptions which returns an array of cell text or parsed values (based on the filter parser setting); if that doesn't return the values you want, grab the values yourself and return an array in that function.
Here is a basic example of how to use it: http://jsfiddle.net/Mottie/856bzzeL/117/ (related to Is there a way in tablesorter to filter to select only rows where the field is empty?)
$(function(){
$('table').tablesorter({
theme: 'blue',
widgets: ['zebra', 'filter'],
widgetOptions: {
filter_functions: {
0: {
'{empty}' : function (e, n, f, i, $r, c) {
return $.trim(e) === '';
}
}
},
filter_selectSource: {
0: function (table, column, onlyAvail) {
// get an array of all table cell contents for a table column
var array = $.tablesorter.filter.getOptions(table, column, onlyAvail);
// manipulate the array as desired, then return it
array.push('{empty}');
return array;
}
}
}
});
});

Error while adding multiple items to an entity

for (int i = 0; i < skus.Count; i++)
{
sku item = new sku();
item = skus[i];
sku sku = CompanyDbContext.skus.Where(s => s.item_no == item.item_no).FirstOrDefault();
if (sku == null) // ok to insert [no duplicate item numbers]
{
CompanyDbContext.skus.Add(item);
}
}
CompanyDbContext.SaveChanges();
I'm getting
collection was modified enumeration operation may not execute
error. How can I fix this ?
As mentioned in the comments, this happens because you are modifying the collection which you are looping through as you're performing your work.
One option you have is to create a temporary collection and add your sku items to that, and finally add the contents of the temporary List<sku> to your CompanyDbContext
// Create a new temporary list
List<sku> tempSkus = new List<sku>();
for (int i = 0; i < skus.Count; i++)
{
// Let's assign item to skus[i] immediately, we don't need a new instance here when we're later re-pointing to an existing instance
sku item = skus[i];
// Use LINQ Any function to determine whether there are any existing SKU's already
bool existingSku = CompanyDbContext.skus.Any(s => s.item_no == item.item_no);
// There are no duplicates, let's add this sku item to our temporary List
if(!existingSku)
{
tempSkus.Add(item);
}
}
// Add the Range of tempSkus List to the CompanyDbContext
CompanyDbContext.skus.AddRange(tempSkus);
CompanyDbContext.SaveChanges();
Or if you prefer LINQ
// Create a new temporary list
List<sku> tempSkus = skus.Where(p => CompanyDbContext.skus.Any(s => s.item_no != p.item_no)).ToList();
// Add the Range of tempSkus List to the CompanyDbContext
CompanyDbContext.skus.AddRange(tempSkus);
CompanyDbContext.SaveChanges();
The problem is that you are modify the same thing that you are iterating. As best practice you should update your method something like this:
//get search predicat from List<sku> skus
var item_nos = skus.Select(s=>s.item_no).ToList();
//items already in repo
var addedItems = CompanyDbContext.skus.Where(s => item_nos.Contains(s.item_no)).ToList();
var newItems = skus.Except(addedItems).ToList();
foreach(var sku in newItems){
CompanyDbContext.skus.Add(item);
}
CompanyDbContext.SaveChanges();

Delete selected rows in bwu-grid by using the delete key

I'm using the bwu-grid and want to have the delete key to delete rows. Here is the code I have right now:
_grid.onKeyDown.listen((e) {
print("onKeyDown ${e.keyCode}");
if(e.keyCode == 46)
{
var rows = _grid.getSelectedRows();
for (var i = 0, l = rows.length; i < l; i++) {
var item = _dataView.getItem(rows[i]);
var rowid = item["id"];
_dataView.deleteItem(rowid);
// This should probably be handled in another event?
_grid.invalidate();
_grid.render();
}
}
});
It works but the problem is that if I enter edit mode on a cell and hit the delete key to delete a character inside the cell then the same code gets run and deletes the whole row. So I guess I need a way to determine if the cell is in edit mode or not. Or maybe I am going about this the wrong way?

Is there any way get datarow from datatable without using foreach

Controller
foreach (DataRow temp in act.Rows)
{
_oResolutionModel.activityNo = temp["ActivityID"].ToString();
_oResolutionModel.assignTechnician = temp["TechNo"].ToString();
_oResolutionModel.recommendation = temp["RECOMMENDATION"].ToString();
_oResolutionModel.jobStart = (DateTime)temp["JobStart"];
_oResolutionModel.jobEnd = (DateTime)temp["JobEnd"];
_oResolutionFacade.setResolutionID(_oResolutionModel.activityNo);
DataTable res = _oResolutionFacade.getResolution(_oAppSetting.ConnectionString);
foreach (DataRow x in res.Rows)
{
_oResolutionModel.solution = x["Resolution"].ToString();
_oResolutionModel.remarks = x["Remarks"].ToString();
_oResolutionList.Add(_oResolutionModel);
break;
}
_oResolutionList.Add(_oResolutionModel);
break;
}
In here my _oResolutionList count = 1, meaning there's two data in it and it duplicated the first data. I want to have only 1 data in my _oResolutionList. Do I need to add some code in my inner Foreach or should I change something on it.?
Or You can suggest me how to delete the second data entry.?
Instead of using a foreach loop. You can also check the nullity first and then assign.
You can also do :
_oResolutionFacade.setResolutionID(_oResolutionModel.activityNo);
DataTable res = _oResolutionFacade.getResolution(_oAppSetting.ConnectionString);
_oResolutionModel.solution = res.Rows[0]["Resolution"].ToString() ?? string.Empty; //To make sure that if it is null it will assign to an empty string
_oResolutionModel.remarks = res.Rows[0]["Remarks"].ToString()?? string.Empty;
You have many solutions to deal with that.
Hope it will help you

Can I sum an EntityFramework Navigation column in my DataGridView

I have two tables/entities, event and eventRegistrations. I have a gridview showing events, but I would like it to have a column that sums eventRegistrations.SomeNumber.
I can get a count of eventRegistrations using the navigation object.
How can I get the sum?
If you can count the event registrations with event.EventRegistrations.Count() you should be able to get the sum using the LINQ Sum extension method of IEnumerable<T>:
var sum = event.EventRegistrations.Sum(er => er.SomeNumber);
Handled it like this:
public string GetRegSum(object _eventReg)
{
System.Collections.Generic.List<FormRouterData.family_fishing_reg> theseRegs = (System.Collections.Generic.List<FormRouterData.family_fishing_reg>)_eventReg;
var sum = theseRegs.Sum(er => int.Parse(er.num_group));
if (sum == 0) { return string.Empty; }
else { return sum.ToString(); }
}

Resources