Mysterious rspec errors after Rails upgrade - unit-testing

I recently tried to upgrade Rails (to v5.2.2) in a bunch of my code repos and two of them failed their unit tests after the upgrade. After some digging around and comparing the failed and working versions of Gemfile.lock line by line, I discovered upgrading activestorage (v5.2.1 -> 5.2.2) was the culprit and I was able to replicate it and reverse it. It seems like an issue has been introduced (at least for the tests; I haven't seen this issue using my code in person) where the test suite can't pick up element property updates after a .click event. Has anyone encountered this as well? Might this be a bug?
Here's a sample of the error msgs I was getting:
1) Notifications notifications drawer when clicking the nav will open the drawer
Failure/Error: expect(page).to have_selector('.notifications-drawer', visible: true)
expected to find visible css ".notifications-drawer" but there were no matches. Also found "View all notifications", which matched the selector but not all filters.
# ./spec/features/notifications_spec.rb:88:in `block (4 levels) in <top (required)>'
2) Notifications mark notification as read behavior when a notification is loaded will be marked as read after clicked
Failure/Error: expect(page).to have_css('.notification.read')
expected to find css ".notification.read" but there were no matches
# ./spec/features/notifications_spec.rb:140:in `block (4 levels) in <top (required)>'
Here are the two tests that fail after the upgrade:
context 'when clicking the nav' do
before do
find('.notifications-drawer-link').click
end
it 'will open the drawer' do # line 87
expect(page).to have_selector('.notifications-drawer', visible: true)
end
it 'will have a link to all notifications' do
expect(page).to have_selector('.notifications-drawer-link')
end
end
context 'when a notification is loaded' do
it 'will initially be marked as unread' do
expect(page).to have_css('.notification.unread')
end
it 'will be marked as read after clicked' do # line 137
notify_element = '[data-notify-id="' + mock_notifications[:items][0][:id] + '"]'
page.find(notify_element).find('.notifications-date-stamp').trigger("click")
expect(page).to have_css('.notification.read')
end
end

Related

undefined method 'document' for nil:NilClass while running Rails-tutorial with rspec

I am following the railstutorial by Michael Hartl, and I don't understand the reason for failing tests in Chapter 5. The book used the minitest framework but I decided to use RSpec. To do this, I deleted the test folder and included rspec-rails in my Gemfile then ran bundle install and rails g rspec:install to generate my spec folders. However, there are some tests that I feel convenient running with minitest syntax such as assert_select in static_pages_controller_spec.rb file. Here is how my spec file looks like:
require "rails_helper"
RSpec.describe StaticPagesController, type: :controller do
describe "GET #home" do
it "returns http success" do
get :home
expect(response).to have_http_status(:success)
end
it "should have the right title" do
get :home
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
end
describe "GET #help" do
it "returns http success" do
get :help
expect(response).to have_http_status(:success)
end
it "should have the right title" do
get :help
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
end
describe "GET #about" do
it "returns http success" do
get :about
expect(response).to have_http_status(:success)
end
it "should have the right title" do
get "about"
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
end
end
When I run the tests with RSpec, this is what I get as the failure error:
StaticPagesController GET #home should have the right title
Failure/Error: assert_select "title", "Ruby on Rails Tutorial Sample App"
NoMethodError:
undefined method `document' for nil:NilClass
# ./spec/controllers/static_pages_controller_spec.rb:11:in `block (3 levels)
in <top (required)>'
The same error message (No Method error) appears in each of the failing tests.
How can I fix it? Is there something I am doing wrong.
The reason for this error is that RSpec doesn't render views for controller specs by default. You can enable view rendering for a particular group of specs like this:
describe FooController, type: :controller do
render_views
# write your specs
end
or you can enable it globally by adding this somewhere in your RSpec config:
RSpec.configure do |config|
config.render_views
end
See https://www.relishapp.com/rspec/rspec-rails/v/2-6/docs/controller-specs/render-views for more information.
The problem is that assert_selected is a MiniTest construct whereas you are using RSpec. You will need to use RSpec mechanisms for expecting view content https://relishapp.com/rspec/rspec-rails/v/3-4/docs/view-specs/view-spec or add capybara to your Gemfile and use the capybara matchers: https://gist.github.com/them0nk/2166525

Ruby on Rails App Testing with Rspec and Capybara

when i create a feature test for my application get the following error:
ActionController::RoutingError:
No route matches [GET] "/example"
My application uses subdomains and sub-applications(engines/modules) within these subdomains. Now when i set for Capybara the app_host or default_host through an feature_subdomain_helper like
Capybara.app_host = "example.lvh.me" or
Capybara.default_host = "example.lvh.me"
and into my rails_helper.rb i add the following code line
config.extend SubdomainHelpers, type: :feature
I get the same error. Now i think the configured subdomain are not considered by my feature test.
My Rspec Version is: 3.2
and Capybara Version is: 2.4.4
My sample feature test looks like:
require 'rails_helper'
feature 'Example Page' do
scenario 'visit example page' do
visit "/example"
expect(page).to have_content 'Example'
end
end
Have someone an idea what i do wrong?
Edit:
Mainapp routes:
constraints(Subdomain) do
mount Example::Engine => '/', as: 'example'
end
Engine routes:
Example::Engine.routes.draw do
scope '/example', nav_scope: 'example' do
end
end
The names of Capybara.default_host and Capybara.app_host are slightly misleading since they both need to be set as URLs to function properly
Capybara.default_host = "http://example.lvh.me"
If that doesn't fix your issue check rake routes and make sure the action you think is mounted at '/example' really is.

RSpec/Capybara "expect(page).to have_content" test fails while content is in page.html

I am writing a Rails app applying BDD using RSpec & Capybara. One of my tests continues to fail. The goal of the test is to check whether for each Machine record displayed on the index page, clicking on the edit link, results in visualising the details edit page. When I run my application, this functionality works. So I guess, there's something wrong with my RSpec scenario.
Here's the failing test:
Failures:
1) existing machines have a link to an edit form
Failure/Error: expect(page).to have_content(#existing_machine.model)
expected to find text "RX22" in "Toggle navigation uXbridge Catalogue Settings Brands Machine Types Machine Groups Repair States Titles User Signed in as john#example.com Sign out Machine details Brand TORO Model Machine type ZITMAAIER Description Engine Purchase Price Unit Price VAT Minimal Stock Current Stock Warehouse Location"
# ./spec/features/machine_spec.rb:50:in `block (2 levels) in <top (required)>'
Here's the code of the test:
RSpec.feature 'existing machines' do
before do
#john = User.create!(email: 'john#example.com', password: 'password')
login_as #john
brand = Brand.create!(name: 'TORO')
machinegroup = Machinegroup.create!(name: 'GAZON')
machinetype = Machinetype.create!(name: 'ZITMAAIER', machinegroup_id: machinegroup.id)
#existing_machine = Machine.create!(brand_id: brand.id, model: 'RX22', machinetype_id: machinetype.id, description: 'fantastic machine', engine: '100PK' )
end
scenario 'have a link to an edit form' do
visit '/machines'
find("a[href='/machines/#{#existing_machine.id}/edit']").click
expect(page).to have_content('Machine details')
expect(page).to have_content(#existing_machine.model)
end
end
When debugging the scenario, the #existing_machine object seems correctly populated through the .create!() method in the before do block.
screenshot of debug window in IDE
When inspecting the page.html in the debugger, I do see the "RX22" string appearing.
screenshot of page.html inspection
So why does RSpec/Capybara not see the same content when executing the expect(page).to have_content(#existing_machine.model)?
RX22 is the value of an input element not text content so you need to check for it differently. Something like
expect(page).to have_field('Model', with: 'RX22')
should work

Capybara Poltergeist feature test failing on CI but passes locally

I'm having weird issues with some of my feature tests using Capybara with poltergeist driver.
The test should perform a simple checkout in my online shop.
They all pass fine on my local MacBook as well as on an Ubuntu vagrant box. However on CI services like Codeship, Wercker or Semaphore they fail with the very same error.
My spec:
require 'rails_helper'
describe 'Checkout' do
let!(:product) { FactoryGirl.create(:product) }
it 'checks out via CreditCard', js: true do
visit products_path
expect(page.body).to have_link('Test Product 1')
click_link('Test Product 1')
#rest of spec ommitted
end
end
The error I get on CI is:
2) Checkout checks out via CreditCard
Failure/Error: click_link('Test Product 1')
Capybara::ElementNotFound:
Unable to find link "Test Product 1"
To me this is super weird, as the first expectation 'expect(page.body).to have_link('Test Product 1')' seems to pass but then it fails on the next step where it should actually click the link it just assured to be present on the page?
I then reconfigured poltergeist driver as follows to gather more debug information.
Snippet of rails_helper.rb:
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, {js_errors: false,
#inspector: true,
phantomjs_logger: Rails.logger,
logger: nil,
phantomjs_options: ['--debug=no', '--load-images=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1'],
debug: true
})
end
Capybara.server_port = 3003
Capybara.app_host = 'http://application-test.lvh.me:3003' # lvh.me always resolves to 127.0.0.1
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = :poltergeist
Capybara.default_wait_time = 5
Now I can see on CI console that the test successfully visits my products_path and the expected html page (including my the link it should click) is being returned.
I removed the rest of the HTML response to make it more readable:
{"name"=>"visit", "args"=>["http://application-test.lvh.me:3003/products"]}
{"response"=>{"status"=>"success"}}
{"name"=>"body", "args"=>[]}
{"response"=>"--- snip --- <div class=\"info\">\n<a class=\"name color-pomegranate\" href=\"/en/products/6\">\nTest Product 1\n</a>\n850,00 \n</div> --- snap ---"}
{"name"=>"find", "args"=>[:xpath, ".//a[./#href][(((./#id = 'Test Product 1' or normalize-space(string(.)) = 'Test Product 1') or ./#title = 'Test Product 1') or .//img[./#alt = 'Test Product 1'])]"]}
{"response"=>{"page_id"=>4, "ids"=>[0]}}
{"name"=>"visible", "args"=>[4, 0]}
{"response"=>false}
{"name"=>"find", "args"=>[:xpath, ".//a[./#href][(((./#id = 'Test Product 1' or contains(normalize-space(string(.)), 'Test Product 1')) or contains(./#title, 'Test Product 1')) or .//img[contains(./#alt, 'Test Product 1')])]"]}
{"response"=>{"page_id"=>4, "ids"=>[1]}}
{"name"=>"visible", "args"=>[4, 1]}
{"response"=>false}
The last two find actions repeat until Capybara reaches its timeout, then the test fails.
I double checked the xpath Capybara uses via some online xpath validators, but as expected it matches the HTML link.
I also used capybara-screenshot gem to dump the HTML body on failure and the link in question is also present.
So why is the test still failing?
Is there any race condition that I'm not aware of? Why is it passing locally but on none of the CI services?
Here are my gem version:
capybara (2.4.4)
capybara-screenshot (1.0.3)
database_cleaner (1.3.0)
factory_girl (4.5.0)
factory_girl_rails (4.5.0)
poltergeist (1.5.1)
rails (4.1.8)
rspec (3.1.0)
rspec-rails (3.1.0)
and phantomjs 1.9.7
While I can't reproduce this, I remember having this problem before. I believe your line:
expect(page.body).to have_link('Test Product 1')
is passing because the link is literally on the body of the html page, even though it may be hidden due to CSS or JS behavior. However, the line:
click_link('Test Product 1')
definitely checks for visibility before clicking the link. You should check your spec_helper.rb configurations to make sure:
Capybara.ignore_hidden_elements = true
is present, so that the first line wouldn't pass. I think I also had to change the first line I mentioned to:
# Change page.body to page, to look at the rendered page, not the literal one
expect(page).to have_link('Test Product 1')
Once you do this, the first line blocks the thread and waits until the link becomes visible. Then the rest of the test will pass.
Hope this solves it.

Read MS Word .doc file with ruby and win32ole

I'm trying to read .doc file with ruby, I use win32ole library.
IT my code:
require 'win32ole'
class DocParser
def initialize
#content = ''
end
def read_file file_path
begin
word = WIN32OLE.connect( 'Word.Application' )
doc = word.activedocument
rescue
word = WIN32OLE.new( 'Word.Application' )
doc = word.documents.open( file_path )
end
word.visible = false
doc.sentences.each{ |x| #content = #content + x.text }
word.quit
#content
end
end
I kick off doc reading with DocParser.new.read_file('path/file.doc')
When I run this using rails c - I don't have any problems, it's working fine.
But when I run it using rails (e.g. after button click), once in a while (every 3-4 time) this code crashes with error:
WIN32OLERuntimeError (failed to create WIN32OLE object from `Word.Application'
HRESULT error code:0x800401f0
CoInitialize has not been called.):
lib/file_parsers/doc_parser.rb:14:in `initialize'
lib/file_parsers/doc_parser.rb:14:in `new'
lib/file_parsers/doc_parser.rb:14:in `rescue in read_file'
lib/file_parsers/doc_parser.rb:10:in `read_file'
lib/search_engine.rb:10:in `block in search'
lib/search_engine.rb:43:in `block in each_file_in'
lib/search_engine.rb:42:in `each_file_in'
lib/search_engine.rb:8:in `search'
app/controllers/home_controller.rb:9:in `search'
Rendered c:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.0ms)
Rendered c:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb (2.0ms)
Rendered c:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb (2.0ms)
Rendered c:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb (56.0ms)
Aditionaly, this code read doc file successfully, but RAILS CRASHES AFTER A FEW SECONDS:
look at this gist
What is my problem? How can I fix it?
Please, help!
Don't know the difference between rails c and rails, so I'll give some random advise.
First, it is a bad idea to run this in a webserver, each time Word is run on the server, so what happens if multiple users start using this at the same time ?
You'd better convert your .doc files to another format first like .rtf or .docx (a batch conversion ?) and then use other gems that don't require Word itself.
If you keep it like this, consider to not close word (remove the word.quit) buit only close the document itself, the instance will be picked up the next time by the WIN32OLE.connect
While testing you'de better keep word visible so that you can better see what is happening (errors ?).
I notice your path uses forward slashes while in this case backslashes are needed but since your code runs a few times before the error i suppose that is not the problem.
Hope this helps.
I upgrade my ruby from 1.9.3 to 2.0.0.
Now rails doesn't crashes and I have not problems with win23ole and reading old version MS Word documents.
I guess the problem was in memory usage - cause new ruby (>2.0.0) use new Garbage Collector.