Back to Repositories

Testing Chrome WebDriver Integration in Capybara

This test suite validates Chrome-specific functionality in Capybara, focusing on browser automation and WebDriver integration. It includes comprehensive testing of storage handling, timeout configurations, and form interactions specific to Chrome implementations.

Test Coverage Overview

The test suite provides extensive coverage of Chrome-specific Capybara implementations.

Key areas tested include:
  • Storage management (localStorage and sessionStorage)
  • Browser timeout configurations
  • Date and time input handling
  • Custom driver configurations
  • Browser log access and management

Implementation Analysis

The testing approach utilizes RSpec’s shared examples pattern for comprehensive driver validation. The suite implements custom Chrome WebDriver configurations, including headless mode support, download directory management, and storage clearing behaviors.

Framework-specific features include:
  • Dynamic capability version handling
  • Custom driver subclassing
  • Conditional test skipping based on platform and driver versions

Technical Details

Testing tools and configuration:
  • Selenium WebDriver with Chrome-specific options
  • RSpec test framework
  • Custom Capybara driver registrations
  • Configurable timeout settings
  • Environment-specific Chrome binary paths
  • Headless mode support

Best Practices Demonstrated

The test suite exemplifies several testing best practices for browser automation.

Notable practices include:
  • Modular test organization using shared examples
  • Comprehensive error handling
  • Platform-specific test conditioning
  • Clean session management
  • Explicit timeout configurations
  • Flexible driver configuration patterns

teamcapybara/capybara

spec/selenium_spec_chrome.rb

            
# frozen_string_literal: true

require 'spec_helper'
require 'selenium-webdriver'
require 'shared_selenium_session'
require 'shared_selenium_node'
require 'rspec/shared_spec_matchers'

CHROME_DRIVER = :selenium_chrome

Selenium::WebDriver::Chrome.path = '/usr/bin/google-chrome-beta' if ENV.fetch('CI', nil) && ENV.fetch('CHROME_BETA', nil)

Selenium::WebDriver.logger.ignore(:selenium_manager)

browser_options = if ENV['HEADLESS']
  Selenium::WebDriver::Options.chrome(args: ['--headless=new'])
else
  Selenium::WebDriver::Options.chrome
end

# Chromedriver 77 requires setting this for headless mode on linux
# Different versions of Chrome/selenium-webdriver require setting differently - jus set them all
browser_options.add_preference('download.default_directory', Capybara.save_path)
browser_options.add_preference(:download, default_directory: Capybara.save_path)

Capybara.register_driver :selenium_chrome do |app|
  version = Capybara::Selenium::Driver.load_selenium
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
  driver_options = { browser: :chrome, timeout: 30 }.tap do |opts|
    opts[options_key] = browser_options
  end

  Capybara::Selenium::Driver.new(app, **driver_options).tap do |driver|
    # Set download dir for Chrome < 77
    driver.browser.download_path = Capybara.save_path
  end
end

Capybara.register_driver :selenium_chrome_not_clear_storage do |app|
  version = Capybara::Selenium::Driver.load_selenium
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
  chrome_options = { browser: :chrome, clear_local_storage: false, clear_session_storage: false }.tap do |opts|
    opts[options_key] = browser_options
  end

  Capybara::Selenium::Driver.new(app, **chrome_options)
end

Capybara.register_driver :selenium_chrome_not_clear_session_storage do |app|
  version = Capybara::Selenium::Driver.load_selenium
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
  chrome_options = { browser: :chrome, clear_session_storage: false }.tap do |opts|
    opts[options_key] = browser_options
  end

  Capybara::Selenium::Driver.new(app, **chrome_options)
end

Capybara.register_driver :selenium_chrome_not_clear_local_storage do |app|
  version = Capybara::Selenium::Driver.load_selenium
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
  chrome_options = { browser: :chrome, clear_local_storage: false }.tap do |opts|
    opts[options_key] = browser_options
  end
  Capybara::Selenium::Driver.new(app, **chrome_options)
end

Capybara.register_driver :selenium_driver_subclass_with_chrome do |app|
  version = Capybara::Selenium::Driver.load_selenium
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
  subclass = Class.new(Capybara::Selenium::Driver)
  chrome_options = { browser: :chrome, timeout: 30 }.tap do |opts|
    opts[options_key] = browser_options
  end

  subclass.new(app, **chrome_options)
end

module TestSessions
  Chrome = Capybara::Session.new(CHROME_DRIVER, TestApp)
end

skipped_tests = %i[response_headers status_code trigger]

Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::Chrome) if ENV['CI']

Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_DRIVER.to_s, capybara_skip: skipped_tests do |example|
  case example.metadata[:full_description]
  when /#click_link can download a file$/
    skip 'Need to figure out testing of file downloading on windows platform' if Gem.win_platform?
  when /Capybara::Session selenium_chrome node #shadow_root should get visible text/
    pending "Selenium doesn't currently support getting visible text for shadow root elements"
  when /Capybara::Session selenium_chrome node #shadow_root/
    skip 'Not supported with this chromedriver version' if chromedriver_lt?('96.0', @session)
  end
end

RSpec.describe 'Capybara::Session with chrome' do
  include Capybara::SpecHelper
  ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples|
    include_examples examples, TestSessions::Chrome, CHROME_DRIVER
  end

  context 'storage' do
    describe '#reset!' do
      it 'clears storage by default' do
        session = TestSessions::Chrome
        session.visit('/with_js')
        session.find(:css, '#set-storage').click
        session.reset!
        session.visit('/with_js')
        expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty
        expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
      end

      it 'does not clear storage when false' do
        session = Capybara::Session.new(:selenium_chrome_not_clear_storage, TestApp)
        session.visit('/with_js')
        session.find(:css, '#set-storage').click
        session.reset!
        session.visit('/with_js')
        expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
        expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
      end

      it 'can not clear session storage' do
        session = Capybara::Session.new(:selenium_chrome_not_clear_session_storage, TestApp)
        session.visit('/with_js')
        session.find(:css, '#set-storage').click
        session.reset!
        session.visit('/with_js')
        expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty
        expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
      end

      it 'can not clear local storage' do
        session = Capybara::Session.new(:selenium_chrome_not_clear_local_storage, TestApp)
        session.visit('/with_js')
        session.find(:css, '#set-storage').click
        session.reset!
        session.visit('/with_js')
        expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
        expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
      end
    end
  end

  context 'timeout' do
    it 'sets the http client read timeout' do
      expect(TestSessions::Chrome.driver.browser.send(:bridge).http.read_timeout).to eq 30
    end
  end

  describe 'filling in Chrome-specific date and time fields with keystrokes' do
    let(:datetime) { Time.new(1983, 6, 19, 6, 30) }
    let(:session) { TestSessions::Chrome }

    before do
      session.visit('/form')
    end

    it 'should fill in a date input with a String' do
      session.fill_in('form_date', with: '06/19/1983')
      session.click_button('awesome')
      expect(Date.parse(extract_results(session)['date'])).to eq datetime.to_date
    end

    it 'should fill in a time input with a String' do
      session.fill_in('form_time', with: '06:30A')
      session.click_button('awesome')
      results = extract_results(session)['time']
      expect(Time.parse(results).strftime('%r')).to eq datetime.strftime('%r')
    end

    it 'should fill in a datetime input with a String' do
      session.fill_in('form_datetime', with: "06/19/1983\t06:30A")
      session.click_button('awesome')
      expect(Time.parse(extract_results(session)['datetime'])).to eq datetime
    end
  end

  describe 'using subclass of selenium driver' do
    it 'works' do
      session = Capybara::Session.new(:selenium_driver_subclass_with_chrome, TestApp)
      session.visit('/form')
      expect(session).to have_current_path('/form')
    end
  end

  describe 'log access' do
    let(:logs) do
      session.driver.browser.then do |chrome_driver|
        chrome_driver.respond_to?(:logs) ? chrome_driver : chrome_driver.manage
      end.logs
    end

    it 'does not error getting log types' do
      expect do
        logs.available_types
      end.not_to raise_error
    end

    it 'does not error when getting logs' do
      expect do
        logs.get(:browser)
      end.not_to raise_error
    end
  end
end