I am using Ruby on Rails 3.2.2, cucumber-rails-1.3.0, rspec-rails-2.8.1 and capybara-1.1.2 with the Selenium driver. I have an issue about concurrency on running JavaScript scenarios (supposed that my case is related to a concurrency issue).
In my .feature file I have:
Feature: ...
...
#javascript
Scenario: ...
Given ...
When I log out
Then I should be redirected to the home page
In my step definition .rb file I have:
Then /^I should be redirected to the home page$/ do
current_path.should == root_path
end
When I run the cucumber command line in the Terminal window so to run tests, I get this error:
$ cucumber
Using the default profile...
Feature: ...
...
#javascript
Scenario: ...
...
When I log out
Then I should be redirected to the home page
expected: "/"
got: "/user" (using ==) (RSpec::Expectations::ExpectationNotMetError)
...
However if in my step definition .rb file I use
Then /^I should be redirected to the home page$/ do
sleep(1)
current_path.should == root_path
end
it works as expected: all tests pass.
So, even if the problem seems to be solved, there is a better way to handle my issue? if so, what is the "right"/"common used" solution (maybe using some Capybara API)?
It isn't concurrency. The problem is that cucumber isn't synchronized with your application. The test side doesn't know anything about whether your action has completed its execution or not. So when you're asserting the path your application hasn't generated the response to actually do redirect.
If you'll take a look into capybara you'll see that it polls for the element to appear. (With guard max wait time). So you can either take the same approach and do a polling for some time or you can take a lazier approach and in your test before testing for path add a step that will check for some content on the target page to appear. So you can rely on capybara's built in mechanism.
Related
I am new to Minitest / Capybara / Selenium. But I want to test my destroy Controller action. I an trying the following and it is failing
test "destroy" do
companies_count = Company.count
visit company_path(#company)
click_on "Delete"
page.driver.browser.switch_to.alert.accept
assert_equal (companies_count - 1), Company.count
end
OUTPUT:
test_destroy FAIL (2.17s)
Expected: 6
Actual: 7
Tried this way also.
test "destroy" do
assert_difference('Company.count', -1) do
delete company_url(#company)
end
end
OUTPUT:
Minitest::UnexpectedError: NoMethodError: undefined method `delete' for #<CompaniesControllerTest:0x000056171e550038>
Can someone help me in testing my destroy action?
Assuming you're using a modern version of Rails (5.2/6) and a standard system test configuration (not running parallel tests in threads) then the concerns in the answer of Gregório Kusowski are irrelevant because the DB connection is shared between your tests and your application, preventing the issue of the tests not being able to see your apps changes.
Also assuming you're using Selenium in these system tests, the main problem you're dealing with is that actions in the browser occur asynchronously from your tests, so just because you've told your test to accept the dialog box doesn't mean the action to delete the company has completed when it returns. The way to verify that is to just sleep for a little bit before checking for the change in count. While that will work it's not a good final solution because it ends up wasting time. Instead, you should be checking for a visual change that indicates the action has completed before verifying the new count
test "destroy" do
companies_count = Company.count
visit company_path(#company)
accept_confirm do
click_on "Delete"
end
assert_text "Company Deleted!" # Check for whatever text is shown to indicate the action has successfully completed
assert_equal (companies_count - 1), Company.count
end
This works because Capybara provided assertions have a waiting/retrying behavior that allows the application up to a specific amount of time to catch up with what the test is expecting.
Note: I've replaced the page.driver... with the correct usage of Capybaras system modal API - If you're using page.driver... it generally indicates you're doing something wrong.
This is very likely to happen because what you execute directly in your test happens in a transaction, and your web-driver is triggering actions that happen on another one. You can read more about how it happens here: https://edgeguides.rubyonrails.org/testing.html#testing-parallel-transactions
Here is a similar issue: Rails integration test with selenium as webdriver - can't sign_in
And as it is stated in the Rails Guides and the similar question, you will probably have to use a solution like http://rubygems.org/gems/database_cleaner
If you don't want to do this, the other option you have is to validate that your action was successful via the web-driver, like for example asserting that there are 6 rows in the table you list all companies.
For RSpec Capybara Test Case [ Selenium ], I have near about 7 to 8 spec files. Few of the test cases are dependent on each other. For example, before deleting an product, I have to create the product.
But when test cases excution starts, delete product based rspec runs before the create product rspec.
File Name:-
product_delete.rspec
product_listing.rspec
product_newly_added.rspec
Command : rspec
.rspec file in root folder
--require spec_helper
--format html
--out ./log/rspec_results.html
--color
Test case failed while execution for delete product.
Is there any way to define the sequence of file execution while running RSpec.
Test cases should be independent. For your delete test case you can use factory and create a record then delete it in a single test case as shown in example.
just define factory once and use it to create records, in this way DRY wont be violated.
describe 'POST destroy' do
before(:each) do
#obj = build(:factory_name)
#obj.save
end
it 'it has status 200' do
post :destroy, {"id" => #obj.id}
expect(ClassOfObj.count).to eq(0)
end
end
One possible approach is to not separate these actions into their own test cases. With feature specs you test whole features, not single buttons. So, your test might look like this:
Navigate to new item page. Make sure form is displayed
Fill out the form. Click submit. Verify that success message is displayed on screen.
Verify that you have been redirected to item index page. Verify that newly created item is indeed present on the page.
Click "delete" button.
Confirm that you're on index page and that item is no longer displayed.
As mentioned by most/all the other answers, your tests should be independent, and RSpec supports running tests in random order to guarantee that. One of the easiest ways to implement testing in these conditions is to use factories for the creation of your test data (FactorGirl, etc). In this case you would end up with a test along the lines of
feature "deleting of products" do
scenario "removes last product" do
create(:product) # Use factory to create one product
visit products_path
expect(page).to have_css('div.product', count: 1) # verify there is only one product shown on the page
click_link('delete') # click the delete button
expect(page).to have_text("Product deleted!") # check for a visible change that indicates deletion has completed
visit products_path
expect(page).not_to have_css('div.product') # No products shown any more - you may need to expect for something else first if the products are dynamically loaded to the page to ensure that has completed
end
end
You could check the DB contents rather than revisiting the products_path, but direct DB querying in feature tests is generally a bad smell since it's coupling user experience with implementation details.
If using this in Rails < 5.1 with a JS capable driver, you'll probably need to install database_cleaner and turn off transaction mode for JS tests - https://github.com/teamcapybara/capybara#transactions-and-database-setup and https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example. In Rails 5.1+ the DB connection is shared between the app and tests so you can generally leave transactional testing enabled and database_cleaner is unneeded.
New to Ruby, Rails and TDD. I'm using RSpec with Capybara and Capybara webkit.
Trying to test if a div element exists on a page.
Test Code:
require 'spec_helper'
describe "Login module" do
before do
visit root_path
end
it "should have a module container with id mLogin" do
page.should have_css('div#mLogin')
end
it "should have a module container with id mLogin", :js => true do
page.evaluate_script('$("div#mLogin").attr("id")').should eq "mLogin"
end
end
The first test passes but the second test fails with:
Login module should have a module container with id mLogin
Failure/Error: page.evaluate_script('$("div#mLogin").attr("id")').should eq "mLogin"
expected: "mLogin"
got: nil
Ran the JS in browser dev tools and get "mLogin" rather than nil.
Any ideas? Thanks.
find('div#mLogin')[:id].should eq 'mLogin'
See this from doc:
#evaluate_script
Evaluate the given JavaScript and return the result. Be careful when using this with scripts that return complex objects, such as jQuery statements. execute_script might be a better alternative.
evaluate_script always return nil, as far as I remember.
Anyway, your second test seems like is testing if capybara works, because your first test is enough.
One likely problem is that the have_css matcher supports Capybara's synchronization feature. If the selector isn't found right away, it will wait and retry until it is found or a timeout elapses.
There's more documentation about this at http://rubydoc.info/github/jnicklas/capybara#Asynchronous_JavaScript__Ajax_and_friends_
On the other hand, evaluate_script runs immediately. Since this is the first thing you do after visiting the page, there's a race condition: it's possible that it executes this script before the page has finished loading.
You can fix this by trying to find an element on the page that won't appear until the page is loaded before you call evaluate_script.
Alternately, you can wrap your call in a call to synchronize to explicitly retry, but this is not generally recommended. For situations like this, you're much better off using Capybara's built-in matchers. The evaluate_script method should only be used as a last resort when there is no built-in way to accomplish what you need to do, and you need to take a lot of care to avoid race conditions.
I have a cucumber step that involves the following:
When /^I select from the Open table project with details "([^"]*)"$/ do |details|
find('.details', :text => details).click
end
It fails with the following error message Unable to find css ".details" (Capybara::ElementNotFound)
This step fails about 90% of the time I run it, but occasionally it works. I inspect the page source, and the page does indeed have a css class .details with text that corresponds to details.
Is this some kind of timing issue or issue with cucumber? Or is it more likely that, now that Cucumber has driven me bat-poop insane, I'm not seeing things correctly?
Based on this post: Why does it seem like the Capybara "wait for page to load" timer works for matchers but not finders?
It seems that the find method does not wait for the page to be loaded before doing it's thing. Which means you have a race condition, and cucumber is losing about 90% of the time.
You can warp it in a wait_until block to specify your own timeout and pass the test
page.wait_until(5) { find('.details', :text => details) }
find('.details', :text => details).click
When I use the visit method in Cucumber's step definitions and then run the step through Capybara's Selenium driver it's passed despite the controller isn't implemented.
Here's an example:
Feature
# features/one.feature
Feature: One
#selenium
Scenario: One
Given I visit the example page
Step definition
# features/step_definitions/example_steps.rb
Given /^I visit the example page$/ do
visit example_path
end
Route
# config/routes.rb
Example::Application.routes.draw do
resource :example
end
Controller
isn't implemented
Result
Feature: One
#selenium
Scenario: One
Given I visit the example page
1 scenario (1 passed)
1 step (1 passed)
0m18.162s
However, when I use the RackTest driver all works as it expected to be and the routing exception is risen unless a controller is implemented.
Here's the same example but with the usage of RackTest:
Feature
# features/one.feature
Feature: One
#rack_test
Scenario: One
Given I visit the example page
Result
Feature: One
#rack_test
Scenario: One
Given I visit the example page
uninitialized constant ExamplesController (ActionController::RoutingError)
./features/step_definitions/example_steps.rb:2:in `/^I visit the example page$/'
features/one.feature:5:in `Given I visit the example page'
Failing Scenarios:
cucumber features/one.feature:4 # Scenario: One
1 scenario (1 failed)
1 step (1 failed)
How can I force Capybara to raise the routing error when using the Selenium driver?
Thanks.
Ruby 1.9.2;
Ruby on Rails 3.1.0.rc1;
Cucumber 0.10.3;
Cucumber-rails 0.5.0;
Capybara 1.0.0.beta1;
Selenium-webdriver 0.2.0.
The Selenium driver does not fail when it receives a "failure" http code like 500. If you left your config/environments/test.rb at it's defaults, there should be a line config.action_dispatch.show_exceptions = false. So you can either set this to true or you would have to add some more steps to ensure the page is actually showing what you expect.