Capybara/Selenium/Chrome: test passes only when calling save_screenshot - ruby-on-rails

I'm in the process of migrating from Poltergeist to headless Chrome.
Gemfile:
group :test do
# Capybara - Headless, JavaScript-executing browser for Selenium
gem 'selenium-webdriver' # Selenium webdriver (needed to use Chrome driver)
gem 'webdrivers', '~> 4.0', require: false # Run Selenium tests more easily with automatic installation and updates for all supported webdrivers.
gem 'capybara-screenshot' # Automatically save screen shots when a scenario fails
...
end
spec/support/capybara.rb:
Capybara.javascript_driver = :selenium_chrome_headless
I have spec which tests whether focusing an element applies a CSS class to an element.
it 'shows the fullscreen toggler on focus', js: true do
visit new_user_path
page.execute_script("$('#user_about').focus()")
expect(page).to have_css '.textarea-fullscreenizer-toggler'
end
With Poltergeist it passed. With :selenium_chrome_headless it does not:
expected to find visible css ".textarea-fullscreenizer-toggler" within #<Capybara::Node::Element tag="div" path="/HTML/BODY[1]/MAIN[1]/DIV[1]/DIV[1]/FORM[1]/FIELDSET[1]/DIV[1]/DIV[1]/DIV[5]"> but there were no matches. Also found "", which matched the selector but not all filters.
With :selenium_chrome though, it passes! So it seems that the headless and non-headless driver do different things! This is pretty uncomfortable.
Interestingly, when putting a save_screenshot after focusing the element, the spec passes with headless chrome, too!
What can I do here? I'm on the right track? Should I try https://github.com/twalpole/apparition driver instead?

You can also try something like this for headless chrome in the helper capybara.rb file.
require "selenium/webdriver"
Capybara.register_driver :chrome do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome)
end
Capybara.register_driver :headless_chrome do |app|
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
chromeOptions: { args: %w(headless disable-gpu) }
)
Capybara::Selenium::Driver.new app,
browser: :chrome,
desired_capabilities: capabilities
end
Capybara.javascript_driver = :headless_chrome
This worked for me. Might work for you as well.

Seems like a timing issue to me. My guess would be that the jQuery call you use to trigger focus takes some time while the matcher is already executed. However, since it finds an element, which is not yet visible though, it raises the error. Inserting the screenshot may consume just enough time to have JavaScript evaluated your command.
Have you tried to explicitly match for a visible element like expect(page).to have_selector('.textarea-fullscreenizer-toggler', visible: true)?
Also, the focus trigger could also be done like that: find('#user_about').trigger('focus'). This may also resolve the issue since it returns after it triggered the event.

The error you provided is obviously not being returned for the code as shown, since the code you've provided isn't scoping anything or checking for text. In the future it would be much better if the error matched the code. That being said the answer below is a guess based on the info available.
The issue is most likely that your JS execution is happening before the page has fully loaded and therefore the page is resetting focus. This can happen because visit does not guarantee the page is fully loaded/stable when it returns. To synchronize that you need an expectation before the execute_script call that checks for something on the page that would indicate the page is loaded/stable.
visit new_user_path
expect(page).to have_css('input#first_input:focus') # wait until the initially focused element is loaded/displayed/focused
page.execute_script("$('#user_about').focus()")
Note that using execute_script is generally a bad idea in tests though since it's not doing what a user would - you'd be much better off having Capybara do whatever actions a user would perform to make the element you care about focused.

Related

Error while using cucumber with rails for automated testing

I'm starting in the automated testing area and am having the error below when I am running one of my tests. The test would be to fill in a datetime field, of type input, with the start time that the machine stopped. I'm running Cucumber with Capybara on a system written in Ruby on Rails and Ext Js.
Error: wrong number of arguments (given 1, expected 0) (ArgumentError)
Bellow is my env.rb for the project:
require 'capybara'
require 'capybara/cucumber'
require 'report_builder'
require 'selenium-webdriver'
require 'webdrivers/chromedriver'
Capybara.javascript_driver = :webkit
Capybara.configure do |config|
config.default_driver = :selenium_chrome
config.default_max_wait_time=10
config.app_host='http://localhost:3000/'
end
My exemple.feature is like:
Scenario: start of the machine stop
Given that I accessed the system
And marked 'Yes' in 'Did the machine stop?'
And I fill the start of the machine stop with '2020-05-12 16:00:00' #UTC
When saving the form
Then the success message should appear
My steps.rb is something like below:
And("I fill the start of the machine stop with {string}") do
#machine_stop = machine_stop
#MD.machine_stop_start(#machine_stop)
end
My page.rb:
class Machine_downtime
include Capybara::DSL
def machine_stop_start
find('input[id="datetimefield-1312-inputEl"]').click
end
end
I've done a Google search to find possible solutions but I haven't found any similar cases. I tried to look at the Cucumber documentation but I didn't find anything specific to my problem or Rails.
I can stop using a strings in the step and use a table, but I have more scenarios in this case and I would like to continue using strings, for easier maintenance and to avoid having to change more than 100 test scenarios.
I appreciate anyone who can help me with the problem.
P.s. Ruby on Rails runs on a different project.
Update:
I insert in the machine_stop_start method the variable I created in the step and the error no longer occurs.
class Machine_downtime
include Capybara::DSL
def machine_stop_start (machine_stop)
find('input[id="datetimefield-1312-inputEl"]').click
end
end
Now I am able to click on the desired field but it is not being filled with the desired information.
I will try to find what I may be doing wrong.

Capybara click_link works wrong

I have such bug. Here is my code:
it "shows places sorted by date of creation" do
click_button( I18n.t("models.places.actions.index.sort_by"))
click_link(I18n.t("models.places.actions.index.date_of_creation"))
sorted_places_names = places.map(&:name).reverse
link_names = all("a.place-link").map(&:text)
expect(link_names).to eq(sorted_places_names)
end
And my problem is that click link here must to send params:
"?by_created_at: true" and controller response with sorted places by date of creation, in descendant order.
My problem is when capybara clicks on this link, GET request have only path, without params needed.I`m using poltergeist here.
Also I have such test:
it "shows orders today" do
today_order.customer.reputations << create(:reputation, place: place)
visit place_statistics_loyalty_path(place)
click_link(I18n.t("statistics.loyalty.today"))
expect(page).to have_selector("#order_#{today_order.id}")
end
It tests out similar behaviour. And it works properly, but here I am not using js.
Is it javascript driver problem ?
Thanks. Sorry for bad text, it`s my first question.
What you're looking to do is use Poltergeist's "send_key" method as shown here in the documentation:
https://github.com/teampoltergeist/poltergeist#sending-keys
Setting the keys using that method and then proceeding with clicking the link should work.
Are you sure you're actually using poltergeist for that test since there is no js: true or driver: :poltergeist metadata on it? If it was not using poltergeist and actually just using the rack_test driver then no JS would get excuted and you would likely see the behavior you're seeing.
If you are actually using the poltergeist driver then it's possible you have a race condition, due to click_button being asynchronous, which could cause the by_created_at parameter not to be set before the link click actually happens. You can test if that is the issue by putting a sleep 2 between the click_button and click_link calls

Ruby on Rails - RSpec Javascript Test with Capybara (new to RoR)

New to Ruby, Rails and TDD. I'm using RSpec with Capybara and Capybara webkit.
Trying to test if a div element exists on a page.
Test Code:
require 'spec_helper'
describe "Login module" do
before do
visit root_path
end
it "should have a module container with id mLogin" do
page.should have_css('div#mLogin')
end
it "should have a module container with id mLogin", :js => true do
page.evaluate_script('$("div#mLogin").attr("id")').should eq "mLogin"
end
end
The first test passes but the second test fails with:
Login module should have a module container with id mLogin
Failure/Error: page.evaluate_script('$("div#mLogin").attr("id")').should eq "mLogin"
expected: "mLogin"
got: nil
Ran the JS in browser dev tools and get "mLogin" rather than nil.
Any ideas? Thanks.
find('div#mLogin')[:id].should eq 'mLogin'
See this from doc:
#evaluate_script
Evaluate the given JavaScript and return the result. Be careful when using this with scripts that return complex objects, such as jQuery statements. execute_script might be a better alternative.
evaluate_script always return nil, as far as I remember.
Anyway, your second test seems like is testing if capybara works, because your first test is enough.
One likely problem is that the have_css matcher supports Capybara's synchronization feature. If the selector isn't found right away, it will wait and retry until it is found or a timeout elapses.
There's more documentation about this at http://rubydoc.info/github/jnicklas/capybara#Asynchronous_JavaScript__Ajax_and_friends_
On the other hand, evaluate_script runs immediately. Since this is the first thing you do after visiting the page, there's a race condition: it's possible that it executes this script before the page has finished loading.
You can fix this by trying to find an element on the page that won't appear until the page is loaded before you call evaluate_script.
Alternately, you can wrap your call in a call to synchronize to explicitly retry, but this is not generally recommended. For situations like this, you're much better off using Capybara's built-in matchers. The evaluate_script method should only be used as a last resort when there is no built-in way to accomplish what you need to do, and you need to take a lot of care to avoid race conditions.

With Capybara, how can I tell whether the driver currently being used supports JavaScript?

In my Rails application, I have a set of cucumber acceptance tests that test various pages of my application. With cucumber, tagging a specific test (scenario) with #javascript causes that scenario to run using a JavaScript driver instead of a simpler driver that does not support JavaScript.
Is there an easy way for my tests to determine whether they are currently being run with a driver that supports JavaScript or one that doesn't? I want this so that I can make my tests behave slightly differently if they are being run with JavaScript enabled.
In case anyone's interested, I took a look at the documentation for Capybara and found another possible solution:
if Capybara.current_driver == Capybara.javascript_driver
# Supports JavaScript
else
# Doesn't support JavaScript
end
It's kind of obnoxious, but this is what I landed on. Think I got it from a gist somewhere, sorry I lost the link to it but at least it's pretty straightforward:
def javascript_test?
[:selenium, :webkit, :chrome, :poltergeist].include?(Capybara.current_driver)
end
If you define (or require in your Gemfile) other js enabled drivers, you'd have to add them to this list.
Looks to me from this answer like the easiest way to do this is to set an instance variable in a Before hook:
Before('#javascript') do
#javascript = true
end
Then you can test if a scenario is tagged in step definitions by checking the value of that instance variable:
When /^I go to the homepage$/ do
if #javascript
...
else
...
end
end
I've tested this and it seems to work.
You could also simply probe for the capability and rescue any exception. This is probably the most precise and compatible approach:
def support_js?
return #support_js unless #support.nil?
#support_js = begin
page.evaluate_script("1 + 1")
true
rescue Capybara::NotSupportedByDriverError => _err
false
end
end

Why I get blank pages when using selenium/webkit with capybara?

I'm trying to test my cucumber scenarios with capybara and selenium but I get blank pages all the time.
I read this: Capybara + RSpec only sees blank pages in controller specs. Why?
But It's not exactly the same because that is with rspec.
My env.rb has:
Capybara.default_driver = :webkit
I have try to change it to :selenium but it's the same story.
Any help?
Why not use these drivers only for javascript, do you really need them as default driver?
Do you want to always use them?
It could just be a timeout issue..
If you want to set them for JavaScript only do this instead (env.rb):
Capybara.javascript_driver = :webkit
And decorate your scenarios with #javascript as such:
#javascript
Scenario: Customers List

Resources