Capybara Selenium webdriver Transactional Rollbacks work arounds - ruby-on-rails

I'm writing a couple of Feature Specs for an app and using the default Selenium webdriver that comes with Capybara. This is the spec I have written.
DatabaseCleaner.cleaning do
find(:css,'.dropdown-toggle').click
click_on "Locations"
find(:css, "#location-8-upgradesub-60").click
value1 = find(:css, "#location-8-review-subscription").text
value1.should be == '(2) Reviews (Paid)'
end
I'm facing 2 issues with this snippet:
1) Capybara isn't waiting for the XHR to get over and is coming out of the test before that. It works if I give a sleep condition for about 10 sec.
UPDATE
Solved 1) by setting Capybara.default_wait_time = 15 and writing a helper to make sure jQuery isn't active. page.evaluate_script('jQuery.active').zero?
2) I'm not able to rollback the DB transaction that takes place when selenim simulates the test. I see an INSERT and COMMIT in the test.log but no ROLLBACK because of which I need to keep changing my specs every time I run the test. If I use,DatabaseCleaner.strategy = :truncation, my entire DB gets wiped out and that is not something I want.
I've done some extensive googling on this issue and haven't been able to find an efficient work around. I've tried using the same transactional thread too, for the test server. Haven't had fruitful results with too! Any heads up or help would be greatly appreciated. Thanks in advance!
UPDATE
I followed this link https://relishapp.com/rspec/rspec-rails/docs/transactions and put my spec inside a before(:each) block and stored the value in an #value1 instance variable to compare it with the desired value within the it block. I haven't had any luck with that too.
before(:each) do
find(:css,'.dropdown-toggle').click
click_on "Locations"
find(:css, "#location-8-upgradesub-60").click
wait_for_ajax #Helper method to wait for ajax call to get over
find(:css, "#location-8-review-subscription").should be_visible
#value1 = find(:css, "#location-8-review-subscription").text
end
it "should open the dropdown, find Location and open it's modal", js:true do
#value1.should be == '(2) Reviews (Paid)'
end

With 1), I think have_content or have_selector will work. These methods will wait for some seconds before checking the content/selector exist. You could config this time via spec_helper.rb. You could put have_content/have_selector BEFORE your find(..).click to make sure it is exist before next tests.

Finally found a work around. I added this code snippet in spec_helper.rb. Not using Database Cleaner anymore.
Reference: http://www.opinionatedprogrammer.com/2011/02/capybara-and-selenium-with-rspec-and-rails-3/#comment-441060846. The entire comment thread is pretty useful.
ActiveRecord::ConnectionAdapters::ConnectionPool.class_eval do
def current_connection_id
# Thread.current.object_id
Thread.main.object_id
end
end

Related

Rails - Minitest + Capybara + Selenium - Test destroy action

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.

Capybara click_link works wrong

I have such bug. Here is my code:
it "shows places sorted by date of creation" do
click_button( I18n.t("models.places.actions.index.sort_by"))
click_link(I18n.t("models.places.actions.index.date_of_creation"))
sorted_places_names = places.map(&:name).reverse
link_names = all("a.place-link").map(&:text)
expect(link_names).to eq(sorted_places_names)
end
And my problem is that click link here must to send params:
"?by_created_at: true" and controller response with sorted places by date of creation, in descendant order.
My problem is when capybara clicks on this link, GET request have only path, without params needed.I`m using poltergeist here.
Also I have such test:
it "shows orders today" do
today_order.customer.reputations << create(:reputation, place: place)
visit place_statistics_loyalty_path(place)
click_link(I18n.t("statistics.loyalty.today"))
expect(page).to have_selector("#order_#{today_order.id}")
end
It tests out similar behaviour. And it works properly, but here I am not using js.
Is it javascript driver problem ?
Thanks. Sorry for bad text, it`s my first question.
What you're looking to do is use Poltergeist's "send_key" method as shown here in the documentation:
https://github.com/teampoltergeist/poltergeist#sending-keys
Setting the keys using that method and then proceeding with clicking the link should work.
Are you sure you're actually using poltergeist for that test since there is no js: true or driver: :poltergeist metadata on it? If it was not using poltergeist and actually just using the rack_test driver then no JS would get excuted and you would likely see the behavior you're seeing.
If you are actually using the poltergeist driver then it's possible you have a race condition, due to click_button being asynchronous, which could cause the by_created_at parameter not to be set before the link click actually happens. You can test if that is the issue by putting a sleep 2 between the click_button and click_link calls

RSpec get around unique requirement in testing

My Signup database has an index on email with a unique requirement. This is great, but the problem is that I'm trying to run integration tests, and every time I go rake rspec spec/features/...rb, unless I did rake db:test:purge, rake db:test:prepare first, it runs into the unique problem and refuses to run. How do I streamline this?
From the code below, you can see that every time I'm running the test, I'm creating a set of seed data with my before(:all), but since the seed data is always the same, this is driving the uniqueness error.
I'm happy to just put this seed data elsewhere or create it some other way, as long as my test suite is still able to run using this seed data.
describe "how requests should flow" do
before(:all) do
#signup_dd = Signup.create(email:"example#example.com")
end
it "should have 2 inventories and 2 signups to start" do
Signup.count.should == 1
end
describe "request creation" do
before do
Signup.find_by_id(#signup_dd)
visit '/requests/new'
save_and_open_page
fill_in '#borrow__1', :with => 1
click_button
end
it "should affect new Requests and Borrows" do
...
end
end
end
There are two ways to fix this:
Remove the (:all) from the before block. RSpec will execute the before block for each test. It will then undo itself after each test. This is really what you want as it ensures changes made by each test do not bleed into other tests. This is usually the recommended approach.
Keep the (:all), but then add a (:after) block to undo your changes. With the :all argument, the before block is only executed once instead of every time. However, it doesn't automatically undo itself like :each, so the :after block becomes necessary. It is up to you, however, to figure out what needs to go in there. In your example, for instance, it might be:
after(:all) do
Signup.delete_all # Or whatever undoes what the before block did
end
See this blog post regarding the use of the two techniques.
when you use before(:all), you need use after(:all) to clean up the data you created in before(:all)

Ruby on Rails - RSpec Javascript Test with Capybara (new to RoR)

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.

factory_girl + rspec doesn't seem to roll back changes after each example

Similar to the problem described here:
http://rpheath.com/posts/411-how-to-use-factory-girl-with-rspec
in Short (shorten'd code):
spec_helper:
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
factories.rb:
Factory.define :state do
f.name "NY"
end
in my spec
before(:each) do
#static_model = Factory(:state) # with validate uniqueness of state name
end
error:
duplicate entry name "NY" etc.
Question:
Shouldn't rspec clear database before each spec example and hence not throwing duplicate entry errors?
Things i think off:
do you use rake spec to run your testsuite: that builds up the database from scratch (to make sure nothing is sticking)
do you use, anywhere, a before (:all) ? Because whatever you create inside a before :all should be deleted again in a after :all or it keeps on existing.
Question: Shouldn't rspec clear database before each spec example and hence not throwing duplicate entry errors?
RSpec with DatabaseCleaner or RSpec Rails with use_transactional_fixtures will clear the DB as long as your created the data in the example itself. before :all do ... end is considered outside of the example, because the data remains untouched across multiple examples. Whatever you create in before :all you have to delete in after :all.
In order to delete whatever you create automatically use before :each do ... end. Be aware the same data will be created and removed 10 times if you have 10 examples. The difference between before :all and before :each is better explained here: rails rspec before all vs before each
Some more possible causes:
There's still a states.yml fixture sitting around
Someone played around on script/console test and forgot to clean up afterwards.
You might also find it's because you haven't wrapped the statement in:
describe "what it should do" do
#static_model = Factory(:state) # with validate uniqueness of state name
end
I discovered that was the change that solved this problem:
Why isn't factory_girl operating transactionally for me? - rows remain in database after tests
I have had similar questions about what sort of starting state one can expect when using FG and RSpec.
While I too wait for clarity, Database Cleaner could be a good fix: http://rubydoc.info/gems/database_cleaner/0.6.7/frames
hth -
Perry
When you use Factory(:state) wich is a shortcut to Factory.create(:state), factory_girl returns you a saved object.
Use Factory.build(:state) instead.
Dude maybe your yaml fixtures from regular unit tests get mixed into your rspec?

Resources