Search code examples
ruby-on-railsrspeccapybara

Rspec not loading instance variable


I had this working previous, now it mysteriously does not. ruby 2.4.1, rails 5.0.1.rc2, capybara 3.6, rspec_rails 3.5, factorygirl 4.7 (2017 project)

The page controller (root_path) needs SiteCounter.find(1) (seeded), which factorygirl is doing.

If I run all 3 examples at the same time (or any combination of 2 of them) only the first one works, because for the rest SiteCounter.find(1) does not exist

If I run each one individually, they all pass.

describe "Sign In", type: :system do

  @site_counter = create_site_counter
  
  before do
    @fan = create :fan
    visit root_path
  end

  scenario "valid with correct credentials" do
    expect(page).to have_link "LOG IN"
    find('#myNavbar > ul.nav.navbar-nav.navbar-right.signlogin > li:nth-child(2) > a').click
    expect(page).to have_current_path new_fan_session_path
    expect(page).to have_xpath('/html/body/div[1]/div/div/div/div/header/h3', text: 'Sign in')
    expect(page).to have_button("Sign in")
    expect(page).to have_button("Sign in").and have_xpath('/html/body/div[1]/div/div/div/div/header/h3', text: 'Sign in')
    fill_in "Email", with: @fan.email
    fill_in "Password", with: @fan.password
    click_button "Sign in"
    find('.dropdown-toggle').click
    expect(page).to have_link "Logout"
    expect(page).to have_current_path root_path
  end

  scenario "invalid with unregistered account" do
    find('#myNavbar > ul.nav.navbar-nav.navbar-right.signlogin > li:nth-child(2) > a').click
    fill_in "Email", with: Faker::Internet.email
    fill_in "Password", with: "FakePassword123"
    click_button "Sign in"

    expect(page).to have_no_link "Logout"
  end

  scenario "invalid with invalid password" do
    find('#myNavbar > ul.nav.navbar-nav.navbar-right.signlogin > li:nth-child(2) > a').click
    fill_in "Email", with: @fan.email
    fill_in "Password", with: "FakePassword123"
    click_button "Sign in"

    expect(page).to have_no_link "Logout"
  end
end

UPDATE

As per Thomas Walpole suggestions (I appreciate all your posts btw), I have modified the code to be more soft baked, and also took away factorygirl's task of creating SiteCounter and added it into the page controller. I have also made the second two example expects more positive:

scenario "valid with correct credentials" do
    
    #fist LOG IN link
    within('#myNavbar') do
      expect(page).to have_link "LOG IN"
    end

    #second LOG IN link
    within('.follow-login') do
      expect(page).to have_link "LOG IN"
    end

    within('#myNavbar') do
      click_link('LOG IN')
    end  

    fill_in "Email", with: @fan.email
    fill_in "Password", with: @fan.password
    click_button "Sign in"
    find('.dropdown-toggle').click
    expect(page).to have_link "Logout"
    expect(page).to have_current_path root_path
  end

  scenario "invalid with unregistered account" do
    within('#myNavbar') do
      click_link('LOG IN')
    end
    fill_in "Email", with: Faker::Internet.email
    fill_in "Password", with: "FakePassword123"
    click_button "Sign in"

    expect(page).to have_selector(:link_or_button, 'Sign in')
  end

  scenario "invalid with invalid password" do
    find('#myNavbar > ul.nav.navbar-nav.navbar-right.signlogin > li:nth-child(2) > a').click
    fill_in "Email", with: @fan.email
    fill_in "Password", with: "FakePassword123"
    click_button "Sign in"

    expect(page).to have_selector(:link_or_button, 'Sign in')

  end
end

if this code is up to par I will move on to the second system test of 50 i need to write by 5pm.

=================

UPDATE

scenario "valid with correct credentials" do
    
    #fist LOG IN link
    within('#myNavbar') do
      expect(page).to have_link "LOG IN"
    end

    #second LOG IN link
    within('.follow-login') do
      expect(page).to have_link "LOG IN"
    end

    within('#myNavbar') do
      click_link('LOG IN')
    end  

    fill_in "Email", with: @fan.email
    fill_in "Password", with: @fan.password
    click_button "Sign in"
    find('.dropdown-toggle').click
    expect(page).to have_link "Logout"
    expect(page).to have_current_path root_path
  end

  scenario "invalid with unregistered account" do
    within('#myNavbar') do
      click_link('LOG IN')
    end
    fill_in "Email", with: Faker::Internet.email
    fill_in "Password", with: "FakePassword123"
    click_button "Sign in"
    expect(page).to have_css '.alert'
    expect(page).to have_selector(:link_or_button, 'Sign in')
  end

  scenario "invalid with invalid password" do
    within('#myNavbar') do
      click_link('LOG IN')
    end    
    fill_in "Email", with: @fan.email
    fill_in "Password", with: "FakePassword123"
    click_button "Sign in"
    expect(page).to have_css '.alert'
    expect(page).to have_selector(:link_or_button, 'Sign in')

  end
end

this works and is as close as positive as I can get on this page, Mr. Walpole. expect(page).to have_content("Email or password fail.") caused a weird error that was removed only by deleting <!DOCTYPE html> from my layouts/application, as thoroughly outlined in the comments.


Solution

  • You can't just set an instance variable and expect it to be used by your application. Your application is run in it's own thread or process (depending on config) and won't see instance variables you set in your spec.

    Beyond that your tests have a number of other issues

    1. Your selectors are way too specific and will lead to very brittle tests. ie. Why use find('#myNavbar > ul.nav.navbar-nav.navbar-right.signlogin > li:nth-child(2) > a').click rather than just doing click_link('LOG IN') which also then obviates the need to do expect(page).to have_link "LOG IN" since the click_link would fail if the link didn't exist
    2. Prefer CSS over XPath 99.9% of the time, it's faster, easier to read, and doesn't have the // vs .// problem
    3. If run with an asynchronous driver (seleniun, etc - basically anything other than rack_test) then your 2nd and 3rd tests aren't actually testing anything. This is because click_link will return immediately after clicking and isn't guaranteed to wait for the actions triggered by that click to occur. That means the have_no_link will immediately be validated against your login page (which wouldn't have a 'Logout' link on) and pass, never actually waiting for the login failure to occur. Instead you need to do a positive expectation against something that should be on the page to indicate the login attempt has failed.