TinyMCE: Capybara::ElementNotFound: Unable to find visible frame "content_ifr" - ruby-on-rails

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.

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

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

Capybara - NameError: uninitialized constant Capybara::TimeoutError

I am creating integration tests for my rails application.
The application I am working to is a little slow. In my test, I execute a certain action within the website (a "saving" - which reloads in the end the page) and the following capybara action runs before the page is actually reloaded.
I cannot use "sleep (seconds)" as this freezes the "reloading" itself.
So I wanted to give a try to this github idea: https://gist.github.com/metaskills/1172519
but I get the following error:
NameError: uninitialized constant Capybara::TimeoutError
Can someone tell me why I get this error and what does it mean?
As you posted, you're trying to make a method which waits for the ajax requests to finish.
But there's a better way to do this:
You have a view, which loads a modal (remote, with ajax). You should not do something like the wait_until method. Or even though not with using while true.
The best way of doing this, is to set an unique html element on the modals content:
<!-- in your modal view/partial -->
<span id="modal"></span>
... modal code
When you then use Capybara like this:
find("#modal")
The find method automatically waits for all ajax requests to finish.
See https://www.varvet.com/blog/why-wait_until-was-removed-from-capybara/ for more inputs.
The reason you're getting the error is because the Capybara::TimeoutError class was removed in Capybara v2, along with the #wait_until method. As the answer by #RaVeN states you should just be telling Capybara to expect some content or elements on the page which will make Capybara wait for it to appear automatically (as long as you're using a JS capable driver)
expect(page).to have_content("Some content that appears after the page has loaded") # will wait up to Capybara.default_max_wait_time seconds for the content to appear
or if the path of the page changes you could do
expect(page).to have_current_path('<the new path you want to wait to load>')
As an aside - there is no reason sleep in tests should pause a page loading since the tests, app, browser each run in separate threads/processes assuming you're running a JS capable driver. If you're not running a JS capable driver and are instead using the default rack_test driver then waiting/sleeping for anything is pointless because every action occurs synchronously.

Is the element draggable ? Capybara

I'm developping my application with Rails.
So, my question is quite simple and though, I haven't find any answer on the Internet.
I have some div with class="draggable" on my view.
I applied to all the element having this class the method .draggable(); found here :
http://jqueryui.com/draggable/
Then, in my capybara test, I want to check if all of the ".draggable" element really are draggable or not.
And I don't know what to use to verify if they're actually draggable or not.
With the chrome jquery console, I noticed that when I run ('.draggable').data(), there a uiDraggable object.
Anyways, I don't know how to write my test. If someone has an idea....?
thank you

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