Capybara Select2 Help w/Firefox - ruby-on-rails

For some reason im running into problems with Select2 and Firefox w/Geckodriver.
Select2 fields I used to be able to just say page.select 'Text', from: 'Label' however that no longer works I just get a Element <option> could not be scrolled into view (Despite being scrolled into view). Right now im doing something similar to this:
select2Fields = page.all('.select2-selection')
select2Fields[0].click
page.find('.select2-search__field').set('Text To Set')
within('.select2-results') do
page.find('li', text: 'Text To Click').click
end
It's ugly and doesn't fit with my Page Object Model method, since I have to sorta know which select2 field it is. It doesn't seem to be when finding it with a label.
Any ideas? It's very frustrating since it worked with Chrome but the latest chromedriver has issues with the newest capybara versions.

Not sure what you were using that you were able to ever use select with a select2 widget, it never should have worked, and the fact it did would have been a bug. The reason is the actual <select> element (which is what Capybaras select method works with) is non-visible on the page, and select2 replaces it with a JS driven widget. You need to do exactly what a user would do, which is click to make the widget show up then click on the <li> element which represents the correct entry. This can all be moved into a helper method and potentially some custom selectors which boils down to something like this
Capybara.add_selector(:select2) do
xpath do |locator, **options|
xpath = XPath.descendant(:select)
xpath = locate_field(xpath, locator, options)
xpath = xpath.next_sibling(:span)[XPath.attr(:class).contains_word('select2')][XPath.attr(:class).contains_word('select2-container')]
xpath
end
end
Capybara.add_selector(:select2_option) do
xpath do |locator|
# Use anywhere to escape from the current scope since select2 appends
# the choices to the end of the document
xpath = XPath.anywhere(:ul)[XPath.attr(:class).contains_word('select2-results__options')][XPath.attr(:id)]
xpath = xpath.descendant(:li)[XPath.attr(:role) == 'treeitem']
xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
xpath
end
end
def select_from_select2(value, from: nil, **options)
select2 = if from
find(:select2, from, options.merge(visible: false))
else
select = find(:option, value, options).ancestor(:css, 'select', visible: false)
select.find(:xpath, XPath.next_sibling(:span)[XPath.attr(:class).contains_word('select2')][XPath.attr(:class).contains_word('select2-container')])
end
select2.click
find(:select2_option, value).click
end
That should let you call select_from_select2 just like you would call select and it will find the select2 widget associated with the given <select> element (hidden by select2) and choose the correct entry from it.

I had tested the Thomas´ answer but it doesn´t work for me. When Capybara click in the desired option, the select2 box close itself and set the 0 option. Finnaly, I made a walkaround as I check the option I want as selected and trigger the change.select2 event. I know that I dont really test the select2 box.
def self.select2 (page, datos)
page.execute_script("$('##{datos[:from]}').select2('open')")
if page.find(".select2-results li", text: datos[:texto]).click
page.execute_script("$('##{datos[:from]} option[value=\"#{datos[:valor]}\"]').prop('selected', true)")
page.execute_script("$('##{datos[:from]}').trigger('change.select2')")
end
page.find(:css, '#' + datos[:from]).value
end
As I keep my module Helper without include it in tests, I needed to include self in the name of the method and take 'page' from the capybara´test as parameter.
The variable 'datos' is a hash with the selector, the text of the option and its value.
As select2 box close when capybara click on it, I wrap the walkaround inside the if clause to be sure that some parts of the select2 box were working.
Finally, I returned the current value of the select to test it (really, it doesnt needed as I set the option with that value as 'selected')
I hope it would help anyone.

Related

Capybara not seeing updates to the DOM made by Javascript

I'm running Rails 5.x, with, Cucumber, Siteprism and Capybara through chromedriver. Most things work except..
I have a tiny bit of javascript that changes the class on an element in response to an event. But Capybara never sees the change. It only ever sees the class the element has when the page initially loaded.
Using Chrome, and debugging my Cucumber steps, I can see the element has the new class, but Capybara doesn't see it.
This must be an issue other people have encountered and solved, though I can't find the right subject title.
example coffeescript
$(document).on('focus', 'tbody#item-entry > tr > td > input', (e) ->
$(#).closest('tr').addClass('focused-row')
$(#).closest('td').addClass('focused-cell')
)
example html after the focus event has been triggered
<tr class="focused-row">
<td>ignore this </td>
</tr>
The purpose is to change the background colour of the row containing an input element that has focus. It works.
But Capybara, can't see the class, but it can see any classes added when the page is loaded. e.g.
expect(siteprism_stuff.root_element['class']).to match(/focused-row/)
Ignore the SitePrism stuff, that just gets the right element. root_element is the Capybara class for the dom node.
Now I know it's getting the right Capybara element because if I change my view to put stuff in the class for each row, then it sees that perfectly OK. What it can't see is the any new class added via Coffeescript. Although it's visible in the Chrome inspector, and changes the background color of the focused row as required.
You're specifying an "ends with" CSS attribute selector ($=)
input[class$='form-control']
which since the class attribute for the element you're interested in
<input type="search" class="form-control form-control-sm" placeholder="" aria-controls="universitiesTable">
doesn't end with 'form-control' is correctly not matching. You probably just want to use a normal CSS class selector input.form-control if continuing to do it the way you are. Any of the following options should find the search field and fill in the data you are trying to fill in.
fill_in 'Search:', with: string
fill_in type: 'search', with: string
find(:field, type: 'search').set(string)
find('input.form-control').set(string)
Note: Your question is still unclear as to whether you are seeing the class added in the inspector in test mode, and whether the line color is changing while the tests are running (or whether you're only seeing that in dev mode) - This answer assumes the JS is actually running in test mode and you're seeing the line color change while the tests are running.
You don't show how you're actually triggering the focus event but I'll assume you're clicking the element. The thing to understand when working with Capybara is that the browser works asynchronously, so when something like click has been done, the actions triggered by that click have not necessarily been done yet. Because of that, whenever doing any type of expectation with page elements you should always be using the matchers provided by Capybara rather than the basic matchers provided by RSpec. The Capybara provided matchers include waiting/retrying behavior to handle the asynchronous nature of dealing with the browser. In this case, assuming siteprism_stuff.root_element is the row element then you could be doing something like
expect(siteprism_stuff.root_element).to match_css('.focused-row')
or depending on exactly how your siteprism page objects are setup you could pass the class option to the siteprism existence checker
# `page_section` and `have_row` would need to be replaced with whatever is correct for your site prism page object
expect(page_section).to have_row(class: ['.focused-row'])

How can I add a Tooltip to a record in ActiveAdmin index page?

My app has a pretty standard index page that lists all records in an ActiveRecord table.
I want to add a tooltip that provides some custom info when the mouse hovers over a row in the index page. However, my Google and Stackoverflow searches have yielded nothing on-target. (I suspect that if I were more familiar with ActiveAdmin and its components, I might have found the answer embedded in the documents I scanned.)
Can anybody provide me with the missing link? Thanks!
A colleague reminded me of the HTML4+ 'title' attribute, which actually displays a text-only tooltip when hovering on the element. Here is how I was able to implement it:
app/admin/some_models.rb
ActiveAdmin.register SomeModel do
...
index do
...
column some_field do |some_model|
div(title: 'tooltip text - can be a helper method call') do
some_model.some_field # the value to be displayed in the column
end
end
...
If a plain text tooltip is not sufficient, it would be necessary to add an onmouseover event listener to a style defined in the div or in the css defined for the div's class (class: must be specified in the div), then add a javascript function in app/assets/javascripts/active_admin.js or elsewhwere.
I hope this helps someone.
There are several options available here: http://www.unheap.com/section/user-interface/tool-tips/
You can do this in one line if you pass title as an optional argument to an attributes table row:
attributes_table do
row 'I am a row label', title: 'I am a tooltip', &:some_attribute_name
end
If your first (label) argument isn't a string or symbol, it will default to using the title argument as the label. You can read more about this in the ActiveAdmin source code for attributes_table.

Is it possible to interact with hidden elements with capybara?

I have a file field that has opacity: 0 and is overlaping a fake button. Its a common css technic to fake a sort of "Upload button" that displays consistently across different browsers.
Capybara doesn't allows me to call attach_file on that input. The error is Selenium::WebDriver::Error::ElementNotVisibleError: Element is not currently visible and so may not be interacted with.
Anybody knows any way to force capybara to interact with invisible elements?
The answer is still unanswered, but I've found a work around. Nothing intelligent, just make visible the element with a simple script
page.execute_script %Q{
$('#photos').css({opacity: 1, transform: 'none'});
}
I post it for the record.
You can interact with hidden elements using the visible: false property in Capybara.
If you want to click on hidden element use:
find("#photos", visible: false).click
Don't use click_button('#photo') directly
The author of Capybara recommends setting Capybara.ignore_hidden_elements immediately prior to needing to see the invisible element, and resetting it afterwards:
Capybara.ignore_hidden_elements = false
click_button 'my invisible button'
Capybara.ignore_hidden_elements = true
In general interacting with non-visible elements should not be possible when using Capybara (you can find them using the visible: false/hidden option in most finders but not actually do anything to them). However, the file input is a special case because of how common it is to hide the element and, due to security restrictions, no other way to actually add a file by interacting with the pages visible elements. Because of this attach_file has a make_visible option which can be used to have Capybara make the element visible, attach the file, and then reset the CSS to the original setting.
attach_file('photos', file_path, make_visible: true)
I ended up resolving it a different route.
execute_script() was giving me a hard time (it would freeze test execution on FireFox), so this is what I did:
I already had an appropriate javascript file. I appended the following
<% if ENV["RAILS_ENV"] == "test" %>
$('#photos').show()
<% end %>
I also had to append .erb to my javascript file for proper Rails asset handling.
And in my test file, I was already setting ENV["RAILS_ENV"] = "test"
This way I could just dumb down the UI for test, and yet maintain the look and feel for production.
Miquel, thanks for workaraund.
I have similar issue for interacting with hidden file input on C# binding for Selenium Webdriver 2.35 and Firefox 24. To make file selection working did similar trick:
((IJavaScriptExecutor)webdriver).ExecuteScript("$('#fileUploadInput').css({opacity: 1, transform: 'none'});");
IWebElement e = webdriver.FindElement(By.CssSelector("input#fileUploadInput")));
e.SendKeys("c:\\temp\\inputfile.txt");
I've done it this way with elements that has the CSS style display:none; set:
page.execute_script("$('.all-hidden-elements').show();");
all('.all-hidden-elements').first.click
If the hidden element is nested in a visible parent element (e.g. a hidden input inside a visible label), you can click on the parent instead. If you still want to find the input by ID, you can traverse to the parent like so:
find('#hidden_input').find(:xpath, '..').click

How to test jQuery TokenInput field using Selenium

I'm unable to test a Tokeninput field in a form using selenium. The situation is when we type something, it gives a list to options to select but those options aren't part of the DOM. The text fills the field but doesn't select the item.
The code which I have written is:
Given admin user is on schedule interview page
And he select "obie[1]" and "DHH[1]" from the candidate name(s) auto sugget field
**step defination**
Given /^he select "([^"]*)" and "([^"]*)" from the candidate name\(s\) auto sugget field$/ do |arg1, arg2|
within(:css, "#interview_template_candidate_names_input") do
fill_in('tmp',:with => arg1) --tmp is name of the token input field
find("li:contains('obie[1])'").click
save_and_open_page
end
end
I finally succeeded in making this work. Here's the gist: https://gist.github.com/1229684
The list is part of the dom (div.token-input-dropdown), it's added as the last child of the body element, which is probably why you didn't see it.
If you understand what the tokeninput plugin is doing, you can get a better idea of what you need to do. For each tokeninput you create, the plugin:
creates a ul.token-input-list (immediately before input#your_input_id)
creates a ul.token-input-list input#token-input-your_input_id
hides the input#your_input_id
creates a div.token-input-dropdown
So the most challenging part is finding the correct ul.token-input-list, because you have to find it based on its position relative to the original input, and the selenium webdriver doesn't let you navigate the dom.
After that, you just fill in the input#token-input-your_input_id and "click" on the div.token-input-dropdown li option that matches what you're looking for.

How to get the foreign key column data when it is used in a select box

I have this ruby on rails code
<%= builder.select(:serving_size_id, '') %>
I have not specified any options on purpose because I set the options in a different way when the page loads (using jQuery and Ajax).
The question: Is there any way I can get the value from the column "serving_size_id" but not change that line? I have a partial which I use it for new and edit and I think it would be sweet if I can do the setting of the selected index in JS.
Any ideas?
I'm not sure I completely understand your question, but if you want to set the value of the select field with JavaScript, you need to obtain the value in JavaScript at some point. I can think of two ways of doing this:
1) When you get the options via AJAX, have the server indicate which one is selected. This can be done by returning HTML <option> tags with selected="selected" set for one of them. To do this, your AJAX request is going to have to provide information about the object this select field is for (so the server can look up the object's current serving_size_id value).
2) When you render the field in your original partial, also render some JavaScript which sets the current value of the field, for example, underneath what you have above:
<%= javascript_tag "var ssid = '#{builder.object.serving_size_id}';" %>
Then, after the options are retrived via AJAX, the ssid variable is checked and the correct option is selected.
using jQuery in rails is easy but a little more difficult than prototype.
ex: "div id="serving_size" class="nice" rel="<%=h num%>">Stuff Goes Here.../div>"
in application.js do the following:
//application.js
$(document).ready(function(){
if($('#serving_size'){
$('#serving_size').live("mouseover",function(){
//we are hovering over specific div id serving size
if($('#serving_size').hasAttr('rel')){
alert($('#serving_size').attr('rel'); //your dynamic rel value, and fire function
}
}
}
if('.nice'){
$('.nice').live("mouseover",function(){
//we are now hovering over any item on page with class nice
if($(this).hasAttr('rel')){
//we are now using jQuery object ref and finding if that obj has attr rel
alert($(this).attr('rel')); // shows dynamic rel value
}
}
}
});
If you use the above code you should be able to do anything you want and fire any custom code from each of your set event callbacks.
The 'live' function in jQuery is great because it can be called on items that will eventually be on the page (eg. if you fill in something with ajax, jQuery will be prepared for that item being in the page)
I hope this help.

Resources