Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is It Possible To Use Selenium/Capybara Webkit/Poltergeist Without Patching ActiveRecord?

According to the Capybara Docs there are two separate strategies for handling problems with Selenium not being able to access data generated in a test:

  1. Monkey patch ActiveRecord:

    class ActiveRecord::Base @@shared_connection = nil def self.connection @@shared_connection || retrieve_connection end end ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

  2. Use truncation rather than transactions

I've set up DatabaseCleaner as outlined by Avdi Grimm here, and although looking at my logs I can clearly see DatabaseCleaner is using truncation, for example:

(7.4ms) TRUNCATE TABLE "galleries", "photos", "users" RESTART IDENTITY CASCADE;

… when I run my feature, any models I create as part of the setup, are not available to the driver. I can see the models exist within the scenario, but if I print them out to the page, they don't exist.

The only way I can get the driver to see the models is if I use the ActiveRecord monkey-patch above, but this seems to call lots of strange secondary issues - tests hang etc.

Why is it that the driver (Selenium or Capybara-Webkit) cannot see models created in the tests even when I use DatabaseCleaner with a truncation strategy and how can I get Capybara working with a JavaScript driver?

Note: The test passes fine using the default driver

My DatabaseCleaner configuration:

RSpec.configure do |config|

  # Clean DB completely
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  # Set default strategy (non-js/Rack::Test)
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  # Set strategy for tests using JS (slower)
  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  # Wrap DatabaseCleaner around each test (before & after)
  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

end

Here is an example of a feature that I would like to use JavaScript to test:

feature "User deletes a photo" do

  before {
    as_signed_in_user
    @gallery = create(:gallery_with_photos)
  }

  scenario "successfully deletes a photo from a gallery", js: true do

    photo_title = @gallery.photos.first.title
    puts "GALLERY #{Gallery.find(1)}" # <Gallery:0x007f9b928fe8e8>
    visit gallery_path(@gallery) # JavaScript driver can't find gallery

    within '.photos-list' do
      click_link photo_title
    end
    click_link('Delete Photo')

    expect(current_path).to eq( gallery_path(@gallery) )
    expect(page).not_to have_link(photo_title)

  end

end

When using the JavaScript driver, this test fails with:

  1) User deletes a photo successfully deletes a photo from a gallery
     Failure/Error: Unable to find matching line from backtrace
     ActiveRecord::RecordNotFound:
       Couldn't find Gallery with 'id'=1

As suggested by Dave Schweisguth in the comments, I have tried moving the js:true out to the feature rather than the scenario without it having any effect.

like image 439
Undistraction Avatar asked Oct 20 '22 06:10

Undistraction


1 Answers

Ensure you are using last version of database-cleaner, capybara, selenium-webdriver . Also make sure that all those gems are in test group and are require: false

Then in your spec_helper.rb file you can control the order of loading by requiring rails before those gems. Ensure also a correct settings for RAILS_ENV and use_transactional_fixtures

spec_helper.rb

require 'rubygems'

ENV['RAILS_ENV'] ||= 'test'

# Load rails + environment
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'

# Load database cleaner
require 'database_cleaner'

# Load capybara and its dependencies
require 'capybara/rspec'
require 'capybara/rails'
require 'selenium-webdriver'

RSpec.configure do |config|
  # Do not use transactional, let database_cleaner do the work
  config.use_transactional_fixtures = false

  # Database cleaner config
  config.before(:suite) { DatabaseCleaner.clean_with :truncation }
  config.before(:each) { DatabaseCleaner.strategy = :transaction }
  config.before(:each, js: true) { DatabaseCleaner.strategy = :truncation }

  config.before(:each) { DatabaseCleaner.start }
  config.after(:each) { DatabaseCleaner.clean }
end

Feature spec

Try using describe ... type: :feature instead, and moving js: true on top, like this:

describe "User deletes a photo", type: :feature, js: :true do
  before { ... }
  scenario "successfully deletes a photo from a gallery" do
    ...
  end
end

Your code

1- Do not write puts "GALLERY #{Gallery.find(1)}" even for a quick debugging. It may raise an error in some case and lead to confusion (i.e make you think that the problem is not solved when indeed it is). Use this instead puts "GALLERY #{Gallery.find(@gallery.id)}"

2- Also it's not a good practice in rspec to use instance variable, so instead of this:

before {
  as_signed_in_user
  @gallery = create(:gallery_with_photos)
}

Your should write this

before { as_signed_in_user }
let!(:gallery) { create(:gallery_with_photos) }

Note that I use let! with a bang, it's equivalent to a before. So in the rest of your spec you must replace @gallery by gallery

like image 66
Benj Avatar answered Oct 22 '22 23:10

Benj