select two cells from table with span class - html-agility-pack

How can I select two cells in table bases on span class?
my html looks like this.
what I want is to select innertext of span class="store-name-span"
and span class="price"
<table class="list mixed zebra-striped">
<tbody>
<tr data-pris_typ="normal">
<td class="span4-5">
<span class="store-name-span">Electroworld</span>
<a data-drg="store-2641" class="drg-sidebar"></a>
</td>
<td class="span3 cell-bar">
<span class="chart-bar price" style="width:50px"></span>
<span class="price" title="Uppdaterad 2013-02-18 08:23">1 690:-</span>
</td>
</tr>
<tr data-pris_typ="normal">
<td class="span4-5">
<span class="store-name-span">Webhallen</span>
<a data-drg="store-113" class="drg-sidebar"</a>
</td>
<td class="span3 cell-bar">
<span class="chart-bar price" style="width:50px"></span>
<span class="price" title="Uppdaterad 2013-02-18 13:55">1 690:-</span>
</td>
</tr>
</tbody>
</table>
var Nodes = from x in doc2.DocumentNode.Descendants()
//where x.Attributes["class"].Value == "store-name-span"
where x.Name == "span" && x.Attributes["class"].Value == "store-name-span"
select x.InnerText;

I'd use xpath for this:
var nodes = doc.DocumentNode.SelectNodes("//span[#class='store-name-span' or #class='price']");
foreach (var node in nodes)
Console.WriteLine(node.InnerText);
By using LINQ:
var nodes = doc.DocumentNode.Descendants("span")
.Where(s =>
s.GetAttributeValue("class", null) == "store-name-span" ||
s.GetAttributeValue("class", null) == "price"
);
this will get you:
Electroworld
1 690:-
Webhallen
1 690:-

In that particular HTML layout, you can do:
var items = doc.DocumentNode.SelectNodes("//tr[#data-pris_typ='normal']").Select(x => new
{
Store = x.SelectSingleNode(".//span[#class='store-name-span']").InnerText,
Price = x.SelectSingleNode(".//span[#class='price']").InnerText
});
On items you'll get what you need. Each item will be an anonymous type with the Store and Price fields.
One important thing:
You might want to clean the fields (like Price) using HttpUtility.HtmlDecode(). To do that you will have to add a reference to the System.Web assembly.

I would use a combination of querySelectorAll and fetching innerHTML.
queryselectors work both on calling globally (on document) as well as for a single element.

Related

Unable to click on Select link present in last column of web table

I want to search Seller and have to click on select link for selected one. When I type seller name, it shows only record for selected seller.
I tried with following code, its not working. Can anyone please help
cy.get('input[name="search"]',{ timeout: 10000 }).type(this.data1.vehicle1_seller1)
//cy.wait(6000)
Cypress.config('defaultCommandTimeout', 10000);
cy.get('td[class="span-3"] div').each(($el, index, $list) => {
if ($el.text().includes('STB002')) {
// cy.contains("Select").eq(index).click()
cy.get('.span-1-5 > div > a > span').contains('select').eq(index).click({force:true})
}
}
this is the DOM structure >
<table>
<tbody>
<tr class="even">
<td class="span-3">
<div title="06V001">06V001</div> == $0
</td>
<td>
<div title="06 Vauxhall Ormskirk">06 Vauxhall Ormskirk</div>
</td>
<td class="span-1-5">
<div>
<a id="link57" href="./wicket/page?7-1.-seller-table-body-rows-10-cells-3-cell-link">
<span>select</span>
</a>
</div>
</td>
</tr>
<tr class="odd">
</tr>
<tr class="even">
</tr>
</tbody>
</table>
The HTML table is set out in rows and cells, exactly as you see it on the screen.
Your test is searching for the cell containing the text, but really you want to search for the row containing the text, then get the select button of that row.
The basic test would be
cy.contains('tr', 'STB002')
.within(() => {
// now inside the row
cy.contains('span', 'select').click()
})
The next problem is the car STB002 isn't on the first page, so you won't find it straight after loading.
Maybe use the search box to load that row (as you have in one screen-shot). I can't say what that code is, because the DOM picture doesn't include the search box.

How to get between two br tags in xpath?

I have a table with td like this
<td>
<span> Washington US <br>98101 Times Square</span>
</td>
I can get all the elements in the page, but I need to get those two values separately. If that isn't possible I would like to somehow get 98101 Times Square
I have tried doing something like string(//tr[3]//td[2])/ but all I get is the two text joined together.
You can select the text child nodes in the span element with span/text() so assuming your posted path selects the td containing the span you want //tr[3]//td[2]/span/text().
Here is a sample:
$html = <<<EOD
<html>
<body>
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3,1</td>
<td>
<span> Washington US <br>98101 Times Square</span>
</td>
</tr>
</body>
</html>
EOD;
$doc = new DOMDocument();
$doc->loadHTML($html);
$xpath = new DOMXPath($doc);
$textNodes = $xpath->query('//tr[3]//td[2]/span/text()');
foreach ($textNodes as $text) {
echo $text->textContent . "\n";
}
Outputs
Washington US
98101 Times Square
Try
td/span/node()[1]
and
td/span/node()[3]
Or
td/span/text()[1]
td/span/text()[2]

Finding only required links on a webpage using xpath

I have a webpage that has more than hundred links on a webpage. I am trying to see if i can only store the required number of web links and store them in a array and run a loop for verifying data within each individual links.
The number of links are dynamic and change as per the user, so each time the page loads it has to find the specified links.
This is what i currently have under the for loop for each user:
List<WebElement> linksize = driver.findElements(By.xpath(obj.getProperty("totalbookings")));
int linksCount = linksize.size();
String[] linksENR = null;
linksENR = new String[linksCount];
System.out.println(linksCount);
if (linksCount > 0)
{
System.out.println("We have enrollment");
System.out.println(linksCount);
for (int i = 1; i <= linksCount; i++)
{
driver.navigate().to(linksENR[i]);
System.out.println("here");
}
}
else
{
System.out.println("No enrollment record found");
}
The xpath for the elements that i am trying to click for user 1 is and there is one element:
.//*[#id='006g0000007RH1V_00Nb0000009Yc1K_body']/table/tbody/tr[2]/th/a
The xpath for the elements that i am trying to click for user 2 is as follows and there are two elements:
.//*[#id='006g0000007RH1V_00Nb0000009Yc1K_body']/table/tbody/tr[2]/th/a
.//*[#id='006g0000007RH1V_00Nb0000009Yc1K_body']/table/tbody/tr[3]/th/a
This is what i am using to search with the xpath:
totalbookings = .//*[contains(#id, "7RH1V_00Nb0000009Yc1K_body") and contains(#tagName , "a")]
Output:
No enrollment record found
Is there way to uniquely identify only few chosen links with the xpath? Please assist.
Here is the HTML information for the element:
<table class="list" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr class="headerRow"></tr>
<!--
ListRow
-->
<tr class="dataRow even first" onmouseover="if (window.hiOn){hiOn(this);}" onmouseout="if (window.hiOff){hiOff(this);}" onfocus="if (window.hiOn){hiOn(this);}" onblur="if (window.hiOff){hiOff(this);}">
<td class="actionColumn"></td>
<th class=" dataCell " scope="row">
<a class=" firepath-matching-node" href="/a1Bg0000001ih6M"></a>
</th>
<td class=" dataCell CurrencyElement"></td>
<td class=" dataCell "></td>
<td class=" dataCell "></td>
<td class=" dataCell "></td>
<td class=" dataCell numericalColumn"></td>
<td class=" dataCell "></td>
</tr>
<!--
ListRow
-->
<tr class="dataRow odd last" onmouseover="if (window.hiOn){hiOn(this);}" onmouseout="if (window.hiOff){hiOff(this);}" onfocus="if (window.hiOn){hiOn(this);}" onblur="if (window.hiOff){hiOff(this);}"></tr>
</tbody>
</table>
I would do it more like this:
List<WebElement> pl = driver.findElements(By.xpath(".//a[contains(#id,'totalbookings')]"));
if (pl.size() > 0)
{
System.out.println("We have enrollment. Looping through links...");
for ( WebElement we : pl )
{
we.click();
System.out.println("Clicked link: " + we.toString() );
thisPageObject.verifyPageLoaded();
driver.executeScript("window.history.go(-1)");
org.openqa.selenium.browserlaunchers.Sleeper.sleepTight(1000);
}
} else {}
System.out.println("No enrollment record found");
}
I guess contains(#tagName , "a") is intended to mean that the current element has a descendent element a. But it actually means that the current element has an attribute named tagName, and this attribute's string value contains the letter "a".
This says to me that you are trying to do something moderately complex with a tool that you really know little about. There's no shame in not knowing XPath, but a few hours spent learning the fundamentals will spare you many hours of trial and error and trying to communicate your problem on web forums.
To do what I think you intended to do, you can use the following XPath expression:
.//*[contains(#id, "7RH1V_00Nb0000009Yc1K_body")]//a
That is if you're trying to select the links themselves. If you're trying to select the element whose id you're testing, as your totalbookings XPath expression implies, you could do
.//*[contains(#id, "7RH1V_00Nb0000009Yc1K_body") and .//a]
but that would not select the links, which was your stated goal.
Also, both of the above look for descendants of the current element (whatever that is). With driver.findElements() you don't seem to have a current node, so it would make more sense to remove the . from the beginning of the XPath and just use //*... to make it clear that you're looking for nodes anywhere in the web page, not just in some chosen subtree.

Get first and second td element in row

I have an ajax call attached to the click event of a pic inside a table row. Once the pic is clicked and the click event initiated, I need to grab the first and second td elements from that row. I'm new to jQuery so what I have below is my latest attempt (not working..). The variables firstName and lastName both wind up being undefined after those lines are executed
$('.checkErrors').click(function () {
var firstName = $(this).parent('tr td:first-child').val();
var lastName = $(this).parent('tr td:nth-child(2)').val();
$.ajax({
type: 'GET',
url: '#Url.Action("GetErrors","AgentTransmission")',
data: { term: $(this).attr('id') },
.
.
});
});
Here is a sample table row. The image in the last td element contains the .click event. I would like to grab the first two that contain the text "phinneas" and "ferbseven".
<tr>
<td>
phinneas
</td>
<td>
ferbseven
</td>
<td nowrap>
7735
</td>
<td>
Agent
</td>
<td>
SAF
&nbsp
07070900
</td>
<td>
6/5/2013 10:35:38 AM
</td>
<td>
DANTAK
</td>
<td class="errorPlus">
Error
</td>
<td>
Details
<span> | </span>
Edit
</td>
<td align=center id=2358>
<img src="/Content/images/magnify.gif" class="checkErrors" id=2358 alt="Program Details" />
</td>
</tr>
use closest
var $tr = $(this).closest(´tr´);
var firstName = $tr.find('td:first-child').text();
var lastName = $tr.find('td:nth-child(2)').text();
Also, you need to use text instead of val for td elements since it make sense only on input controls.
First only form elements have .val property so .
You are supposed to use .text since it is a td
Try using :eq psuedo selector
var $tr;
$tr.find('td:eq(0)').text();
$tr.find('td:eq(1)').text();
To get their content, you can do this:
var cells = $(this).closest('td').siblings('td');
var firstName = cells.eq(0).text();
var firstName = cells.eq(1).text();
Those last two lines can also be:
var firstName = $(cells[0]).text();
var firstName = $(cells[1]).text();

xpath selecting text from link in <td> & text from <td>

I have the following code which works very well:
rows = diary_HTML.xpath('//*[#id="main"]/div[2]/table/tbody/tr')
food_diary = rows.collect do |row|
detail = {}
[
["Food", 'td[1]/text()'],
["Calories", 'td[2]/text()'],
["Carbs", 'td[3]/text()'],
["Fat", 'td[4]/text()'],
["Protein", 'td[5]/text()'],
["Cholest", 'td[6]/text()'],
].each do |name, xpath|
detail[name] = row.at_xpath(xpath).to_s.strip
end
detail
end
However the "Food" td does not only include text, but also a link from which I want to get the text.
I know I can use 'td[1]/a/text()'to get the link text, but how do I do both?
'td[1]/a/text()' or 'td[1]/text()'
EDITED - Added Snippet.
I am trying to include the <tr class="meal_header">
<td class="first alt">Breakfast</td> on the first row, all lines with other regular tds on other rows whilst excluding td1 on the bottom row.
<tr class="meal_header">
<td class="first alt">Breakfast</td>
<td class="alt">Calories</td>
<td class="alt">Carbs</td>
<td class="alt">Fat</td>
<td class="alt">Protein</td>
<td class="alt">Sodium</td>
<td class="alt">Sugar</td>
</tr>
<tr>
<td class="first alt">
<a onclick="showEditFood(3992385560);" href="#">Hovis (Uk - White Bread (40g) Toasted With Flora Light Marg, 2 slice</a> </td>
<td>262</td>
<td>36</td>
<td>9</td>
<td>7</td>
<td>0</td>
<td>3</td>
</tr>
<tr class="bottom">
<td class="first alt" style="z-index: 10">
Add Food
<div class="quick_tools">
Quick Tools
<div id="quick_tools_0" class="quick_tools_options hidden">
<ul>
<li><a onclick="showLightbox(200, 250, '/food/quick_add?meal=0&date=2013-04-15'); return false;">Quick add calories</a></li>
<li>Remember meal</li>
<li>Copy yesterday</li>
<li>Copy from date</li>
<li>Copy to date</li>
</ul>
</div>
<div id="recent_meals_0" class="recent_meal_options hidden">
<ul id="recent_meal_options_0">
<li class="header">Copy from which date?</li>
<li>Sunday, April 14</li>
<li>Saturday, April 13</li>
</ul>
</div>
</div>
</td>
<td>285</td>
<td>39</td>
<td>9</td>
<td>10</td>
<td>0</td>
<td>3</td>
<td></td>
The short answer is: use Nokogiri::XML::Element#text, it will give the text of the element plus subelements (your a for example).
You can also clean that code up quite a bit:
keys = ["Food", "Calories", "Carbs", "Fat", "Protein", "Cholest"]
food_diary = rows.collect do |row|
Hash[keys.zip row.search('td').map(&:text)]
end
And as a final tip, avoid using xpath with html, css is so much nicer.
I think you can achieve this by altering the logic to look at element content when you don't have an explicit text() extraction in the xpath
rows = diary_HTML.xpath('//*[#id="main"]/div[2]/table/tbody/tr')
food_diary = rows.collect do |row|
detail = {}
[
["Food", 'td[1]'],
["Calories", 'td[2]/text()'],
["Carbs", 'td[3]/text()'],
["Fat", 'td[4]/text()'],
["Protein", 'td[5]/text()'],
["Cholest", 'td[6]/text()'],
].each do |name, xpath|
if xpath.include?('/text()')
detail[name] = row.at_xpath(xpath).to_s.strip
else
detail[name] = row.at_xpath(xpath).content.strip
end
end
detail
end
You could also add e.g. a symbol to the array, to describe how you were extracting the data, and have a case block which handled items depending on what the last stage was to do following the xpath
Note you could also do what you want by walking the node structure returned by xpath recursively, but that seems like overkill if you just want to ignore markup, links etc.

Resources