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.
I am trying to use capybara in my feature tests, but I keep getting the above error. However, my tests work when non Capybara functions are involved.
Here is the settings in my env.rb:
Capybara.server_host = 45454
#Capybara.server_host = host
Capybara.app_host = 'http://localhost:45454'
Capybara.default_driver = :poltergeist
PATH variable is also set for Phantomjs
Following is the steps definition file where I am facing the issue.
Given(/^I navigate to home page$/) do
visit '/'
end
And /^I take screenshot$/ do
page.save_screenshot
end
Following is the feature file
Scenario: To validate the page shows up
Given I navigate to home page
And I take screenshot
Here is the output:
Scenario: To validate the page shows up←[90m # features/home.feature:8←[0m
←[31mGiven I navigate to home page←[90m # features/step_definitions/
home_steps.rb:8←[0m←[0m
←[31m wrong argument type Fixnum (expected String) (TypeError)←[0m
←[31m ./features/step_definitions/home_steps.rb:9:in `/^I navigate to
home
page$/'←[0m
←[31m features/home.feature:9:in `Given I navigate to home page'←[0m
←[36mAnd I take screenshot←[90m # features/step_definitions/
home_steps.rb:12←[0m←[0m
←[31m wrong argument type Fixnum (expected String) (TypeError)←[0m
←[31mFailing Scenarios:←[0m
←[31mcucumber features/home.feature:8←[0m←[90m # Scenario: To validate the
page
shows up←[0m
1 scenario (←[31m1 failed←[0m)
2 steps (←[31m1 failed←[0m, ←[36m1 skipped←[0m)
0m0.649s
Capybara.server_host needs to be the hostname/ip of an interface Capybara can bind the AUT to, not a number.
You're probably trying to set the port, which would be
Capybara.server_port = 45454
and then judging by your setting of app_host (which probably isn't necessary) you also want to be setting
Capybara.server_host = 'localhost'
Firstly it is showing error I have use #Watir.default_timeout = 900 also I try change the time in config file inside webrick still it doesn't work
The errors are
Net::ReadTimeout
Secondly for the next error I even tried changing port number still it doesn't work
Errno::ECONNREFUSED (Connection refused - connect(2) for "127.0.0.1" port 70 55):
I want to list all the seller name and price but it only list 2 sellers name and price I want them all
require 'selenium-webdriver'
require 'phantomjs'
require 'watir'
browser = Watir::Browser.new: chrome
browser.window.maximize
browser.goto "url"
browser.div(: class => 'sellCont').uls.each do |list |
puts list.lis.first.text# For dealer name
puts list.li(: class => 'price')# For price
end
browser.close
Are you getting all required data when you load given url first time without click on any link or button on browser?
As you said your example one-to-one as your code. It is really strange because it does not look like a valid ruby code. This code works in my console (irb)
require 'selenium-webdriver'
require 'watir-webdriver'
browser = Watir::Browser.new :chrome
browser.window.maximize
browser.goto "https://paytm.com/shop/p/gionee-e7-mini-black-MOBGIONEE-E7-MIHAPP44414CBBDB36C?psearch=organic%7Cundefined%7Cgionee%20e7%7Cgrid"
browser.div(:class => 'sellCont').uls.each do |list |
puts list.li.first.text# For dealer name
puts list.li(:class => 'price')# For price
end
browser.close
Pay attention that you don't use phantomjs (maybe you need it in general but not in the given example) so it was removed. And I require watir-webdriver not watir (I just take it from one of my project)
Perhaps this isn't actually the issue I'm experiencing, but it seems that when I "click_link" a link with target="_blank", the session keeps the focus on the current window.
So I either want to be able to switch to the new window, or to ignore the _blank attribute - essentially, I just want it to actually go to the page indicated by the link so I can make sure it's the right page.
I use the webkit and selenium drivers.
I submitted my findings thus far below. A more thorough answer is much appreciated.
Also, this only works with selenium - the equivalent for the webkit driver (or pointing out where I could discover it myself) would be much appreciated.
Capybara >= 2.3 includes the new window management API. It can be used like:
new_window = window_opened_by { click_link 'Something' }
within_window new_window do
# code
end
This solution only works for the Selenium driver
All open windows are stores in Selenium's
response.driver.browser.window_handles
Which seems to be an array. The last item is always the window that was most recently opened, meaning you can do the following to switch to it.
Within a block:
new_window=page.driver.browser.window_handles.last
page.within_window new_window do
#code
end
Simply refocus for current session:
session.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
Referenced on the capybara issues page: https://github.com/jnicklas/capybara/issues/173
More details on Selenium's window switching capabilities: http://qastuffs.blogspot.com/2010/10/testing-pop-up-windows-using-selenium.html
This is now working with Poltergeist. Although window_handles is still not implemented (you need a window name, i.e. via a JavaScript popup):
within_window 'other_window' do
current_url.should match /example.com/
end
Edit: Per comment below, Poltergeist now implements window_handles since version 1.4.0.
Capybara provides some methods to ease finding and switching windows:
facebook_window = window_opened_by do
click_button 'Like'
end
within_window facebook_window do
find('#login_email').set('a#example.com')
find('#login_password').set('qwerty')
click_button 'Submit'
end
More details here: Capybara documentation
I know this is old post, but for what its worth in capybara 2.4.4
within_window(switch_to_window(windows.last)) do
# in my case assert redirected url from a prior click action
expect(current_url).to eq(redirect['url'])
end
Seems like it is not possible with capybara-webkit right now: https://github.com/thoughtbot/capybara-webkit/issues/271
:-(
At the same time https://github.com/thoughtbot/capybara-webkit/issues/129 claims it is possible to switch windows with within_window.
Also https://github.com/thoughtbot/capybara-webkit/issues/47 suggests that page.driver.browser.switch_to().window(page.driver.browser.window_handles.last) works. Ah well, on to code reading.
The code at https://github.com/thoughtbot/capybara-webkit/blob/master/lib/capybara/webkit/browser.rb at least has some references that suggest that the API that works for webdriver / firefox is also working for webkit.
Now within_window implemented for capybara-webkit http://github.com/thoughtbot/capybara-webkit/pull/314 and here you can see how to use it http://github.com/mhoran/capybara-webkit-demo
As of May 2014 the following code works on capybara-webkit
within_window(page.driver.browser.window_handles.last) do
expect(current_url).to eq('http://www.example.com/')
end
To explicitly change window, you use switch_to_window
def terms_of_use
terms_window = window_opened_by do
click_link(#terms_link)
end
switch_to_window(terms_window)
end
An after that method browser will work in the new page, instead of wrap everything in a within_window block
This works for me in capybara-webkit:
within_window(windows.last) do
# code here
end
(I'm using capybara 2.4.1 and capybara-webkit 1.3.0)
You can pass a name, url or title of the window also
(But now its dipricated)
let(:product) { create :product }
it 'tests' do
visit products_path
click_link(product.id)
within_window(product_path(product)) do
expect(page).to have_content(product.title)
end
end
You can pass a labda or a proc also
within_window(->{ page.title == 'Page title' }) do
click_button 'Submit'
end
wish it stretches the method usage to more clearly understaing
I had this issue when opening links in an gmail window: I fixed it like this:
Given /^(?:|I )click the "([^"]*)" link in email message$/ do |field|
# var alllinks = document.getElementsByTagName("a");
# for (alllinksi=0; alllinksi<alllinks.length; alllinksi++) {
# alllinks[alllinksi].removeAttribute("target");
# }
page.execute_script('var alllinks = document.getElementsByTagName("a"); for (alllinksi=0; alllinksi<alllinks.length; alllinksi++) { alllinks[alllinksi].removeAttribute("target"); }')
within(:css, "div.msg") do
click_link link_text
end
end
The best idea is to update capybara to the latests version (2.4.1) and just use
windows.last
because page.driver.browser.window_handles is deprecated.
The main implementation (window_opened_by) raises an error for me:
*** Capybara::WindowError Exception: block passed to #window_opened_by opened 0 windows instead of 1
So, I resolve it by this solution:
new_window = open_new_window
within_window new_window do
visit(click_link 'Something')
end
page.driver.browser.window_handles
# => ["CDwindow-F7EF6D3C12B68D6B6A3DFC69C2790718", "CDwindow-9A026DEC65C3C031AF7D2BA12F28ADC7"]
I personally like the following approach since it works correctly regardless of JS being enabled or not.
My Spec:
it "shows as expected" do
visit my_path
# ...test my non-JS stuff in the current tab
switch_to_new_tab
# ...test my non-JS stuff in the new tab
# ...keep switching to new tabs as much as necessary
end
# OR
it "shows as expected", js: true do
visit my_path
# ...test my non-JS stuff in the current tab
# ...also test my JS stuff in the current tab
switch_to_new_tab
# ...test my non-JS stuff in the new tab
# ...also test my JS stuff in the new tab
# ...keep switching to new tabs as much as necessary
end
Test helpers:
def switch_to_new_tab
current_browser = page.driver.browser
if js_enabled?
current_browser.switch_to.window(current_browser.window_handles.last)
else
visit current_browser.last_request.fullpath
end
end
def js_enabled?
Capybara.current_driver == Capybara.javascript_driver
end
I'm adding a test task in my Rakefile, similar to this:
namespace :test do
desc "Test lib source"
Rake::TestTask.new(:lib) do |t|
t.libs << "test"
t.pattern = 'test/lib/**/*_test.rb'
t.verbose = true
end
end
and then adding (have also done using "enhance" with the same result:
task :test => [ 'test:lib' ]
My problem is that if there is an error encountered in test:lib, the suite stops running. That's not a terrible thing, but ideally it would go on to run the rest of the suite to let me know that there are more issues later in the suite.
Anyone know how to make it report the errors/failures in test:lib but go on to run the full suite?
Thanks!
Used something along these lines (minus the "on the fly" bit):
http://toolmantim.com/articles/creating_rake_testtasks_on_the_fly