I'm noticing a strange issue that I haven't been able to solve for a few days.
I have a Rails 5 API server with system tests using RSpec and Capybara + Selenium-webdriver driving headless Chrome.
I'm using Capybara.app_host = 'http://localhost:4200' to make the tests hit a separate development server which is running an Ember front-end. The Ember front-end looks at the user agent to know to then send requests to the Rails API test database.
All the tests run fine except for ones which use RSpec file fixtures.
Here's one spec that is failing:
describe 'the affiliate program', :vcr, type: :system do
fixtures :all
before do
Capybara.session_name = :affiliate
visit('/')
signup_and_verify_email(signup_intent: :seller)
visit_affiliate_settings
end
it 'can use the affiliate page' do
affiliate_token = page.text[/Your affiliate token is \b(.+?)\b/i, 1]
expect(affiliate_token).to be_present
# When a referral signs up.
Capybara.session_name = :referral
visit("?client=#{affiliate_token}")
signup_and_verify_email(signup_intent: :member)
refresh
# It can track the referral.
Capybara.session_name = :affiliate
refresh
expect(page).to have_selector('.referral-row', count: 1)
# When a referral makes a purchase.
Capybara.session_name = :referral
find('[href="/videos"]').click
find('.price-area .coin-usd-amount', match: :first).click
find('.cart-dropdown-body .checkout-button').click
find('.checkout-button').click
wait_for { find('.countdown-timer') }
order = Order.last
order.force_complete_payment!
Rake::Task['affiliate_referral:update_amounts_earned'].invoke
# It can track the earnings.
Capybara.session_name = :affiliate
refresh
amount = (order.price * AffiliateReferral::COMMISSION_PERCENTAGE).floor.to_f
amount_in_dom = find('.referral-amount-earned', match: :first).text.gsub(/[^\d\.]/, '').to_f * 100
expect(amount).to equal(amount_in_dom)
end
end
This will fail maybe 99% of the time. There is the odd case where it passes. I can get my test suite to eventually pass by running it on a loop for a day.
I ended up upgrading all versions to the latest (Node 10, latest Ember, latest Rails) but the issue persists.
I can post a sample repo that reproduces the issue later. I just wanted to get this posted in case anyone has encountered the issue.
Here's a typical stack trace when the timeout happens:
1.1) Failure/Error: page.evaluate_script('window.location.reload()')
Net::ReadTimeout:
Net::ReadTimeout
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/webmock-3.3.0/lib/webmock/http_lib_adapters/net_http.rb:97:in `block in request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/webmock-3.3.0/lib/webmock/http_lib_adapters/net_http.rb:110:in `block in request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/webmock-3.3.0/lib/webmock/http_lib_adapters/net_http.rb:109:in `request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/http/default.rb:121:in `response_for'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/http/default.rb:76:in `request'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/http/common.rb:62:in `call'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/bridge.rb:164:in `execute'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/oss/bridge.rb:584:in `execute'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/remote/oss/bridge.rb:267:in `execute_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/selenium-webdriver-3.14.0/lib/selenium/webdriver/common/driver.rb:211:in `execute_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/capybara-3.8.2/lib/capybara/selenium/driver.rb:84:in `execute_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/capybara-3.8.2/lib/capybara/selenium/driver.rb:88:in `evaluate_script'
# /home/mhluska/.rvm/gems/ruby-2.5.1/gems/capybara-3.8.2/lib/capybara/session.rb:575:in `evaluate_script'
# ./spec/support/selenium.rb:48:in `refresh'
# ./spec/support/pages.rb:70:in `signup_and_verify_email'
# ./spec/system/payment_spec.rb:43:in `block (3 levels) in <top (required)>'
I should point out it doesn't always happen with page.evaluate_script('window.location.reload()'). It can happen with something benign like visit('/').
Edit: I tried disabling Ember FastBoot (server-side rendering) using the DISABLE_FASTBOOT env variable and suddenly all tests pass. I'm thinking that somehow the RSpec fixtures are causing Ember FastBoot to not finish rendering in some cases. This certainly lines up with dropped connections I've occasionally seen in production logs.
I've been experimenting with the client code and it may be due to my use of FastBoot's deferRendering call.
Edit: I'm using the following versions:
ember-cli: 3.1.3
ember-data: 3.0.2
rails: 5.2.1
rspec: 3.8.0
capybara: 3.8.2
selenium-webdriver: 3.14.0
google chrome: 69.0.3497.100 (Official Build) (64-bit)
Edit: I'm using this somewhat flaky Node/Express library fastboot-app-server to do server-side rendering. I've discovered that it sometimes strips important response headers (Content-Type and Content-Encoding). I'm wondering if this is contributing to the issue.
Edit: I added a strict Content Security Policy to make sure there are no external requests running during the test suite that could be causing the Net::ReadTimeout.
I inspect the Chrome network tab at the point when it locks up and it seems to be loading nothing. Manually refreshing the browser allows the tests to pick up and continue running. How strange.
I've spent a couple weeks on this now and it may be time to give up on Selenium tests.
I upgraded to Chrome 70 and chromedriver 2.43. It didn't seem to make a difference.
I tried using the rspec-retry gem to force a refresh when the timeout occurs but the gem seems to fail to catch the timeout exception.
I've inspected the raw request to chromedriver where things hang. It looks like it's always POST http://127.0.0.1/session/<session id>/refresh. I tried refreshing in an alternate way: visit(page.current_path) which seems to fix things!
I finally got my test suite to pass by switching page.driver.browser.navigate.refresh to visit(page.current_path).
I know it's an ugly hack but it's the only thing I could find to get things working (see my various attempts in the question edits).
I looked at the request to chromedriver that was causing the timeouts each time: POST http://127.0.0.1/session/<session id>/refresh. I can only guess that it's some kind of issue with chromedriver. Perhaps incidentally, it only hangs when multiple chromedriver instances are active (which happens when multiple Capybara sessions are being used).
Edit: I needed to account for query params as well:
def refresh
query = URI.parse(page.current_url).query
path = page.current_path
path += "?#{query}" if query.present?
visit(path)
end
I tried just doing visit(page.current_url) but that was giving timeouts as well.
Related
Specs: Rails 5.2, Ruby 2.5.1, Yarn 1.91.1, React-Rails 2.6.0, selenium-webdriver 3.14, chromedriver 77, capybara gem 3.29 , Ubuntu 18.04
Issue: I created a questionnaire using react components in a Rails app which works when run in development mode. I can click the yes or no button then a callback function renders the new question text. However my selenium integration tests don't pick this up. The page object continues to have the same text for the first question so I receive an error like this
Failure/Error: expect(question.text).to include('last question')
expected "first question" to include "last question"
The test itself looks like this in features/questionnaire.rb
RSpec.feature "Onboarding Questionnaire", type: :feature, js:true do
(...)
it...
question = find('h3.question')
expect(question.text).to include('first question')
yes = find('#yes')
yes.click
sleep 5
question = find('h3.question')
expect(question.text).to include('last question')
The problem arises after yes.click. I thought at first my animations were causing the issue, but I removed those and it is just using setState in my callback function.
Here is my react callback function
saveAnswer(e) {
questionnaire = this
++questionnaire.state.page
questionnaire.setState({page: questionnaire.state.page})
}
Here is my driver config
Capybara.javascript_driver = :selenium_chrome_headless
Now using sleep works as desired. Here are the changes I made.
1) I removed the deprecated gem chromedriver-helper and replaced it with webdrivers
2) Added require 'webdrivers' to the rails_helper.rb file.
3) Found error in my react code (which wasn't showing up in development or being logged in chrome webdriver) of questionnaire = this which I changed to var questionnaire = this.
Integration tests now pass fine using selenium_chrome_headless
I have a select2 v4 that loads options through AJAX.
I am running a Cucumber test where I need to select 2 options of the list, but I can't seem to make the list open up and load (which normally gets populated when I type 2 or characters).
I have tried:
As suggested here:
#session.execute_script("$('#publish_to').select2('open')")
and
#session.first(".input.publish_to .select2-container").click
and
#session.first("#publish_to").find(".select2-choice").click
which do not give me an error, but I am not getting the options to select, so I am assuming that the click is not really working. Things I have tried to select the options:
# This one cannot find the css:
#session.find(".select2-results__options", text: client.email).click
# This one gives me a Timeout error
#session.evaluate_script "$('#publish_to').val(#{client.id}).trigger('change')"
# This one gives me a Timeout error
#session.evaluate_script "$('.select2-search__field').trigger('keydown').val('#{client.email}').trigger('keyup')";
sleep 10
#session.find('.select2-search__option', text: client.email).click
Anything with trigger gives me a Timeout error, so I tried waiting for jQuery.active but I never got a true even waiting for 2 minutes:
counter = 0
timeout_in_sec = 120
while counter < timeout_in_sec && #session.evaluate_script('jQuery.active').zero?
sleep 1.second
counter+=1
end
I tried using the gem capybara-select2 running:
#session.select2 client.email, css: '#publish_to', search: true
but I get the error undefined methodselect2' for #and I haveWorld(CapybaraSelect2)in myenv.rb`
I am using Cucumber v3.1.2 with ruby gem 'cucumber-rails'
The poltergeist driver is roughly equivalent to a 7 year old version of Safari which means it doesn't support a lot of current JS/CSS. This means your issue could simply be that select2 is no longer compatible with Poltergeist (without a lot of polyfilling). You're going to be much better off updating to using a real browser (stable - chrome via selenium, etc) or one of the direct to Chrome drivers (highly beta) that have spun off Poltergeist (Apparition is one of them). Those will allow you to run with a visible browser (useful for debugging) or headless.
The following code uses Chrome via selenium and interacts with the select2 demo site to select an entry that is loaded via Ajax.
require "selenium/webdriver"
require "capybara/dsl"
sess = Capybara::Session.new(:selenium_chrome)
sess.visit("https://select2.org/data-sources/ajax")
sess.first('.select2-container', minimum: 1).click
sess.find('.select2-dropdown input.select2-search__field').send_keys("capy")
sleep 5 # just to watch the browser search
sess.find('.select2-results__option', text: 'teamcapybara/capybara').click
sess.assert_selector(:css, '.select2-selection__rendered', text: 'teamcapybara/capybara')
sleep 5 # just to see the effect
I've experienced an odd interface glitch.
[44, 53] in /document.rb
44: code
45: code
46:
47: def code
48: byebug
=> 49: code
50: code
51: code
52: end
53:
(byebug) {here is where you type}
When I type, the characters are not displayed at all. I can type p "yo" or whatever I want, and when I hit enter it runs the code. Essentially I can use byebug in a pinch, but it's really frustrating when I can't see what I'm typing.
I've used byebug in the past with this same laptop, and this issue is fairly recent.
I was assisting a friend, and when he used byebug the same issue happened. I haven't been able to find anything online.
TL;DR: Add this line in config/puma.rb:
# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
Reproducing & Why this is happening
In development mode, set a debugger in a controller action and send a request to this action (e.x. index). After 60 seconds Puma, Rails' default server, will terminate the worker. Unfortunately, the next time a debugger is triggered you will be unable to see any text you type in the terminal.
This is a known bug and is fixed in Rails 7+. The above line was added to change Puma's worker timeout to a more appropriate level (1 hour) when in development mode.
Other Causes
As u/casper pointed out in a comment above, Spring can also cause similar behaviour. Stop your server and run bin/spring stop and this may resolve your issue.
If you're experience only some text appear when you type, this may be a result of parallelized tests. For example, if you type, "Hello World" but only "Hol" appears (i.e. some letters), you can disable parallelized tests following u/sajad-rastegar's advice above. Simply comment this line in test/test_helper.rb: parallelize(workers: :number_of_processors).
This problem occurs when the tests run in parallel. Commenting this line in test_helper should fix the problem:
parallelize(workers: :number_of_processors)
I have been writing rspec tests using capybara and selenium-webdriver. Almost without fail, whenever I run one of these tests, the console output is gone.
For instance:
~/code/code> bundle exec rspec spec/features/interactions_spec.rb
InteractionsSpec
~/code/code>
Thats just about all I ever see. The browser launches, performs the action's I've coded, but I don't see the usual output.
Sometimes I'll see this as output instead (yay!):
InteractionsSpec
login as admin works
login as user works
Finished in 16.84 seconds (files took 7.9 seconds to load)
2 examples, 0 failures
What could be causing the output from the tests to disappear? This makes it nearly impossible to write tests - as I have no idea what has run, what has passed, what has failed, or why they have failed.
I'm using these Gems, but performing a bundle update does not change the behavior.
capybara (2.4.4)
capybara-screenshot (1.0.3)
rspec (3.0.0)
rspec-activemodel-mocks (1.0.1)
rspec-collection_matchers (1.0.0)
rspec-core (3.0.3)
rspec-expectations (3.0.3)
rspec-mocks (3.0.3)
rspec-rails (3.0.2)
rspec-support (3.0.3)
selenium-webdriver (2.44.0)
shoulda-matchers (2.6.2)
Other involved software:
Firefox 33.1.1 (Chrome 39.0.2171.65 (64-bit) has the same behavior)
OSX 10.9.5
Rails 4.1.4
Update
This seemed to fix the problem for a while, even with a sleep of 1ms. However, that was only a temporary fix and this problem persists.
RSpec.configure do |config|
config.before(:each, :type => :feature) do
sleep(0.5)
end
end
Looks to me like the logger is getting silenced via a call to logger.silence() by some other thread.
When you put the sleep() in the code, then you yielded to some other thread that fixed the logging level - that's why it didn't matter how long the sleep was, it was the context switch that was fixing the issue.
When you upgraded the activesupport gem, this commit here https://github.com/rails/activerecord-session_store/commit/f92d1135fc620cb4d65239ef286b267945bbbbc6 fixes the issue (as you said). Reading that commit, notice a new threadsafe implementation of logger.silence. That's why it fixes your issue.
The old implementation of active_support/logger_silence.rb here isn't threadsafe:
require 'active_support/concern'
module LoggerSilence
extend ActiveSupport::Concern
included do
cattr_accessor :silencer
self.silencer = true
end
# Silences the logger for the duration of the block.
def silence(temporary_level = Logger::ERROR)
if silencer
begin
old_logger_level, self.level = level, temporary_level
yield self
ensure
self.level = old_logger_level
end
else
yield self
end
end
end
There seems to be a number of issues related to thread safety and race conditions in initializing the rails environment under capybara, which uses multiple threads. Rob's answer explains why it starts working (logging is fixed to be thread-save in activerecord-session_store).
It seems that these issues and commits are also related
https://github.com/rails/rails/issues/15089
https://github.com/jnicklas/capybara/issues/1419
https://github.com/jnicklas/capybara/commit/a4fc9d96f705e9ac4732c5fa05ab79d947743599
I want to use cache-money but I don't want to start automatically caching everything (I'm working with a large production app, terabytes of data etc). How do I use it for only the models that I specify? Right now I've got:
# initializers/cache_money.rb
require 'cache_money'
config = (cfg = YAML.load(IO.read(File.join(RAILS_ROOT, "config", "memcached.yml"))))[RAILS_ENV] || cfg["defaults"]
$memcache = MemCache.new(config)
$memcache.servers = config['servers']
$local = Cash::Local.new($memcache)
$lock = Cash::Lock.new($memcache)
$cache = Cash::Transactional.new($local, $lock)
and then in the model I want to cache with cache-money:
# my_model.rb
class MyModel < ActiveRecord::Base
is_cached :repository => $cache
# ...
end
But this doesn't work; the call to is_cached gives the following error: NoMethodError: undefined method `create' for Config:Module
Any ideas? Failing that, is there anywhere I can go for help with cache-money? I couldn't find a mailing list or anything.
I think this is a bug in the cache_money code.
There are forks available on github that fix this bug, eg:
http://github.com/quake/cache-money
The fix can be seen with this commit:
http://github.com/quake/cache-money/commit/54c3d12789f31f2904d1fe85c102d7dbe5829590
I've just experienced the same problem trying to deploy an application. Running on my development machine it was fine, but it failed with this error on the production machine.
Apart from the architecture (OSX vs CentOS) the only difference i could see was that the ruby versions were different (1.8.6 p114 vs 1.8.6 p0). After upgrading the server to the latest 1.8 version (1.8.7 p160) this error went away.