System tests config: RSpec + Cuprite

Setup rspec with Cuprite driver for Capybara, using the "Evil Martians" configuration (slightly updated for Rails 7.0.3)
Icons/chart bar
Used 89 times
Created by
A Alessandro Fazzi

Usage
This is tested on a `rails new` fresh install.
At the end of the setup the script will auto-test itself.

In order to use the config just `require 'system_helper'` on top of your system tests instead of the usual 'rails_helper' (which will remain untouched).

The configuration is an updated version of the one narrated here https://evilmartians.com/chronicles/system-of-a-test-setting-up-end-to-end-rails-testing (thanks!).

The template does not remove

gem "selenium-webdriver"
gem "webdrivers"

from the default `Gemfile`, thus it's up to you.

Run this command in your Rails app directory in the terminal:

rails app:template LOCATION="https://railsbytes.com/script/zams23"
Template Source

Review the code before running this template on your machine.

# frozen_string_literal: true

unless File.read('Gemfile').match?(/gem ['"]cuprite/)
  gem 'cuprite', group: 'test'

  Bundler.with_unbundled_env do
    run 'bundle install'
  end
end

unless File.read('Gemfile').match?(/gem ['"]rspec-rails/)
  gem 'rspec-rails', groups: %w[development test]
  rails_command 'generate rspec:install'

  Bundler.with_unbundled_env do
    run 'bundle install'
  end
end

create_file 'spec/system/support/better_rails_system_tests.rb' do
  <<~'RUBY'
    # frozen_string_literal: true

    module BetterRailsSystemTests
      # Use our `Capybara.save_path` to store screenshots with other capybara artifacts
      # (Rails screenshots path is not configurable https://github.com/rails/rails/blob/49baf092439fc74fc3377b12e3334c3dd9d0752f/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb#L79)
      def absolute_image_path
        Rails.root.join("#{Capybara.save_path}/screenshots/#{image_name}.png")
      end

      # Make failure screenshots compatible with multi-session setup.
      # That's where we use Capybara.last_used_session introduced before.
      def take_screenshot
        return super unless Capybara.last_used_session

        Capybara.using_session(Capybara.last_used_session) { super }
      end
    end

    RSpec.configure do |config|
      config.include BetterRailsSystemTests, type: :system
      config.include Rails.application.routes.url_helpers

      # Make urls in mailers contain the correct server host.
      # It's required for testing links in emails (e.g., via capybara-email).
      config.around(:each, type: :system) do |ex|
        was_host = Rails.application.default_url_options[:host]
        Rails.application.default_url_options[:host] = Capybara.server_host
        ex.run
        Rails.application.default_url_options[:host] = was_host
      end
    end
  RUBY
end

create_file 'spec/system/support/capybara_setup.rb' do
  <<~'RUBY'
    # frozen_string_literal: true

    # Usually, especially when using Selenium, developers tend to increase the max wait time.
    # With Cuprite, there is no need for that.
    # We use a Capybara default value here explicitly.
    Capybara.default_max_wait_time = 2

    # Normalize whitespaces when using `has_text?` and similar matchers,
    # i.e., ignore newlines, trailing spaces, etc.
    # That makes tests less dependent on slightly UI changes.
    Capybara.default_normalize_ws = true

    # Where to store system tests artifacts (e.g. screenshots, downloaded files, etc.).
    # It could be useful to be able to configure this path from the outside (e.g., on CI).
    Capybara.save_path = ENV.fetch('CAPYBARA_ARTIFACTS', './tmp/capybara')

    # The Capybara.using_session allows you to manipulate a different browser session, and thus,
    # multiple independent sessions within a single test scenario. That’s especially useful for
    # testing real-time features, e.g., something with WebSocket.
    #
    # This patch tracks the name of the last session used. We’re going to use this information to
    # support taking failure screenshots in multi-session tests.
    Capybara.singleton_class.prepend(Module.new do
      attr_accessor :last_used_session

      def using_session(name, &block)
        self.last_used_session = name
        super
      ensure
        self.last_used_session = nil
      end
    end)
  RUBY
end

create_file 'spec/system/support/cuprite_setup.rb' do
  <<~'RUBY'
    # frozen_string_literal: true

    # First, load Cuprite Capybara integration
    require 'capybara/cuprite'

    # Configure Capybara to use :cuprite driver by default
    Capybara.default_driver = Capybara.javascript_driver = :cuprite

    module CupriteHelpers
      # Drop #pause anywhere in a test to stop the execution.
      # Useful when you want to checkout the contents of a web page in the middle of a test
      # running in a headful mode.
      def pause
        page.driver.pause
      end

      # Drop #debug anywhere in a test to open a Chrome inspector and pause the execution
      def debug(*args)
        page.driver.debug(*args)
      end
    end

    RSpec.configure do |config|
      config.include CupriteHelpers, type: :system

      # Make sure this hook runs before others
      config.prepend_before(:each, type: :system) do
        # Use JS driver always
        driven_by :cuprite, using: :chrome, options: {
          window_size: [1200, 800],
          # See additional options for Dockerized environment in the respective section of this article
          browser_options: {},
          # Increase Chrome startup wait time (required for stable CI builds)
          process_timeout: 10,
          # Enable debugging capabilities
          inspector: ENV['INSPECTOR'],
          # Allow running Chrome in a headful mode by setting HEADLESS env
          # var to a falsey value
          headless: !ENV['HEADLESS'].in?(%w[n 0 no false])
        }
      end
    end
  RUBY
end

create_file 'spec/system/support/precompile_assets.rb' do
  <<~"RUBY"
    # frozen_string_literal: true

    require 'rake'

    #{Rails.application.class}.load_tasks

    RSpec.configure do |config|
      # Skip assets precompilcation if we exclude system specs.
      # For example, you can run all non-system tests via the following command:
      #
      #    rspec --tag ~type:system
      #
      # In this case, we don't need to precompile assets.
      next if config.filter.opposite.rules[:type] == 'system' || config.exclude_pattern.match?(%r{spec/system})

      config.before(:suite) do
        Rake::Task['assets:precompile'].invoke
      end
    end
  RUBY
end

create_file 'spec/system_helper.rb' do
  <<~'RUBY'
    # frozen_string_literal: true

    # https://evilmartians.com/chronicles/system-of-a-test-setting-up-end-to-end-rails-testing

    # Load general RSpec Rails configuration
    require 'rails_helper'

    # Load configuration files and helpers
    Dir[File.join(__dir__, 'system/support/**/*.rb')].sort.each { |file| require file }
  RUBY
end

create_file 'spec/system/cuprite_driver_spec.rb' do
  <<~'RUBY'
    require 'system_helper'

    RSpec.describe 'Cuprite driver', type: :system do
      it 'works' do
        visit 'https://example.com:443'
        sleep 3
        within('h1') do
          expect(page).to have_content('Example Domain')
        end
      end
    end
  RUBY
end

run 'HEADLESS=false bundle exec rspec spec/system/cuprite_driver_spec.rb'

run 'rm -f spec/system/cuprite_driver_spec.rb' if yes?(
  'We used spec/system/cuprite_driver_spec.rb just for testing the new setup. '\
  'Would you like to delete it?'
)
Comments

Sign up or Login to leave a comment.