Why does Capybara not find an element if I remove js: true (because it's very slow) from my test?
My test:
require 'spec_helper'
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome)
end
feature 'signal trading platform', integration: true do
scenario 'check context' do
visit '/'
find('a', text: 'SIGNAL TRADER').click
end
end
=>
Capybara::ElementNotFound:
Unable to find css "a" with text
Removing the js: true metadata means (most likely) your test is running with the rack_test driver. The rack_test driver doesn't process JS at all and doesn't process most CSS. Therefore if the test worked before you removed the js: true metadata, either your page loading is dependent on javascript or the text was being affected by CSS which is no longer processed ( text-transform, etc). Without seeing the actual HTML it's impossible to give a more specific answer.
Related
A Rails app that uses remote testing to test a CakePHP app running on a Vagrant Ubuntu VM.
My OS is macOS High Sierra.
'rspec/rails'
'capybara/rails'
'capybara/mechanize'
'capybara/poltergeist'
'phantomjs'
If I run rspec ./spec/features/my_tests_folder/,
the first 2 tests in the folder always pass and the rest always end up with Capybara::Poltergeist::TimeoutError:.
If I run any of the tests in that folder individually, they ALL pass ALWAYS.
There are 7 test files total. They each have 1 feature with 1 scenario. All are js: true.
I have tried increasing :timeout in Capybara.register_driver and increasing default_max_wait_time in Capybara.configure. Neither changed the outcome.
I've also played around with Capybara.reset! after and before each test. It didn't seem to matter, either.
When I ran this with config.order = :random, sometimes 5 out of 7 had the erros, sometimes only 2 out of 7. But, there were always some errors and some passing. Also, every test was in the errors group at least once.
I'm running out of ideas. What could cause something like this?
UPDATE (to include Capybara and Poltergeist configs and example of a failing test):
Configs in rails_helper.rb:
Capybara.register_driver :poltergeist do |app|
options = {
:timeout => 90, # default is 30
:js_errors => false,
:phantomjs => Phantomjs.path,
# :debug => true
}
Capybara::Poltergeist::Driver.new(app, options)
end
Capybara.register_driver :mechanize do |app|
driver = Capybara::Mechanize::Driver.new(app)
driver.configure do |agent|
agent.user_agent_alias = 'Mac Safari'
end
driver
end
Capybara.configure do |config|
config.run_server = false
config.app_host = "http://my_vm_domain.com"
config.javascript_driver = :poltergeist
config.default_driver = :mechanize
config.default_max_wait_time = 10 # default is 2
end
Example of failing test (not failing, but getting Capybara::Poltergeist::TimeoutError:):
require 'rails_helper'
feature 'agente visualiza estoques de um passeio', js: true do
scenario 'com sucesso' do
agente_log_in
visit '/roteiro/show/723'
find(:partial_href, 'new_passeio/723').click
select 'Passeio Teste', from: 'passeio_produto'
fill_in 'passeio_data', with: '11/11/2017'
within('button#estoque_controls_0') do
within('div#estoque_0_hora') do
expect(page).to have_content('07:30')
end
within('span#estoque_0_vagas') do
expect(page).to have_content('3')
end
end
within('button#estoque_controls_1') do
within('div#estoque_1_hora') do
expect(page).to have_content('10:00')
end
within('span#estoque_1_vagas') do
expect(page).to have_content('5')
end
end
end
end
Code from agente_log_in.rb in support folder:
def agente_log_in
Capybara.app_host = "http://my_vm_domain.com"
visit '/usuario/logout'
visit '/'
fill_in 'data[AdmUsuario][usuario]', with: 'agente'
fill_in 'data[AdmUsuario][senha]', with: 'pa$$w0rd'
click_on 'Entrar'
end
Code for that :partial_href find:
module Selectors
Capybara.add_selector(:partial_href) do
xpath {|href| XPath.descendant[XPath.attr(:href).contains(href)] }
end
end
Everything is fine with the other tests that are in the app's other folders. They're also fine if I run the tests in this folder individually. The problem only seems to happen when I run THIS specific folder as a whole.
After including the extra information requested by Thomas Walpole, I continued searching and studying possibilities.
I eventually came across poltergeist's issue #781 on GitHub, which describes a situation very similar to mine, and eventually presents a wait_for_ajax solution.
Before implementing it in my project, I read more about waiting for Ajax and found this post very helpful, too.
In the end, I chose jasonfb's code from GitHub because it seemed more thorough and informative.
It worked like a charm! My tests now pass consistently. I was even able to remove customization for :timeout and default_max_wait_time.
The CakePHP app in question is very js heavy and the specific part that this folder tests is particularly full of Ajax requests.
I have below spec code for testing user login feature.
feature "User login" do
context "using browser", :js => true do
before(:each) do
visit "/"
first(:link, "Login", visible: :any).click
end
scenario "with valid details" do
...
# rest of code.
end
end
end
It is working fine in my local machine with headless chrome. Here is my Capybara javascript_driver config in spec_helper.rb.
Capybara.register_driver :headless_chrome do |app|
options = Selenium::WebDriver::Chrome::Options.new(
args: %w[headless disable-gpu no-sandbox]
)
Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end
Capybara.javascript_driver = :headless_chrome
But not working with gitlab-ci, below is error i am getting.
Failure/Error: first(:link, "Login", visible: :any).click
NoMethodError: undefined method 'click' for nil:NilClass
./spec/features/user_login_spec.rb:6:in 'block (3 levels) in '
/usr/local/bundle/gems/rspec-retry-0.5.6/lib/rspec/retry.rb:115:in
'block in run'
/usr/local/bundle/gems/rspec-retry-0.5.6/lib/rspec/retry.rb:104:in
'loop'
/usr/local/bundle/gems/rspec-retry-0.5.6/lib/rspec/retry.rb:104:in
'run'
/usr/local/bundle/gems/rspec-retry-0.5.6/lib/rspec_ext/rspec_ext.rb:12:in
'run_with_retry'
I found this blog to use headless chrome but nothing helped.
Here are the versions of dependencies i am using.
google-chrome-stable (63.0.3239.108-1)
chromedriver (2.34)
By default first doesn't have waiting/retrying behavior, which means if a matching link doesn't exist when it runs it won't wait for one to appear. Additionally passing visible: :any to find an element that you're then going to call click on doesn't make any sense since you can't click on non-visible elements so that would cause an error too. If you do actually need/want to use first (multiple matching links that you can't scope down to one) then you should be using
before(:each) do
visit "/"
first(:link, "Login", minimum: 1).click
end
The minimum: 1 option will trigger first to wait/retry up to Capybara.default_max_wait_time seconds for a matching link to appear on the page. This is one of the reasons using first and all are generally
bad choices when looking for elements to actually interact with (unless you've done a previous find for something that guarantees the page is in a stable state, or use any of the count options to trigger waiting/retrying behavior). If there aren't actually more than one matching link on the page then you should just be using
before(:each) do
visit "/"
click_link('Login') # same as find(:link, 'Login').click
end
I have a rich frontend in my application. Some of my tests not works well with poltergeist, because of animations and AJAX requests, but works fine with selenium.
How can i use them together in one project and in one test session?
If you're using the standard RSpec configuration with Capybara (require 'capybara/rspec') then you can override the normal driver that would be used for a given test with :driver metadata
it "should do something", driver: :selenium do
# will use the selenium driver for this test
end
it "should do something else", driver: :poltergeist do
# will use the poltergeist driver for this test
end
that could also be specified on the enclosing feature if you want the whole feature to use a specific driver
feature "blah balh", driver: :selenium do
# all scenarios here would use the selenium driver unless overridden with their own :driver metadata
I found solution.
Created macros in spec/support/selenium_macros.rb:
module SeleniumMacros
def use_selenium_webdriver
before(:all) do
Capybara.javascript_driver = :selenium
Capybara.current_driver = :selenium
end
after(:all) do
Capybara.current_driver = :poltergeist
Capybara.javascript_driver = :poltergeist
end
end
end
spec/rails_helper.rb
RSpec.configure do |config|
config.extend SeleniumMacros, type: :feature # add macros for acceptance tests
using example
spec/features/example_feature_spec.rb
feature 'Add files to question' do
use_selenium_webdriver
this feature will be work with selenium, after it will be executed it activates poltergeist webdriver.
P.S. Sorry for my english.
I am using the gem Browser
to detect mobile devices and redirect them to a different landing on a rails app. The code I'm using is this one :
Rails.configuration.middleware.use Browser::Middleware do
redirect_to mobile_landing_path if browser.device.mobile? || browser.device.ipad? || browser.device.tablet?
end
Now I need to do a feature test using Rspec and capybara, and I'm a little lost here. Really appreciate the help .
Tests I have right now is using capybara user agent and looks like this: but it seems it stills gets the base landing and not the mobile one
feature 'Visiting the site from an' do
context 'Android device' do
background do
set_user_agent(:android)
end
scenario 'I should be redirected to mobile landing' do
visit root_path
expect(page).to have_current_path(mobile_landing_path)
end
end
end
The browser gem middleware will only be run when the HTTP_ACCEPT header is set and matches %r[(text/html|/)] . By default the rack-test driver doesn't set any headers. You can either overwrite the provided driver registration like
Capybara.register_driver :rack_test do |app|
Capybara::RackTest::Driver.new(app, :headers => { 'HTTP_ACCEPT' => 'text/html' })
end
or you can create a new driver registration
Capybara.register_driver :rack_test_with_html_accept do |app|
Capybara::RackTest::Driver.new(app, :headers => { 'HTTP_ACCEPT' => 'text/html' })
end
and then add driver: :rack_test_with_html_accept metadata to your tests that need that header to be set
feature 'Visiting the site from an', driver: :rack_test_with_html_accept do
...
end
My setup is using poltergeist as the Capybara driver for all my tests, both JS and non-JS.
# spec/rails_helper.rb
require "capybara/poltergeist"
# ...
# ...
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, js_errors: true)
end
Capybara.configure do |config|
config.ignore_hidden_elements = true
Capybara.default_driver = :poltergeist
Capybara.javascript_driver = :poltergeist
end
I have some tests where I confirm that certain features on my app are still working even with javascript disabled. For those tests, I of course disable javascript with js: false.
describe "accessibility" do
describe "JavaScript disabled", js: false do
before(:each) { visit root_path }
it "user can still log in" do
# ...
end
end
end
However, I'm noticing that these js:false tests still use JavaScript. I can confirm this by printing debug statements to the console log in JavaScript.
Is there a way to disable JavaScript when using poltergeist? Or is it always enabled? Is it even valid to use poltergeist as a non-JS driver?
Thanks!
No, there doesn't seem to be a way to use poltergeist without Javascript (unless you modify poltergeist yourself). According to this Github issue it would require support in phantomjs, which is available in a patch but not in master.