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);
}
Related
This is through the Blazor Server App.
I have a text file that looks like this:
TEXT00
Some title
TEXT10
8
DATA
110,4,2
110,0,6
110,0,32
110,4,16
110,0,16
110,4,3
110,0,2
...
...
There are two things I want to accomplish:
First I want such a file be loaded on to an editable table, where the numbers under the DATA line should go in each their own editable cell.
Illustration:
Tempo
Length
secs
110
4
2
110
0
6
110
0
32
Secondly I want the content in the cells being able to be saved, such that it replaces the original text file in the directory.
With the press of a button, the file gets loaded in the a cell which is editable with the use of contenteditable="true". I have tried but failed at loading the numbers into their own cells. The save file button doesn't work when it comes to data cells.
Here is the open button, table and save button:
<button #onclick="OpenFile">Open file</button>
<div class="table-wrapper-scroll-y my-custom-scrollbar">
<table class="table table-bordered table-striped mb-0">
<thead class="bg-light">
<tr>
<th>Title: </th>
</tr>
<tr>
<th>Tempo</th>
<th>Length</th>
<th>Secs</th>
</tr>
</thead>
<tbody>
<tr>
<td><div contenteditable="true">#_contents</div> </td>
<td><div contenteditable="true"></div></td>
<td><div contenteditable="true"></div></td>
</tr>
<tr>
<td><div contenteditable="true"></div> </td>
<td><div contenteditable="true"></div></td>
<td><div contenteditable="true"></div></td>
</tr>
</tbody>
</table>
</div>
<button #onclick="SaveFile">Save file</button>
Here are the functions which loads the file and the one which should save the new one.
#code {
string _contents { get; set; } = string.Empty;
void OpenFile()
{
_contents = File.ReadAllText(#"path");
}
void SaveFile()
{
File.WriteAllText(#"path", _contents);
}
}
Does anyone have some knowledge on how to insert the numbers in cells such that when saved, the txt file gets replaced by the edits?
To achive this you'll need to use binding elements. For example:
<input #bind="YourProperty" type="text" />
This needs to be done for each column and every row. The best solution would look something like this:
List<WhateverItem> items = new(); // This list should hold all your objects from your text file
public class WhateverItem
{
public string Tempo { get; set; }
public string Length { get; set; }
public string Secs { get; set; }
}
Then in .razor
#foreach (var item in items)
{
<tr>
<td>
<input #bind="item.Tempo" type="text" />
</td>
<td>
<input #bind="item.Length" type="text" />
</td>
<td>
<input #bind="item.Secs" type="text" />
</td>
</tr>
}
When you save, you'll need to recreate the content out of the objects with a StringBuilder.
If you want to validate the fields, think of using an EditForm. Keep in mind that you can also bind to other types besides of string. You'll need to change the input type then.
Example of parsing the file into objects:
List<WhateverItem> items = new();
foreach (string line in System.IO.File.ReadLines(#"c:\test.txt"))
{
string[] values = line.Split(',');
if(values.Length == 3)
{
// Keep in mind you can also convert to values right here if you want
items.Add(new WhateverItem
{
Tempo = values[0],
Length = values[1],
Secs = values[2]
});
}
}
I am developing add to read web browser data and store it into a dictionary.
During this process, I need to access data By ID but the IDs are not Unique on the page. The page looks like this.
<div id="ID1">
<tbody>
<tr>
<td id="1000" data-field="1">
text
</td>
</tr>
</tbody>
<div id="ID2">
<tbody>
<tr>
<td id="1000" data-field="2">
Some other text
</td>
</tr>
</tbody>
both div elements are on the same page
when I get element By Id It only gives me the first element, not the second one.
Here is My code
HtmlElement myElements = webBrowser1.Document.GetElementById("ID2");
HtmlElement myElements2 = myElements.Document.GetElementById("1000");
if (myElements2.InnerText != null)
{
//Do something
}
How Can I get the inner text of the second element by ID
This is the best and the easiest answer I came up with
I figured out the data-field is a unique value in the page so I looped through the elements and compared it with data-field
HtmlElement Buildingcontacts = webBrowser1.Document.GetElementById("ID2");
HtmlElementCollection ifiels = Buildingcontacts.Document.GetElementsByTagName("td");
foreach (HtmlElement element in ifiels)
{
string datafieldx = element.GetAttribute("data-field");
if (datafieldx == "2")
{
if (element.InnerText != null)
{
//do Somthing
}
}
}
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.
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")));
I have table to collect Contacts information if contact have more than one contact the Different phones and emails.the user can add more row to add more contacts to Customer.
I use JavaScript to add new row and html controls
My Problem is when user have more then one row. I don't have idea how to insert more than 1 contact at sometime and how to get data from html controls in row that added by Javascript
HTML tags
<asp:Panel ID="p_contacts" runat="server" GroupingText="Contacts">
<table id="tableContact">
<thead>
<tr>
<th scope="col">
Contact
</th>
<th scope="col">
Contact Type
</th>
<th scope="col">
Status
</th>
<th scope="col">
Note
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input name="contact1" id="contact1" runat="server" onclick="return contact1_onclick()">
</td>
<td>
<select name="contactType1" id="contactType1" runat="server">
<option value="">Please select</option>
<option value="1">Email</option>
<option value="2">Phone</option>
</select>
</td>
<td>
<select name="status1" id="status1" runat="server">
<option value="">Please select</option>
<option value="1">Active</option>
<option value="2">Not Active</option>
</select>
</td>
<td>
<input name="noteContact1" id="noteContact1" runat="server">
</td>
</tr>
</tbody>
</table>
<button id="AddContact">
<img src="images/button-add_blue.png" alt="btnAdd" height="26" width="26" /></button>
</asp:Panel>
Javascript
$("#AddContact").click(function () {
// add new row to table using addTableRow function
addTableRow($("#tableContact"));
// prevent button redirecting to new page
return false;
});
// function to add a new row to a table by cloning the last row and
// incrementing the name and id values by 1 to make them unique
function addTableRow(table) {
// clone the last row in the table
var $tr = $(table).find("tbody tr:last").clone();
// get the name attribute for the input and select fields
$tr.find("input,select").attr("name", function () {
// break the field name and it's number into two parts
var parts = this.id.match(/(\D+)(\d+)$/);
// create a unique name for the new field by incrementing
// the number for the previous field by 1
return parts[1] + ++parts[2];
// repeat for id attributes
}).attr("id", function () {
var parts = this.id.match(/(\D+)(\d+)$/);
return parts[1] + ++parts[2];
});
// append the new row to the table
$(".date").datepicker();
$(table).find("tbody tr:last").after($tr);
};
C#
protected void btn_add_Click(object sender, EventArgs e)
{
try
{
//customer contact inforation
Contacts contact = new Contacts();
contact.CustomerID = 1;
contact.ContactDetail= contact1.Value.ToString();
contact.LabelContactTypeID = (int)contactType1.SelectedIndex;
contact.Status = Convert.ToBoolean(status1.SelectedIndex);
contact.Note = noteContact1.Value.ToString();
//bool successedAddCustomer = Customer.AddNewCustoemr(cust);
bool successedAddCustomer_Contacts = Contacts.AddNewCustomer_Contact(contact);
if (!successedAddCustomer_Contacts)
{
Response.Write("Customer add");
}
else
Response.Write("Can't add new customer");
}
catch
{ }
}
Add in a new hidden field that will contain the count of the added items. In your code behind, use the Request.Form collection to find the items in a loop, something like
for (int i = 0, i < Request.form["HiddenFieldHere"], i++)
{
Contacts contact = new Contacts();
contact.CustomerID = i;
contact.ContactDetail= Request.Form[string.format("contact{0},i)].ToString();
}
this example ain't perfect, but I hope it gives you some direction.