rspec - unitarias - Pruebas de integración de Capybara/AngularJS no confiables/FlakeyJS con problemas de sincronización
test unitarios angular 5 (2)
¿Cómo puedo hacer que estas pruebas pasen de manera confiable?
Actualmente estas pruebas son flakey.
A veces pasan. Algunas veces fallan.
A continuación se muestra la configuración, el código y la salida que demuestran este problema.
Las sugerencias para superar este problema serán muy apreciadas y estoy seguro que ayudarán a muchos otros, así que ¡por favor comenten!
Ambiente del Código de Prueba
- Rails 3.2
- RSpec 2.x
- Capybara
- Duende
- PhantomJS
- AngularJS
- Versión 47.0.2526.106 de Google Chrome (64 bits)
Prueba de Gems desde 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)
Cosas que he intentado
- Asegúrate de usar los mezcladores DSL en espera de Capybara
- Asegúrese de que mi limpiador de base de datos esté configurado correctamente
- Pruebe cada elemento de la página suponiendo que podría no estar en la página y aún podría estar cargando
- Reducir las pruebas inconsistentes
- Ejecutar prueba inconsistente solo
Identifique el código de las DSL de Capybara que desencadenan resultados de prueba inconsistentes.
- es decir, crear un nuevo registro y asumir que la página ha sido redirigida y que el registro está en la página click_on
o
- .click no consistentemente ''funcionando''
- Actualice Capybara a la última versión (en una rama separada)
- Poltergeist actualizado y RSpec a la última versión (en una rama separada, aún trabajando en esto)
Recursos que utilicé
[1] Capybara El DSL
[2] Consejos sobre Capybara, PhantomJs, Poltergeist y Rspec
Y muchos más...
Cómo se realizaron las pruebas
rspec spec/integration/costings/show_costing_spec.rb --format documentation
Código de prueba
show_costing_spec.rbrequire "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
Resultados de la prueba
Test Run 1: Failing
Opciones de ejecución: incluir {: focus => true} exclude {: slow => true}
Todos los ejemplos fueron filtrados; ignorando {: focus => true}
Mostrar un nuevo cálculo de costos en la lista, muestra el cálculo de costos después de la creación muestra los detalles del nuevo cálculo de costos después de la creación (FAILED - 1)
Fallas:
1) Muestre un nuevo costo en el listado,
muestra los detalles del nuevo cálculo de costos después de la creación
Fallo / Error: espera (página) .to tener_contenido ("prueba1")
esperado #has_content? ("test1") para devolver verdadero, obtuvo falso
# ./spec/integration/costings/show_costing_spec.rb:20:in block (3 niveles) en
# ./spec/integration/costings/show_costing_spec.rb:19:in bloque (2 niveles) enTerminado en 5,46 segundos 2 ejemplos, 1 falla
Test Run 2: Passing
Opciones de ejecución: incluir {: focus => true} exclude {: slow => true}
Todos los ejemplos fueron filtrados; ignorando {: focus => true}
Mostrar un nuevo costo en el listado,
muestra el costeo después de la creación
muestra los detalles del nuevo cálculo de costos después de la creaciónTerminado en 3.57 segundos 2 ejemplos, 0 fallas
Actualización 1
Se actualizaron las gemas de prueba a las siguientes versiones:
carpincho (2.6.2) desde (2.1.0)
database_cleaner (1.5.1) desde (0.7.1)
debug_inspector (0.0.2)
Protector-paquete (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)
poltergeist (1.9.0) desde (1.4.1)
carriles-transportador (0.0.17)
palanca (0.10.3) desde (0.9.12)
rack (1.4.7)
prueba en rack (0.6.3)
rieles (3.2.21)
rails-assets-angular (1.4.9) from (1.3.20)
rspec-rails (3.4.2) de (2.11.4)
simplecov (0.8.2)
piñones (2.2.3)
zeus (0.13.3)
zeus-parallel_tests (0.2.1)
Result 1
Desgraciadamente, la actualización de estas gemas no pareció marcar la diferencia y mis pruebas aún no eran tan precisas.
Actualización 2
Implementé las sugerencias de Tom Walpole. Me aseguré de que mi admin_sign_in espere a que complete_inicio.
También actualicé la configuración de mi database_cleaner como sugirió Tom.
Result 2
Para mi stack, estos cambios no parecían tener un efecto.
Nota: Si uno no está usando AngularJS, creo que estos cambios marcarán la diferencia. Entonces, gracias Tom por tus sugerencias.
Actualización 3
Necesitaba obtener más información sobre lo que estaba sucediendo durante mis pruebas. Hay sugerencias en la red para iniciar sesión, usar capturas de pantalla para guardar gemas y cosas por el estilo, pero no sentí que estas serían las más eficientes. Quería especificar dónde quería que se detuviera la prueba y ver el contenido de las variables y los campos de formulario. En un navegador sería ideal.
Lo que utilicé
Estaba usando "save_and_open_page" e "print page.html" para depurar.
Lo que me moví a
Como estaba ejecutando RSpec 3.4.2, agregué un método de ayuda de depuración:
rails_helper.rb
def debugit
puts current_url
require ''pry''
binding.pry
end
Result 3
Se imprimiría una URL en la consola y la prueba se pausaría. En esta etapa, podría navegar a la URL, iniciar sesión, navegar a la página de prueba y ver lo que la prueba de Capibara había hecho.
Esto me permitió identificar que la fuente de mis problemas surgió cuando la prueba utilizaba DSL fill_in de capibara. En algunas ejecuciones de prueba, los campos se llenarían correctamente y el formulario se enviaría. En el otro escenario, el formulario se rellenaría correctamente, pero el botón de enviar se golpearía demasiado rápido. El resultado aquí es que se creó un registro, pero los campos de entrada de nombre y descripción no se conservaron.
Actualización 4
Descubrí que debido a que estaba usando formularios y tablas de entrada AngularJS, AngularJS necesitaba un poco de tiempo para enlazar a los campos de entrada. Si no se permitió esta vez, los datos de entrada no se guardarán.
El carpincho proporciona métodos de espera como "dentro" y "buscar". Usé estos pero no ayudaron con el problema de tiempo de enlace AngularJS. Encontré ng-si se podría usar para crear una instrucción if para esperar que un elemento particular que significa que los enlaces de AngularJS a los campos de formulario se completen.
Así que utilicé los métodos de espera de Capybara para esperar los campos que quería llenar y utilicé AngularJS ''ng, si no para mostrar los campos hasta que estén listos.
Implementación
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
¡Las pruebas finalmente pasan! Sin embargo, tengo todos estos métodos de búsqueda con xpath que garantizan que los elementos específicos y difíciles de orientar se esperen en ...
Actualización 5
A pesar de que en mi gemfile estaba ejecutando el gem phantomJS versión 2.1.1 mi versión de línea de comandos era solo 1.X. Esto demostró ser significativo.
Actualicé mi línea de comando phantomJS versión a 2.1.1. Al mismo tiempo, me aseguré de que todos mis cuadros de entrada, botones, tablas y títulos tuvieran identificadores únicos. Luego pude eliminar todas las ocurrencias de find (: xpath) sin romper las pruebas.
Result 5
Este conjunto de pruebas ahora pasa todo el tiempo confiablemente ¡Exactamente lo que quería! ¡Sí!
Lo más inmediato que salta es que su admin_sign_in
realidad no espera a que complete el sign_in. Esto significa que su llamada a create_costing
puede ocurrir sin que se haya configurado la cookie de sesión en su navegador. La última línea en su método admin_sign_in
debería ser algo así como
expect(page).to have_text(''You are signed in'') # whatever message is shown upon sign in
o
expect(page).to have_current_path(''/'') # whatever path an admin is redirected to upon signing in
Eso asegurará que el inicio de sesión se haya completado y, por lo tanto, las cookies de sesión se hayan configurado en su navegador.
Además, la configuración del limpiador de la base de datos debe usar un bloque append_after en lugar de después; consulta https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example
La cuestión
Surgieron problemas cuando la prueba usaba DSL fill_in de capibara. En algunas ejecuciones de prueba, los campos se llenarían correctamente y el formulario se enviaría. En el otro escenario, el formulario se rellenaría correctamente, pero el botón de enviar se golpearía demasiado rápido. El resultado aquí es que se creó un registro, pero los campos de entrada de nombre y descripción no se conservaron.
1. Al completar formularios, asegúrese de que los enlaces AngularJS estén completos y que se utilicen los métodos de espera de Capybara.
Las sentencias ng-if de AngularJS deben usarse para no mostrar campos de formulario hasta que estén listas.
Esto debe hacerse junto con el uso de los métodos de espera de Capybara para garantizar que el campo fill_in solo se envíe una vez que se haya completado la carga de formulario.
index.html.erb o equivalente:
<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. Actualizó la versión de línea de comando de PhantomJS a la última (2.1.1)
Esto pareció permitir que las pruebas se ejecuten sin tantos métodos de espera de Capybara para lograr pruebas confiables.
Código de prueba actualizado
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