Get table cell content based on content of cell in neighbourhood - ruby

I have a table with the following structure:
<table class="table_class">
<tr>
<td>Label A</td>
<td>Value A</td>
<td>Label B</td>
<td><div>Value BChange</div></td>
</tr>
<tr>
<td>Label C</td>
<td><div>Value C</div></td>
<td>Label D</td>
<td><div><span><span><img src="image/source.jpg"<img src="another/image.gif"></span>Value D</span> Change</div></td>
</tr>
</table>
I would like to get the the values ("Value A", "Value B", ...), but the only unique identifier for the table cells containing those values, are the table cells left to them ("Label A", "Label B", ...).
Any idea how to handle this properly within a PageObject?
Thanks in advance,
Christian

You could use an XPath with the following-sibling axis to find the values of adjacent cells.
For example, the following page object has a method that will find the label cell based on its text. From there, navigate to the next td element, which should be the associated value.
class MyPage
include PageObject
def value_of(label)
# Find the table
table = table_element(class: 'table_class')
# Find the cell containing the desired label
label_cell = cell_element(text: label)
# Get the next cell, which will be the value
value_cell = label_cell.cell_element(xpath: './following-sibling::td[1]')
value_cell.text
end
end
page = MyPage.new(browser)
p page.value_of('Label A')
#=> "Value A"
p page.value_of('Label B')
#=> "Value BChange"
Depending on your goals, you could also refactor this to use the accessor methods. This would allow you to have the methods for returning the value cell, its text, checking its existence, etc:
class MyPage
include PageObject
cell(:value_a) { value_of('Label A') }
cell(:value_b) { value_of('Label B') }
def value_of(label)
table = table_element(class: 'table_class')
label_cell = cell_element(text: label)
value_cell = label_cell.cell_element(xpath: './following-sibling::td[1]')
value_cell
end
end
page = MyPage.new(browser)
p page.value_a
#=> "Value A"
p page.value_a?
#=> true

Related

Getting text value in closest preceding sibling by class with Mechanize/Nokogiri

Currently I'm looping over table rows and getting values from a td, putting them in a sorted hash identified by a value in a sibling td:
Ruby snippet
#counts = Hash.new
agent.page.search('.child').each do |child|
#counts[child.css('td')[0].text.strip!] = child.css('td')[1].text.gsub(/,/,'').to_i
end
puts #counts.sort_by{|k,v| v}.reverse.to_h
HTML structure
<tr class="parent">
<td class="info">Type</td>
<td>12,000</td>
</tr>
<tr class="child">
<td class="info">Sub Type</td>
<td>9,000</td>
</tr>
<tr class="child">
<td class="info">Sub Type</td>
<td>3,000</td>
</tr>
<tr class="parent">
<td class="info">Type</td>
<td>11,000</td>
</tr>
<tr class="child">
<td class="info">Sub Type</td>
<td>11,000</td>
</tr>
Now I would like to change the hash keys, by concatenating them with the text value in a td belonging to the parent tr. So in the above HTML structure, instead of "Sub Type" => 9000, "Sub Type" => 3000, etc. I would like to get "Type Sub Type" => 9000, "Type Sub Type" => 3000, etc.
How do I get the first preceding sibling with a certain class, when the number of siblings is unknown?
You can look at this a different way, loop through all tr elements (parent and child), keep the last found parent type and then concatenate the last parent type when you get to a child.
#counts = Hash.new
parent = nil
agent.page.search('.parent, .child').each do |node|
type = node.css('td')[0].text.strip
value = node.css('td')[1].text.gsub(/,/, '').to_i
if node['class'].include? 'parent'
parent = type
else
#counts["#{parent} #{type}"] = value
end
end
puts #counts.sort_by{|k,v| v}.reverse.to_h
Also, hashes are by nature an unsorted data structure. If you want to retain order, then your best bet would be an array of tuples. In other words, [['Type Sub Type', 12000], ['Type Sub Type', 11000], ..., ['Type Sub Type', 3000]]. Just remove the .t_h at the end of your last line to get that kind of result.

How to get a HTML table row with Capybara

I am trying to scan rows in a HTML table using partial href xpath and perform further tests with that row's other column values.
<div id = "blah">
<table>
<tr>
<td>link</td>
<td>29 33 485</td>
<td>45.2934,00 EUR</td>
</tr>
<tr>
<td>link</td>
<td>22 93 485</td>
<td>38.336.934,123 EUR</td>
</tr>
<tr>
<td>link</td>
<td>394 27 3844</td>
<td>3.485,2839 EUR</td>
</tr>
</table>
</div>
In cucumber-jvm step definition, I performed this much easily like below (I am more comfortable using Ruby)
#Given("^if there are...$")
public void if_there_are...() throws Throwable {
...
...
baseTable = driver.findElement(By.id("blah"));
tblRows = baseTable.findElements(By.tagName("tr"));
for(WebElement row : tblRows) {
if (row.findElements(By.xpath(".//a[contains(#href,'key=HONDA')]")).size() > 0) {
List<WebElement> col = row.findElements(By.tagName("td"));
tblData dummyThing = new tblData();
dummyThing.col1 = col.get(0).getText();
dummyThing.col2 = col.get(1).getText();
dummyThing.col3 = col.get(2).getText();
dummyThing.col4 = col.get(3).getText();
dummyThings.add(dummyThing);
}
}
I am clueless here
page.find('#blah').all('tr').each { |row|
# if row matches xpath then grab that complete row
# so that other column values can be verified
# I am clueless from here
row.find('td').each do { |c|
}
page.find('#blah').all('tr').find(:xpath, ".//a[contains(#href,'key=HONDA')]").each { |r|
#we got the row that matches xpath, let us do something
}
}
I think you are looking to do:
page.all('#blah tr').each do |tr|
next unless tr.has_selector?('a[href*="HONDA"]')
# Do stuff with trs that meet the href requirement
puts tr.text
end
#=> link 29 33 485 45.2934,00 EUR
#=> link 22 93 485 38.336.934,123 EUR
This basically says to:
Find all trs in the element with id 'blah'
Iterate through each of the trs
If the tr does not have a link that has a href containing HONDA, ignore it
Otherwise, output the text of the row (that matches the criteria). You could do whatever you need with the tr here.
You could also use xpath to collapse the above into a single statement. However, I do not think it is as readable:
page.all(:xpath, '//div[#id="blah"]//tr[.//a[contains(#href, "HONDA")]]').each do |tr|
# Do stuff with trs that meet the href requirement
puts tr.text
end
#=> link 29 33 485 45.2934,00 EUR
#=> link 22 93 485 38.336.934,123 EUR
Here is an example of how to inspect each matching row's link url and column values:
page.all('#blah tr').each do |tr|
next unless tr.has_selector?('a[href*="HONDA"]')
# Do stuff with trs that meet the href requirement
href = tr.find('a')['href']
column_value_1 = tr.all('td')[1].text
column_value_2 = tr.all('td')[2].text
puts href, column_value_1, column_value_2
end
#=> file:///C:/Scripts/Misc/Programming/Capybara/afile?key=HONDA
#=> 29 33 485
#=> 45.2934,00 EUR
#=> file:///C:/Scripts/Misc/Programming/Capybara/afile?key=HONDA
#=> 22 93 485
#=> 38.336.934,123 EUR
If you need the table row, you could probably use something like the ancestor method:
anchors = page.all('#blah a[href*="HONDA"]')
trs = anchors.map { |anchor| anchor.ancestor('tr') }

couldn't create the hash with option values and <div> text

HTML Code:
<div id="empid" title="Please first select a list to filter!"><input value="5418630" name="candidateprsonIds" type="checkbox">foo <input value="6360899" name="candidateprsonIds" type="checkbox"> bar gui<input value="9556609" name="candidateprsonIds" type="checkbox"> bab </div>
Now I would like to get the below using selenium-webdriver as
[[5418630,foo],[6360899,bar gui],[9556609,bab]]
Can it be done?
I tried the below code:
driver.find_elements(:id,"filtersetedit_fieldNames").each do |x|
puts x.text
end
But it is giving me the data as string "foo bar gui bab" on my console. Thus couldn't figure out - how to create such above expected Hash.
Any help on this regard?
The only way I know to get the text nodes like that would be to use the execute_script method.
The following script would give you the hash of option values and their following text.
#The div containing the checkboxes
checkbox_div = driver.find_element(:id => 'empid')
#Get all of the option values
option_values = checkbox_div.find_elements(:css => 'input').collect{ |x| x['value'] }
p option_values
#=> ["5418630", "6360899", "9556609"]
#Get all of the text nodes (by using javascript)
script = <<-SCRIPT
text_nodes = [];
for(var i = 0; i < arguments[0].childNodes.length; i++) {
child = arguments[0].childNodes[i];
if(child.nodeType == 3) {
text_nodes.push(child.nodeValue);
}
}
return text_nodes
SCRIPT
option_text = driver.execute_script(script, checkbox_div)
#Tidy up the text nodes to get rid of blanks and extra white space
option_text.collect!(&:strip).delete_if(&:empty?)
p option_text
#=> ["foo", "bar gui", "bab"]
#Combine the two arrays to create a hash (with key being the option value)
option_hash = Hash[*option_values.zip(option_text).flatten]
p option_hash
#=> {"5418630"=>"foo", "6360899"=>"bar gui", "9556609"=>"bab"}

parsing JSON into Ruby iterator

I have a JSON file with parsed data stored in a #colors instance variable, as follows:
[{:color=>"red", :value=>"#f00"} {:color=>"green", :value=>"#0f0"} {:color=>"blue", :value=>"#00f"} {:color=>"cyan", :value=>"#0ff"} {:color=>"magenta", :value=>"#f0f"} {:color=>"yellow", :value=>"#ff0"} {:color=>"black", :value=>"#000"}]
Now I want to iterate through this output to create a table in a view where there is
<tr><td>color</td><td>value</td></tr>
When I derive another instance variable like this --
#even_colors = #colors.values_at(* #colors.each_index.select {|i| i.even?}).map(&:values)
I get an array of arrays consisting of every other color/value pair
[["red", "#f00"], ["blue", "#00f"], ["magenta", "#f0f"], ["black", "#000"]]
But what I want to create two separate arrays, one consisting only of the color names indicated by :color (red, blue, etc.) and the other consisting of just the hexs indicated by :value (#f00, #00f, etc.). I can't seem to figure out how to do that. Anyone have any suggestions? Thanks ...
You could do it with two passes through #colors:
names = #colors.map { |h| h[:color] }
hexes = #colors.map { |h| h[:value] }
Or you could it with on pass:
parts = #colors.each_with_object({ :names => [ ], :hexes => [ ]}) do |h, parts|
parts[:names].push(h[:color])
parts[:hexes].push(h[:value])
end
then look at parts[:names] for the color names and parts[:hexes] for the hex values.
I don't really see why you want to split #colors up though, you could produce your table straight from#colors:
<table>
<% #colors.each do |h| %>
<tr><td><%= h[:color] %></td><td><%= h[:value] %></td></tr>
<% end %>
</table>
Breaking #colors into two arrays seems like busy-work to me.

Cucumber - How to write a test case to change the sort ordering of items listed in a table?

I am sort of new to cucumber and have gotten hung up on a test case.
There is a table on a page which lists a bunch of products, one of the cells contains up & down graphics - these are controls which the user clicks on to move the sort ordering of that product up or down in a catalogue which the end user can browse.
How can I select the 2nd product listed in the table, find its id & click on its "up" or down link?
Here is the table (shortened for readability):
<table id="product_container">
<tr>
<th>Order Position</th>
</tr>
<tr>
<td>Up
Down
</td>
</tr>
</table>
Thanks for any advice!
The id attribute of HTML element needs to be unique on the page: http://www.w3.org/TR/html401/struct/global.html#h-7.5.2
The easiest way to select product would be to get a reference to it's row:
class ProductsTable
def initialize(driver)
#driver = driver
end
def table
#driver.find_element(:id, "product_container")
end
def products
table.find_elements(:tag_name, "td").map{|element|
Products.new(element)
}
end
end
class Products
def initialize(element)
#elem = element
end
def up
#elem.find_element(:class, "product_up")
end
def down
#elem.find_element(:class, "product_down")
end
end
driver = Selenium::WebDriver.for :chrome
driver.get "http://link_to_testing_page"
tabl = ProductsTable.new(driver)
To push first product up:
tabl.products.first.up.click
down:
tabl.products.first.down.click
Cucumber step definition:
When /^I push product (\d+) (.*)$/ do |product, where|
product = product.to_i - 1
tabl.products[product].send(where).click
end

Resources