unitarios unitarias unit test pruebas hacer como rspec phantomjs capybara integration-testing poltergeist

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

  1. Rails 3.2
  2. RSpec 2.x
  3. Capybara
  4. Duende
  5. PhantomJS
  6. AngularJS
  7. 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

  1. Asegúrate de usar los mezcladores DSL en espera de Capybara
  2. Asegúrese de que mi limpiador de base de datos esté configurado correctamente
  3. Pruebe cada elemento de la página suponiendo que podría no estar en la página y aún podría estar cargando
  4. Reducir las pruebas inconsistentes
  5. Ejecutar prueba inconsistente solo
  6. 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''
  7. Actualice Capybara a la última versión (en una rama separada)
  8. 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.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

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) en

Terminado 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ón

Terminado 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