Is there a better alternative to using sleep in Capybara? - ruby-on-rails

In my test I have a step where I fill out a field and press enter. This field then returns a result set on the next page.
Here is my helper method:
def search(term)
fill_in('query-text', :with => term)
click_button('search-button')
end
After that I have a step that simply says:
page.should have_content(tutor)
However, the issue is that even though the page after my page loads with the results, the step after it passes even if it should be false. I have set a debugger in the step and when I manually check it, the assertion fails as I expect it too. My assumption is that the the next step is checking before the page even reloads. I placed a sleep at the end of my search method to make it look like:
def search(term)
fill_in('query-text', :with => term)
click_button('search-button')
sleep 5
end
But I feel like using sleep is a hacky way to resolve this issue. I am using Capybara 2 so the use of wait_until is removed. Is there a better way to handle this issue rather than relying on sleep?

Do you have tutor text in your HTML page hidden? has_content? returns true for any text present in html, including hidden text which is not visible. So I would rather replace it with expect(page).to have_text(tutor), which checks only for visible text on a page. .text is also pretty slow method, so these extra split seconds may be handy for you.
Another approach is to restore wait_until method in your spec helpers, since it's really handy in most cases:
def wait_until(timeout = DEFAULT_WAIT_TIME)
Timeout.timeout(timeout) do
sleep(0.1) until value = yield
value
end
end
In any case it will be better than waiting for a fixed timeout each time.

This test passes as tutor is already present on the page.
So you should check not for tutor but for something else, e.g. for element with text tutor that is present only after page reload.

yes, you're right wait_until is removed, new method is #synchronize, but I don't know for now how to use it:)
look into
http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara
https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/base.rb#L44

Related

Is there a built-in Capybara method to click on an element multiple times?

I have a form with a '+' icon to increase the number value inside of another tag.
So with Capybara I can just click on it like this:
all('.qty-input')[0].find('.more').click
But I want to be able to set the amount of clicks through a variable. I tried doing this but it raises an error because click does not accept any parameters.
all('.qty-input')[0].find('.more').click(number_of_clicks)
I think I can create a simple method to this, like this, but is it necessary? Doesn't Capybara have anything built-in for several clicks?
def multiple_clicks element, number_of_clicks
number_of_clicks.times{|n| element.click}
end
Edit:
The original code I put there was
def multiple_clicks element, number_of_clicks
number_of_clicks.map{|n| element.click}
end
Which doesn't make sense, so I edited it using times instead of map method.
No, Capybara doesn't have anything like that built-in

RSpec Capybara Element at X no longer present in the DOM

I have a RSpec test with this:
within all('tr')[1] do
expect(page).to have_content 'Title'
expect(page).to have_content 'Sub Title'
end
And it's failing at expect(page).to have_content 'Title' with the following error message:
Element at 54 no longer present in the DOM
I have not been able to find the exact meaning of what this error message means and this test is flakey, sometimes it passes, sometimes not.
Unlike other capybara finders, all doesn't really wait for elements to appear in the DOM. So if your table is not fully loaded, it simply goes straight to the expectation within that block and could potentially fail. That could easily explain why sometimes it fails and other times it succeeds.
I suggest using another expectation before this block and ensure that the table is fully loaded first. I don't know what your DOM looks like, but you can try something like:
expect(page).to have_css('tr td', :count => 15)
So at this point, you've waited for 15 rows to show up in the DOM prior to moving onto your next steps. Hope that helps.
As the other answer details, #all by default doesn't wait for elements to appear so it's possible you're not actually getting the elements you think you are. Rather than the solution in the other answer, it is possible to make #all wait for elements to appear by specifying one of the :count, :minimum, :maximum, or :between options.
within all('tr', minimum: 10)[1] do
...
end
for instance. This is all assuming that the action before the #within in your test isn't triggering an ajax action that is replacing existing rows in a table with other rows. If row replacement is happening then you may be running into one of the biggest downsides of using #all -- when using #all the returned elements cannot automatically be re-queried if they leave the page and are replaced, since their entire query can't be stored with them (no index into the results). In that case you're better off changing the code to
within find(:xpath, './/tr[2]') do
...
end
This way the element you're searching within can be reloaded automatically if needed

click element if it exists in capybara

I wish to click a popup message that appears on my test app if it is present. I am new to capybara and cant seem to find a way to do this. I have previous experience with watir and if I were doing it with watir it would be something like:
if browser.link(:text, "name").exists? do
browser.link(:text, "name").click
end
How can I do the same in capybara? Note this link will not always appear hence why I wish to have the if statement.
A straight of the head code is to just invoke a has_link? matcher and then click_link action:
if page.has_link?('name')
page.click_link('name')
end
But it will be not the fastest solution as Capybara will make two queries to driver to get element: first one in has_link? and the second one in click_link.
A better variant may be to make only one query to get an element:
# This code doesn't check that an element exists only at one place and just chooses the first one
link = first('name')
link.click if link
or
# This code checks that element exists only at one place
links = all('name')
unless links.empty?
links.count.should == 1
link = links.first
link.click
end
Personally I would go with has_link?/click_link implementation as the second variant does't check that element exists only at one place and the third one is too long.
In case I used has_css? query :
if page.has_css?("button #popUpButton")
click_button(#popUpButton")
end
You can use first, with option minimum: 0
item = first ".dropdown-item", minimum: 0
item.click if item&.visible?
Have you tried doing something like:
if page.find('.id') do
click_link('Some Link') # or page.find('.id').click
else
page.should_not have_selector('.id') # or something like that
end

How can I alleviate timing/AJAX woes using Capybara/Capybara Webkit/RSpec click_button and page.select?

For the sake of simplicity, I've left out most of my test and only included the offending code. It is:
click_button('Search')
page.select 'Preferred', :from => 'ticket_service_type'
When I run this, I receive the following:
Failure/Error: page.select 'Preferred', :from => 'ticket_service_type'
Capybara::ElementNotFound:
cannot select option, no select box with id, name, or label 'ticket_service_type' found`
The AJAX request this button click event triggers doesn't have anything to do with the select tag, so reversing the order in the test causes the test to pass. I know that Capybara's default wait time is 2 seconds and so I changed it to 10 with:
Capybara.default_wait_time = 10
This does not cause the test to pass. How can I get these two methods to play nice with one another and work in the order in which a user would operate the web page?
(Had I posted the code from my spec, I bet this would have been solved quickly.)
From The Cucumber Book (emphasis mine):
Luckily, Capybara knows how to deal with this situation in a simple way. If we add an explicit call to find, passing a CSS selector for a DOM element on the page that doesn’t yet exist, Capybara will wait a little (50ms) and try again until the element appears. If it doesn’t appear after a while (two seconds by default, though this is configurable), it will raise an exception, causing the step definition to fail.
So have your AJAX write something to the DOM then find() it in your step definition. It's not ideal. In my case I'm introducing a (hidden) DOM element just to facilitate testing but I haven't found another way.
Be sure to add :js => true to integration tests which depend upon JavaScript.

Generated string won't save to database... but tack "A" on the end and it will

In my application, these "planners" (essentially, article ideas) follow predetermined templates, written in Markdown, with some specific syntax here:
Please write your answer in the following textbox: [...]
Please write your answer in the following textarea:
...So here, on line, you should write one thing.
...Here, on line 2, you should write another.
...
...
...
Essentially, [...] is a text input, and a group of lines starting with ... are a textarea. That's not really the issue - it's just to explain what part of this code is doing.
On actions new and edit, the standard planner form is displayed, with the correct fields based on the template (for new) or current planner body (for edit). On save, the template's fields are filled in with params[:fields], and the resulting Markdown is saved as the planner's body. The code, I'd hope, is now possible to follow, knowing this context. Only relevant controller code is provided, and it uses make_resourceful.
class Staff::PlannersController < StaffController
make_resourceful do
actions :all
before :create do
find_planner_format
if #planner_format
current_object.body = fields_in_template #planner_format.body
else
flash[:error] = 'Planner format not found!'
redirect_to staff_planners_path
end
current_object.user = #current_user
end
before :update do
current_object.body = fields_in_template(current_object.body)
end
end
private
def fields_in_template(template)
fields = params[:fields] || {}
if fields[:inline]
template.gsub! /\[\.\.\..*\]/ do
"[...#{fields[:inline].shift}]"
end
end
if fields[:block]
template.gsub! /^\.{3}.*(\n\.{3}.*)*$/ do
fields[:block].shift.split("\n").collect { |line|
"...#{line}"
}.join("\n")
end
end
current_object.body = template
end
end
And now, the mystery: in the update action, changes to the body are not saved. After debugging, I've determined that the issue does not lie only in current_object.save, since the following before :update code does what you would expect:
before :update do
current_object.body = 'test string'
end
In fact, even this gets the expected result:
before :update do
current_object.body = fields_in_template(current_object.body) + 'a'
end
So now, the question: why is Rails so insistent that it not save the result of the function - and even then, only when it comes from update? More debugging showed that the object attribute is set, and even claims to save successfully, but reloading the object after save reverts the changes.
At first it looked like the resulting string was just a "poisoned" variable of sorts, and that rebuilding the string by appending "a" removed that strange state. However, the following code, which ought to add an "a" and remove it again, also failed to save.
before :update do
new_body = fields_in_template(current_object.body) + 'a'
new_body.slice! -1
current_object.body = new_body
end
This is just bizarre to me. What am I doing wrong here, and what can I possibly do to debug further? (Or if you happen to instantly see my mistake, that'd be nice, too...)
EDIT: After checking SQL logs (not sure why I didn't think to do this earlier), it would seem that Rails doesn't seem to acknowledge the new body attribute as actually being different, even though checking the string in the debugger confirms that it is. As such, Rails doesn't even run an UPDATE query unless something else is modified, in which case body is not included.
Got it! Sometimes it just helps to state the question out loud...
The deal is, I had forgotten that, when passing current_object.body to fields_in_template, it was being passed by reference. As such, all gsub! methods were running directly on current_object.body, so Rails acknowledged no real "changes" by the time I set body to what had just been set.
The solution:
def fields_in_template(template)
template = template.dup
# ...
end
Thanks for letting me talk to myself, and mission accomplished!
I'm not a Ruby programmer but does adding an 'a' convert the type of the variable to string? Maybe your variable is of the wrong type without adding 'a'.

Resources