Ненадежные / Flakey Capybara / AngularJS интеграционные тесты с проблемами времени

Как сделать так, чтобы эти тесты прошли надежно?

В настоящее время эти тесты ненадежны.
Иногда они проходят. Иногда они терпят неудачу.
Ниже приведены настройки, код и выходные данные, демонстрирующие эту проблему.
Мы будем очень благодарны за предложения по устранению этой проблемы, и я уверен, что они помогут многим другим, поэтому, пожалуйста, прокомментируйте!

Среда тестового кода

  1. Рельсы 3.2
  2. RSpec 2.x
  3. Водосвинка
  4. Полтергейст
  5. PhantomJS
  6. AngularJS
  7. Google Chrome версии 47.0.2526.106 (64-разрядная)

Тестирование драгоценных камней из Gemfile.lock

capybara (2.1.0)
database_cleaner (0.7.1)
debug_inspector (0.0.2)
guard-bundler (0.1.3)
guard-livereload (1.2.0)
guard-rspec (2.1.2)
jasminerice (0.0.10)
pg (0.17.1)
phantomjs (2.1.1.0)
poltergeist (1.4.1)
protractor-rails (0.0.17)
pry (0.9.12)
rack (1.4.7)
rack-test (0.6.3)
rails (3.2.21)
rails-assets-angular (1.3.20)
rspec-rails (2.11.4)
simplecov (0.8.2)
sprockets (2.2.3)
zeus (0.13.3)
zeus-parallel_tests (0.2.1)

Вещи, которые я пробовал

  1. Убедитесь, что я использую ожидающие сопоставления DSL Capybara
  2. Убедитесь, что мой очиститель базы данных настроен правильно
  3. Протестируйте каждый элемент страницы, предполагая, что его может не быть на странице, но он все еще загружается.
  4. Сузить несогласованные тесты
  5. Выполнить непоследовательный тест в одиночку
  6. Определите код Capybara DSL, который приводит к противоречивым результатам тестирования.

    • i.e. creating a new record and assuming the page has redirected and that the record is on the page click_on

    or

    • .click не всегда "работает"
  7. Обновите Capybara до последней версии (в отдельной ветке)
  8. Обновлен Poltergeist и RSpec до последней версии (в отдельной ветке, все еще работаю над этим)

Ресурсы, которые я использовал

[1] Capybara The DSL
[2] Советы по Capybara, PhantomJs, Poltergeist и Rspec
И многие более...

Как проводились тесты

rspec spec/integration/costings/show_costing_spec.rb --format documentation

Тестовый код

show_costing_spec.rb
require "spec_helper"

RSpec.describe "Show a new costing in the listing," do

  before :each do
    admin_sign_in
    create_costing("test1")
  end

  it "shows the costing after creation" do
    within "#costings_table" do
      expect(page).to have_css("#name", text: "test1")
    end
  end

  it "shows the details of the new costing after creation" do
    expect(page).to have_content("Costings")
    within "#costings_table" do
      expect(page).to have_content("test1")
      all("#show").last.click
    end

    expect(page).to have_content("Costing Details")
    expect(page).to have_css("#name", text: "test1")
  end
end  
spec_helper.rb
# This file is copied to spec/ when you run 'rails generate r spec:install'  
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
# Add library functions here so we can test them.
require File.expand_path(File.dirname(__FILE__) + "/../lib/general")
require 'rspec/rails'
require 'rspec/autorun'

# Integration Testing
require 'capybara/poltergeist'
Capybara.register_driver :poltergeist_debug do |app|
 Capybara::Poltergeist::Driver.new(app, :inspector => true)  
end
Capybara.javascript_driver = :poltergeist_debug
Capybara.default_driver = :poltergeist_debug

# Capybara Integration Test Helpers
def admin_sign_in
  visit "/login"
  #Create staff member in database
  Staff.make!(:admin)
  #Log In
  fill_in "staff_username", with: "adminstaff"
  fill_in "staff_password", with: "password"
  click_button "login"
end

def create_costing(item)
  visit "/api#/costings"
  click_on "new_btn"
  within "#form_costing" do
    find("#name", match: :first).set("#{item}")
    find("#description", match: :first).set("test description")    
    find("#from_date", match: :first).set("15/02/2016")
    find("#cost_hourly_cents", match: :first).set("1.00")
    click_on "create_btn"
  end
end

RSpec.configure do |config|
  config.before(:suite) do
    # Requires supporting ruby files with custom matchers and macros, etc,
    # in spec/support/ and its subdirectories.
    require File.expand_path(File.dirname(__FILE__) + "/support/blueprints")
    Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
  end

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # Allow a 'focus' tag so that we can run just a few tests which we are currently working on
  config.treat_symbols_as_metadata_keys_with_true_values = true
  config.filter_run focus: true
  config.run_all_when_everything_filtered = true
  config.filter_run_excluding :slow unless ENV["SLOW_SPECS"]

  # Defer Garbage Collection
  config.before(:all) { DeferredGarbageCollection.start }
  config.after(:all)  { DeferredGarbageCollection.reconsider }

  # 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 = false
  # config.infer_spec_type_from_file_location!

  # Configure Database Cleaner
  config.include Capybara::DSL
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

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

Результаты теста

Test Run 1: Failing

Параметры запуска: include {: focus => true} exclude {: slow => true}

Все примеры были отфильтрованы; игнорируя {: focus => true}

Показать новую калькуляцию в листинге, показывает калькуляцию после создания, показывает детали новой калькуляции после создания (ОТКАЗ - 1)

Неудачи:

1) Показать новую калькуляцию в листинге,
показывает детали новой калькуляции после создания
Сбой / ошибка: expect (page) .to have_content ("test1")
ожидаемый #has_content? (" test1 "), чтобы вернуть истину, получено false
# ./spec/integration/costings/show_costing_spec.rb:20:в блоке (3 уровня) в
# ./spec/integration/costings/show_costing_spec.rb: 19: в блоке (2 уровня) в

Завершено за 5,46 секунды 2 примера, 1 сбой

Test Run 2: Passing

Параметры запуска: include {: focus => true} exclude {: slow => true}

Все примеры были отфильтрованы; игнорируя {: focus => true}

Показать новую калькуляцию в листинге,
показывает калькуляцию после создания,
показывает детали новой калькуляции после создания.

Завершено за 3,57 секунды 2 примера, 0 сбоев

Обновление 1

Обновлены гемы тестирования до следующих версий:
capybara (2.6.2) из ​​(2.1.0)
database_cleaner (1.5.1) из (0.7.1)
debug_inspector (0.0.2)
guard-bundler (0.1.3)
guard-livereload (1.2.0)
guard-spec (2.1.2)
jasminerice (0.0 .10)
pg (0.17.1)
phantomjs (2.1.1.0)
полтергейст (1.9.0) из (1.4.1)
транспортир-рельсы ( 0.0.17)
pry (0.10.3) из (0.9.12)
стойка (1.4.7)
испытательная стойка (0.6.3)
рельсы (3.2.21)
rails-assets-angular (1.4.9) из (1.3.20)
rspec-rails (3.4.2) из ​​(2.11.4) )
simplecov (0.8.2)
звездочки (2.2.3)
zeus (0.13.3)
zeus-parallel_tests (0.2.1)

Result 1

К сожалению, обновление этих драгоценных камней, похоже, не имело никакого значения, и мои тесты все еще были нестабильными.

Обновление 2

Я реализовал предложения Тома Уолпола. Гарантированно, что мой admin_sign_in ждет завершения sign_in.

Также обновил мою настройку database_cleaner, как предложил Том.

Result 2

На мой стек эти изменения, похоже, не повлияли.

Примечание. Если кто-то не использует AngularJS, я считаю, что эти изменения могли бы изменить ситуацию. Так что спасибо Том за ваши предложения.

Обновление 3

Мне нужно было получить больше информации о том, что происходило во время моих тестовых прогонов. В сети есть предложения для регистрации, использования драгоценных камней для сохранения снимков экрана и тому подобного, но я не чувствовал, что это будет наиболее эффективным по времени. Я хотел указать, где я хотел бы приостановить тест и просмотреть содержимое переменных и полей формы. В браузере было бы идеально.

Что я использовал
Я использовал save_and_open_page и print page.html для отладки.

К чему я перешел
Когда я работал с RSpec 3.4.2, я добавил вспомогательный метод отладки:

rails_helper.rb

def debugit
  puts current_url
  require 'pry'
  binding.pry
end

Result 3

URL-адрес будет напечатан в консоли, и тест будет приостановлен. На этом этапе я мог бы перейти по URL-адресу, войти в систему, перейти на тестовую страницу и посмотреть, что сделал тест Capybara.

Это позволило мне определить, что источник моих проблем возник, когда тест использовал capybara's fill_in DSL. В некоторых тестовых запусках поля будут заполнены правильно, и форма будет отправлена. В другом случае форма будет заполнена правильно, но кнопка отправки будет нажата слишком быстро. В результате создается запись, но поля ввода имени и описания не сохраняются.

Обновление 4

Я обнаружил, что, поскольку я использовал формы и таблицы ввода AngularJS, AngularJS требовалось немного времени для привязки к полям ввода. Если бы это было не разрешено на этот раз, входные данные не были бы сохранены.

Capybara предоставляет такие методы ожидания, как «внутри» и «найти». Я использовал их, но они не помогли с проблемой времени привязки AngularJS. Я обнаружил, что ng-if можно использовать для создания оператора if для ожидания определенного элемента, который будет означать, что привязка AngularJS к полям формы завершена.

Поэтому я использовал методы ожидания Capybara, чтобы дождаться полей, которые я хотел заполнить, и использовал AngularJS ng-if, чтобы не показывать поля, пока они не будут готовы.

Реализация
index.html.erb

<div  ng-if="tableParams.data">
  <table id="costings_table ng-table="tableParams" class="table">
    <td id="field1">{{table.field1}}</td>
    <td id="field2">{{table.field2}}</td>
  </table>
</div>

Result 4

Наконец-то испытания прошли! Однако у меня есть все эти методы поиска с xpath, гарантирующие, что конкретные и трудные для таргетинга элементы ожидаются ...

Обновление 5

Несмотря на то, что в моем gemfile я запускал gem phantomJS версии 2.1.1, моя версия командной строки была только 1.X. Это оказалось важным.

Я обновил свою версию командной строки phantomJS до 2.1.1. В то же время я убедился, что все мои поля ввода, кнопки, таблицы и заголовки имеют уникальные идентификаторы. Затем я смог удалить все вхождения find (: xpath), не нарушая тестов.

Result 5

Этот набор тестов теперь надежно проходит все время! Именно то, что я хотел! Да!


person orion    schedule 23.02.2016    source источник


Ответы (2)


Сразу бросается в глаза то, что ваш admin_sign_in на самом деле не дожидается завершения входа в систему. Это означает, что ваш вызов create_costing может происходить без установки файла cookie сеанса в вашем браузере. Последняя строка в вашем admin_sign_in методе должна быть примерно такой:

expect(page).to have_text('You are signed in') # whatever message is shown upon sign in

or

expect(page).to have_current_path('/') # whatever path an admin is redirected to upon signing in

Это обеспечит фактическое завершение входа в систему и, следовательно, установку файлов cookie сеанса в вашем браузере.

Также в вашей конфигурации очистки базы данных должен использоваться блок append_after, а не после - см. https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example

person Thomas Walpole    schedule 23.02.2016
comment
Спасибо, Том. Ваша рекомендация помогла, но не решила проблему для этого стека. Как я уже упоминал в своем сообщении выше, если бы я не использовал AngularJS, я думаю, ваше предложение помогло бы. Но у этой проблемы было много уровней, и другие проблемы, также упомянутые выше, препятствовали исправлению. Я добавил +1, так как это помогло решить проблему. - person orion; 17.03.2016

Проблема

Проблемы возникли, когда в тесте использовался fill_in DSL capybara. В некоторых тестовых запусках поля будут заполнены правильно, и форма будет отправлена. В другом случае форма будет заполнена правильно, но кнопка отправки будет нажата слишком быстро. В результате создается запись, но поля ввода имени и описания не сохраняются.

1. При заполнении форм убедитесь, что привязки AngularJS завершены и используются методы ожидания Capybara.

Операторы ng-if в AngularJS нужно было использовать, чтобы не отображать поля формы, пока они не будут готовы.
Это нужно было сделать в сочетании с использованием методов ожидания Capybara, чтобы гарантировать, что поле fill_in отправляется только после завершения загрузки формы.

index.html.erb или аналогичный:

<div  ng-if="tableParams.data">
  <table id="costings_table ng-table="tableParams" class="table">
    <td id="field1">{{table.field1}}</td>
    <td id="field2">{{table.field2}}</td>
   </table>
</div>

2. Обновил версию PhantomJS для командной строки до последней (2.1.1).

Это, казалось, позволяло запускать тесты без большого количества методов ожидания Capybara, чтобы получить надежные тесты.

Обновленный тестовый код
show_costing_spec.rb

require "rails_helper"

RSpec.describe "Show a new costing in the listing,", :type => :feature do

  before :each do
    admin_sign_in
    create_costing("test1")
  end

  it "shows the costing after creation" do
    within "#costings_table" do
      expect(page.find("#code2")).to have_content("2")
      expect(page.find("#name2")).to have_content("test1")
    end
  end

  it "shows the details of the new costing after creation" do
    within "#costings_table" do
      click_on "show2"
    end

    expect(page.find("#page_title")).to have_content("Costing Details")
    expect(page.find("#code")).to have_content("2")
    expect(page.find("#name")).to have_content("test1") 
    expect(page.find("#description")).to have_content("test description")
  end
end

rails_helper.rb

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)

# Add library functions here so we can test them.
require File.expand_path(File.dirname(__FILE__) + "/../lib/general")

require 'rspec/rails'
require 'devise'

RSpec.configure do |config|
  config.before(:suite) do
    # Requires supporting ruby files with custom matchers and macros, etc,
    # in spec/support/ and its subdirectories.
    require File.expand_path(File.dirname(__FILE__) + "/support/blueprints")
    Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

    # Setup Devise before it is used in rails_helper
    config.include Devise::TestHelpers, :type => :controller
    Devise.stretches = 1 # Improves speed.
end

config.include Capybara::DSL, :type => :feature
  config.mock_with :rspec

# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"

# Allow a 'focus' tag so that we can run just a few tests which we are currently working on
config.filter_run focus: true
config.run_all_when_everything_filtered = true
config.filter_run_excluding :slow unless ENV["SLOW_SPECS"]

# Defer Garbage Collection
config.before(:all) { DeferredGarbageCollection.start }
config.after(:all)  { DeferredGarbageCollection.reconsider }

# Integration Testing
require 'capybara/rspec'
require 'capybara/poltergeist'

Capybara.register_driver :poltergeist_debug do |app|
  Capybara::Poltergeist::Driver.new(app, {:inspector => true, js_errors: false })  
end

Capybara.javascript_driver = :poltergeist_debug
Capybara.default_driver = :poltergeist_debug

# Debugging tools
def debugit
  puts current_url
  require 'pry'
  binding.pry
end

# 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 = false

#Show Deprications As Errors with full backtracing
config.raise_errors_for_deprecations!

#rest of the file....
# Final part of Configure Database Cleaner

Capybara.default_max_wait_time = 5
config.use_transactional_fixtures = false

config.before(:suite) do
  if config.use_transactional_fixtures?
    raise(<<-MSG)
      Delete line `config.use_transactional_fixtures = true` from
      rails_helper.rb (or set it to false) to prevent uncommitted
      transactions being used in JavaScript-dependent specs. During 
      testing, the app-under-test that the browser driver connects to 
      uses a different database connection to the database connection 
      used by the spec. The app's database connection would not be 
      able to access uncommitted transaction data setup over the 
      spec's database connection.
     MSG
  end
  DatabaseCleaner.clean_with(:truncation)
end  

config.before(:each) do
  DatabaseCleaner.strategy = :transaction
end

config.before(:each, type: :feature) do
  # :rack_test driver's Rack app under test shares database connection
  # with the specs, so continue to use transaction strategy for speed.
  driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test

    if !driver_shares_db_connection_with_specs
      # Driver is probably for an external browser with an app
      # under test that does *not* share a database connection with the
      # specs, so use truncation strategy.
      DatabaseCleaner.strategy = :truncation
    end
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

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


def admin_sign_in
  visit "/login"

  #Create staff member in database
  Staff.make!(:admin)

  #Log In
  fill_in "staff_username", with: "adminstaff"
  fill_in "staff_password", with: "password"
  click_button "login"

  expect(page).to have_text('Logout')
end

def create_costing(item)
  @item = item
  visit "/api#/costings"

  expect(page).to have_selector("#new_btn")
  click_on "new_btn"

  expect(page).to have_text("New Costing")
  within "#form_costing" do
    fill_in "name", with: "#{@item}"
    fill_in "description", with: "test description"
    fill_in "from_date1", with: "15/02/2015" 
    fill_in "cost_hourly_cents1", with: "12.00"

    expect(page).to have_selector("#create_btn")
    click_on "create_btn"
  end
  expect(page.find("#page_title")).to have_content("Costings")
end
person orion    schedule 17.03.2016