Capybara
Configuration
Capybara.register_driver :selenium_chrome do |app| options = Selenium::WebDriver::Chrome::Options.new options.add_argument("--headless=new") Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end Capybara.javascript_driver = :selenium_chrome
The above shows how to configure the JavaScript feature tests to use a headless Google Chrome to automate the browser. Headless means that the web browser will be running in the background. Sometimes I use the term headed to refer to a web browser that has an open GUI, but I may have made that word up.
Example: debugging step definitions
# Saves a screenshot to tmp/capybara Then "screenshot" do @i ||= 0 @i += 1 page.save_screenshot("screenshot#{@i}.png", full: true) puts "Page screenshot saved to screenshot#{@i}.png" end Then "show JavaScript console" do puts page.driver.browser.logs.get(:browser) end # rubocop:disable Lint/Debugger Then "pry" do binding.pry end # rubocop:enable Lint/Debugger
This is some debugging code used to debug test code. I'm not a big believer in test code test code (code being used to test test code). The ||= is a null-coalescing assignment that assigns the instance variable here @i to 0 if @i is nil. It saves screenshots to anki_books/tmp/capybara.
As an experiment to demonstrate the dependency on these debugging steps to Selenium WebDriver, I added Then screenshot to a feature test that had the @javascript tag, removed that tag, and executed the test:
$ bundle exec cucumber features/tests/books/dragging_basic_notes_between_articles.feature Using the default profile... # Anki Books, a note-taking app to organize knowledge, # is licensed under the GNU Affero General Public License, version 3 # Copyright (C) 2023 Kyle Rego Feature: Creating an article Background: Adding an article to a book # features/tests/books/dragging_basic_notes_between_articles.feature:7 Given there is a user "test_user", email "test@example.com", and password "1234asdf!!!!" # features/step_definitions/user_steps.rb:16 And the user "test_user" has a book called "My first book" # features/step_definitions/books_steps.rb:7 Then screenshot # features/step_definitions/debugging_steps.rb:8 Capybara::Driver::Base#save_screenshot (Capybara::NotSupportedByDriverError) <internal:kernel>:90:in `tap' ./features/step_definitions/debugging_steps.rb:11:in `"screenshot"' features/tests/books/dragging_basic_notes_between_articles.feature:10:in `screenshot' And the book "My first book" has the article "test article 1" # features/step_definitions/article_steps.rb:10 And the article "test article 1" has 2 basic notes # features/step_definitions/article_steps.rb:15 And the book "My first book" has the article "test article 2" # features/step_definitions/article_steps.rb:10 And the article "test article 2" has 2 basic notes # features/step_definitions/article_steps.rb:15 And I am logged in as the user "test_user" with password: "1234asdf!!!!" # features/step_definitions/user_steps.rb:7 And I click the "Books" link # features/step_definitions/shared/link_steps.rb:7 And I click the last "My first book" link # features/step_definitions/shared/link_steps.rb:12 And I click the "My first book" link # features/step_definitions/shared/link_steps.rb:7
With :rack_test driver that does not attempt to execute JavaScript, capybara throws an exception called Capybara::NotSupportedByDriver.
Aside on Ruby constants
If the identifier in a Ruby program starts with a capital letter, it is a constant. Looking at Capybara::NotSupportedByDriver, Capybara is a module being used as a namespace for a class called NotSupportedByDriver. Modules and classes in Ruby are constants. It is common for a gem to namespace all of its API inside a module with a name closely related to the gem name. For example, the Anki Record gem uses a namespace AnkiRecord. If we do a quick git clone git@github.com:teamcapybara/capybara.git, cd capybara, and code . to open it in Visual Studio Code, we can see some related code in capybara.rb:
# (... means I omitted code) module Capybara ... class NotSupportedByDriverError < CapybaraError; end ... end
Example: a step definition that uses a regular expression
Regular expressions are pretty hard so I will try to sprinkle some examples throughout. I wouldn't spend too long trying to understand this if it makes no sense.
This step definition from capybara_steps.rb in capybara is similar to an example you will also find in Anki Books:
When(/^I visit the (?:root|home) page$/) do visit('/') end
Regular expressions in Ruby can be created using the Regexp.new constructor and a few different literal syntaxes (/pat/ and %r{pat} are examples of Ruby regex literals). The ^ matches the beginning of the line, and $ matches the end of the line. This is different from using \A and \z which match the start and end of a Ruby string, respectively. The distinction could be important because $ could be exploited as an attack vector (not here though, this step definition that is just part of test code would never be an attack point on any app). The ?: in the parentheses has the effect of making the parentheses be only used for grouping, and not capturing. This just relates to how the parentheses has multiple purposes in regular expressions and here it's only being used to group this part where the | is saying it can match either root or home. This regular expression would match either of these strings: "I visit the root page" and "I visit the home page."
Article notes
What is the optional argument given to the RSpec it method to switch to the Capybara.javascript_driver (:selenium by default)?
js: true
Does the Capybara DSL vary with the specific browser and headless driver being used?
No
What is the fast, but limited driver that is the default used by Capybara and does not support JavaScript?
:rack_test
What is the line of code to make Capybara use :selenium as the default driver instead of :rack_test?
Capybara.default_driver = :selenium