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)