How to get more details out of Capybara at failure time? - ruby-on-rails

My shopping list is...
ruby-2.0.0-p247
rails (4.0.1)
rspec-rails (2.14.0)
capybara (2.0.3)
capybara-selenium-remote (0.0.1)
The point is RSpec feature-tests that drive Capybara, that drive a Firefox web browser hitting our website.
Now ogle this failing ... expectation:
old_date = 'November 16, 2013 04:00 pm'
expect(page).to have_field('somethingDate', with: old_date)
When it fails, it emits this:
Failure/Error: expect(page).to have_field('somethingDate', with: old_date)
expected #has_field?("somethingDate", {:with=>"November 16, 2013 04:00 pm"}) \
to return true, got false
The general question this: How to write an assertion that reveals _everything_ it knows about a situation. Instead of almost NOTHING: "got false".
I would expect, based on my (cough) experience with assert_select and assert_xpath, to at least see a snip of the HTML around the fault point. If not, then maybe at LEAST the value that has_field? DID find in that field. (And keep in mind this is only RAILS FOUR, already...)
So the two sub-questions are:
Would a different assertion tell me more? (If so, why is has_field? supposed to be the industry flagship assertion here?)
Is there some way to configure or tune or plug-in has_field?, to emit more details at fault time?

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.

Poltergeist/Capybara test unable to find CSS intermittently

I'm using Capybara and Poltergeist and cannot for the life of me get all my tests to consistently pass. I have this one issue in particular with a date selector. It should be really simple - user clicks on input, out puts a selection of months (first image). A month is then clicked, and then a day selection appears (second image), on which a day of the month is selected.
Now, my code looks as follows:
all(:css, 'input.from_date').last.click
expect(page).to have_css(".datepicker-months")
within(:css, '.datepicker-months') { find('.month', :text => 'Jun', match: :first).click }
expect(page).not_to have_css(".datepicker-months")
expect(page).to have_css(".datepicker-days")
within(:css, '.datepicker-days') { find('.day', :text => work[:start_date].stamp('31').to_i.to_s, match: :first).click } #.to_i.to_s used to remove leading zeros
page.assert_no_selector('.datepicker-days')
Sometimes it passes, but most of the time it says:
expected to find css ".datepicker-days" but there were no matches
or
expected not to find css ".datepicker-months", found 1 match: "« 2015 » JanFebMarAprMayJunJulAugSepOctNovDec"
If I try and debug this by calling binding.pry, I can run the commands step by step in the console and it works perfectly. My timeout is set to more than enough (i think). Any ideas why this test fails intermittently?
My config:
Capybara.javascript_driver = :poltergeist
Capybara.default_wait_time = 60
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, {
timeout: 60,
js_errors: false,
phantomjs_logger: File.open("log/phantomjs.log", "a")})
end
UPDATE:
When adding sleep(0.5) after every step in the process, it passes each and every time. This is bad practice and I'm supposed to be able to write tests without doing this. :/
Whenever I run into these inconsistencies, I try to find other things to assert against so that you don't have to count on sleep.
If there are any sort of animations that start/stop, you can try to assert against those.
You can also try to be more specific in your current expectation, since it seems like you had no trouble clicking on the object you found
expect(page).not_to have_css('.datepicker-months .month', :text => 'Jun')
If any of that gets you any farther let us know.

cucumber show me the page not showing page (Solution: it was on the wrong line)

Adding the line "Then show me the page" to a cucumber scenario works on my colleague's computer with the same setup, but not on mine. In fact, adding in a pause step definition and calling it seems to be completely ignored.
Step definitions:
Then /^show me the page$/ do
save_and_open_page
end
AfterStep('#pause') do
print "Press Return to continue"
STDIN.getc
end
feature:
(line 25) Scenario Outline: An authorised user visits the blah..,
Given a blah: ... blah ...
And I am logged in as <role>
And the date is 1 April 2012
When I go to the blah page
And I click "foobar"
Then I should see "Blah foobar"
And I should see "Foobar on Mon Apr 1 00:00:00 2012 by <email>"
Then show me the page
And ...
Command line:
cucumber features/blah/foos.feature:25 --require features --tags #pause
Using Firefox 16.02 (Avoiding the bugs in 17)
Both machines have git-fetch'd, pull'd, bundle install'd so all gems are identical, both using Rbenv versions * 1.9.3-p327-perf. I may be missing something obvious...
Update: tmp/capybara/ does not contain the file, so it isn't being saved now...
Solution thanks to Beerlington: Move the show me the page line up one. There was failure before show me the page, thus it did not get shown.
Copying the answer from the comments in order to remove this question from the "Unanswered" filter:
Solution thanks to Beerlington: Move the show me the page line up one. There was failure before show me the page, thus it did not get shown.

Select mixed mode content in Capybara

I am trying to extract mixed mode content using Capybara. I did it using Nokogiri, but wonder why similar is not possible with Capybara.
require 'nokogiri'
doc = Nokogiri::HTML("<h1><em>Name</em>A Johnson </h1>")
puts doc.at_xpath("//h1/text()").content
It works, but when I try same XPath selector in Capybara it doesn't work.
visit('http://stackoverflow.com')
puts find(:xpath, "//h1/text()").text
It raises error:
[remote server] file:///tmp/webdriver-profile20120915-8089-kxrvho/extensions/fxdriver#googlecode.com/components/driver_component.js:6582:in `unknown': The given selector //h1/text() is either invalid or does not result in a WebElement. The following error occurred: (Selenium::WebDriver::Error::InvalidSelectorError)
[InvalidSelectorError] The result of the xpath expression "//h1/text()" is: [object Text]. It should be an element.
How to extract this text?
Capybara requires a driver, and the XPath will be executed by the driver. From your error message, it is clear you are using selenium-webdriver, which will use a browser's native XPath implementation where available. For IE, it usees its own.
You appear to be using a combination where the XPath implementation is not fully compliant. You can try to change the driver or browser, but if you really want to use Nokogiri to extract content, you should be able to do the following:
doc = Nokogiri::HTML(page.html)
puts doc.at_xpath("//h1/text()").content
I do not believe Capybara or Selenium-Webdriver have any support for directly accessing text nodes. However, if you do not want to use nokogiri, you can use selenium-webdriver to execute javascript.
You can do this (in Capybara using Selenium-Webdriver):
element = page.find('h1').native
puts page.driver.browser.execute_script("return arguments[0].childNodes[1].textContent", element)
#=> A Johnson

How Can I Make ActiveResource XML Parsing More Consistent?

I'm using ActiveResource to consume a REST webservice provided by Redmine (a bug-tracking tool). That webservice produces XML like the following:
<custom_field name="Issue Owner" id="15">Fred Fake</custom_field>
<custom_field name="Needs Printing" id="16">0</custom_field>
<custom_field name="Review Assignee" id="17">Fran Fraud</custom_field>
<custom_field name="Released On" id="20"></custom_field>
<custom_field name="Client Facing" id="21">0</custom_field>
<custom_field name="Type" id="22">Bug</custom_field>
<custom_field name="QA Assignee" id="23"></custom_field>
<custom_field name="Company Name" id="26"></custom_field>
<custom_field name="QA Notes" id="27"></custom_field>
<custom_field name="Failed QA Attempts" id="28">2</custom_field>
However, when ActiveResource parses that, and I iterate through the results printing them out, I get:
Fred Fake
0
Fran Fraud
#<Redmine::Issue::CustomFields::CustomField:0x5704e95d>
0
Bug
#<Redmine::Issue::CustomFields::CustomField:0x32fd963>
#<Redmine::Issue::CustomFields::CustomField:0x3a68f437>
#<Redmine::Issue::CustomFields::CustomField:0x407964d6>
2
That's right, it throws out all of the attribute info from anything with a value, but keeps the attribute info from the empty elements.
Needless to say, this makes things rather difficult when you're trying to find the value for id 15 (or whatever). Now I can reference things by their position, but that's very brittle, because those elements are likely to change in the future. I assume there has to be some way to make ActiveResource keep the attribute info, but since I'm not doing anything special.
(My ActiveResource extension is just five lines long: it extends ActiveResource, defines the url, username and password of the service, and that's it).
So, does anyone know how I can make ActiveResource not parse this XML so strangely?
This is a known issue with ActiveResource apparently:
https://github.com/rails/rails/issues/588
Unfortunately, nothing appears to be done about it & the issue was closed. If you're feeling up to it, the Rails 3 code for updating ActiveResource and Hash.from_xml to preserve all attributes are all in the gist below and you could create a tailored version in your Redmine module to fix it:
https://gist.github.com/971598
Update:
An alternative, as it appears ActiveResource will not be part of Rails 4 core and will be spun out as a separate gem, would be to use an alternative ORM for REST APIs, like Her.
Her allows you to use a custom parser for your XML. This is an example custom parser called Redmine::ParseXML:
https://gist.github.com/3879418
So then all you need to do is create a file like config/initializers/her.rb:
Her::API.setup :url => "https://api.xxxxx.org" do |connection|
connection.use Faraday::Request::UrlEncoded
connection.use Redmine::ParseXML
connection.use Faraday::Adapter::NetHttp
end
and you get a Hash like the following:
#<Redmine::Issue(issues) issues={:attributes=>{:type=>"array", :count=>1640},
:issue=>{:id=>4326,
:project=>{:attributes=>{:name=>"Redmine", :id=>1}},
:tracker=>{:attributes=>{:name=>"Feature", :id=>2}},
:status=>{:attributes=>{:name=>"New", :id=>1}},
:priority=>{:attributes=>{:name=>"Normal", :id=>4}},
:author=>{:attributes=>{:name=>"John Smith", :id=>10106}},
:category=>{:attributes=>{:name=>"Email notifications", :id=>9}},
:subject=>"\n Aggregate Multiple Issue Changes for Email Notifications\n ",
:description=>"\n This is not to be confused with another useful proposed feature that\n would do digest emails for notifications.\n ",
:start_date=>"2009-12-03",
:due_date=>{},
:done_ratio=>0,
:estimated_hours=>{},
:custom_fields=>{
:custom_field=>[
{:attributes=>{:name=>"Issue Owner", :id=>15}, "value"=>"Fred Fake"},
{:attributes=>{:name=>"Needs Printing", :id=>16}, "value"=>0},
{:attributes=>{:name=>"Review Assignee", :id=>17}, "value"=>"Fran Fraud"},
{:attributes=>{:name=>"Released On", :id=>20}},
{:attributes=>{:name=>"Client Facing", :id=>21}, "value"=>0},
{:attributes=>{:name=>"Type", :id=>22}, "value"=>"Bug"},
{:attributes=>{:name=>"QA Assignee", :id=>23}},
{:attributes=>{:name=>"Company Name", :id=>26}},
{:attributes=>{:name=>"QA Notes", :id=>27}},
{:attributes=>{:name=>"Failed QA Attempts", :id=>28}, "value"=>2}]},
:created_on=>"Thu Dec 03 15:02:12 +0100 2009",
:updated_on=>"Sun Jan 03 12:08:41 +0100 2010"}}>

Resources