RSpec-rails-capybara - different failures with :js

2019-03-15 22:46发布

问题:

I'm building a setup screen for billing of individuals. The controller/views are in the Admin namespace.

When run the first test without :js => true I get one failure, which I assume is down to the fact that the link does not work as its a helper which uses a js script to build a nested set of fields (Based on Railscasts Single form, multiple tables - Nested Attributes).

Failures:

  1) Patient Setup create patient bill heading - with extended details -with valid data
     Failure/Error: fill_in "Extended Bill Heading", :with => 'Regular Registration'
     Capybara::ElementNotFound:
       cannot fill in, no text field, text area or password field with id, name, or label 'Extended Bill Heading' found
     # (eval):2:in `fill_in'
     # ./spec/requests/admin/patient_setup_pages_spec.rb:52:in `block (4 levels) in <top (required)>'

Finished in 0.83047 seconds
3 examples, 1 failure, 2 pending

But when I use :js = true, I'm getting a failure which appears to be from the login with Invalid user/password flashing up on the screen when running.

Failures:

  1) Patient Setup create patient bill heading - with extended details -with valid data
     Failure/Error: click_link 'System'
     Capybara::ElementNotFound:
       no link with title, id or text 'System' found
     # (eval):2:in `click_link'
     # ./spec/requests/admin/patient_setup_pages_spec.rb:22:in `block (2 levels) in <top (required)>'

Finished in 6.33 seconds
3 examples, 1 failure, 2 pending

Here is the code that backs all this up.

spec/requests/admin/patient_setup_spec.rb

require 'spec_helper'

feature 'Patient Setup' do

  let!(:ci_user) { FactoryGirl.create(:user, 
                                       name: "configuration engineer", 
                                       password: "password",
                                       password_confirmation: "password"
                                     ) }
  let!(:admin_role) { FactoryGirl.create(:admin_role) }
  let!(:assignment) { FactoryGirl.create(:assignment, 
                                         :role => admin_role, 
                                         :user => ci_user 
                                         ) } 

  before do 
    visit login_path
    fill_in "Name", with: "configuration engineer"
    fill_in "Password", with: "password"
    click_button "Login"
    save_and_open_page
    click_link 'System'
    click_link 'Patient Setup'
  end

  describe "create patient bill heading" do
    before do
      click_link 'New Bill Heading'
      fill_in 'Bill heading', :with => 'Consultation' 
      fill_in 'Abbreviation', :with => "CON"       
    end

    context "- no extended details" do
      pending

      scenario "- with valid data" do
        pending
        click_button 'Create Patient bill heading' 
        page.should have_content('Patient Bill Heading created.')
      end
    end

    context "- with extended details", :js => true do #(without :js => true 1st error)
      before do
        # save_and_open_page
        click_link "Extended Bill Heading"
        # save_and_open_page        
      end

      scenario "-with valid data" do
        save_and_open_page
        fill_in "Extended Bill Heading", :with => 'Regular Registration'
      end
    end

  end
end

Here is my factory setup.

spec/factories.rb

FactoryGirl.define do

  # Users, Roles
  factory :user do
    name     "Joesephine Bloggs"
    password "testmenow"
    password_confirmation "testmenow"
  end

  factory :admin, :class => User do
    sequence(:name) { |n| "Administrator-#{n}" }
    password "adminiam"
    password_confirmation "adminiam"
    after(:create) do |user|
      FactoryGirl.create(:assignment, :role => FactoryGirl.create(:admin_role), :user => user )
    end
  end

  factory :role do
    description { "Clerical-#{rand(99)}" }

    factory :admin_role do
      description "Admin"
    end
  end

  factory :assignment do
    user
    role
  end

  # Patients Module

  factory :patient_bill_heading do


      sequence(:bill_heading) { |n| "bill-heading-#{n}" }
      sequence(:abbreviation) { |n| "abbreviation-#{n}" }


      factory :delete_patient_bill_heading, :class => PatientBillHeading do
        bill_heading :bill_heading
        abbreviation :abbreviation
      end

  end  
end

Here is the link in my view to call the helper that generates the nested attribute fields.

<p>
  <%= link_to_add_fields "Extended Bill Heading", f, :patient_extended_bill_headings %>
</p>

And here is the helper.

helpers/application_helper.rb

  def link_to_add_fields(name, f, association, options={})
    defaults = {
      :partial_name => nil
    }
    options = defaults.merge(options)

    new_object = f.object.send(association).klass.new
    id = new_object.object_id
    fields = f.fields_for(association, new_object, child_index: id) do |builder|
      if options[:partial_name].nil?
        render(association.to_s.singularize + "_fields", f: builder)
      else
        render(options[:partial_name], f: builder)
      end
    end
    link_to("#{name} <i class='icon-plus icon-white'></i>".html_safe, 
            '#', 
            class: "btn btn-success add_fields",
            data: {id: id, fields: fields.gsub("\n", "")}
           )
  end

I'm trying to improve my RSpec testing knowledge, as I have successfully built this working in my application after an hour trying to figure out why I was getting test failures. So in the app it works, but I want to understand how to make my test pass.

Is my assertion that one error is due to using js to create the link, and capybara is not running it as I don't use the :js => true option?

Can anybody see what I'm doing wrong when using the :js => true option?

回答1:

You probably have config.use_transactional_fixtures = true in your spec_helper.rb. It doesn't work with Capybara javascript specs because the server and the browser client run on separate threads. Due to the fact that database transactions on the server are not visible to the client, the client has no clue about the user you created in the let!(), therefore the user cannot login to the system.

You need to turn off transactional fixtures and clean your database before/after each run (consider the gem database_cleaner) for your js specs.

RSpec.configure do |config|
  config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with :truncation
  end

  config.before(:each) do
    if example.metadata[:js]
      DatabaseCleaner.strategy = :truncation
    else
      DatabaseCleaner.strategy = :transaction
    end
    DatabaseCleaner.start
  end

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

The above code snippet is taken from the contact manager app readme and slightly modified