Rails + Capybara: clicking link with evaluate_script freezes webdriver - ruby-on-rails

I run the following in my js: true request spec:
page.evaluate_script("$('#sign-up').click();")
That opens the modal successfully. However, the webdriver freezes at that point, regardless of what comes next in the spec. After a long pause, I get:
Failure/Error:
Timeout::Error:
Timeout::Error
# ./spec/requests/my_spec.rb:14:in `block (3 levels) in <top (required)>'
I want to use evaluate_script instead of 'click_on' in this case, because there is no href attribute on that particular link (click_on doesn't work). How do I get it to work without timing out?

It's due to a bug in Selenium. Found the answer here: https://groups.google.com/forum/?fromgroups=#!topic/ruby-capybara/YcZwyPdMJFU
It doesn't hang when replacing page.evaluate_script with:
page.driver.browser.execute_script

Related

Capybara + Selenium-webdriver + RSpec file fixtures + SSR giving Net::ReadTimeout

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.

byebug isn't displaying the output as I type

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)

How to control time for capybara / phantomjs tests

I want to test that some deadlines are getting displayed to users correctly in different timezones and at different times of day. My tests are using capybara+rspec+phantomjs.
I am passing a block to Timecop.travel(datetime) and the code in the test within that block is getting the mocked datetime correctly, but it looks like PhantomJS / the mocked browser are not getting the mocked time.
Is there any known way to get PhantomJS to work with Timecop? Or other ways to mock out or manipulate time for testing purposes?
Here's a simple example to illustrate what I mean.
time_spec.rb:
it "should show the Time travel date" do
# current date is 2017-01-24
Date.today.should == Date.parse("2017-01-24")
Timecop.travel( Time.parse("2001-01-01 01:01") ) {
sign_in(user)
visit "/#{user.username}"
Date.today.should == Date.parse("2001-01-01")
page.should have_text("Today is 2001-01-01")
page.should have_text("Javascript says 2001-01-01")
}
end
user.html.erb:
<p>Today is <%= Time.now.iso8601 %></p>
<script>
var now = moment().format()
$('p').append("<p>Javascript says "+now+"</p>")
</script>
output of running the test:
Failures:
1) Dashboard should show the time travel date
Failure/Error: page.should have_text("Javascript says 2001-01-01")
expected to find text "Javascript says 2001-01-01" in
"Today is 2001-01-01T01:01:00-08:00 Javascript says 2017-01-24T12:36:02-08:00"
# ./spec/features/time_spec.rb:67:in `block (3 levels) in <top (required)>'
# /gems/ruby-2.2.0/gems/timecop-0.8.0/lib/timecop/timecop.rb:147:in `travel'
# /gems/ruby-2.2.0/gems/timecop-0.8.0/lib/timecop/timecop.rb:121:in `send_travel'
# /gems/ruby-2.2.0/gems/timecop-0.8.0/lib/timecop/timecop.rb:62:in `travel'
# ./spec/features/time_spec.rb:59:in `block (2 levels) in <top (required)>'
As you've discovered TimeCop only adjusts the servers time, but the browser uses system time. There are a number of JS libraries to allow for time faking, that all work by mocking the JS Date Class. One I have used successfully is Sinon.JS which I implemented in my _head.html.haml file by doing
- if defined?(Timecop) && Timecop.top_stack_item
= javascript_include_tag "testing/sinon-1.17.3.js"
- unix_millis = (Time.now.to_f * 1000.0).to_i
:javascript
sinon.useFakeTimers(#{unix_millis});
before requiring any other JS. This will set the browser time in any page rendered to whatever Timecop is set to.

Why Capybara cant find css?

I try to write simple feature spec, but I dont understand Capybara behavior. So, I have that code:
> parent.class
=> Capybara::Node::Element
> parent.find(:css, 'button.remove-arrow')
=> Capybara::ElementNotFound: Unable to find css "button.remove-arrow"
from /Users/weare138/.rvm/gems/ruby-2.3.3/gems/capybara-2.11.0/lib/capybara/node/finders.rb:44:in `block in find'
but when i try to do call via web-driver:
> parent.native.find(:css, 'button.remove-arrow')
it return me right answer
=> [#<Capybara::Poltergeist::Node tag="button" path="//HTML[1]/BODY[1]/DIV[1]/SECTION[1]/DIV[1]/FORM[1]/DIV[2]/DIV[2]/DIV[1]/DIV[2]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/BUTTON[1]">]
what I do wrong?
The most likely reason is that the node isn't actually visible on the page, which Capybara takes into account, but the driver doesn't. If
parent.find(:css, 'button.remove-arrow', visible: :false)
finds the element it would confirm that visibility is indeed the difference. If not then the actual HTML/CSS would be needed to know why.

How can I get selenium/cucumber to interact with a "hidden" link that is now in a lightbox/colorbox?

I'm using the colorbox library to display lightboxes within my application (http://colorpowered.com/colorbox/). The code seems to work fine in the browser, but I'm having trouble getting the automated test to work.
Here is some inline HTML that gets generated:
<div class='hidden'>
<div id='override'>
Are you sure you want to override this action?
<br>
Cancel</li>
Override once
</div>
</div>
This is the javascript that opens the colorbox:
$.fn.colorbox({innerWidth:600, inline:true, href:'#override', scrolling:false});
That code opens the lightbox and display the contents of my '#override' div. In my cucumber tests I can find text from inside that div, but I get an error if I try to follow a link:
When I follow "link that generates override"
Then I should see "Are you sure you want to override this action?" within "#cboxContent"
Then I should see "Cancel" within "#cboxContent"
# Then show me the page
When I follow "Cancel" within "#cboxContent"
Gives this error:
Element is not currently visible and so may not be interacted with (Selenium::WebDriver::Error::ElementNotDisplayedError)
[remote server] resource://fxdriver/modules/atoms.js:7903:in `'
[remote server] file:///var/folders/nn/nn5oYAICGPawlH+1W406+k+++TI/-Tmp-/webdriver-profile20110524-2973-1d5qq7w/extensions/fxdriver#googlecode.com/components/nsCommandProcessor.js:249:in `'
[remote server] file:///var/folders/nn/nn5oYAICGPawlH+1W406+k+++TI/-Tmp-/webdriver-profile20110524-2973-1d5qq7w/extensions/fxdriver#googlecode.com/components/nsCommandProcessor.js:298:in `'
[remote server] file:///var/folders/nn/nn5oYAICGPawlH+1W406+k+++TI/-Tmp-/webdriver-profile20110524-2973-1d5qq7w/extensions/fxdriver#googlecode.com/components/nsCommandProcessor.js:313:in `'
[remote server] file:///var/folders/nn/nn5oYAICGPawlH+1W406+k+++TI/-Tmp-/webdriver-profile20110524-2973-1d5qq7w/extensions/fxdriver#googlecode.com/components/nsCommandProcessor.js:190:in `'
./features/step_definitions/web_steps.rb:35
./features/step_definitions/web_steps.rb:14:in `with_scope'
./features/step_definitions/web_steps.rb:14:in `with_scope'
./features/step_definitions/web_steps.rb:34:in `/^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/'
features/prerequisites.feature:38:in `When I follow "Cancel" within "#cboxContent"'
Has anyone seen this error before and how did you get around it? (If I uncomment the 'show me the page' step then it also works)
I am having a similar issue with the Jquery-mobile framework.
From what I understand, Capybara is supposed to wait until all the AJAX calls are completed before doing a check or moving on. However, if you are having an issue where you think the page isn't finished loading you could try adding a simple "sleep 1" to Capybara's stock "I press" step to see if that helps.

Resources