Why Capybara feature tests don't wait? - ruby-on-rails

I have a small trouble with my tests suite: when I run spec which the checking ajax action on the page, sometimes I get random error
Failure/Error: expect(page).to have_content 'DHH'
This error shows very rarely (about 1/100), but this very confused me. I decided this a 'race condition' cause, and I add this config in my spec/rails_helper.rb
Capybara.default_max_wait_time = 10
but this is don't help for me, and I decided add timestamps
it 'adds new DHH', js: true do
find('#modal_new_dhh').click
fill_in('name', with: 'DHH')
p 'click button'
p Time.now.strftime('%H:%M:%S.%L')
click_button('Submit')
p 'checking content'
p Time.now.strftime('%H:%M:%S.%L')
expect(page).to have_content 'DHH'
p 'after checking content'
p Time.now.strftime('%H:%M:%S.%L')
end
and see that
"click button"
"17:34:43.083"
"before checking content"
"17:34:43.127"
"after checking content"
"17:34:43.213"
why Capybara don't wait after click button?
sorry for my bad English

The wait in your example occurs in the have_content matcher. Where you're outputting times from will never show a delay because click_button has nothing to wait for, it just clicks a button and moves on (because it has no idea what it would wait for, clicking a button could do anything), however the have_content matcher will wait up to Capybara.default_max_wait_time for the content to appear.
Note your find, 'fill_in' and click_button calls also wait for the relevant elements to appear BEFORE performing their actions

As you said this is a race condition. As to why it happens, I can't really say, the only reason I could think of would be that there is a difference between a user experience and an automated testing, because computers are very fast.
I had faces the same challenge sometime ago and I found this tutorial which showed me a way to go about resolving these kind of issues. I hope it would be useful to you too.

Related

Rspec, Capybara, selenium_chrome_headless. Wait for response after submit remote form

In use
Rails 6, Rspec rspec-rails (3.9.0), Capybara capybara (3.31.0), selenium_chrome_headless
I try to submit ajax form with remote: true. How can I wait for response?
Now works sleep 0.2 but I really don't like approach like this.
I found another way:
Timeout.timeout(Capybara.default_wait_time) do
loop do
active = page.evaluate_script('jQuery.active')
break if active == 0
end
end
but it doesn't work.
My recodr should dissapear after request:
expect(page).to_not have_content('User name')
Any suggestion? Thanks for advance
You should set an expectation for whatever visibly changes in the page when the request is done. If that means the text "User name" disappears from the page when the request has finished then your expectation of
expect(page).not_to have_content('User name')
or
expect(page).to have_no_content('User name')
will wait until that content (same as have_text) isn't visible on the page anymore (up to Capybara.default_wait_time seconds), which would imply the request has completed. If that text is on the page multiple times and only being removed from one location then you could use a count option in have_content or scope your not have content expectation to the specific section of the page where the text is being removed from.

Capybara should have_content is not waiting long enough

So I am writing an acceptance test using capybara. The scenario was to connect our newsletter system to external mail service.
We will get redirected to our external service page to request access to the external mail service. And we will be redirected back to our system page when succeed.
When "I grant authorization" do
fill_in "username", :with => "myapp"
fill_in "password", :with => "secret"
click_button "Allow Access"
end
Then "I should see 'Connection Established'" do
page.should have_content "Connection Established"
end
And "I should see that I'm connected to Sample External Service" do
page.should have_content "Connection Established: Sample External Service"
page.should have_content "Deactivate Sample External Service syncing"
end
But if I am not using sleep before the page.should have_content "Connection Established". The spec will fail. From what I know, using sleep is not the best practice, because it will make our test run slow.
How to make it waiting until it got redirected back to our system
There are 3 ways to adjust the maximum amount of time Capybaras methods will wait for their expectations to be true/elements to exist
Capybara.default_max_wait_time = <seconds> - This is the global setting which should be set high enough for the vast majority of your method calls
Capybara.using_wait_time(<seconds>) do ... end - This temporarily changes default_max_wait_time inside the block and then returns it to its original setting when done. This makes sense when you have a number of methods you want to call with a new wait time, or you need to call a helper method with the wait time set to a different value.
:wait option - All Capybara finder and expectation methods accept a :wait option which will change the maximum wait time for that method call. This makes sense to use when you have a specific case that requires a bit more waiting than normal
# This will wait up to 10 seconds for the content to exist in the page
page.should have_content "Connection Established: Sample External Service", wait: 10
Note: In the future when posting questions it is generally helpful if you provide the full exact error message you get as part of your question.
For page transitions, I like to wait for the URL to change first, and then wait for content on the page. It gives you a more specific error if the redirect fails, and it naturally splits the long wait into two chunks. With two opportunities to hit default_max_wait_time, the timeout is potentially doubled without actually changing it. If it's still not enough, you can always pass a custom timeout into have_current_path with the wait: parameter.
expect(page).to have_current_path("externalservice.com")
expect(page).to have_content("Connection Established")
There might be a page.should equivalent, but I think the official guidance is to move to the expect syntax, since the should syntax was deprecated.
You can use capybara_watcher gem, it is an elegant way of waiting for the pege to have a change in its content.
Check it out on RubyGems.org
Example:
wait_until_content_has "Connection Established" do |text|
page.should have_content text
end
The perks of using this is that the sleep time is the actual time the page takes to have a change and you can configure it to exit after the second you choose if the page didn't change.
My solution to similar problems:
module WaitUntil
def wait_until(max_wait_time = Capybara.default_max_wait_time)
Timeout.timeout(max_wait_time) do
sleep(0.2) until yield
end
end
end
Using:
wait_until { page.has_css?('li', text: 'test', visible: true) }
You can use Capybara.using_wait_time(seconds) to temporarily change the value of Capybara.default_max_wait_time for special cases:
Capybara.using_wait_time(10) do
page.should have_content "Connection Established"
end

Intermittent failing feature tests using Capybara/Rspec: expect { click }.to change { ... }

We have a few feature specs that occasionally fail that a rerun fixes. I have a lot of experience testing around Capybara timing, but in this particular case I'm not actually sure how to fix it. Trying to web search for this has been surprisingly ineffective.
Here's the code we have that fails:
expect {
click_on 'Save'
}.to change { Report.count }.from(1).to(2)
There's a couple of different ways I can think to do this:
expect(Report.count).to eq 1
click_on 'Save'
expect(Report.count).to eq 2
Or perhaps:
expect(Report.count).to eq 1
click_on 'Save'
expect(page).to have_something
expect(Report.count).to eq 2
But given that these tests fail so randomly and infrequently, it's difficult to test that we're doing it right.
What is the correct way to verify a count changed based on a click in Capybara?
Firstly, direct DB checks in feature/system tests (which I assume is what you're writing since you're using Capybara is generally a bad code smell since you should generally be verifying things through visual changes on the page (they make more sense for request/controller tests).
If you insist on doing direct DB feature tests you still need to use visual checks to synchronize those checks. This is because actions like click_on have no knowledge of any further browser action those clicks initiate and can just return immediately after clicking. Since the count won't actually be changed until after a request is processed you need to delay until that request is completed (usually done by waiting for a visible page change that indicates it has completed). Therefore the correct way would be
expect(Report.count).to eq 1
click_on 'Save'
expect(page).to have_something # wait for something that indicates the request triggered by click_on has completed
expect(Report.count).to eq 2
which could also be written as
expect {
click_on 'Save'
expect(page).to have_something
}.to change { Report.count }.from(1).to(2)
or more flexibly as
expect {
click_on 'Save'
expect(page).to have_something
}.to change { Report.count }.by(1)

Rails, Capybara - capybara cannot find added html elements sometimes

I have a function below
def fill_in_sources_details
click_button 'New Source'
assert_text 'Sources'
fill_in 'text', :with => #source.text
fill_in 'url', :with => #source.url
end
After New Source clicked, new fields added to the page. Sometimes the test fails because the test doesn't wait for new elements to be added so new fields cannot be found be Capybara. I tried to add assert_text 'Sources' which is obvious but it still fails sometimes. Any idea how to fix it?
A number of things could be going on here, some of which depend on which driver and driver version you're using.
You don't actually have a field that matches 'text' - That's not likely here since it works sometimes.
The initial click_button 'New Source' is missing the actual button. This could happen depending on which driver you're using if the button animates on to the page (slide in, zoom in, etc). You can tell if that's what's happening by putting a sleep 5 before the click_button. If that fixes the issue then you should look into disabling animation in test mode. It will speed up your tests and make them more reliable.
The click_button is happening but it takes too long for the fields to show on the page (slow ajax request, etc). Putting the sleep 5 after the click_button should have diagnosed that, however maybe it's taking longer than 5 seconds. The assert_text 'Sources' is a good attempt to diagnose that, as long as the text 'Sources' would only be on the page when the input is visible but that text seems too generic for that (it's probably on the current page already). You can always pass a wait option to fill_in, which will make it wait longer for the element to appear
fill_in 'text', :with => #source.text, wait: 20
You have an intermittent error in your app that's preventing the fields from showing. Diagnose that by catching the ElementNotFound, pausing the app and looking at the errors in your browser
begin
fill_in 'text', :with => #source.text, wait: 20
rescue Capybara::ElementNotFound
binding.pry # byebug, etc - whatever debugging you use
end

how to test a stale link click (or link I can't get to) using capybara/rspec

Artists cannot rate their own artworks
it "should display error if voting on own artwork", :js => true do
sign_in
visit "/upcoming"
click_link "like_post_1"
page.should have_content("Can't vote on your own artwork")
end
This was passing just fine.
However, I can't click on like_post_1 anymore because I added a feature to prevent voting links from appearing next to your own artworks.
Does this mean I no longer need test coverage for this scenario because it's extremely rare that someone can click on a voting link for their own artwork? Or should still have coverage to test the ajax response, because it's not tested anywhere else and it's possible for some stale page of links to somehow exist in a tabbed browser window. If so... how do I test it if I cannot call click_link?
I could try to create a POST request to create the vote, but capybara doesn't support posts, and I can't test the ajax response that way...
Or is there a way to simulate tabbed browsing in capybara?
Suggestions?
You can use CSS display:none or visibility:hidden for the own artwork instead of eliminating the link from the DOM. You might have to set
Capybara.ignore_hidden_elements = false
Another way is giving up Capybara and putting them into the controller/model spec. Controller/model spec might be a better place for the extremely rare case or safeguarding your app case.
Please try this:
Use sleep function.
sleep 10 use to wait process upto 10 seconds..
it "should display error if voting on own artwork", :js => true do
sign_in
visit "/upcoming"
click_link "like_post_1"
sleep 10
page.should have_content("Can't vote on your own artwork")
end

Resources