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.
Related
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.
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)
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'])
I have a very simple Rails app with a react component that just displays "Hello" in an existing div element in a particular page (let's say the show page).
When I load the related page using its URL, it works. I see Hello on the page.
However, when I'm previously on another page (let's say the index page and then I go to the show page using Turbolinks, well, the component is not rendered, unless I go back and forth again. (going back to the index Page and coming back to the show page)
From here every time I go back and forth, I can say that the view is rendered twice more time.Not only twice but twice more time! (i.e. 2 times then 4, then 6 etc..)
I know that since in the same time I set the content of the div I output a message to the console.
In fact I guess that going back to the index page should still run the component code without the display since the div element is not on the index page. But why in a cumulative manner?
The problems I want to solve are:
To get the code run on the first request of the show page
To block the code from running in other pages (including the index page)
To get the code run once on subsequent requests of the show page
Here the exact steps and code I used (I'll try to be as concise as possible.)
I have a Rails 5.1 app with react installed with:
rails new myapp --webpack=react
I then create a simple Item scaffold to get some pages to play with:
rails generate scaffold Item name
I just add the following div element in the Show page (app/views/items/show.html.erb):
<div id=hello></div>
Webpacker already generated a Hello component (hello_react.jsx) that I modified as following in ordered to use the above div element. I changed the original 'DOMContentLoaded' event:
document.addEventListener('turbolinks:load', () => {
console.log("DOM loaded..");
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
I then added the following webpack script tag at the bottom of the previous view (app/views/items/show.html.erb):
<%= javascript_pack_tag("hello_react") %>
I then run the rails server and the webpack-dev-server using foreman start (installed by adding gem 'foreman' in the Gemfile) . Here is the content of the Procfile I used:
web: bin/rails server -b 0.0.0.0 -p 3000
webpack: bin/webpack-dev-server --port 8080 --hot
And here are the steps to follow to reproduce the described behavior:
Load the index page using the URL http://localhost:3000/items
Click New Item to add a new item. Rails redirects to the item's show page at the URL localhost:3000/items/1. Here we can see the Hello React! message. It works well!
Reload the index page using the URL http://localhost:3000/items. The item is displayed as expected.
Reload the show page using the URL http://localhost:3000/items/1. The Hello message is displayed as expected with one console message.
Reload the index page using the URL http://localhost:3000/items
Click to the Show link (should be performed via turbolink). The message is not shown neither the console message.
Click the Back link (should be performed via turbolink) to go to the index page.
Click again to the Show link (should be performed via turbolink). This time the message is well displayed. The console message for its part is shown twice.
From there each time I go back to the index and come back again to the show page displays two more messages at the console each time.
Note: Instead of using (and replacing) a particular div element, if I let the original hello_react file that append a div element, this behavior is even more noticeable.
Edit: Also, if I change the link_to links by including data: {turbolinks: false}. It works well. Just as we loaded the pages using the URLs in the browser address bar.
I don't know what I'm doing wrong..
Any ideas?
Edit: I put the code in the following repo if interested to try this:
https://github.com/sanjibukai/react-turbolinks-test
This is quite a complex issue, and I am afraid I don't think it has a straightforward answer. I will explain as best I can!
To get the code run on the first request of the show page
Your turbolinks:load event handler is not running because your code is run after the turbolinks:load event is triggered. Here is the flow:
User navigates to show page
turbolinks:load triggered
Script in body evaluated
So the turbolinks:load event handler won't be called (and therefore your React component won't be rendered) until the next page load.
To (partly) solve this you could remove the turbolinks:load event listener, and call render directly:
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(document.createElement('div'))
)
Alternatively you could use <%= content_for … %>/<%= yield %> to insert the script tag in the head. e.g. in your application.html.erb layout
…
<head>
…
<%= yield :javascript_pack %>
…
</head>
…
then in your show.html.erb:
<%= content_for :javascript_pack, javascript_pack_tag('hello_react') %>
In both cases, it is worth nothing that for any HTML you add to the page with JavaScript in a turbolinks:load block, you should remove it on turbolinks:before-cache to prevent duplication issues when revisiting pages. In your case, you might do something like:
var div = document.createElement('div')
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(div)
)
document.addEventListener('turbolinks:before-cache', function () {
ReactDOM.unmountComponentAtNode(div)
})
Even with all this, you may still encounter duplication issues when revisiting pages. I believe this is to do with the way in which previews are rendered, but I have not been able to fix it without disabling previews.
To get the code run once on subsequent requests of the show page
To block the code from running in other pages (including the index page)
As I have mentioned above, including page-specific scripts dynamically can create difficulties when using Turbolinks. Event listeners in a Turbolinks app behave very differently to that without Turbolinks, where each page gets a new document and therefore the event listeners are removed automatically. Unless you manually remove the event listener (e.g. on turbolinks:before-cache), every visit to that page will add yet another listener. What's more, if Turbolinks has cached that page, a turbolinks:load event will fire twice: once for the cached version, and another for the fresh copy. This is probably why you were seeing it rendered 2, 4, 6 times.
With this in mind, my best advice is to avoid adding page-specific scripts to run page-specific code. Instead, include all your scripts in your application.js manifest file, and use the elements on your page to determine whether a component gets mounted. Your example does something like this in the comments:
document.addEventListener('turbolinks:load', () => {
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
If this is included in your application.js, then any page with a #hello element will get the component.
Hope that helps!
I was struggling with similar problem (link_to helper method was changing URL but react content was not loaded; had to refresh page manually to load it properly). After some googling I've found simple workaround on this page.
<%= link_to "Foo", new_rabbit_path(#rabbit), data: { turbolinks: false } %>
Since this causes a full page refresh when the link is clicked, now my react pages are loaded properly. Maybe you will find it useful in your project as well :)
Upon what you said I tested some code.
First, I simply pull out the ReactDOM.render method from the listener as you suggested in your first snippet.
This provide a big step forward since the message is no longer displayed elsewhere (like in the index page) but only in the show page as wanted.
But something interesting happen in the show page. There is no more accumulation of the message as appended div element, which is good. In fact it's even displayed once as wanted. But.. The console message is displayed twice!?
I guess that something related to the caching mechanism is going on here, but since the message is supposed to be appended why it isn't displayed twice as the console message?
Putting aside this issue, this seems to work and I wonder why it's necessary in the first place to put the React rendering after the page is loaded (without Turbolinks there was the DOMContentLoaded event listener)?
I guess that this has do with unexpected rendering by javascript code executed when some DOM elements are yet to be loaded.
Then, I tried your alternative way using <%= content_for … %>/<%= yield %>.
And as you expected this give mitigate results ans some weird behavior.
When I load via the URL the index page and then go to the show page using the Turbolink, it works!
The div message as well as the console message are shown once.
Then if I go back (using Turbolink), the div message is gone and I got the ".. unmounted.." console message as wanted.
But from then on, whenever I go back to the show page, the div and the console message are both never displayed at all.
The only message that's displayed is the ".. unmounted.." console message whenever I go back to the index page.
Worse, if I load the show page using the URL, the div message is not displayed anymore!? The console message is displayed but I got an error regarding the div element (Cannot read property 'appenChild' of null).
I will not deny that I completely ignore what's happening here..
Lastly, I tried your last best advice and simply put the last code snippet in the HTML head.
Since this is jsx code, I don't know how to handle it within the Rails asset pipeline / file structure, so I put my javascript_pack_tag in the html head.
And indeed, this works well.
This time the code is executed everywhere so it makes sense to use page-specific element (as previously intended in the commented code).
The downside, is that this time the code could be messy unless I put all page-specific code inside if statements that test for the presence of the page-specific element.
However since Rails/Webpack has a good code structure, it should be easily manageable to put page-specific code into page-specific jsxfiles.
Nevertheless the benefit is that this time all the page-specific parts are rendered at the same time as the whole page, thus avoiding a display glitch that occurs otherwise.
I didn't address this issue at the first place, but indeed, I would like to know how to get page specific contents rendered at the same time as the whole page.
I don't know if this is possible when combining Turbolink with React (or any other framework).
But in conclusion I leave this question for later on.
Thank you for your contribution Dom..
How do I keep PhantomJS from returning the page to Capybara for several seconds to allow AJAX to finish loading? The HTML body I am getting back is:
<!--div class='loading'>PLEASE WAIT WHILE PAGE LOADS</div-->
Which is telling me that PhantomJS returned the page before the page finished loading its AJAX assets. Is there any way I can slow this down? I've tried using the rasterize.js solution but it looks as though any script I add to poltergeist's "phantomjs_options" hash is overlooked.
Using a Capybara method that waits for you, assert that content is on the page that should exist on the page only after AJAX has completed. For example, if the page should say "Page loaded!" only after AJAX has completed, and assuming you're using RSpec, do this:
expect(page).to have_content("Page loaded!")
You can then safely test other things on the page that depend on AJAX having completed.
Capybara methods that wait for specified content to appear include find and find_* methods, within, has_content?/has_no_content?, has_css?/has_no_css?, has_selector?/has_no_selector?, click_*, fill_in, check, uncheck, select and choose. Some Capybara methods don't wait, including current_path, visit, execute_script, evaluate_script and node accessors like text and value. all and first wait if you give them options that give them something specific to wait for (:count, :minimum, :maximum, :between), otherwise they don't. It's quite a minefield. (I took the lists mostly from this blog post.)
There are other ways to wait (sleep (bad), polling the database until a change caused by AJAX appears, etc.) but using Capybara methods that wait for you is the usual and recommended way.
Not sure what you are actually waiting for css wise on the loaded page, but the way I would do it is:
Capybara.using_wait_time(x) { page.find('css_here') }
where x is the number of seconds you want capybara to allow before erroring if it cannot find the css.
expect(page).to have_content "Page loaded!", wait: x