I've created a 'lil helper file to DRY up my specs:
module RemoteMacros
def go_to_another_localhost(port)
Capybara.current_driver = :selenium
Capybara.app_host = "localhost:#{port}"
Capybara.run_server = false
end
def go_webapp
Capybara.current_driver = :rack_test
Capybara.app_host = "localhost:3000"
Capybara.run_server = true
end
end
And here's the spec that goes with it:
require "rails_helper"
describe "Placing an ad", :type => :feature do
it "(1) The ad token should match across domains" do
go_to_another_localhost(8000)
visit "/"
expect(page).to have_css( 'iframe[data-nc-token="demo-token"]' )
end
it "(2) can go places" do
go_webapp
visit "/"
save_and_open_page
expect(page).to have_content( 'lol' )
end
end
At first glance, it seems as though everything should work as expected (how many times has THAT been said? ;D ). However, my second test returns red, with a rather peculiar message:
Placing an ad
(1) The ad token should match across domains
(2) can go places (FAILED - 1)
Failures:
1) Placing an ad (2) can go places
Failure/Error: visit "/"
NoMethodError:
undefined method `empty?' for nil:NilClass
What I'm working on is some cross-domain JS. I need to verify that my JS in another domain matches an attribute of a model in my actual Rails app. I got the first part to work, where I leave my app to go to the other domain, but the second part blows up in my face, when I'm returning the app_host to the original location.
How I should I modify my code to accomplish my intention?
Related
I'm attempting to set up system tests with Capybara and Selenium on an existing Rails 5.1 (Upgraded from Rails 4) app that already had capybara based feature tests. Here's what I've done so far.
In the gem file under group :development, :test:
gem 'chromedriver-helper'
gem 'selenium-webdriver'
gem 'rack_session_access'
In the environments/development.rb and environments/test.rb:
config.action_mailer.default_url_options = { host: 'localhost:3000' }
In the spec\rails_helper.rb:
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome)
end
Capybara.configure do |config|
config.default_max_wait_time = 10 # seconds
config.default_driver = :selenium
config.app_host = 'http://localhost:3000'
config.server_host = 'localhost'
end
The issues I'm having are both with new systems tests and old feature tests.
With the system tests it appears that Capybara isn't creating a page object as I get undefined local variable or method 'page' Additionally when I duplicate the same test under the feature test directory I don't have this issue.
With the old Capybara feature tests, working with the rackTest driver, a Chrome window opens but I get No route matches [GET] "/rack_session/edit"
config.middleware.use RackSessionAccess::Middleware is already present in the environments/test.rb
Example system test:
require 'rails_helper'
describe User do
let(:user) { create :user }
let(:membership) { create :membership, admin: true}
let(:admin) { create :user, memberships: [membership] }
context 'viewing the index' do
it 'directs you to the appropriate page' do
set_current_user(admin)
visit root_url
click_button 'Manage Users'
expect(page.current_url).to end_with users_path
expect(page).to have_selector 'h1', text: 'Users'
end
end
end
Example feature test:
require 'rails_helper'
describe 'edit an assignment' do
let(:roster) { create :roster }
let(:user) { create :user, rosters: [roster] }
before :each do
Timecop.freeze Date.new(2018, 1, 10)
set_current_user(user)
end
after :each do
Timecop.return
end
context 'returns the user to the appropriate index page' do
let(:date_today) { Date.new(2017, 4, 4) }
let(:start_date) { Date.new(2017, 3, 31) }
let(:month_date) { date_today.beginning_of_month }
it 'redirects to the correct URL' do
visit roster_assignments_url(roster, date: date_today)
visit new_roster_assignment_url(roster, date: start_date)
click_button 'Create'
expect(current_url)
.to eq roster_assignments_url(roster,
date: month_date)
end
In the spec_helper:
def set_current_user(user)
page.set_rack_session user_id: user.id
end
You need to have the gem puma installed. New rails 5 projects have it installed by default, but your app was made in Rails 4, which is why it didn't.
Why is this? Well, if you were to do a bundle update (and I'll admit I can't explain why) you'd get this error when trying to run the specs, which is a lot more explanatory:
System test integration requires Rails >= 5.1 and has a hard dependency on a webserver and `capybara`, please add capybara to your Gemfile and configure a webserver (e.g. `Capybara.server = :webrick`) before attempting to use system tests.
Googling this error led me to this page, which explains Capybara needs a server.
After adding puma, I'm able to run system tests on your application.
A number of issues here.
Why are you setting - config.app_host = 'http://localhost:3000' ??? That would run the tests against your dev instance rather than the test instance Capybara starts. app_host really should only ever need to be set if you are doing subdomain based testing. This could be the reason for the no route error (normally rack_session_access would only be included in the test environment), or that could be caused by having not actually included the middleware as specified in the rack_session_access gem readme.
NEVER do expectations against current_path/current_url directly, instead use the provided matchers or you'll have flaky tests
expect(page).to have_current_path(users_path)
page is just an alias for Capybara.current_session and is a member of the module Capybara::DSL. If it's not available in the scope of your tests that it most likely means Capybara::DSL isn't included. That would normally be done by rspec-rails - https://github.com/rspec/rspec-rails/blob/master/lib/rspec/rails/vendor/capybara.rb#L21 - so it's possible you haven't actually set the test type to 'system'. If it's that it's not available in your spec_helper methods, just using Capybara.current_session instead is usually easier.
From time to time, some specs of my Rails app seem to fail randomly because suddenly English is not the default language anymore, but German:
expected: "Project test customer - Project test name (Audit, Access for all, 2015-06-15).pdf"
got: "Project test customer - Project test name (Audit, Zugang für alle, 2015-06-15).pdf"
As one can see, the part "Access for all" suddenly is "Zugang für alle". I googled for a solution and it seems that I18n.locale is a global object, so when it's changed in a spec, it persists.
The problem doesn't always occur, but I can reproduce it when specifying a seed like this: rspec --seed 51012. So it really seems to be some issue with a spec being executed before (or after) some other spec.
I have a feature spec which tests whether the locale can be changed, like this:
it 'offers contents in german' do
visit root_path(locale: :de)
expect(page).to have_content 'Willkommen'
end
I suspect this could be the problematic spec, and when it's run early, it has impact on other specs.
I hoped that I could solve it by setting the language in the spec back to the default like this:
it 'offers contents in german' do
visit root_path(locale: :de)
expect(page).to have_content 'Willkommen'
I18n.locale = :en
end
Didn't work, also this one didn't:
it 'offers contents in german' do
visit root_path(locale: :de)
expect(page).to have_content 'Willkommen'
visit root_path(locale: :en)
end
I'm a bit clueless now. How could I debug the situation, so I definitely find the source of the problem and fix it (or at least work around it)?
Update
Using Dave's answer (rspec --bisect) I found the problem.
# application_controller_spec.rb
describe ApplicationController do
controller(ApplicationController) do
def index
render text: 'Hello World'
end
end
describe 'locale parameter' do
it 'is set to english when not available in the request' do
get :index
expect(I18n.locale).to eq :en
end
it 'can be set through the request' do
get :index, locale: :de
expect(I18n.locale).to eq :de
end
end
end
Depending on the order that these specs were run, the locale was set to :de or :en for following specs.
I fixed it with BoraMa's suggested code snippet:
RSpec.configure do |config|
config.after(:each) { I18n.locale = :en }
end
I'm still a bit astonished that RSpec/Rails don't do this on their own automatically...
Comment out the spec that you suspect and run rspec --seed 51012. If that passes, you know the culprit.
If not, this looks like a job for rspec --bisect. Do
rspec --seed 51012 --bisect
and let RSpec find the minimal set of examples (probably 2) that reproduce the failure. You can then decide what to do about the culprit.
This feature is available in RSpec 3.3 and better in RSpec 3.4. More here: https://relishapp.com/rspec/rspec-core/docs/command-line/bisect
I'm writing a typical test in my application where I create a model through a form and check that the model count equals 1.
The test fails because there are already multiple records in the test DB, and this count increases each time I run my tests. It looks like each example isn't happening inside a transaction (being rolled back) like it's supposed to, and I don't know why.
I have this line in my spec_helper.rb file, which is supposed to run each example in a transaction:
config.use_transactional_fixtures = true
Here is my spec that keeps generating model objects:
require 'spec_helper'
describe "Admin artwork pages" do
subject { page }
let(:gallery) { FactoryGirl.create(:gallery) }
describe "artwork creation" do
context "with valid attributes" do
it "creates new artwork" do
visit admin_gallery_artworks_path(gallery_id: gallery.id)
click_link 'Add new artwork'
fill_in 'artwork_title', with: 'Still Life'
click_button 'Create Artwork'
page.should have_text 'Successfully created'
Artwork.count.should eq 1
end
end
end
end
Here's the error message from Rspec:
Failures:
1) Admin artwork pages artwork creation with valid attributes creates new artwork
Failure/Error: Artwork.count.should eq 1
expected: 1
got: 153
(compared using ==)
Edit: Contents of my spec_helper.rb file:
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rails'
require 'capybara/rspec'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
# Include route helpers
config.include Rails.application.routes.url_helpers
#
# Take the FactoryGirl out of FactoryGirl.create
config.include FactoryGirl::Syntax::Methods
end
I'm using Rails 4.0.0.rc1, Ruby 1.9.3, FactoryGirl and rspec-rails 2.13.0 Thanks for any help.
It turns out that Rails 4 is supported starting in rspec-rails 2.13.1 - I was using 2.13.0. After upgrading, the specs took place within a transaction like they were supposed to.
Thanks to everyone who took the time to post help.
I believe the problem is the way you have your test written and less to due with config.use_transactional_fixtures = true. Focus on the bottom of the error that says (compared using ==)
Try to use the expecting change rspec syntax instead
Change this:
click_button 'Create Artwork'
page.should have_text 'Successfully created'
Artwork.count.should eq 1
To this:
expect { click_button 'Create Artwork' }.to change { Artwork, :count }.by(1)
page.should have_text 'Successfully created'
Let me know if this helps
You're running a request spec: when you call visit the code under test is run in a server instance (in the same process). In particular this means that it's using a different thread.
As a result the application code ends up using a different database connection, and since transactions are a per connection thing there is no transaction used when your controller inserts records into the database.
There are several ways to address this. One is to abandon rspec's transactional fixtures and use the database_cleaner gem. You can set it up so that controller and model specs use transactions but request specs use truncate to forcibly clear out tables.
Another approach is to try and force both the spec code and the server code to use the same database connection, this eliminating the problem. You can see this approach in this answer. In my experience this works pretty well until you start using a capybara driver such as poltergeist which will run any javascript on the page and your page fires ajax requests.
The approach I've been using is to set the active record connection pool size to 1: there is only 1 connection allowed so everyone will use the same one. You do then have to do some work to ensure that connections are returned to the pool or your spec just hangs.
I wrote up the details a while ago as a blog post, but in a nutshell you need to
call ActiveRecord::Base.clear_active_connections! before calling methods like visit, click and so on
hack config.middleware.insert_before ActiveRecord::ConnectionAdapters::ConnectionManagement so that it clears the connection after each request (by default it doesn't do this in tests).
Solution
This worked. The main essence is that I have to set the Capybara.server_port and Capybara.app_host and sign in manually in the sign in form. Capybara.app_host cannot be set with a dynamic subdomain unless its declared in a variable. All urls has to be hard coded.
require 'spec_helper'
feature 'customer' do
let(:user) {FactoryGirl.create(:user)}
let(:firm) {user.firm}
let(:customers) {"http://#{firm.subdomain}.lvh.me:31234/customers"}
let(:root_url) {"http://#{firm.subdomain}.lvh.me:31234/"}
before(:all) do
Capybara.server_port = 31234
sub = firm.subdomain
Capybara.app_host = root_url
end
def sign_in_on_js
visit root_url
fill_in "Email", :with => user.email
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("Signed in successfully.")
end
scenario "make new", js: true do
sign_in_on_js
visit customers
page.should have_content("Add new customer")
find("#dialog_customer").click
page.should have_content("Create new customer")
end
end
Original question
I am making a multitenant app in rails. There is going to be a lot of javascript. But, I cant get the testing to work.
When not running :js = true every thing works. The problem arises in specs like this one
let(:customers) {"http://#{firm.subdomain}.lvh.me:3003/customers"}
scenario "Statistics select", :js => true do
visit customers
page.should have_content("Add new customer")
end
The poltergeist web driver for capybara cannot find the url and returns a blank page
Failure/Error: page.should have_content("Add new customer")
expected there to be text "Add new customer" in ""
I have this in my spec_helper.rb
require 'capybara/rspec'
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, :debug => true)
end
Poltergeist and phantomjs try to deliver. I get this output
{"name"=>"set_debug", "args"=>[true]}
{"response"=>true}
{"name"=>"visit", "args"=>["http://subdomain2.lvh.me:3003/statistics"]}
poltergeist [1362522132943] state default -> loading
{"response"=>{"status"=>"fail"}}
Do I need to have a server running during testing to make this work?
I've tried selenium and capybara-webkit, but phantomjs has gotten closest to success.
I have also tried to change the hosts file in different ways( maybe not correct )
Any tips on setup are welcome!
Update
Starting to get desperate. I now start the rails server
rails s -e test -p 3001
and then run my tests.
Now I get redirected to the sign in page. I have this in the specs
before(:each) do
login_as(user, :scope => :user)
end
How can I sign in the test user on the rails test server without going trough the sign in process manually for every spec
Capybara already starts a server for you, to quote the docs:
Some Capybara drivers need to run against an actual HTTP server. Capybara takes care of this and starts one for you in the same process as your test, but on another thread. Selenium is one of those drivers, whereas RackTest is not.
Within your test you can use the visit method with a relative url, for example:
visit("/statistics")
Capybara will direct this request to the server it just started for this test.
When your want to use an absolute url within your test, you can, but you should also specify the port the server is running on. This port is being randomly chosen during the test. Some drivers have a method available to retrieve the port number.
For example when you use the Capybara-Webkit driver:
Capybara.current_session.driver.server_port
To visit an absolute url you can then use:
port_number = Capybara.current_session.driver.server_port
visit("http://127.0.0.1:#{port_number}/statistics")
Within the test specs probably a method login_as won't work. You have to log in with a few simple steps. For example:
before(:each) do
visit "/login"
fill_in "Email", :with => "my#email.com"
fill_in "Password", :with => "secret"
click_button "Login"
end
To test multiple subdomains you can set the Capybara.app_host. Take a look at this question for a detailed explanation.
UPDATE
Capybara 2 includes a nice feature called always_include_port which will automatically add the port number the server is running on.
Capybara.always_include_port = true
So instead of
visit("http://127.0.0.1:#{port_number}/statistics")
you can now use
visit("/statistics")
and it will automatically connect to http://127.0.0.1:#{port_number}/statistics.
If you want to test multiple subdomains with Capybara.app_host, you could use a domain name which always resolves to 127.0.0.1 for example lvh.me.
For example, if you specify Capybara.app_host = "http://example.lvh.me" it will run the tests using the example subdomain.
I am getting an error that my method is not defined. The error is "undefined local variable or method 'mock_auth_hash', despite including the module which defined mock_auth_hash in the spec_helper.
I'm trying to do some rspec integration testing with omniauth and I'm following this link https://gist.github.com/kinopyo/1338738
Please help me figure out what's wrong because I've spent way too many hours trying to figure this one out...maybe I need a fresh pair of eyes on it...
Here are my files
spec->support->omniauth_macros.rb
module OmniauthMacros
def mock_auth_hash
OmniAuth.config.mock_auth[:facebook] = {'provider' =>"facebook",
'uid' =>"12345",
'info'=>{'name'=>"John Doe"},
'credentials'=>{'token'=>"AAABBBCCC"}}
end
end
spec->spec_helper.rb
RSpec.configure do |config|
.
.
.
config.include OmniauthMacros #I've also tried putting config.include(OmniauthMacros)
.
.
end
OmniAuth.config.test_mode = true
spec->requests->game_pages_spec.rb
describe "signing in" do
before {visit root_url}
mock_auth_hash
before {click_button "Login with Facebook"}
it {should have_selector('h1',text: 'Welcome')}
end
Your call to mock_auth_hash has to go within a before or it.