Why does an `ajax:success' event not fire in Rails system test? - ruby-on-rails

I have a system test that fails because the proper ajax:success event is not being fired in the test environment. The event is correctly fired when testing manually in the browser.
Here's the link the user (and the test) clicks:
link_to 'Note', note_product_option_path(#product, option), remote: true, data: { note_link: true }
The JS (CoffeeScript) event, which takes the response and inserts into the body of the document:
NOTE_LINK = '[data-note-link="true"]'
$ ->
$('body').on 'ajax:success', NOTE_LINK, (e) ->
alert 'ajax:success'
$('.option-note').remove()
data = e.detail[2].response
$div = $('<div></div>').html(data)
.addClass('option-note')
$('body').append($div)
Here's the system testing setup:
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
include Devise::Test::IntegrationHelpers
Capybara.register_driver :headless_chrome do |app|
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('headless')
options.add_argument('window-size=1480x1680')
Capybara::Selenium::Driver.new(app, :browser => :chrome, :options => options)
end
end
In my system test:
visit product_path(option.product)
click_link 'Note'
assert_selector '.option-note', wait: 5
That assertion always fails, even though it always works when I test in the browser. The alert is never caught or fired by Capybara either, but always appears when manually checking. The test log shows that the proper JS is being rendered by the controller.
What am I missing here?

Check the browser console for any other JS errors and fix them. The big difference between the development and test environments is that the JS assets get concatenated into one file in the test environment which means an error in one JS can prevent JS from other files being run. In the development mode that doesn’t happen because each file is loaded separately so an error can only affect other code in the same file

Related

Getting " wrong argument type Fixnum (expected String) (TypeError)" while running the Cucumber tests whenever it encounters the Capybara function

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'

Increase poltergeist timeout for a specific capybara click_button call

I have a rails project, which I'm testing with rspec/capybara/poltergeist/phantomjs. I know I can increase the general poltergeist timeout with the general settings
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, timeout: 2.minutes)
end
But is there a way to increase the timeout for a specific request?
I have a page with a button (id=submit) which kicks off a longish (90-120 seconds) running process, before returning. I'm working on optimizing the back end to shorten the request time, but in the meanwhile, I want to increase the timeout for that specific request when testing, so something along the lines of
click_button 'submit', wait: 180
You can do
Capybara.using_wait_time(180) do
click_button 'submit'
end
Another thing you can do is
# capybara.rb
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, timeout: 30)
end
Capybara.register_driver :poltergeist_long do |app|
Capybara::Poltergeist::Driver.new(app, timeout: 180)
end
# wherever.rb
session = Capybara::Session.new(:poltergeist_long)
session.visit("http://thatlongwaittime.com")
Timeuouts for specific requests can be increased by increasing the value of default wait time which is generally configured in you env.rb file.
To understand this well lets take below mention code:
Cucumber file:
When Joe is on abc page
Then Joe clicks submit button
Step definition for clicking submit button:
Then(/^Then Joe clicks submit button$/) do
Capybara.default_wait_time = 120 // increasing the default wait time to 180 seconds
click_button('submit') // performing the action
Capybara.default_wait_time = DEFAULT_WAIT_TIME // reset the wait time to its default value after clicking submit button.
end
Note: The value of DEFAULT_WAIT_TIME can be configured in env.rb file
Hope this helps :)

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.

With Capybara, how do I switch to the new window for links with "_blank" targets?

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

How do I confirm a javascript popup with Capybara?

I've tried several examples found online, but with no luck. I am looking to confirm the confirm message of a delete link. The last attempt was the code below, but that resulted in an Capybara::NotSupportedByDriverError error.
def confirm_dialog
page.evaluate_script('window.confirm = function() { return true; }')
end
Adding an answer for those hitting this in 2016 and beyond. You can now use Capybara directly to accept a confirmation box. You do this by wrapping the code that causes the confirmation box to appear in the accept_confirm function.
accept_confirm do
click_link 'Destroy'
end
First of all switch to using Selenium as the driver by putting an #javascript tag in front of your scenario.
The following code in your cucumber step will then confirm the dialogue:
page.driver.browser.switch_to.alert.accept
# or
page.driver.browser.switch_to.alert.dismiss
# or
page.driver.browser.switch_to.alert.text
As #NobbZ said, this question has been asked and answered before here: How to test a confirm dialog with Cucumber?.
More selenium documentation available here too: http://code.google.com/p/selenium/wiki/RubyBindings#JavaScript_dialogs
for capybara-webkit:
page.driver.browser.accept_js_confirms
page.driver.browser.reject_js_confirms
which is still working, but the documentation says also:
page.driver.accept_js_confirms!
page.driver.accept_js_confirms!
See https://github.com/thoughtbot/capybara-webkit , search "accept_js_confirms"
I've had timing issues with browser dialogs in a CI environment so I'm polling for a dialog before accepting it:
def accept_browser_dialog
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
wait.until {
begin
page.driver.browser.switch_to.alert
true
rescue Selenium::WebDriver::Error::NoAlertPresentError
false
end
}
page.driver.browser.switch_to.alert.accept
end
I had to use a sleep in the webkit test since it would fail everynow and then otherwise.
Here is what I came up with after reading everyones posts:
if page.driver.class == Capybara::Selenium::Driver
page.driver.browser.switch_to.alert.accept
elsif page.driver.class == Capybara::Webkit::Driver
sleep 1 # prevent test from failing by waiting for popup
page.driver.browser.accept_js_confirms
else
raise "Unsupported driver"
end
try to add :js => true to your test.
RSpec’s metadata feature can be used to switch to a different driver.
Use :js => true to switch to the javascript driver, or provide a
:driver option to switch to one specific driver. For example:
it 'will use the default js driver' :js => true do
...
end
In Capybara its very simple to accept the model window. Even we can do the same in selenium but its little tough for people who are not aware about selenium.
page.accept_modal #This will accept the modal window
page.dismiss_modal #This will Reject/Dismiss the modal window
I would guess that you have to add selenium to your gem-file and configure it and capybara that capybara uses selenium as the driver.
I think also that How to test a confirm dialog with Cucumber? is very similar to your question, especially the accepted answer.

Resources