How can I handle small waits with Capybara? - capybara

I'm trying to test a page which when it initially comes up the UI is available for a split second, then some async call happens and the screen is blocked for a second. After the async call some options in a dropdown get populated and the UI is refreshed. I think Capybara is inputting some text in the fields in that split second the UI is available before the async call. After which, the fields are cleared or changed (some of my Capybara inputs seem to be missing). I'm trying to avoid a manual wait since I heard Capybara should be able to deal with kind of thing naturally. Is there any way to do it?

To have Capybara wait you need to tell it what to wait for - so if you want to wait until the async call is completed you need to determine what change the async call response makes to the page. If all it does is populate some options in a dropdown then you can do something like
if using RSpec
expect(page).to have_select('select_id_name_or_label_text', with_options: ['option populated by call', 'another option populated by call'])
if not using RSpec
page.assert_selector(:select, 'select_id_name_or_label_text', with_options: ['option populated by call', 'another option populated by call'])

Related

How to find out why rails feature test fails

we're currently working on a piece of mapping software, where we use Leaflet with custom left and right sidebars as well as a text-filter where we filter for different POI features. The whole thing looks like this:
The flow is as follows
A user visits a map under a unique link
The controller renders the HTML template first (no data is bein published)
Inside our javascript an ajax call fetches the data and renders markers, some panels, etc., etc.
We use capybara with poltergeist for all our feature tests.
In our master everything is working as it should be.
In another branch I added password protection, hence a bootstrap modal pops up if a map is password protected and has not yet been unlocked within the current session.
Everything is working fine except for some feature tests that fail lately and after messing around with stuff I still don't have a clue why exactly.
Let's see for example this test
feature 'Places map filter', js: true do
before do
#map = create :map, :full_public
create :place, :unreviewed, categories: 'Playground', map: #map
visit map_path(map_token: #map.public_token)
find('.open-sidebar').trigger('click')
end
scenario 'Nothing filters nothing' do
show_places
show_events
show_places_list_panel
expect(page).to <...>
end
...
end
Capybara claims to be unable to find some css elements. Calling screenshot_and_open_image reveals that it is still showing an overlay (hiding everything else) until all data have been loaded. Something seems to be hanging within my Javascript...
.
I've been messing around with the test-environment, which had an effect:
config.action_controller.asset_host = "file://#{::Rails.root}/public"
config.assets.prefix = 'assets_test'
The test passes since the data is now there. A screenshot reveals missing assets, which is guided by a proper warning message Not allowed to load local resource: <path>. I'm puzzled since querying the data happens via an ajax-call from one of the files that capybara tells to be unaccessible.
I don't know how to continue, since I don't want to start skipping tests. I hope you can help guiding me finding the error.
Thanks in advance,
Andi
Update
Thanks to Thomas for his hint on ES6 features. I used poltergeist's inspector mode and hence was able to discover an arrow function I introduced! That's why the JS driver couldn't deal with a callback I was passing to a promise which did not resolve...
Firstly, ensure you have js_errors: true in your Poltergeist driver registration - https://github.com/teampoltergeist/poltergeist#customization - so that you will get runtime JS errors reported.
Secondly, if you're using any ES6+ features in your JS code, make sure you transpiling them into ES5 compatible code since Poltergeist/PhantomJS only supports JS <= ES5, and will silently fail at JS parse time if it parses JS using features like let.
And finally, by using trigger you are bypassing Poltergeists checks that the button is actually clickable by the user, so make sure you're not clicking a button too early (before whatever behavior gets attached to the button is actually attached)

Geb: Element is no longer attached to the DOM inside waitFor

I am getting an "Element is no longer attached to the DOM" error from Geb tests. The thing that's confusing me is that the error is from within waitFor itself -- I inserted the wait specifically to allow the async activity on the page to complete before moving ahead with clicking a link, which was previously the source of the same error. If the wait itself fails, now I'm at a loss.
The code is something like
waitFor { $("div", text: "... search string ... ") }
$("a", id: "element-id").click()
and the stack trace shows that the waitFor itself is actually the problem:
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:187)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:145)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:554)
at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:268)
at org.openqa.selenium.remote.RemoteWebElement.getText(RemoteWebElement.java:152)
at geb.navigator.NonEmptyNavigator.matches_closure28(NonEmptyNavigator.groovy:474)
at geb.navigator.NonEmptyNavigator.matches(NonEmptyNavigator.groovy:471)
at geb.navigator.NonEmptyNavigator.filter_closure2(NonEmptyNavigator.groovy:63)
at geb.navigator.NonEmptyNavigator.filter(NonEmptyNavigator.groovy:63)
at geb.navigator.NonEmptyNavigator.find(NonEmptyNavigator.groovy:48)
at geb.content.NavigableSupport.$(NavigableSupport.groovy:96)
at geb.Browser.methodMissing(Browser.groovy:193)
at geb.spock.GebSpec.methodMissing(GebSpec.groovy:51)
at [my test]_closure7([my test].groovy:147)
at [my test]_closure7([my test].groovy)
at geb.waiting.Wait.waitFor(Wait.groovy:106)
From the stacktrace I can see that you use that selector inside of a test class and not a module so the possibility of a module base element being detached can be ruled out.
If this is happening consistently for you then it means that one of the elements selected by the div selector gets removed from DOM before its text is being retrieved to filter on it.
There are two reasons why this can happen:
Your selector is very slow - selecting all div elements in a page and then filtering them based on text in the JVM can take a lot of time. Assuming that you use the default waiting preset then if that selector takes more than 5 seconds then the waitFor {} block will simply run once, get the exception and never retry because it runs out of time. You should do as much filtering as possible in the browser, that is use a CSS3 compatible selector and use Geb's text filtering extension on an as small as possible element set.
Your page is async in a periodic way and it changes quicker than the selector is able to filter based on element text. This would be again possible because your selector looks like it could be potentially very slow.
Basically I would suggest coming up with a more specific selector than what you have there currently.

Proper way to remember multiple parameters across requests in Rails

My application feature a "main" page where most of the action happens: There are tags for filtering and a list of results in a (paginated) table, plus the possibility to select some or all results in a "shopping cart".
This page has to keep track of a whole lot of things: what tags are selected, what items are selected, and how the result table is sorted and what page it's on. Everything has to persist, so if I select a new tag, the page must partially reload but remember everything (sorting, what's selected).
Right now I'm handling everything with parameters, and for each action taken on the page, all links (select a tag/item, change page, sort table) are updated to include previous parameters + the relevant new addition. This works, obviously, but it feels kind of inefficient, as I have to reload more of the page than I want to. How is this situation normally handled? I can't find that much info on google at all, but it doesn't feel like a particularly uncommon case.
tl;dr: How to best make sure all links (to the same page) always include everything previously selected + the new action. There are a lot of links (one per tag to select/deselect, one per result item to select/deselect, one per sort option, one per page)
There are five ways to do that:
Method 1: By parameters
You mentioned this. I never think of this as it's too troublesome. Anyway it's still a solution for very simple case.
Method 2: By cookie
Save the settings to a cookie and read the cookie in controller to arrange layout settings.
Method 3: By LocalStorage
Similar to cookie but allows more space.
Method 4: By Session
If you are using ActiveRecord to save session, this could be the best solution for pure pages loading. Save the user preferences into session and load it in next layout.
Method 5: Use Ajax
This is the best solution IMO. Instead of whole page loading, use Ajax to refresh/retrieve changes you need. Using together with above method, a user can even continue his last preferences. This is the most powerful and should be applicable to your case which looks like a web app than a website.
Have you tried creating model for all those attributes? and just always load the 'latest' when on the page load, if you dont need them you can always have a flag for that session.

How do you test a submit button being disabled after clicking in capybara?

We are trying to disable a certain submit button after clicking on it. Something like this:
assert !page.has_css?("#review_button[disabled='disabled']")
click_button "Review"
assert page.has_css?("#review_button[disabled='disabled']")
The problem, of course, is that the form submits before the second assertion is checked. Is there any way to disable the actual submission of the form, or suspend it until after the second assertion is checked?
I remember having this same issue and never found a good way to do this, because as you said, it only gets to the next assert when the "Review" action completes.
What I ended up doing, considering that the action that the button did took a long time (and thus justifies having the button be disabled) is to make the action create a Delayed Job job and then let it run asynchronously. Then it's pretty easy to mock that out and make it sleep for a few seconds (or what have you) to check that the button is disabled.

Railscast doesn't recommend a solution for production, I'm looking for a reason why

In this railscast our good friend Mr. Bates walks through a solution to creating an app that can search, sort, and paginate a set of data. When going through AJAX searching he provides a solution that will display results of the search the moment a user enters input into the search box. Here is his solution:
$('#products_search input').keyup(function () {
$.get($('#products_search').attr('action'), ↵
$('#products_search').serialize(), null, 'script');
return false;
});
However he states "Note that this is only a quick demo and isn’t the best way to do this. There are several jQuery plugins that you can use if you do something like this in a production app." I'm looking for an explanation on why he believes this isn't suitable for production. Thanks in advance!
There are two major issues I see with this solution. The first is that you are making an HTTP (AJAX) request every time a key is pressed, which will not be the most efficient way of doing this. The second is that you are basically calling eval in the response, and eval is bad as it can lead to malicious users executing code you don't want to be executed.
Some suggestions on improving:
Use a proper JSON parser and pass the data back as JSON. (you can use $.getJSON)
Throttle the request - don't do it on every keyUp, maybe start a timer and only submit the request if no keys have been pressed in the last second, meaning it won't make lots of calls for people who type fast.
Cache the response. If you have already searched for something, then there is no point fetching the data twice. Keep a note (in a JS Object) of previous calls in this session and their results.

Resources