get sibling node without relying on parent index? - dom

I find it easy to get the text values inside cells whose row has a reliable attribute, eg. $browser.tr(:class, "datarow2_sm")
However I also need to grab the data from the very next <tr> in the table, defined only as <tr class="">.
Its HTML contents don't have anything very unique either, watir-speaking.
One reluctant method to catch that row is:
cell1value = $browser.tr(:class, "datarow2_sm").parent[3][1].text
cell2value = $browser.tr(:class, "datarow2_sm").parent[3][2].text.to_f # etc.
But I don't want to rely on a fixed index [3] as such things in the wider table may shift. In addition to .parent is there anything like .sibling (.next/*.previous* like in Mechanize)? Perhaps one that would prefer a node of the same type (tr to another tr not a td or some non-row node in the DOM)?

You could use the css adjacent sibling selector. Note that Watir-Webdriver only currently supports css selectors for elements node.
You would do the following (noting that the to_subtype is to convert it back to a TableRow rather than Element):
puts b.element(:css, "tr.datarow2_sm + tr").to_subtype.text
Update
If you want to get the second cell in that next row, you can do one of the following:
puts b.element(:css, "tr.datarow2_sm + tr").to_subtype[1].text
puts b.element(:css, "tr.datarow2_sm + tr").td(:index, 1).text

CSS has a special selector for this called adjacent sibling selector:
Adjacent sibling selectors have the following syntax: E1 + E2, where
E2 is the subject of the selector. The selector matches if E1 and E2
share the same parent in the document tree and E1 immediately precedes
E2, ignoring non-element nodes (such as text nodes and comments).

Related

How to avoid finding ancestors with Protractor cssContainingText locator

I have a test that needs to determine if a span exists; I want to locate the span by the text it contains for scaleability. I tried to use element(by.cssContainingText('*', 'Test Text')) but it turns out that in addition to matching the span, it also matches every ancestor it has. How do I avoid this (.last will not work as there are multiple spans that contain the text and I want to locate the first one)?
Preferably, I want a locator that works exactly like buttonText except without the button restriction.
This will get the first element on the page that has the text that you provide:
element(by.xpath('//*[contains(text(), "test text")]'));
If you wanted to get all of the elements, just tack on a .all after element.

Separating Google child labels from parent labels

I want to retrieve all labels a user has at Google and display them in a neat way, like Google themselves do:
When I fetch this hierarchy of labels with the Gmail API I get the following data:
GET https://www.googleapis.com/gmail/v1/users/me/labels?key={YOUR_API_KEY}
I use "/" as a delimiter in order to figure out the parent label of a certain child label. This worked great until I realized I could create labels with "/" in the name. Bummer.
Is there another way to do this, or should I stop my users from being able to create labels with "/" in them, and just live with the potential "bad" labels they might have created elsewhere? Thanks.
It's possible to handle this case correctly, though it requires a little extra work than just splitting on the '/'.
Suppose you have the labels:
foo
bar
baz/qux
When you fetch all the labels, you'll get back 3 entries:
"foo"
"foo/bar"
"foo/bar/baz/qux"
Note that while "foo" and "foo/bar" exist, there is no "foo/bar/baz". That label doesn't exist because 'baz/qux' is the literal label name.
Rather than simply split, try prefix matching instead. The parent label is going to be the longest label (+ trailing /) that is a prefix of the one your checking. Anything remaining after the prefix is the label name itself.

How do I locate a custom tag like <g>?

I'm having to deal with some graphic items in a page that have <g> tags. I can do something like this to find them by dropping into selenium webdriver:
browser.wd.find_elements( :tag_name => "g" )
What would be the equivalent for watir webdriver?
Also, how would I convert those selenium elements into watir elements?
Could I add support for a <g> tag to watir locally somehow?
Solution 1 - Watir-Webdriver Equivalent:
The equivalent to what you were doing in selenium-webdriver is:
browser.elements( :tag_name => "g" )
So you can do something like this to output the text of each element:
browser.elements( :tag_name => "g" ).each do |x|
puts g.text
end
Solution 2 - Add Support for G Element:
After requiring watir-webdriver, add the following code:
module Watir
module Container
def g(*args)
G.new(self, extract_selector(args).merge(:tag_name => "g"))
end
def gs(*args)
GCollection.new(self, extract_selector(args).merge(:tag_name => "g"))
end
end
class G < Element
end
class GCollection < ElementCollection
def element_class
G
end
end
end
Then you can treat 'g' like any other element. For example:
puts browser.g(:index, 0).text
browser.gs.each{ |x| puts x.text }
The G and GCollection classes will support all the standard element methods. You can add additional methods to the class if there are things specific to that element.
Update - Example of Adding Custom Methods:
To get the cursor style, you would add a method to the G class like this:
class G < Element
def cursor_style()
assert_exists
return #element.style("cursor")
end
end
This will then allow you to get the cursor property like this:
puts browser.g(:index, 0).cursor_style
#=> move
Any custom methods that interact with the element need to start with assert_exists. Then, within the method, you can work with the element using the #element variable.
Note that because the G element inherits from the Element class, you could also have used the built in style method:
puts browser.g(:index, 0).style("cursor")
#=> move
There is no support for non-standard HTML tags in watir or watir-webdriver. In part because the list of possible tag names we'd have to support is endless.
You could monkeypatch in your own custom tags if you want to. In the long run, if you are going to have to deal with those custom tags a lot, that might be your best solution in terms of having stuff that acts just like other standard HTML elements supported by the Watir api.
You can use a CSS selector on the tag name, but that only works if you stick to element objects, as described in Watir::Exception::MissingWayOfFindingObjectException: invalid attribute: :css. Which kinda makes things a little less readable and/or useful if you ask me, but might be a quick and easy solution worth the slight cost in that regard.
Or you can use .driver or .wd methods to access webdriver functionality when you need it for something not supported by watir. (but that's also not quite as readable)

Get parent element name in XPath

I am trying to figure out how to get the name of the parent from a text node's scope.
//text()[name(parent)='p']
How can you get the name of the current node's parent?
If you're trying to test the name, you almost had it:
//text()[name(parent::*)='p']
If you're trying to return the name:
name(//text()/parent::*)
FYI, point of terminology: a text node is not an element.
Anyway, the most succinct way to select the parent of the current node is ..
So, the name of the parent element of the current node (which could be a text node) is name(..)
Substituting that into your XPath expression:
//text()[name(..)='p']
But a less roundabout way to write that would be
//p/text()
(assuming the p elements in the document have no namespace prefix). Either way, you're selecting all text nodes that are children of elements named p.
//text/..[#name='p']
This will get all parents of <text> nodes as long as the parent has a name attribute of p.

Why following XPath statement returns all "a" elements?

print $tree->findvalue('//a[1]');
I am using HTML::TreeBuilder::XPath in perl. Now i expect the above statment to return the value of second "a" element but instead it returns the value of all "a" elements in the page. I cant understand Why?
what you have shall return first a-child of every element.
so //a[1] will work as follows (result will be 2 nodes):
X
Y
a <-- give you this
a
Z
a <-- and this
a
try (//a)[1] instead
That XPATH expression looks for all a elements at every level of the document and the predicate filter selects the first a at every step.
So, depending on how your XML is structured, you might not get every a element (if there were more than one a that were siblings, you would only get the first one of those siblings).
However, if you intended to just select the first a in the document, you could use this expression: (//a)[1]
Wrapping the selection of //a in the parenthesis creates a collection that the predicate filter is then applied, selecting the first in the collection, rather than the first a encountered at each step of the //.