Why rspec doesn't wait completion of previous command? - ruby-on-rails

My rails-app contain rspec-tests using Capybara poltergeist (which uses phantomjs). Frontend is Backbone.js. Some tests are sometimes passed, sometimes not. Debugging shows that the right content is not loaded at the time of inspection. If i add an delay, e.c. sleep (inspection_time = 1), then the test passes.
describe 'Direct visit page / # / desk / page / [ID]' do
    before {sign_in user}
    it 'should contain page title' do
      visit "/#/desk/pages/#{page.id}"
      #sleep (inspection_time = 1)
      expect (page) .to have_css (". page-detail p.title",: text => page.title)
    end
  end
js-tests are performed and so long, and yet unreliable delay increases execution time. Is there a good solution for drivers poltergeist (gem 'poltergeist', '1.6.0')?
Might be worth abandon poltergeist in favor of, for example capybara-webkit?

Testing asynchronous requests with capybara can be tricky. As you noticed capybara does not know to wait for a async request to finish. But the good news is that matchers are smart enough to wait for something to appear.
For example if you call
expect(page).to have_css(".new_data")
And a div with new_data class does not initially exist capybara will wait for X seconds (defined in configuration) to see if it appears. However this is sometimes not enough, and you are probably dealing with some kind of a race condition. Thoughtbot wrote a good article on how to resolve this.

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.

Frontend testing with CircleCI and Minitest: Tests don't wait long enough for page to load

So my tests seem to pass about 75% of the time. The other 25% fail because the testing suite isn't waiting long enough for the page to completely load. On our local machines the test takes about 35s, but on CircleCI's logs it's only 5s. (On local I run the tests with BROWSER=chrome m path/to/test.file)
I'm new to this tech stack so any help is greatly appreciated, even if it's just appropriate reference docs.
it 'should use this form' do
assert page.has_content?('#target_form')
within '#target_form' do
# fill in the form and apply payment
end
# it will throw errors here "can't find css..."
# the text/element won't even have loaded yet
# by the time the test is run
assert_equal '24', find_qa('price').text
end
The way you're writing your assertions isn't utilizing Capybara's waiting/retrying behavior so running on slower hardware (CircleCI compared to your local box) can cause your tests to fail. assert_equal evaluates the two parameters compares them, and it's done. This isn't good because Capybara assumes every action could perform asynchronous actions so it doesn't necessarily wait for a button click to submit and load a new page (because it has no way of knowing what action the button click may produce). However if you use the Capybara provided assertions it will wait/retry the comparison up to Capybara.default_max_wait_time seconds for the comparison to be true. I'm not sure how your find_qa method is defined, but if you've declared a custom :qa selector you could do something like
assert_selector :qa, 'price', text: '24'
If find_qa is just doing a CSS selector then you could do
assert_selector :css, "whatever find_qa('price') produces as a css selector", text: '24'
or you could do
find_qa('price').assert_text('24')
Since you're using minitest you probably want to read - https://github.com/teamcapybara/capybara#using-capybara-with-minitest - and configure Capybara's minitest matchers so the counts of assertions run are correct, and to provide a bunch more specific assertions that will utilize Capybara's waiting/retrying behavior. See https://github.com/teamcapybara/capybara/blob/master/lib/capybara/minitest.rb for the matchers added, which will let you write things like
assert_text find_qa('price'), '24'
assert_xpath 'an XPath selector', ...
assert_title ...
assert_current_path ...
etc.

How do define a fine grained structure when using Capybara with RSpec where all tests are run after one `visit()` call

I can't work out how to make Capybara with RSpec handle anything more than two levels before my expects.
In RSpec, I can use describe, followed by context followed by it and I can also nest these to provide really good structured output.
In Capybara I get feature then scenario which is synonymous with it? and that's that, straight into expect. The result is that I get started, but then there's a huge blob of expects checking everything on the page. I know I could break these down individually in scenarios but I don't want to call expensive visit calls for each check. There's no reason why there shouldn't be 50 expects checking a page, so bringing some extra structure would be great.
Which keywords would one use at the different levels of the following structure, either in Capybara, or Capybara with RSpec?
<level1> "full page of app"
// visit the page once here
<level2> "check headings"
<level3> "h1 has text ..."
expect here ...
expect here ...
</level>
<level3> "there are three h2s"
expect here ...
expect here ...
</level>
</level>
</level>
The crucial bit is visit() - this should only happen once, as it would be hopelessly inefficient to visit once per expect when all the expects are on the same page. Trying before :all and background mean that it only works on the first test, the returned HTML is empty for the rest of the tests.
The question has changed now to be more specific about the visit, so I'm adding a separate answer.
Each of your <level 3> scenarios are isolated test sections by design and as such each one will need to perform their own visit() - that visit can be in a before(:each) higher up the tree if you want, but each will need to visit the page. This is by design to isolate each test from each other. You can perform multiple expects in each <level 3> if that makes sense for whatever is being tested, or you could factor multiple expects out into methods like verify_widget_is_displayed_correctly.
One other thing to consider is that depending on what all those expects are testing are you may want to be verifying some of them in view tests (which Capybara's matchers are available in by default as of Capybara 2.5) rather than in integration tests. Integration (feature) tests really are about verifying the behavior of the app as the user clicks around rather than minute details of the views layout.
When using Capybara with RSpec you're not using Capybara instead of RSpec you are using RSpec with some extra stuff thrown in by Capybara. As such you can still use context, describe, it and nest them just like you can when using RSpec without Capybara. Capybara adds sugar on top but 'feature' is just the same as 'describe' or 'context' with type: 'feature' set. 'scenario' is just an alias for 'it', 'fscenario' is just 'it' with focus: true metadata set, and 'xscenario' is 'it' with skip metadata set
You can see it here - https://github.com/jnicklas/capybara/blob/master/lib/capybara/rspec/features.rb

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.

Sporadically succeeding cucumber tests

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

Resources