Do you know Playwright?
Playwright is emerging as the new standard for front-end testing.
It is faster and more recent than Selenium, and its syntax is clearer than that of Capybara-DSL. See the discussion here: https://discuss.rubyonrails.org/t/rails-7-capybara-speedup-possible/87571.
Notice
Playwright can be used with the Capybara DSL, which is ideal for existing projects. Initially, I tried to keep the old tests in the Capybara DSL while writing the new ones in the Playwright DSL within the same project. However, this approach was unsuccessful, so ultimately I rewrote everything in Playwright.
As recommended, it is configured to create a new instance at the start of each run and open/close a new window for every test.
Playwright within Rails/RSpec
Derived from Docs
Setup
- Remove all Selenium-gems
- add gem
capybara
within grouptest
- add gem
playwright-ruby-client
within grouptest
-
npm i --save-dev playwright
(unlike docs do not install it bynpx i ...
because it would not install dependencies)
a File like playwright_helper.rb
(for a project with devise authentication)
require 'rails_helper' require 'capybara' require 'playwright' require 'support/playwright_helpers' require 'uri' RSpec.configure do |config| config.include Devise::Test::IntegrationHelpers, type: :system config.include PlaywrightHelpers, type: :system # Set default driver for system tests Capybara.default_driver = :null Capybara.javascript_driver = :null video_dir = '/tmp/rails-system-test-videos' config.before(:all, type: :system) do FileUtils.rm_rf(video_dir) FileUtils.mkdir_p(video_dir) @playwright = Playwright.create(playwright_cli_executable_path: './node_modules/.bin/playwright') @browser = @playwright.playwright.firefox.launch(headless: false) end config.after(:all, type: :system) do # Close browser and Playwright after all tests @browser&.close @playwright&.stop end # Define the null driver class CapybaraNullDriver < Capybara::Driver::Base def needs_server? true end end Capybara.register_driver(:null) { CapybaraNullDriver.new } config.around(type: :system) do |example| driven_by :null # Create a new context and page for each test @browser.new_context( record_video_dir: video_dir, baseURL: Capybara.current_session.server.base_url ) do |context| context.set_default_timeout(2000.0) @page = context.new_page # Capture Browser console logs @browser_logs = [] @page.on('console', lambda do |message| @browser_logs << { type: message.type, text: message.text } end) # LOGIN @current_user = FactoryBot.create :user_adm sign_in @current_user example.run # Save video only if the test fails if example.exception puts "file://#{@page.video.path}" else FileUtils.rm(@page.video.path) if @page.video&.path end end end end
support/playwright_helpers.rb
# Extends Playwright::Page with a convenience method to extract the URL path. Playwright::Page.class_eval do def path URI(url).path end end # Helper methods for Playwright in system specs. module PlaywrightHelpers # Retrieves browser logs, optionally filtered by log type and/or a matching pattern. # # @param log_type [String, Symbol, Array<String, Symbol>, nil] The log type(s) to filter by (e.g., 'error', 'warning'). # @param match [Regexp, String, nil] A pattern to filter log messages. # @return [Array<String>, nil] Filtered log messages or nil if no logs match. def browser_logs(log_type: nil, match: nil) log_types = Array(log_type).map(&:to_s).reject(&:empty?) logs = if log_types.any? @browser_logs.select { |log| log_types.include?(log[:type]) } .map { |log| log[:text] } else @browser_logs.map { |log| "[#{log[:type]}] #{log[:text]}" } end logs.select! { |log| log.match?(match) } if match logs unless logs.empty? end end
and a test:
require 'playwright_helper' RSpec.describe "pw", type: :system do it 'test' do @page.goto(root_path) @page.locator('body').wait_for(state: :visible) # => without the .wait_for the test would not fail without the body-tag end end
Here is a Code Example
Mainly importand docs, for writing tests, may be the API Coverage and Page class
Top comments (0)