Capybara/Poltergeist doesn't keep hovered elements visible - ruby-on-rails

I'm trying to set up a automated test suite using capybara (v 2.4.3) and poltergeist (v 1.5.1)
My Problem is that poltergeist needs to click on a img (which is a link) that is only visible if you hover over another element (see screenshot in link)
flags exposed
(sorry but I can not upload images yet, so I linked my screenshot above)
I expose those hidden images by using this command:
find('div#userlogin div.colorswitch_languageswitch a.btn_earth').hover
the whole test looks like this
it 'switches language to english', focus: true, js: true do
find('div#userlogin div.colorswitch_languageswitch a.btn_earth').hover
save_and_open_screenshot # to ensure the flags are displayed, this works
within('div#language_flags') do
click_link('Language_usa') # when I try to click it doesn't switch languages
end
page.should have_content('Login name (email)')
page.should have_content('Forgot password?')
end
I've already turned on the debugging feature of poltergeist and it seems that poltergeist is clicking on the right coordinates but doesn't hit the link.
After this I changed the view a bit and removed the "display:none" style attribute, so the images that need to be clicked are always visible. The test now passes.
So my question is: How do I make poltergeist move the mouse to the images after hovering the parent element like a user would do it, so they don't disappear
PS: If I've forgotten important information please let me know

Related

Why is capybara saying node is obsolete and how to solve?

I have an spec/capybara test which searches for an element and then attempts to run a JS script to scroll the element into view. However, Capybara claims the node is obsolete by the time it attempts to run the JS.
The lines at issue are consecutive. Here they are:
element = page.find(selector, visible: false)
Capybara.current_session.driver.browser.execute_script(script, element.native)
I have done a fair bit of debugging already. When placing a debugger between the find and execute_script lines, calling element indeed returns an obsolete node Obsolete #<Capybara::Node::Element>.
Running page.find(selector, visible: false) within the debugger does not return an obsolete node but rather the normal active node you would expect #<Capybara::Node::Element tag="div" path="/HTML/BODY[1]/DIV[6]/DIV[2]/DIV[1]/DIV[1]/DIV[54]">
Furthermore, removing the two lines and running them manually in the debugger sees capybara correctly find the DOM element, run the JS correctly, and the spec passes
The relevant code:
def scroll_to(selector, align = true)
if align
script = <<-JS
arguments[0].scrollIntoView(true);
JS
else
script = <<-JS
arguments[0].scrollIntoView(false);
JS
end
element = page.find(selector, visible: false)
Capybara.current_session.driver.browser.execute_script(script, element.native)
end
scroll_to(".xdsoft_time[data-hour='13'][data-minute='15']")
Without knowing what's happening on your page it's impossible to say exactly why you're getting the 'obsolete node' error, but that error indicates the node that was originally found is no longer in the page. This can happen if you visit a new page, if the part of the page containing that node is replaced by JS, etc.
Passing visible: false and then trying to scroll that element into the page doesn't make sense though since if the element isn't visible then you'll never be able to scroll it into view (visible means drawn on the page, it does not mean 'in the viewport').
Other issues with your code are
you should not be calling the driver specific execute_script, but rather just use the Capybara session execute_script (generally if you're using Capybara.current_session.driver.browser you're doing something wrong).
page.execute_script(script, element)
Capybara already provides a scroll_to so use it instead of writing your own
page.scroll_to(page.find(selector)) # Defaults to scrolling to the top
If you need control over the alignment of the element just pass the :align option
page.scroll_to(page.find(selector), align: :center) # :top, :bottom, :center

TinyMCE: Capybara::ElementNotFound: Unable to find visible frame "content_ifr"

In a Capybara feature spec, I am attempting to do the following:
within_frame("element_content_content_ifr") do
# do stuff
end
Where element_content_content_ifr is the CSS ID of my tinymce iframe.
I get the error:
Capybara::ElementNotFound:
Unable to find visible frame "element_content_content_ifr"
I've set a pause during the test and inspected element. The iframe with the specified ID is definitely there, but Capybara can't find it. I am not having issues with Capybara finding iframes in other parts of my application, only the TinyMCE iframe.
I have also attempted sleep 5 before executing the within_frame line, but I get the same error. Is there something I'm doing wrong? Is there a proper way to do Capybara tests when TinyMCE is on the page?
Attached is a screenshot of the iframe's visibility on the page, as well as its DOM ancestors:
From the HTML/CSS shown it's confusing how the iframe is shown at all since its ancestor <div role="application" ...> has visibility: "hidden" as a style and there isn't a visible override of that anywhere below. First thing would be to make sure you're running a recent version of Capybara and whatever driver you're using (I assume selenium). If you already are, or that doesn't fix the issue you can try working around it with
within_frame("element_content_content_ifr", visible: false) do
and see if that works.
Beyond that if you can figure out what CSS is making the frame actually visible while inside the hidden element, I would appreciate it if you could file an issue on the Capybara project with enough info to replicate the issue.

Problem with feature spec to check form button is disabled after submit

I am writing an RSpec / capybara feature spec to test that, after a form submit button is pressed, the button is disabled and its text is changed to 'ADDING...'.
In the spec, the button is clicked (and if valid, the form is submitted) with:
find(".Booknow-modal-bookingButton button.primaryCTA").click
And the corresponding javascript that is executed is as follows:
$('form#booknow-room').on('submit', function(e) {
// If form is submitting then html5 validation has passed,
// now disable the button to prevent duplicate submissions.
$('form#booknow-room button.primaryCTA').attr('disabled', true).text('Adding...')
})
When the spec runs I can see that this is working. The next line of the spec is:
expect(page).to have_button('ADDING...', disabled: true)
But I think that, by this time, the form submit has redirected to the basket page so it isn't finding the button, so the test fails.
I have also tried the following variants:
expect(find('.Booknow-modal-bookingButton button.primaryCTA').value).to eq 'ADDING...'
expect(".Booknow-modal-bookingButton button.primaryCTA").to have_content("ADDING...")
expect(page).to eq(".Booknow-modal-bookingButton button.primaryCTA", disabled: true)
But none of them work.
The error message returned by rspec is:
Failure/Error: expect(page).to have_button('ADDING...', disabled: true)
expected to find button "ADDING..." but there were no matches
Assuming clicking the button triggers a full form submission rather than an XHR based submission what you're trying to do may not be possible. This is because of the defined behavior for clicking an element in the WebDriver spec - https://w3c.github.io/webdriver/#element-click. 12.4.1 steps 10, 11, and 12 are the relevant parts where driver implementors may attempt to detect if a navigation is occurring and wait for it to complete. This can mean your check for disabled won't happen until after the page has already changed.
The question to ask next is whether this is something that needs to be tested via end-to-end Capybara tests, or whether it could instead be tested in Javascript only tests.
If you do consider it necessary to be tested in your Capybara tests you could try registering a driver with the page load strategy capability set to "none" which should tell the driver not to wait for any navigations and MAY allow the button state to be checked (you'd probably only want to use that driver for this specific test). Another potential option would be to use execute_script to install an event handler that prevents the actual submit from occurring - although every time you modify the running page code in your test you're potentially reducing the usefulness of the test.
I implemented the other "potential solution" which #Thomas Walpole mentioned -- modify the form, so that when you click the submit button it doesn't actually submit. This allows you to validate that the button is disabled without capybara blocking until the form submits.
page.execute_script(
"$('form#booknow-room').attr('action', 'javascript: void(0);');"
)
page.click_button('Submit')
expect(page).to have_button('Adding...', disabled: true)

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'])

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

Resources