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

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.

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.

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

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

Stubbing helpers rspec

I have a method in application_helper that is called admin_rights? to check if a user should be able to add content to the site. I haven't implemented a user system so it only returns true at the moment. But I am trying to test it, but I can't seem to find out how to stub it out so it returns false in the test. The spec checks for a link that should only be visible when admin_rights? returns true. When i test it manually by changing admin_rights? to false, it works as intended. So I am apparently not stubbing it out correctly.
The Spec is:
context "no admin rights" do
before do
page.stub(:admin_rights?).and_return(false)
visit fencers_path
end
it "should not have add fencer link" do
expect(page).not_to have_link('+ Fekter', href: new_fencer_path)
end
end
I'm looking for the correct way to stub it out or an alternative way to test it.
The test case you posted is an acceptance test. It boots up a server instance and goes through the full stack. You should really not rely on stubbing and mocking in these kind of tests. They should ensure that the application as a whole works and should treat your application as a black box. To replace tiny bits of code is a recipe for very brittle acceptance tests. Also if you run your tests with a driver that runs Javascript then there is no chance to get the stubbing to work because the server runs in a different process than your tests do.
You should implement the logic for admin_rights? and then tune your acceptance test-setup that the logic actually returns false. For example sign in with a normal user, which does not have admin rights. In the end you want your acceptance tests to match closely to the real world scenario.

Resources