How to get all rows and columns in Selenium? - c#

I have a table like this:
Name Places Sex Score
-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
Ken null Male 9.5
Smith London Male 7.5
Joe null null 8.0
I want to get all values of a table in Web using Selenium.
How to get values and show data in the table with columns and rows in above table?
My code to do this:
List<IWebElement> result = new List<IWebElement>();
IList<IWebElement> tableRows = browser.FindElements(By.XPath("id('column2')/tbody/tr"));
foreach (IWebElement rows in tableRows)
{
try
{
if (rows.FindElements(By.XPath("td")).Count == 10)
result.Add(rows);
}
}
And I only get all text of rows like this:
Ken Male 9.5
Smith London Male 7.5
Joe 8.0
As you can see, I only get only rows. And I can't know corresponding value column.
Joe 8.0 is not matched with:
Name Places Sex Score.
The HTML Markup of my table:
<div class="tabbox_F" id="oTableContainer_L">
<table id="column2">
<thead>
<tr class="tabthdwn">
<th>Name</th>
<th>Places</th>
<th>Sex</th>
<th>Score</th>\
</tr>
</thead>
<tbody>
<tr class="table Alpha">
<td>
<div class="name"><span>Ken</span></div>
<div class= "category"><span>Student</span></div>
</td>
<td><div class="address"></div></td>
<td><div class="sex"><h5>Male</h5></div></td>
<td>
<div class="score_math"><b>9.5</b></div>
<div class="score_bio"><b>7.5</b></div>ư
</td>
</tr>
<tr class="table Alpha">
<td>
<div class="name"><span>Joe</span></div>
<div class= "category"><span>Teacher</span></div>
</td>
<td><div class="address"></div></td>
<td><div class="sex"></div></td>
<td>
<div class="score_math"><b>8.0</b></div>
<div class="score_bio"><b>5.5</b></div>ư
</td>
</tr>
</tbody>
</table>
</div>

By looking at only the TDs, you aren't taking advantage of all the info you have in the HTML. Each TD has a class which tells you which bit of info is contained in each TD, e.g. <td class="name"> contains the name. Use that to your advantage to separate the different bits of data.
I would do something like this. I added the Values class to store the data for the row temporarily. If you don't need to reuse the data other than to just dump the values, you can just remove that bit.
class Program
{
static void Main(string[] args)
{
IWebDriver browser = new FirefoxDriver();
List<IWebElement> result = new List<IWebElement>();
IList<IWebElement> tableRows = browser.FindElements(By.XPath("id('column2')/tbody/tr"));
By nameLocator = By.ClassName("td > div.name");
By addressLocator = By.ClassName("td > div.address");
By sexLocator = By.ClassName("td > div.sex");
By scoretextLocator = By.ClassName("td > div.score_text");
// String.Format Method https://msdn.microsoft.com/en-us/library/aa331875(v=vs.71).aspx
Console.WriteLine("{0,10}{1,10}{2,10}{3,10}", "Name", "Address", "Sex", "Score");
foreach (IWebElement rows in tableRows)
{
Values values = new Values();
values.name = rows.FindElement(nameLocator).Text.Trim();
values.address = rows.FindElement(addressLocator).Text.Trim();
values.sex = rows.FindElement(sexLocator).Text.Trim();
values.scoretext = rows.FindElement(scoretextLocator).Text.Trim();
Console.WriteLine("{0,10}{1,10}{2,10}{3,10}", values.name, values.address, values.sex, values.scoretext);
}
}
}
class Values
{
public string name;
public string address;
public string sex;
public string scoretext;
public Values()
{
this.name = "";
this.address = "";
this.sex = "";
this.scoretext = "";
}
}

Why not this way:
List<IWebElement> result = new List<IWebElement>();
IList<IWebElement> tableRows = browser.FindElements(By.XPath("id('column2')/tbody/tr"));
foreach (IWebElement rows in tableRows)
{
IList<IWebElement> allColumns =row.FindElements(By.TagName("td"));
//and how allColumns[0] +1 etc .... gives you each values, including nulls
}

I think the only issue is how you're printing out your rows. Notice that some of the columns have no values. If you are not handling that in your output, then it will come out the way you've shown us above. If you use a debugger and look at the row element, you will likely find that there are still 4 td children in each row.

Related

Failing to get contents under input column

I use C# with Selenium trying to get the contents of row, column and cell from a t able. I have two columns, Name and Favorite Color. I can get contents under the column Name, but I fail to get content under Favorite Color column. The different between two columns is that, Favorite Color uses input tag. Below is HTML page.
<div class="tableBlock">
<table class="tableTag">
<tr>
<th>Name</th>
<th>Favorite Color</tr>
<tr>
<td>Ken Master</td>
<td>
<input type="text" value="yellow" class="favoriteColorInput"/>
</td>
</tr>
<tr>
<td>Adon Matsui</td>
<td>
<input type="text" value="red" class="favoriteColorInput"/>
</td>
</tr>
<tr>
<td>Robert Carlos</td>
<td>
<input type="text" value="Green" class="favoriteColorInput"/>
</td>
</tr>
<tr>
<td>Ronaldo Luis</td>
<td>
<input type="text" value="Green" class="favoriteColorInput"/>
</td>
</tr>
</table>
</div>
I try the follow code to get the content, but I fail to get content under Favoire Color column because it returns back as empty string.
public void TraverseTableElement()
{
//XPath to table
IWebElement tagTable =
webDriver.FindElement(By.XPath("//div[#class='tableBlock']/table"));
//get all rows
IList<IWebElement> tagRows = tagTable.FindElements(By.TagName("tr"));
string text = "";
//getrow
foreach (IWebElement tagRow in tagRows)
{
string td = "";
//get all columns
IList<IWebElement> tagCols = tagRow.FindElements(By.TagName("td"));
//get column
foreach (IWebElement tagCol in tagCols)
{
td = tagCol.GetAttribute("value");
text += td;
}
}
}
You need to read value from <input> for the second column/td
//for columns with textbox
//Edit 24/01/2018
var byTagNameinput = By.TagName("input");
if(tagCol.IsElementPresent(byTagNameinput){
var inputElement = tagCol.FindElement(byTagNameinput);
text+= inputElement.Text
}
====Edit 24/01/2018=====
Yes, you guys are right, It will throw an error.
we have handled this by creating an extension method on IWebElement, which checks whether the item is present or not. You can use the same if you want.
This method needs to be created inside a static class
public static bool IsElementPresent(this IWebElement webElement, By by)
{
try
{
webElement.FindElement(by);
return true;
}
catch (NoSuchElementException)
{
return false;
}
}
To get the values under Favorite Color column you can use the following code block :
List<string> colors = new List<string>();
IList<IWebElement> options = driver.FindElements(By.XPath("//div[#class='tableBlock']/table[#class='tableTag']//tr//td/input[#class='favoriteColorInput' and #type='text']"));
foreach (IWebElement option in options)
{
string temp = option.GetAttribute("value");
colors.Add(temp);
}

Html Agility Pack Loop Through Table - Get cell value based on previous cell value

I have multiple tables and Location Value is given in different index order.
How can I get location value if previous cell string is "Location" when I loop through table. On below example it is cells[7] but on other table it will be 9. How can I conditionally get values after cells inner text is "Location"? Basically find the cell "Location" get inner text of next cell.
Html Table:
<table class="tbfix FieldsTable"">
<tbody>
<tr>
<td class="name">Last Movement</td>
<td class="value">Port Exit</td>
</tr>
<tr>
<td class="name">Date</td>
<td class="value">26/06/2017 00:00:00</td>
</tr>
<tr>
<td class="name">From</td>
<td class="value">HAMBURGE</td>
</tr>
<tr>
<td class="name">Location</td>
<td class="value">EUROGATE HAMBURG</td>
</tr>
<tr>
<td class="name">E/F</td>
<td class="value">E</td>
</tr>
</tbody>
Controller Loop Through:
foreach (var eachNode in driver.FindElements(By.XPath("//table[contains(descendant::*, 'Last Movement')]")))
{
var cells = eachNode.FindElements(By.XPath(".//td"));
cd = new Detail();
for (int i = 0; i < cells.Count(); i++)
{
cd.ActionType = cells[1].Text.Trim();
string s = cells[3].Text.Trim();
DateTime dt = Convert.ToDateTime(s);
if (_minDate > dt) _minDate = dt;
cd.ActionDate = dt;
}
}
In your foreach loop you could use this:
var location = eachNode.FindElement(By.XPath(".//td[contains(text(),'Location')]/following-sibling::td));
Assuming your data is always structured like that I would loop over all the tags and add the data to a dictionary.
Try something like this:
Dictionary<string,string> tableData = new Dictionary<string, string>();
var trNodes = eachNode.FindElements(By.TagName("tr"));
foreach (var trNode in trNodes)
{
var name = trNode.FindElement(By.CssSelector(".name")).Text.Trim();
var value = trNode.FindElement(By.CssSelector(".value")).Text.Trim();
tableData.Add(name,value);
}
var location = tableData["location"];
You would have to add validation and checks for the dictionary and the structure but that is the general idea.

How to add whitespace between tags in Selenium?

I was using Selenium to get data from a table on the web page.
I have HTML with structure:
<table>
<tbody>
<tr>
<td>
<span>1</span>
<span>0</span>
<br>
<span>
<span>Good Luck</span>
<img src="/App_Themes/Resources/img/icon_tick.gif" width="3" height="7">
</span>
</td>
</tr>
<tr>
<td>
<b>Nowaday<br></b>
<p>hook<br>zp</p>
</td>
</tr>
</tbody>
</table>
I using this code to get all values in this table:
ReadOnlyCollection<IWebElement> lstTable = browser.FindElements(By.XPath("table/tbody/tr"));
foreach (IWebElement val in lstTable)
{
ReadOnlyCollection<IWebElement> lstTDElement = val.FindElements(By.XPath("td"));
}
But it shows result of like:
10Good LuckNowadayhookzp
I want to result like this:
1 0 Good Luck Nowaday hookzp
Have whitespace between a tag.
I think should add like this:
<span>1</span>
<span> </span>
<span>0</span>
And:
<b>Nowaday<br></b>
<p> </p>
<p>hook<br>zp</p>
You should try as below :-
ReadOnlyCollection<IWebElement> lstTDElements = browser.FindElements(By.TagName("td"));
var allTextList = lstTDElements.Select(El => EL.Text).ToList();
string FinalString = allTextList.Aggregate(new System.Text.StringBuilder(), (sb, s) => sb.Append(" "+s)).ToString().Replace("\n", "");
Console.WriteLine(FinalString);
Edited :- You can also get separate element togethor with | separator using xpath as below :-
ReadOnlyCollection<IWebElement> lstTable = browser.FindElements(By.XPath("table/tbody/tr"));
foreach (IWebElement val in lstTable)
{
ReadOnlyCollection<IWebElement> lstTDElement = val.FindElements(By.XPath("//td/span | //td/b | //td/p"));
}
Hope it helps...:)

Retrieve the table data with xpath and Selenium

I have HTML with looks basically like the following
....
<div id="a">
<table class="a1">
<tbody>
<tr>
<td><a href="a11.html>a11</a>
</tr>
<tr>
<td><a href="a12.html>a12</a>
</tr>
</tbody>
<table>
</div>
...
The following coding in C# I used, however, I cannot retrieve the URL in this stage
IWebElement baseTable = driver.FindElement(By.ClassName(TableID));
// gets all table rows
ICollection<IWebElement> rows = baseTable.FindElements(By.TagName("tr"));
// for every row
IWebElement matchedRow = null;
foreach(var row in rows)
{
Console.Write (row.FindElements(By.XPath("td/a")));
}
First of all, you gave us invalid markup. Right one:
<div id="a">
<table class="a1">
<tbody>
<tr>
<td>
a11
</td>
</tr>
<tr>
<td>
a12
</td>
</tr>
</tbody>
</table>
</div>
If you have only one anchor in table row, you should use this code to retrieve url:
IWebElement baseTable = driver.FindElement(By.ClassName(TableID));
// gets all table rows
ICollection<IWebElement> rows = baseTable.FindElements(By.TagName("tr"));
// for every row
IWebElement matchedRow = null;
foreach (var row in rows)
{
Console.WriteLine(row.FindElement(By.XPath("td/a")).GetAttribute("href"));
}
You need to get href attribute of found element. Otherwise, row.FindElement(By.XPath("td/a") will print type name of the IWebElement inherited class, because it is an some type object, not string.
This does not look like a valid xpath to me
Console.Write (row.FindElements(By.XPath("td/a")));
try
Console.Write (row.FindElements(By.XPath("/td/a")));

Parse Table with LINQ and HtmlAgilityPack

How can I parse HTML using LINQ on a webpage to get the innerhtml values from the table?
I am using the HtmlAgilityPack and would like to parse some values as good as possible.
the number you see(00000, 00001, 00002..), are unique numbers from the agents.
So maybe there is a way to use LINQ to parse those numbers and get the following values from td's
(Name, 123, state, and info) => 00000, John, 123, IDLE, coffee for each
so I can call them separately and work with them - maybe in a array?
</TH>
</TR>
<TR ALIGN=RIGHT>
<TD ALIGN=LEFT>00000</TD>
<TD ALIGN=LEFT>John</TD>
<TD ALIGN=CENTER>123</TD>
<TD ALIGN=LEFT>IDLE</TD>
<TD ALIGN=LEFT>coffee</TD>
</TR>
<TR ALIGN=RIGHT>
<TD ALIGN=LEFT>00001</TD>
<TD ALIGN=LEFT>Lisa</TD>
<TD ALIGN=CENTER>123</TD>
<TD ALIGN=LEFT>IDLE</TD>
<TD ALIGN=LEFT>coffee</TD>
</TR>
<TR ALIGN=RIGHT>
<TD ALIGN=LEFT>00002</TD>
<TD ALIGN=LEFT>Mary</TD>
<TD ALIGN=CENTER>123</TD>
<TD ALIGN=LEFT>IDLE</TD>
<TD ALIGN=LEFT>coffee</TD>
</TR>
<TR ALIGN=RIGHT>
<TD ALIGN=LEFT>00003</TD>
<TD ALIGN=LEFT>Tim</TD>
<TD ALIGN=CENTER>123</TD>
<TD ALIGN=LEFT>IDLE</TD>
<TD ALIGN=LEFT>coffee</TD>
</TR>
....
Thanks in advance!
This seems a lot like a "please give me the code I need question", which I seriously dislike. Have a look at the following and make sure you understand it:
var doc = ... // Load the document
var trs = doc.DocumentNode.Descendants("TR"); // Give you all the TRs
foreach (var tr in trs)
{
var tds = tr.Descendants("TD").ToArray(); // Get all the TDs
// Turn them into our datastructure
var data = new {
Name = tds[1].InnerText,
Number = tds[2].InnerText,
State = tds[3].InnerText,
Info = tds[4].InnerText,
};
// Do something with data
}
Doing it with LINQ only:
var data = from tr in doc.DocumentNode.Descendants("TR")
let tds = tr.Descendants("TD").ToArray()
select new {
Name = tds[1].InnerText,
Number = tds[2].InnerText,
State = tds[3].InnerText,
Info = tds[4].InnerText,
};
#flindeberg makes a perfectly reasonable answer (+1 to he/she), you could avoid the ToArray like this.
private class Row
{
public string Name { get; set; }
public int Number { get; set; }
public string State { get; set; }
public string Info { get; set; }
}
...
var mappings = new Action<string, Row>[]
{
(value, row) => row.Name = value,
(value, row) => row.Number = int.Parse(value),
(value, row) => row.State = value,
(value, row) => row.Info = value
};
var doc = ... // Load the document
var trs = doc.DocumentNode.Descendants("TR"); // Give you all the TRs
foreach (var tr in trs)
{
var row = new Row();
tr.Descendants("TD").Zip(mappings, (td, map) =>
{
map(td.InnerText, row);
return true;
});
// You now have a populated row.
}

Categories

Resources