Multiple expects within scenario test - ruby-on-rails

I have a scenario test:
scenario 'displays the form' do
# Build the setup for user coming from an external site.
expect(page).to have_selector('#braintree-dropin-frame', count: 1)
# User reloads the page
expect(page).to have_selector('#braintree-dropin-frame', count: 1)
# User visits page from within the website.
expect(page).to have_selector('#braintree-dropin-frame', count: 1)
end
First off, is this the proper usage of a scenario test? I'm essentially testing the same thing but in different scenarios. I feel like this should really be three separate scenario tests inside a context block. Am I misusing scenario?

If this isn't one user flow then they should be separate features/scenarios. You also need to be careful when setting expectations for the same selector multiple times in a test that you've checked it has gone away between (if you are checking it reappears again), otherwise you can end up with tests passing when they shouldn't due to timing and asynchronous behavior issues.
Additionally - since you're checking for a CSS selector you probably want to be using have_css rather than have_selector since it reads nicer and will mean the tests keep working if the default selector type is ever changed from :css

Related

Cucumber, Rails: has_content? test passes even though the string isn't there

Scenario is:
Scenario: View welcome page
Given I am on the home page
Then I should see 'Welcome'
And definition of the step is
Then("I should see {string}") do |string|
page.has_content?(string)
end
The test passes whether the word "welcome" is in the home page or not. What am I doing wrong?
A step will only fail if it throws an exception. By its naming convention, the has_content? method returns false if the content is not in the page, and thus does not throw an exception. This will cause your step to "pass" when you intend it to fail.
You need to make an assertion with some sort of unit testing library (my Ruby is a little rusty)
Then("I should see {string}") do |string|
page.has_content?(string).should_be true
end
You'll need something like RSpec to gain access to a library allowing you to make assertions.
Doing this the way shown in the other answer will work but will not give useful error messages. Instead you want
For RSpec
expect(page).to have_content(string)
For minitest
assert_content(string)
For others
page.assert_content(string)
Note that assert_content/assert_text and have_content/have_text are aliases of each other so use whichever reads better.

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)

Rspec testing, Capybara unable to find link or button

This the part of my requests test that fails:
scenario 'Admin destroys a job posting + gets notified' do
parent = create(:parent)
create(:assignment, user_id: #user.id, role_id: 1)
demand = create(:demand, shift_id: 4)
sign_in(#user)
visit demands_path
click_on 'Destroy'
expect(page).to have_content('successfully')
end
This is the error:
Failure/Error: click_on 'Destroy'
Capybara::ElementNotFound:
Unable to find link or button "Destroy"
And here is the corresponding index view, including a "Destroy" link in the app:
Any idea why this test fails??
Odds are the data you assume is on the page actually isn't. This could be for a number of reasons.
Your page requires JS and you're not using a JS capable driver - see https://github.com/teamcapybara/capybara#drivers
Your sign_in method is defined to fill in user/pass and then click a button, but doesn't have an expectation for content that confirms the user has completed login at the end. This can lead to the following visit occurring before login has completed and therefore not actually logging in. Verify that by inspecting the result of page.html or calling page.save_and_open_screenshot before the click.
Your 'Destroy' "button" is neither an actual <a> element or <button> element. Fix that by either using semantic markup or swapping to find(...).click
You are using a JS capable driver but your records aren't actually visible to the app - this would affect all your tests though so I assume it's probably not this. If this was the case the login would fail and you'd probably need to install database_cleaner and configure for use with RSpec & Capybara - https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example

Capybara integration test object creation

I have a basic integration test that is using Capybara, the problem is that if I do not create the required objects firstly the integration test fails. Am I required to create all objects as the first step in an integration test using Capybara? I am using Rails 4.2.4 with Capybara 2.4.3
Fail
scenario 'if media content contains more than 10 items display pagination links' do
sign_in
# Object creation
11.times do
FactoryGirl.create(:media_content)
end
within '.pagination' do
expect(page).to have_content '1'
end
end
Success
scenario 'if media content contains more than 10 items display pagination links' do
# Object creation
11.times do
FactoryGirl.create(:media_content)
end
sign_in
within '.pagination' do
expect(page).to have_content '1'
end
end
If the objects creation affects the page that you are visit-ing in your capybara test then yes, you need to create the objects before you test for elements on that page because upon visiting the page, its content is already grabbed by the test browser.
I assume that you have a visit "some_login_page" and perhaps a redirect upon successful login in your sign_in method, so when finishing the sign_in, the test browser already visited (i.e. grabbed) the page on which you are trying to test content later.
The only exception that comes to my mind is if you used a delayed AJAX request to grab the newly created elements from the server to the page dynamically - in that case creating the objects after page visit might work ok.

Rspec with capybara facing issue with page.should have_content

I am new to Rspec and here I am trying to test one of the integration test. When I (Using Capybara) clicks button then page content gets replaced with post response. Now i am checking for page content, it present on the page but still my test is failing.
Below is the spec
it "get force check takeover1" do
visit('/las_xml')
select('Showroom', :from => 'urlVal')
fill_in('fpHeaderForce', :with => 'PSD.L.Syn')
fill_in('currentDate', :with => '2013-09-11')
click_button('submit')
page.should have_content('Buy 2013 Labor Law Posters')
end
But Result is,
1) las box get force check takeover1
Failure/Error: page.should have_content('Test page')
expected #has_content?("Buy 2013 Labor Law Posters") to return true, got false
# ./integration/las_box_spec.rb:19:in `block (2 levels) in <top (required)>'
and resulted response html contains,
<div class="las-link" id="">
<div class="laborLink_actual labor_01">
<span class="laborLink"></span>
Buy 2013 Labor Law Posters
</div>
</div>
Possible causes
Provided you're indeed testing a html page as your result excerpt seems to show, there may be several possibilities, here :
the text you're trying to test is not the actual one. Double check there's no typo and you use the same locale if multiple language website
the element containing the text may be hidden. Capybara won't consider the content to exist if it is not visible
the previous click on button may simply not work or may lead to an other page
Ways to isolate problem
As often with capybara since fails are just symptoms of real problem, we can deal with all problems using debugging features :
page.save_screenshot( filename ) : saves a screenshot to given file name (for example, 'shot.png')
save_and_open_page : open an inspectable html page in your browser
binding.pry : from pry gem, always useful to inspect context
For an example of screenshot creation through poltergeist, see this.
Edit : dynamic page problems
As mentioned in comments, problem seems to be tied to javascript content editing.
You should still use debugging mentioned before to be sure to isolate your problem. Once done, you can use capybara's #synchronize method to wait for your content :
page.document.synchronize( 10 ) do
result = page.evaluate_script %($(':contains("Post")').length)
raise Capybara::ElementNotFound unless result > 0
end
The synchronize method will be retried for 10 seconds, as long that the containing block raises a Capybara::ElementNotFound exception (any other exception is not catched). So basically, here, we check (using jQuery) if the "Post" content is present in page, and retry until it is or until ten seconds have passed.
If element was not found, test will fail by raising an uncaught exception.
If element is found, you can now proceed with your regular test. This allow to add timing condition in specs.

Resources