Capybara issue inside a within block - ruby-on-rails

require "spec_helper"
require "rails_helper"
include Capybara::RSpecMatchers
include Capybara::DSL
Capybara.javascript_driver = :webkit
feature "Course", :type => :feature do
scenario "Get index and search for course types", js: true do
visit "/courses"
within("//body") do
find(:xpath, "//input[#id='course_type_id_1']").click
find(:xpath, "//div[#class='course-right-sec']")
expect(page).to have_content('65,171 courses')
expect(page).to have_content('Fundamentals of Design')
end
end
end
The problem i am facing with the above code is that when i find a specific div inside a within block, whether the id of the div is correct or not it passes the test.

Whoa, this is a lot of expectations for one test. While some testing purists would say one expectation per test, feature tests that need to load js are time consuming, but you might be doing too much here. One logical way would be to have one feature test for each of your courses; that way you have a couple expectations for each.
This isn't the exact solution to your problem, but it will help debugging in the future.
All of your expectations are based on the have_content matcher. Basically, if it's in the DOM on load, all of these expectations are going to pass, regardless of your find and click events. Ie. find(id).click
I might be able to help more if you give more context to whats going on here and what you are trying to test for and against. Attaching your js would help also.

Related

Rails controller test with Rspec

I an trying to organize code by making partial html.erb files that are shared frequently(e.g. _form.html.erb)
I want to check whether my partial code works well with different models/controllers, so I am manually doing CRUD from the views.
It would be nicer to test my code automatically using Rspec but I have no idea. Can anyone give me some guidance how to test controller code with Rspec?
To test controller and views together you write feature specs and request specs .
Request specs are lower level specs where you send HTTP requests to your application and write expectations (aka assertions in TDD lingo) about the response. They are a wrapper around ActionDispatch::IntegrationTest. Request specs should be considered the replacement for controller specs, the use of which are discouraged by by the RSpec and Rails teams.
# spec/requests/products_spec.rb
require 'rails_helper'
RSpec.describe "Products", type: :request do
describe "GET /products" do
let!(:products) { FactoryBot.create_list(:product, 4) }
it "contains the product names" do
get "/products"
expect(response).to include products.first.name
expect(response).to include products.last.name
end
end
end
Feature specs are higher level specs that focus on the user story. They often serve as acceptance tests. They use a browser simulator named Capybara which emulates a user clicking his way through the application. Capybara can also run headless browsers (headless chrome, firefox, phantom js, webkit etc) and "real" browsers through selenium. The minitest equivalent is ActionDispatch::SystemTestCase but RSpec features do not wrap it (it took minitest/testunit years to catch up here).
# Gemfile
gem 'capybara'
# spec/features/products_spec.rb
require 'rails_helper'
RSpec.feature "Products" do
let!(:products) { FactoryBot.create_list(:product, 4) }
scenario "when a user views a product" do
visit '/'
click_link 'Products'
click_link products.first.name
expect(page).to have_content products.first.name
expect(page).to have_content products.first.description
end
end
This specs tests the products#index and products#show action as well as the root page and the associated views.
Both types of specs have their strengths and weaknesses. Feature tests are good for testing large swaths of the application but are heavy. Request specs are faster and its easier to replicate a specific request that causes a bug/issue but you're basically just matching HTML with regular expressions which is highly limited.
To check whether partial code works well with different models/controllers. You can add render_views in controller specs.
How to test controller code with Rspec?
Read the official doc https://relishapp.com/rspec/rspec-rails/docs/controller-specs
And this page may help: https://thoughtbot.com/blog/how-we-test-rails-applications

Capybara Selenium webdriver Transactional Rollbacks work arounds

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

xpath can't find anything in rspec test

UPDATE 2 - adding before(:each) block
before(:each) do
...
post_via_redirect(
sub_domain(
path: login_view_path(
#p
),
subdomain: #account.subdomain
),
first_name: "Capybara",
last_name: "RSpec"
)
expect(response).to render_template(:show)
end
UPDATE
response.body has the data I'm looking for
but page.html only has a doctype.
ORIGINAL
I'm trying to do some integration testing. I have this:
require "spec_helper"
feature "Tracking without Javascript" do
...
scenario "when navigating to a content" do
...
# this passes so I know there is a body
expect(response.body).to include(c.name)
find(:xpath, "/html") # see if xpath works...
end
end
and I get the error:
Failure/Error: find(:xpath, "/html")
Capybara::ElementNotFound:
Unable to find xpath "/html"
any ideas?
post_via_redirect doesn't involve Capybara - that directly (more-or-less) interacts with Rails, but not through a browser. That would be why Capybara never points an actual page.
Original (misguided) answer for posterity:
If you are using the newest versions of everything, then Capybara will not find invisible elements by default. Could you try find(:xpath, "/html", visible: false) and see if that works? The other thing I can think of is that your XPath may not be matching as you expect. Try //html instead.
On the meta level, you'll almost never see this kind of assertion, as the end-user doesn't care if you have an html element or not. You will get more useful tests by checking for things that the user can see. I also prefer to use CSS selectors, as other things (JavaScript and CSS) tend to care about them as well. Our whole test suite has maybe one or two XPath selectors for very ugly cases.

Capybara features outside of /features directory

I have some large end-to-end integration tests that for CI purposes I don't want in my spec/features folder in Capybara. Instead I have them in a spec/integration folder. Knowing that Capybara loads differently based on the folder, I put the :type=>:feature option on my describe, like so:
describe 'Recurring Contract Orders', :type=>:feature, :js=>true, :focus=>true do
it "satisifies the use case" do
....
But no luck, I am still getting the:
NameError:
undefined local variable or method `page' for #<RSpec::Core::ExampleGroup::Nested_1:0x007fd396bd2998>
error when I run the test. Is there something else I'm missing?
I don't know if it's something you are still trying to solve, but I was having the same problem. You are able to use the specific Capybara commands if you just include the DSL:
include Capybara::DSL
I haven't found out how to just have Capybara include my /integration folder yet, but this has worked for now.
For future ref, I think the right thing (according to https://github.com/jnicklas/capybara) is to tag the specs with :type => :feature, e.g.
describe "Some pages", :type => :feature do
specify "some behaviour"
end

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.

Resources