Frage

When I run my 'webinar' specs alone they seem to always past, but if I try the whole suite it only passes one of the tests about 50% or the time. I tested this using the same seed each time to see if it had anything to do with the order in which the tests are being executed.

If I slow down my test by putting a sleep in the middle of it then it magically starts passing 100% again. Obviously I don't want to rely on a weak work-around like this and want to figure how to actually fix my problem. require "spec_helper"

require "spec_helper"

describe "ProgramManager::Webinars" do
  let(:program) { create(:program) }
  let(:superuser) { create(:superuser) }

  describe "#index" do
    before { login_as(superuser) }
    let(:delete) { 'Delete' }

    it "displays an edit and destroy link for all webinars" do
      w1, w2, w3 = create(:webinar, program: program), create(:webinar, program: program), create(:webinar, program: program)
      visit program_webinars_path(program)
      [w1, w2, w3].each do |webinar|
        expect(page).to have_link webinar.name, href: edit_program_webinar_path(program, webinar)
        expect(page).to have_link '', href: destroy_warnings_program_webinar_path(program, webinar)
      end
    end

    it "has a link to create a new webinar" do
      visit program_webinars_path(program)
      expect(page).to have_content 'New Webinar'
    end

    it "deletes a webinar", js: true do  #THIS IS THE TEST THAT SOMETIMES FAILS
      webinar = create(:webinar, program: program)
      visit program_webinars_path(program)
      all('.destroy').last.click
      wait_for_ajax
      sleep(1.second)         #THIS IS THE SLEEP THAT FIXES THE FAILURE
      expect { click_link delete }.to change(Webinar, :count).by(-1)
    end
  end

.

FactoryGirl.define do
  factory :webinar do
    program
    name "some name"
    url "some url"
    description "some description"
    speaker "some speaker"
    starts_at Time.now
  end
end

.

FactoryGirl.define do
  factory :program do
    contract
    program_manager factory: :user
    sequence(:name) { |n| "Program-#{n}" }
    description     { "Program description" }
    starts_at       { Time.now }
    ends_at         { Time.now + 10.days }
    color           { "#33f" }
    program_type    { "some program type" }
    verified        { false }
  end
end

.

<div class="col-md-4">
  <%= link_to "<span class='glyphicon glyphicon-plus'></span>".html_safe, new_program_webinar_path(@program), class: 'new-webinar', data: { toggle: 'tooltip', title: 'Add a Webinar' } %>
  <h4>Current Webinars</h4>

  <% if @webinars.empty? %>
    <p>There are currently no webinars to display.</p>
  <% else %>
    <table class="table table-condensed">
      <% @webinars.each do |webinar| %>
        <tr>
          <%= content_tag :td, class: pm_setup_classes(webinar, @webinar) do %>
            <%= link_to "<span class='glyphicon glyphicon-remove'></span>".html_safe, destroy_warnings_program_webinar_path(@program, webinar), class: 'destroy', data: { toggle: 'modal', target: '#ajax-modal' } %>
            <%= link_to webinar.name, edit_program_webinar_path(@program, webinar), class: 'webinar' %>
          <% end %>
        </tr>
      <% end %>
    </table>
  <% end %>

  <%= link_to 'New Webinar', new_program_webinar_path(@program), class: 'btn btn-success btn-block' %>
</div>

.

class ProgramManager::WebinarsController < ProgramManager::ApplicationController
  before_filter :authenticate_user!
  before_filter :webinars

  def new
    @webinar = @webinars.build
    clean_webinars
  end

  def create
    @webinar = @program.webinars.build(params[:webinar])
    clean_webinars
    if @webinar.save
      redirect_to program_webinars_path(@program), success: "Webinar successfully created"
    else
      render :new
    end
  end

  def edit
    @webinar = @program.webinars.find(params[:id])
  end

  def update
    @webinar = @program.webinars.find(params[:id])
    if @webinar.update(params[:webinar])
      redirect_to program_webinars_path(@program), success: "Webinar successfully updated"
    else
      render :edit
    end
  end

  def destroy
    @webinar = @program.webinars.find(params[:id])

    if @webinar.destroy
      redirect_to program_webinars_path(@program), success: "Webinar removed successfully"
    else
      render :index
    end
  end

  def destroy_warnings
    @webinar = @program.webinars.find(params[:id])
    render layout: false
  end

private

  def clean_webinars
    @webinars = @webinars.delete_if(&:new_record?)
  end

  def webinars
    @webinars = @program.webinars
  end
end

I am sorry there is so much code associated with this question. I'm just trying to provide as much info as I can since I have no idea where this bug is from or how to fix it

War es hilfreich?

Lösung

The problem seemed to ultimately be a javascript fade in. The delete button we are trying to press is on a modal that fades in to alert you of the repercussions of your deletion and asks you to confirm. Our wait_for_ajax() helper waited until all active jQuery connections were resolved. The connections would finish so it would move on to the next line of code which told it to click a link on the delete link. The html had a delete link in it so Capybara can find it, but since it is actively fading in... the click doesn't work and the test fails!

Andere Tipps

You can adjust the Capybara.default_wait_time = 5 (default is 2s).

See doc in "Asynchronous JavaScript (Ajax and friends)" section of https://github.com/jnicklas/capybara

This will change the total amount of time Capybara waits before giving up on finding a node, but should not affect the interval at which it will keep trying to check.

It might also be asynchronous operation in your database. We were getting random failures in our specs until we set fsync = on in our PostgreSQL configuration. I think that is a better option than stuffing sleep(#) everywhere.

I don't see the error trace, but if not quite all data is ready on a page you can use the specific gem called rspec-wait to wait a condition for a time (by default 3 sec). So, for example the rspec code become the following:

  visit program_webinars_path(program)
  wait_for(page).to have_content 'New Webinar'

This allow you to wait for some specific HTML (if required) for a time.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top