Skip to content

Instantly share code, notes, and snippets.

@tmaier
Created August 7, 2023 11:49
Show Gist options
  • Save tmaier/a31f190e8de61ba22e7267c4058420fa to your computer and use it in GitHub Desktop.
Save tmaier/a31f190e8de61ba22e7267c4058420fa to your computer and use it in GitHub Desktop.
Custom RSpec/Capybara matchers for data-testid

See my blog post at https://tobiasmaier.info:

UI testing plays a crucial role in ensuring that our applications work correctly and consistently. Traditionally, people either matched strings, referenced CSS classes or element IDs. But the landscape of UI testing is shifting, introducing a new and improved approach: data-testid.

This blog post explains how to use Test IDs with RSpec and Capybara, and introduces two new RSpec matchers (have_test_id and have_test_id_and_css) to simplify UI testing.

Source: https://tobiasmaier.info/posts/2023/06/19/rspec-capybara-data-testid.html

# frozen_string_literal: true
# File: spec/support/matchers/have_test_id.rb
module Matchers
# @!method have_test_id(test_id, on_element: '*')
# Matcher for testing the presence of an element with a specific test id on a specific HTML element.
# If on_element is not provided, the matcher will look for the test_id on any kind of element.
# @param [String] test_id The test id of the element you're looking for.
# @option on_element [String] The HTML element that should have the test_id.
# @return [Boolean] Whether or not the element with the test_id exists on the page.
RSpec::Matchers.define :have_test_id do |test_id, on_element: '*'|
match do |page|
@element_exists = page.has_css?("#{on_element}[#{Capybara.test_id}='#{test_id}']", match: :first)
end
match_when_negated do |page|
@element_does_not_exist = page.has_no_css?("#{on_element}[#{Capybara.test_id}='#{test_id}']", match: :first)
end
failure_message do |_page|
"expected to find #{on_element} element with test id '#{test_id}' but there were none."
end
failure_message_when_negated do |_page|
"expected not to find #{on_element} element with test id '#{test_id}' but it did."
end
end
end
# frozen_string_literal: true
# File: spec/support/matchers/have_test_id_and_css.rb
module Matchers
# @!method have_test_id_and_css(test_id, css, on_element: '*')
# Matcher for testing the presence of an element with a specific test id and specific CSS on a specific HTML element.
# If on_element is not provided, the matcher will look for the test_id on any kind of element.
# @param [String] test_id The test id of the element you're looking for.
# @param [String] css The CSS that the element with the test_id should have.
# @option on_element [String] The HTML element that should have the test_id.
# @return [Boolean] Whether or not the element with the test_id and CSS exists on the page.
RSpec::Matchers.define :have_test_id_and_css do |test_id, css, on_element: '*'|
match do |page|
test_id_css = "#{on_element}[#{Capybara.test_id}='#{test_id}']"
@base_element_exists = page.has_css?(test_id_css, match: :first)
@element_exists = @base_element_exists && page.has_css?("#{test_id_css}#{css}", match: :first)
end
match_when_negated do |page|
test_id_css = "#{on_element}[#{Capybara.test_id}='#{test_id}']"
@base_element_exists = page.has_css?(test_id_css, match: :first)
return true unless @base_element_exists
@element_exists = page.has_css?("#{test_id_css}#{css}", match: :first)
!@element_exists
end
failure_message do |_page|
if !@base_element_exists
"expected to find #{on_element} element with test id '#{test_id}' but there were none."
elsif !@element_exists
"expected #{on_element} element with test id '#{test_id}' to have css '#{css}', but it did not."
end
end
failure_message_when_negated do |_page|
if @base_element_exists
"expected not to find #{on_element} element with test id '#{test_id}' but it did."
elsif @element_exists
"expected #{on_element} element with test id '#{test_id}' not to have css '#{css}', but it did."
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment