Finding the child of a parent's sibling element with WatiN - c#

The scenario that I am looking at is that we have a table with multiple columns. One of those columns has a name, another has a dropdown list. I need to manipulate the dropdown for a row that contains a particular name. I looked at the source output, and tried getting the element's grandparent (the table row) so that I could search for the list. However, there was no such search functionality when I used the parent object.
It seems like there would be a lot of this kind of scenario in automating/testing a site, but I have not found anything after searching for a couple of hours. Any help would be appreciated.
EDIT: The application in question is an ASP.NET, and the output HTML is gnarly at best. However, here is a cleaned up example of what the HTML being searched looks like:
<table class="myGrid" cellspacing="0" cellpadding="3" rules="all" border="1" id="ctl00_content_MyRpt_ctl01_MyGrid" style="border-collapse:collapse;">
<tr align="left" style="color:Black;background-color:#DFDBDB;">
<th scope="col">Name</th><th scope="col">Unit</th><th scope="col">Status</th><th scope="col">Action</th>
</tr>
<tr>
<td>
<span id="ctl00_content_MyRpt_ctl01_MyGrid_ctl02_Name">JOHN DOE</span>
</td>
<td>
<span id="ctl00_content_MyRpt_ctl01_MyGrid_ctl02_UnitType">Region</span>
<span id="ctl00_content_MyRpt_ctl01_MyGrid_ctl02_UnitNum">1</span>
</td>
<td>
<span id="ctl00_content_MyRpt_ctl01_MyGrid_ctl02_Status">Complete</span>
</td>
<td class="dropdown">
<select name="ctl00$content$MyRpt$ctl01$MyGrid$ctl02$ActionDropDown" onchange="javascript:setTimeout('__doPostBack(\'ctl00$content$MyRpt$ctl01$MyGrid$ctl02$ActionDropDown\',\'\')', 0)" id="ctl00_content_MyRpt_ctl01_MyGrid_ctl02_ActionDropDown" class="dropdown">
<option value="123456">I want to...</option>
<option value="Details.aspx">View Details</option>
<option value="Summary.aspx">View Summary</option>
<option value="DirectReports.aspx">View Direct Reports</option>
</select>
</td>
</tr>
<tr>
...
</tr>
</table>

I found a way to do what I wanted. It is probably not the best or most elegant solution, but it works (it is not production code).
private void btnStart_Click(object sender, EventArgs e)
{
using (var browser = new IE("http://godev/review"))
{
browser.Link(Find.ByText("My Direct Reports")).Click();
TableRow tr = browser.Span(Find.ByText("JOHN DOE")).Parent.Parent as TableRow;
SelectList objSL = null;
if (tr.Exists)
{
foreach (var td in tr.TableCells)
{
objSL = td.ChildOfType<SelectList>(Find.Any) as SelectList;
if (objSL.Exists) break;
}
if (objSL != null && objSL.Exists)
{
Option o = objSL.Option(Find.ByText("View Direct Reports"));
if (o.Exists) o.Select();
}
}
}
}
Hopefully this saves someone a little time and effort. Also, I would love to see if someone has a better solution.

Related

Using HtmlAgilityPack with C# to find all href links within td elements in html page

I am attempting to use HtmlAgilityPack package to find each of the href links within td tags throughout an entire html page. The trick is that these tables start deep down into the html structure. I noticed with HtmlAgilityPack you can't just say get all tds that are within trs on a page. There is a parent div wrapped around each table with a class on it "table-group" that I am not showing in my sample below. Maybe I can use that as a starting point? The biggest trouble that I am dealing with is that there are several parent elements above everything in my sample below, but I want to skip all of that and start here.
Here is a sample of the structure I am trying to navigate:
<table>
<thead>
</thead>
<tbody>
<tr>
<td>Link 1</td>
<td>1</td>
</tr>
<tr>
<td>Link 2</td>
<td>2</td>
</tr>
<tr>
<td>Link 3</td>
<td>3</td>
</tr>
</tbody>
</table>
<table>
<thead>
</thead>
<tbody>
<tr>
<td>Link 4</td>
<td>4</td>
</tr>
<tr>
<td>Link 5</td>
<td>5</td>
</tr>
<tr>
<td>Link 6</td>
<td>6</td>
</tr>
</tbody>
</table>
I would like my end result to be:
https://path-to-pdf1
https://path-to-pdf2
https://path-to-pdf3
https://path-to-pdf4
https://path-to-pdf5
https://path-to-pdf6
Here is what I have tried:
var html = #"https://myurl.com";
HtmlWeb web = new HtmlWeb();
var htmlDoc = web.Load(html);
var nodes = htmlDoc.DocumentNode.SelectNodes("//table/tbody/tr/td/a[0]");
foreach (var item in nodes)
{
Console.WriteLine(item.Attributes["href"].Value);
}
Console.ReadKey();
Modify
var nodes = htmlDoc.DocumentNode.SelectNodes("//table/tbody/tr/td/a[0]");
to
var nodes = htmlDoc.DocumentNode.SelectNodes("//table/tbody/tr/td[1]/a");
then you wil get the result you want ,you could read the documents related with XPath for more details
I tried in a MVC project with the same html file:
Update:
I copied the html codes to the html page in my local and get the nodes successfully

ASP-routing from tavlerow instead of anchor

I'm trying to learn asp.net and I'm trying to use the ASP routing directly from the tablerow (tr), instead of for each anchor point inside the cell.
Below code, I'm able to click the "Case ID" in my table, but this leaves some unclickable area around the actual text. I basically want to be able to simply select the row and pass the #sc.ID for a database query in the ViewCase-page.I want end-user to click "category" (and several columns in the future), without having to define tags everyhwere.
#foreach (var sc in Model.Cases)
{
<tr asp-area="" asp-page="ViewCase" asp-route-ID="#sc.ID">
<td>
<a asp-area="" asp-page="ViewCase" asp-route-ID="#sc.ID">
Case #Html.DisplayFor(Moldel => #sc.ID)
</a>
</td>
<td>
#Html.DisplayFor(Moldel => #sc.Category)
</td>
</tr>
}
It seems you do not want not want to specify the a tag every where. Tag helper does not work for table element, you just add js in the tr element like below:
<table class="table">
#foreach (var sc in Model.Cases)
{
<tr onclick="window.location.href='/ViewCase?ID=#sc.ID'">
<td>
Case #Html.DisplayFor(Moldel => #sc.ID)
</td>
<td>
#Html.DisplayFor(Moldel => #sc.Category)
</td>
</tr>
}
</table>

How do I get my XPath to search only within each table?

I have a bit of HTML that looks like this:
<table class="resultsTable">
<tbody>
<tr class="even">
<td width="35%"><strong>Name</strong></td>
<td>ACME ANVILS, INC</td>
</tr>
</tbody>
</table>
and some C# code that looks like this:
var name = document.DocumentNode
.SelectSingleNode("//*[text()='Name']/following::td").InnerText
which happily returns
ACME ANVILS, INC.
However, there's a new wrinkle. The page in question now returns multiple results:
<table class="resultsTable">
<tbody>
<tr class="even">
<td width="35%"><strong>Name</strong></td>
<td>ACME ANVILS, INC.</td>
</tr>
</tbody>
</table>
<table class="resultsTable">
<tbody>
<tr class="even">
<td width="35%"><strong>Name</strong></td>
<td>ROAD RUNNER RACES, LLC</td>
</tr>
</tbody>
</table>
So now I'm working with
var tables = document.DocumentNode.SelectNodes("//table/tbody");
foreach (var table in tables)
{
var name = table.SelectSingleNode("//*[text()='Name']/following::td").InnerText;
...
}
Which falls over, because SelectSingleNode returns null.
How do I get my XPath to actually return a result, searching only within the specific table I have selected?
With the addition of a second table, two adjustments are required:
Change your absolute XPath,
//*[text()='Name']/following::td
to one relative to the current table or tbody element:
.//*[text()='Name']/following::td
Account for there now being more than one td element on the
following:: axis.
Either just grab the first,
(.//*[text()='Name']/following::td)[1]
or, better, use the following-sibling:: axis instead in combination
with a test on the string value of td rather than a test on a text node, which might be buried beneath intervening formatting elements:
.//td[.='Name']/following-sibling::td
See also Difference between Testing text() nodes vs string values in XPath.

Multiplying a textbox with a cell in a dynamically created table with JQuery

I have a dynamically created table with id called "editTable" that looks as follows:
<tbody>
#{var i = 0;}
#foreach (var item in Model)
{
<tr>
<td width="25%">
#Html.DisplayFor(modelItem => item.Product.Name)
</td>
<td width="25%">
#Html.DisplayFor(modelItem => item.Quantity)
</td>
<td width="25%">
<div class="editor-field">
#Html.EditorFor(modelItem => item.UnitPrice)
#Html.ValidationMessageFor(model => item.UnitPrice)
</div>
</td>
<td width="25%" id="total"></td>
</td>
</tr>
}
</tbody>
The 3th td-element consists of a C# textbox that is turned into a element in html.
Now I want to multiply the quantity by the unit price to display this value in the 4th td element next to it. This value should update every time the value in the textbox is adjusted. I am a newbie at JQuery / JavaScript and came up with the following code:
// Calculating quantity*unitprice
$('#editTable tr td:nth-child(3) input').each( function (event) {
var $quant = $('#editTable tr td:nth-child(2)', this).val();
var $unitPrice = $('#editTable tr td:nth-child(3) input', this).val();
$('#editTable tr td:nth-child(4)').text($quant * $unitPrice);
});
This doesn't work and only displays NaN in the 4th element. Can anyone help me updating this code to a working version? Any help would be very much appreciated.
I geussed you accidentally switched units and price because it has more logic to change the number of units then the price. I took your html and javascript and tried to change as little as possible to make it work (I'm not saying the solution is perfect, I just don't want to give you a totaly different example of how to do it).
The html (The C# is irrelevant for this problem):
<table id="editTable">
<tbody>
<tr>
<td width="25%">
Product name
</td>
<td width="25%">
5
</td>
<td width="25%">
<div class="editor-field">
<input id="UnitPrice" name="UnitPrice" type="number" value="2" style="width:40px" />
</div>
</td>
<td width="25%" id="total"></td>
</tr>
</tbody>
</table>
The javascript/jquery (which should run on load):
$('#editTable tr td:nth-child(3) input').each(updateTotal);
$('#editTable tr td:nth-child(3) input').change(updateTotal);
var element;
function updateTotal(element)
{
var quantity = $(this).closest('tr').find('td:nth-child(2)').text();
var price = $(this).closest('tr').find('td:nth-child(3) input').val();
$(this).closest('tr').find('td:nth-child(4)').text(quantity * price);
}
The problem you had were with jquery. I've created a function that recieves an element (in our case it's your UnitPrice input), then it grabs the closest ancestor of type tr (the row it's in) and from there it does what you've tried to do.
You've used jquery selector to get all 2nd cells in all table rows, the closest('tr').find limits it to the current row.
You've tried to use .val() on a td element, you should use either .text() or .html(). Instead, You can also add a data-val="<%=value%>" on the td and then use .data('val').
It will be better to take the units directly from $(element).val() and no going to the tr and then back into the td and the input.
To see it working: http://jsfiddle.net/Ynsgf/1/
I hope I didn't caused you any confusion with my explanation and the options I gave you.
Here is another way to write the jquery part.
$('#editTable tr').each(function (i, row) {
var $quant = $(row).find('.editor-field input').val();
var $unitPrice = $(row).find('.editor-field input').val();
$(row).find('td:nth-child(4)').text($quant * $unitPrice);
});

WebMatrix filling attribute and JavaScript Reading Attribute not producing expected results

I am having an issue with the following code:
SECTION FROM CSHTML FILE:
#if(errorMessage != "")
{
err="err";
}
else
{
err="";
}
#if(errorMessage == "")
{
foreach(var row in db.Query(stringCompiler, EntryID, POIName, DateLastModified, Gender, Race, Height, Weight, HairColor, EyeColor, DOB, Age, SS, DL, DOC, VehicleTag, FBI, Officer, HomePhone, CellPhone, CellPhone2, CellPhone3, POICautions, WorkPhone, WeightedAggregate, Address, AdditionalDescriptors, Aliases, SourceOfInformation, AddressInformation, KnownAssociates, VehicleDescription, Comments, SummarizedIncidents, AllCellPhones, AllPhones, betDOB1, betDOB2, betDLM1, betDLM2, betAge1, betAge2, POILastName, SearchAll))
{
<div class="searchResult">
<table style="background-color: #beebeb;">
<tr>
<td class="entryLabel">ENTRY ID</td>
<td class="entryLabel">FIRST NAME</td>
<td class="entryLabel">LAST NAME</td>
<td class="entryLabel">DATE LAST MODIFIED</td>
<td class="entryLabel">DOB</td>
<td class="entryLabel">AGE</td>
<td class="entryLabel">ADDRESS</td>
<td class="entryLabel">VEHICLE TAG#</td>
<td class="entryLabel">OFFICER</td>
<td class="entryLabel">HOME PHONE</td>
<td class="entryLabel">CELL PHONE</td>
<td class="entryLabel">WEIGHTED AGGREGATE</td>
</tr>
<tr>
<td class="entry">#row.EntryID</td>
<td class="entry">#row.POIName</td>
<td class="entry">#row.POILastName</td>
<td class="entry">#row.DateLastModified</td>
<td class="entry">#row.DOB</td>
<td class="entry">#row.Age</td>
<td class="entry">#row.Address</td>
<td class="entry">#row.VehicleTag</td>
<td class="entry">#row.Officer</td>
<td class="entry">#row.HomePhone</td>
<td class="entry">#row.CellPhone</td>
<td class="entry">#row.WeightedAggregate</td>
</tr>
<tr>
<td colspan="13" style="text-align: center;"><form method="post" action="/ComputeLookupToVAndE.cshtml"><input type="hidden" name="veEntryID" hidden="hidden" readonly="true" value="#row.EntryID" /><br/><input type="submit" value="View & Edit" class="btn3" style="height: 40px; width: 100px;" /><br/><br/></form></td>
</tr>
</table>
</div><br/>
ResultCount += 1;
}
<input type="hidden" id="ResultCount" value="#ResultCount" />
<input id="err" type="hidden" value='#err' />
}
SECTION FROM JAVASCRIPT FILE:
$(document).ready(function () {
if ($("#err").val() == "err")
{
$("#searchForm").attr('action', "/LookUpEntry.cshtml#top");
}
else
{
$("#searchForm").attr('action', "/LookUpEntry.cshtml#searchList");
}
});
Obviously I am trying to change the value of the hidden input element to "err" before the page is rendered by C# if any error messages have been stored in the variable errorMessage.
Once done JavaScript is supposed to read this value and then if it is "err" change the action attribute of the form to append "#top" (for named anchor, so the loaded position on the page changes based off of whether there is an error message or results to view), otherwise the form's action attribute should be changed (still by JavaScript) to append #searchList).
Any idea why JavaScript and C# won't play well together? The errors I am getting are haphazard (like it won't work the first time, then doesn't work at all), anyway based on the code below, it still never goes to (#top), although when trying a few things before this exact coding (I don't remember exactly what they were) it would sometimes (although never the first time) go to the top if it had errored.
Why is jQuery not doing its job? It seems like this should just work to me...
Any ideas?
Thanks for any help
Well, I guess I have it figured out and I suppose it does make sense:
It does change the value as it should, but the action (how the form [or rather where, the way I look at it] handles the data posted) is already set to what it where it will go regardless of the results of the current search. In short: It is a step behind. When I click to search and an error Message is produced, it will rewrite the action as intended, but it will, of course, only come into effect on the next search not the current one as that was already determined on the previous submit.
What's wrong with my code? Nothing syntactically, the methodology (logic) is in err.

Categories

Resources