Capybara writing drop down's options texts into an array - ruby

I'd like to put a dropdown list's options into an array generically in capybara. After the process I'm expecting to have an arrray of strings, containing all dropdown options. I've tried the code below but the length of my array stays 1 regardless of what the option count is.
periods = Array.new()
periods = all('#MainContent_dd')
print periods.length

The problem is that all('#MainContent_dd') returns all elements that have the id MainContent_dd. Assuming this is your dropdown and ids are unique, it is expected that the periods.length is 1 (ie periods is the select list).
What you want to do is get the option elements instead of the select element.
Assuming your html is:
<select id="MainContent_dd">
<option>Option A</option>
<option>Option B</option>
<option>Option C</option>
</select>
Then you can do:
periods = find('#MainContent_dd').all('option').collect(&:text)
p periods.length
#=> 3
p periods
#=> ["Option A", "Option B", "Option C"]
What this does is:
find('#MainContent_dd') - Finds the select list that you want to get the options from
all('option') - Gets all option elements within the select list
collect(&:text) - Collects the text of each option and returns it as an array

#JustinCo's answer has a problem if used driver isn't fast: Capybara will make a query to driver for every invocation of text. So if select contains 200 elements, Capybara will make 201 query to browser instead of 1 which may be slow.
I suggest you to do it using one query with Javascript:
periods = page.execute_script("options = document.querySelectorAll('#MainContent_dd > option'); texts=[]; for (i=0; i<options.length; i++) texts.push(options[i].textContent); return texts")
or (shorter variant with jQuery):
periods = page.evaluate_script("$('#MainContent_dd').map(function() { return $(this).text() }).get()")

Related

I need to click this input only if it has "aria-checked ="true"

I need to click this input only if it has "aria-checked ="true"
<input class="mat-checkbox-input cdk-visually-hidden" type="checkbox" id="mat-checkbox-131-input" tabindex="0" aria-checked="true" style="" xpath="1">
Ruby:
aria_checked = true
if aria_checked = true
impressora_etiqueta = "//mat-checkbox[#id='mat-checkbox-23']/label/div"
page.find(:xpath, impressora_etiqueta).click
end
There are many ways to do what you want - simplest is probably
page.first('#mat-checkbox-23[aria-checked="true"]', minimum: 0)&.click
which will look for the first element with the given id and aria-checked = "true" and click it if one exists.
Note: the id in your test and sample HTML didn't match so I went the id from your test, adjust as needed. Also you have a class of cdk-visually-hidden shown -- If that's actually making the element not visible on the page, then this won't work and you'll need to add more surrounding HTML to your question with a better description of exactly what you're trying to do (you can't click on non-visible elements)

I need to verify whether a label "Annuitant" and its value " RPD" is present or not using parent class

<div class="col-sm-3">
<span>Annuitant:</span>
</div>
<div class="col-sm-3">
<span id="annuitant">
RPD
</span>
</div>
Xpath code that i used previously
findXpath=page.find('label', text: workbook.cell(j,k), :match => :prefer_exact).path
splitXpath=(findXpath.split("/")) #splitting xpath
##Xpath manipulation to get the xpath of "RPD"
count1=splitXpath.count
value1=splitXpath.at(count1-3)
value=splitXpath.at(count1-2)
labelNum=value1.match(/(\d+)/)
i=0
elementNum=labelNum[1].to_i+1
for maxnum in 1..splitXpath.count-4
elementXpath=elementXpath + "/" + splitXpath[maxnum]
end
elementXpath=elementXpath + "/div[" + elementNum.to_s + "]" + "/"+ value
elementXpath=elementXpath + "/" + splitXpath.at(count1-1)
finalElementXpath=elementXpath.sub("label","span")# obtained the xpath of RPD
if (workbook.cell(j+1,k) == (find(:xpath, finalElementXpath).native.text)) # verifying the value RPD is present
Can I use parent class and verify whether "Annuitant" is present and also to check whether Annuitant value is "RPD". Please help me to write a code for this in ruby capybara
Use assert_selector to check if the selector has the text you want. See below:
page.assert_selector('#annuitant', :text => 'RPD', :visible => true)
You can scope Capybara's finders/matchers to any element by either calling them on an element or using within(element) ...
In this case you'd want to scope to at least one level higher in your html document so that both elements you are interested in are contained by the element you're scoping too. Also the class 'col-sm-3' would be a bad choice because it is not going to be unique to these elements. Another thing this comes down to is how rigorous does your check need to be, do you actually need to check the structure of the elements or do you just need to verify the text appears next to each other on the page. If the latter something like
element = find('<selector for parent/grandparent of both elements>') # could also just be `page` if the text is unique
expect(element).to have_text('Annuitant: RPD')
if you do actually need to verify the structure things get more complicated and you would need to use XPath
expect(element).to have_selector(:xpath, './/div[./span[text()="Annuitant:"]]/following-sibling::div[1][./span[normalize-space(text())="RPD"]]')

CKEditor Plugin - Proper behavior of elementPath

Currently, I have the following HTML content
<span criteria="{"animal":["DOG"]}">abc</span> def <span criteria="{"animal":["CAT"]}">ghi</span>
My purpose is
I wish to know my selected text contain criteria attribute?
If it contains criteria attribute, what is its value?
I run the following code.
editor.on('selectionChange', function( ev ) {
var elementPath = editor.elementPath();
var criteriaElement = elementPath.contains( function( el ) {
return el.hasAttribute('criteria');
});
var array = elementPath.elements;
var arrayLength = array.length;
for (var i = 0; i < arrayLength; i++) {
console.log(i + " --> " + array[i].$.innerHTML);
}
if (criteriaElement) {
console.log("criteriaElement is something");
console.log("criteriaElement attribute length is " + criteriaElement.$.attributes.length);
for (var i = 0; i < criteriaElement.$.attributes.length; i++) {
console.log("attribute is " + criteriaElement.$.attributes[i].value);
}
}
});
Test Case 1
When I select my text abc def as follow
I get the following logging
0 --> abc
1 --> <span criteria="{"animal":["DOG"]}">abc</span> def <span criteria="{"animal":["CAT"]}">ghi</span>
criteriaElement is something
criteriaElement attribute length is 1
attribute is {"operator":["DOG"]}
Some doubts in my mind.
I expect there will be 2 elements in elementPath. One is abc, another is def. However, it turns out, my first element is abc (correct), and my second element is the entire text (out of my expectation)
Test Case 2
I test with another test. This time, I select def ghi
I get the following logging
0 --> <span criteria="{"animal":["DOG"]}">abc</span> def <span criteria="{"animal":["CAT"]}">ghi</span>
Some doubts in my mind
Why there is only 1 element? I expect there will be 2 elements in elementPath. One is def, another is ghi.
Although Test Case 1 and Test Case 2 both contain element with entire text, why in Test Case 2, elementPath.contains... returns nothing?
Elementspath is not related to the selection in that way. It represent the stack of elements under the the caret. Imagine a situation like this where [] represents the selection and | represents the caret:
<ul>
<li>Quux</li>
<li>F[oo <span class="bar">Bar</span> <span class="baz">Ba|]z</span></li>
<li>Nerf</li>
</ul>
Your selection visually contains the text "oo Bar Ba" and your caret is in between a and z. At that time, the elementspath would display "ul > li > span". The other span element "bar" is a sibling of the span element "baz" and is thus not displayed, only ascendants are displayed.
You could think of it like that the caret can only exist inside a html TEXT_NODE and the elementspath displays the ascendants of that text node.
What are you trying to eachieve? To display the data in the current selection? Why? Where do you want it to show? How and why do you want it to show? I'm guessing that there is a different way of fillind the requirement that you have than with using the elementspath (I'm think this might be and XY problem).
Too long to be a comment: If your toolbar button action targets elements with the criteria attribute - what if there is one span with a criteria attribute and 1 without? Does their order matter? What if there are two spans with a criteria attribute? What if they are nested like this: <p>F[oo <span criteria="x">Bar <span criteria="y">Ba|]z </span>Quux </span>Xyzzy</p> - the targeting will be difficult. I would suggest that you add a small marker to the elementspath if an element has the attribute, than clicking the marker or rightclicking the element you could edit/view the criteria. You could even visually indicate spans with the attribute within the editor by customizing editor.css with a rule like span[criteria]{ color: red; }.

accessing the text value of last nested <tr> element with no id or class hooks

I need to access the value of the 10th <td> element in the last row of a table. I can't use an ID as a hook because only the table has an ID. I've managed to make it work using the code below. Unfortunately, its static. I know I will always need the 10th <td> element, but I won't ever know which row it needs to be. I just know it needs to be the last row in the table. How would I replace "tr[6]" with the actual last <tr> dynamically? (this is probably really easy, but this is literally my first time doing anything with ruby).
page = Nokogiri::HTML(open(url))
test = page.css("tr[6]").map { |row|
row.css("td[10]").text}
puts test
You want to do:
page.at("tr:last td:eq(10)")
If you do not need to do anything else with the page you can actually make this a single line with
test = Nokogiri::HTML(open(url)).search("tr").last.search("td")[10].text
Otherwise (this will work):
page = Nokogiri::HTML(open(url))
test = page.search("tr").last.search("td")[10].text
puts test
Example:(Used a large table from another question on StackOverflow)
Nokogiri::HTML(open("http://en.wikipedia.org/wiki/Richard_Dreyfuss")).search('table')[1].search('tr').last.search('td').children.map{|c| c.text}.join(" ")
#=> "2013 Paranoia Francis Cassidy"
Is there a particular reason you want an Array with 1 element? My example will return a string but you could easily modify it to return an Array.
You can use CSS pseudo class selectors for this:
page.css("table#the-table-id tr:last-of-type td:nth-of-type(10)")
This first selects the <table> with the appropriate id, then selects the last <tr> child of that table, and then selects the 10th <td> of that <tr>. The result is an array of all matching elements, if youexpect there to be only one you could use at_css instead.
If you prefer XPath, you could use this:
page.xpath("//table[#id='the-table-id']/tr[last()]/td[10]")

Using Html Agility Pack to parse nodes in a context sensitive fashion

<div class="mvb"><b>Date 1</b></div>
<div class="mxb"><b>Header 1</b></div>
<div>
inner hmtl 1
</div>
<div class="mvb"><b>Date 2</b></div>
<div class="mxb"><b>Header 2</b></div>
<div>
inner html 2
</div>
I would like to parse the inner html between the tags in such a way that I can
* associate the inner html 1 with header 1 and date 1
* associate the inner html 2 with header 2 and date 2
In other words, at the time I parse the inner html 1 I would like to know that the html nodes containing "Date 1" and "Header 1" have been parsed (but the nodes containing "Date 2" and "Header 2" have not been parsed)
If I were doing this via regular text parsing, I would read one line at a time and record the last "Date" and "Header" than I had parsed. Then when it came time to parse the inner html 1, I could refer to the last parsed "Date" and "Header" object to associate them together.
Using the Html Agility Pack, you can leverage XPATH power - and forget about that verbose xlinq crap :-). The XPATH position() function is context sensitive. Here is a sample code:
HtmlDocument doc = new HtmlDocument();
doc.Load("your html file");
// select all DIV without a CLASS attribute defined
foreach (HtmlNode div in doc.DocumentNode.SelectNodes("//div[not(#class)]"))
{
Console.WriteLine("div=" + div.InnerText.Trim());
Console.WriteLine(" header=" + div.SelectSingleNode("preceding-sibling::div[position()=1]/b").InnerText);
Console.WriteLine(" date=" + div.SelectSingleNode("preceding-sibling::div[position()=2]/b").InnerText);
}
That will prrint this with your sample:
div=inner hmtl 1
header=Header 1
date=Date 1
div=inner html 2
header=Header 2
date=Date 2
Well, you can do this in several ways...
For example, if the HTML you want to parse is the one you wrote in your question, an easy way could be:
Store all dates in a HtmlNodeCollection
Store all headers in a HtmlNodeCollection
Store all inner texts in another HtmlNodeCollection
If everything is okay and the HTML has that layout, you will have the same number of elements in both 3 collections.
Then you can easily do:
for (int i = 0; i < innerTexts.Count; i++) {
//Get Date, Headers and Inner Texts at position i
}
The following should work:
var document = new HtmlWeb().Load("http://www.url.com"); //Or load it from a Stream, local file, etc.
var dateNodes = document.DocumentNode.SelectNodes("//div[#class='mvb']/b");
var headerNodes = document.DocumentNode.SelectNodes("//div[#class='mxb']/b");
var innerTextNodes = (from node in document.DocumentNode.SelectNodes("//div")
let previous = node.PreviousSibling
where previous.Name == "div" && previous.GetAttributeValue("class", "") == "mxb"
select node).ToList();
//Check here if the number of elements of the 3 collections are the same
for (int i = 0; i < dateNodes.Count; i++) {
var date = dateNodes[i].InnerText;
var header = headerNodes[i].InnerText;
var innerText = innerTextNodes[i].InnerText;
//Now you have the set you want: You have the Date, Header and Inner Text
}
This is a way of doing this.
Of course, you should check for exceptions (that .SelectNodes(..) method are not returning null), check for errors in the LINQ expression when storing innerTextNodes, and refactor the for (...), maybe into a method that receives a HtmlNode and returns the InnerText property of it.
Take in count that the only way you can know, in the HTML code you posted, what is the <div> tag that contains the Inner Text, is to assume it is the one that is next to the <div> tag that contains the Header. That's why I used the LINQ expression.
Another way of knowing it could be if the <div> has some particular attribute (like class="___") or similar, or if it contains some tags inside it and not just text. There is no magic when parsing HTMLs :)
Edit:
I have not tested this code. Test it by yourself and let me know if it worked.

Resources