Capybara should have_content is not waiting long enough - ruby-on-rails

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

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.

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)

Why Capybara feature tests don't wait?

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.

How to access the same DB data that Capybara uses

I have a very simple password reset form, which is just a text field to enter an email and a submit button.
There are some client-side validations using JS, so I use the Capyabara JS driver when writing spec tests for it.
This test just tests that a password reset token is added to the user's auth_info table.
describe "password reset form", js: true do
let(:email) { "foo#example.com" }
# Create existing user with an email so we can reset it's password
let!(:user) { create(:user, email: email) }
before(:each) do
fill_in email_field, with: email
click_button reset_button
end
it "generates a new token" do
# `token` is definitely getting set properly when I pause it here
# with binding.pry and inspect the object using `user.reload`
# But when running the test it always shows up as `nil`
expect(user.reload.auth_info.token).to match(/[A-Fa-f0-9]{32}/)
end
end
As the comment notes, I know for a fact the token is getting properly set when I inspect it directly using binding.pry. But RSpec and Capybara are seeing it as nil, even after refreshing the model using reload.
Is Capybara maintaining a different cache or something?
Thanks!
EDIT: Also tried different combinations of applying the reload to the User model as well as the AuthInfo model, in case I needed to refresh the latter too
You're using a JS capable browser which means click_button is asynchronous. The result of this is you're executing click_button and then immediately checking for the token before the action triggered by the button has occurred. You can verify this by putting sleep 5 before the expect and the test should pass. The correct way to make the test wait before the check is to use capybaras matchers to look for info on the page that changes once the click_button has completed, something like either of the following
expect(page).to have_text('text that appears after click_button has succeeded')
expect(page).to have_selector('div.abcde') #element that appears after click_button has succeeded
Those will make the test wait until the action has completed and then you can check for the token

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