Add `Capybara.test_id` option defaulting to `data-test-id`

This commit is contained in:
Thomas Walpole 2018-07-05 15:29:24 -07:00
parent 9903dd2df4
commit 5b76480e57
12 changed files with 84 additions and 34 deletions

View File

@ -83,6 +83,7 @@ module Capybara
# [threadsafe = Boolean] Whether sessions can be configured individually (Default: false)
# [server = Symbol] The name of the registered server to use when running the app under test (Default: :webrick)
# [default_set_options = Hash] The default options passed to Node::set (Default: {})
# [test_id = Symbol/String/nil] Optional attribute to match locator aginst with builtin selectors along with id (Default: 'data-test-id')
#
# === DSL Options
#
@ -483,6 +484,7 @@ Capybara.configure do |config|
config.enable_aria_label = false
config.reuse_server = true
config.default_set_options = {}
config.test_id = 'data-test-id'
end
Capybara.register_driver :rack_test do |app|

View File

@ -7,11 +7,12 @@ module Capybara
class Config
extend Forwardable
OPTIONS = %i[app reuse_server threadsafe default_wait_time server default_driver javascript_driver].freeze
OPTIONS = %i[app reuse_server threadsafe default_wait_time server default_driver javascript_driver test_id].freeze
attr_accessor :app
attr_reader :reuse_server, :threadsafe
attr_reader :session_options
attr_reader :test_id
attr_writer :default_driver, :javascript_driver
SessionConfig::OPTIONS.each do |method|
@ -79,6 +80,18 @@ module Capybara
@javascript_driver || :selenium
end
##
#
# Set an attribue to be optionally matched against the locator for builtin selector types.
# This attribute will be checked by builtin selector types whenever id would normally be checked.
# If `nil` then it will be ignored.
#
# @params [String, Symbol, nil] id Name of the attribute to use as the test id
#
def test_id=(id)
@test_id = id&.to_sym
end
def deprecate(method, alternate_method, once = false)
@deprecation_notified ||= {}
warn "DEPRECATED: ##{method} is deprecated, please use ##{alternate_method} instead" unless once && @deprecation_notified[method]

View File

@ -27,13 +27,13 @@ module Capybara
##
#
# Finds a link by id, text or title and clicks it. Also looks at image
# Finds a link by id, Capybara.test_id attribute, text or title and clicks it. Also looks at image
# alt text inside the link.
#
# @macro waiting_behavior
#
# @overload click_link([locator], options)
# @param [String] locator text, id, title or nested image's alt attribute
# @param [String] locator text, id, Capybara.test_id attribute, title or nested image's alt attribute
# @param options See {Capybara::Node::Finders#find_link}
#
# @return [Capybara::Node::Element] The element clicked
@ -45,7 +45,7 @@ module Capybara
#
# Finds a button on the page and clicks it.
# This can be any \<input> element of type submit, reset, image, button or it can be a
# \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
# \<button> element. All buttons can be found by their id, Capybara.test_id attribute, value, or title. \<button> elements can also be found
# by their text content, and image \<input> elements by their alt attribute
#
# @macro waiting_behavior
@ -61,7 +61,7 @@ module Capybara
##
#
# Locate a text field or text area and fill it in with the given text
# The field can be found via its name, id or label text.
# The field can be found via its name, id, Capybara.test_id attribute, or label text.
#
# page.fill_in 'Name', with: 'Bob'
#
@ -170,7 +170,7 @@ module Capybara
# @macro waiting_behavior
#
# @param value [String] Which option to select
# @param from: [String] The id, name or label of the select box
# @param from: [String] The id, Capybara.test_id atrtribute, name or label of the select box
#
# @return [Capybara::Node::Element] The option element selected
def select(value = nil, from: nil, **options)
@ -194,7 +194,7 @@ module Capybara
# @macro waiting_behavior
#
# @param value [String] Which option to unselect
# @param from: [String] The id, name or label of the select box
# @param from: [String] The id, Capybara.test_id attribute, name or label of the select box
#
# @return [Capybara::Node::Element] The option element unselected
def unselect(value = nil, from: nil, **options)

View File

@ -89,7 +89,7 @@ module Capybara
# Find a form field on the page. The field can be found by its name, id or label text.
#
# @overload find_field([locator], **options)
# @param [String] locator name, id, placeholder or text of associated label element
# @param [String] locator name, id, Capybara.test_id attribute, placeholder or text of associated label element
#
# @macro waiting_behavior
#
@ -120,7 +120,7 @@ module Capybara
# Find a link on the page. The link can be found by its id or text.
#
# @overload find_link([locator], **options)
# @param [String] locator id, title, text, or alt of enclosed img element
# @param [String] locator id, Capybara.test_id attribute, title, text, or alt of enclosed img element
#
# @macro waiting_behavior
#
@ -139,11 +139,11 @@ module Capybara
#
# Find a button on the page.
# This can be any \<input> element of type submit, reset, image, button or it can be a
# \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
# \<button> element. All buttons can be found by their id, Capbyara.test_id attribute, value, or title. \<button> elements can also be found
# by their text content, and image \<input> elements by their alt attribute
#
# @overload find_button([locator], **options)
# @param [String] locator id, value, title, text content, alt of image
# @param [String] locator id, Capybara.test_id attribute, value, title, text content, alt of image
#
# @overload find_button(**options)
#

View File

@ -382,7 +382,7 @@ module Capybara
##
#
# Checks if the page or current node has a radio button or
# checkbox with the given label, value or id, that is currently
# checkbox with the given label, value, id, or Capybara.test_id attribute that is currently
# checked.
#
# @param [String] locator The label, name or id of a checked field
@ -395,7 +395,7 @@ module Capybara
##
#
# Checks if the page or current node has no radio button or
# checkbox with the given label, value or id, that is currently
# checkbox with the given label, value or id, or Capybara.test_id attribute that is currently
# checked.
#
# @param [String] locator The label, name or id of a checked field
@ -408,7 +408,7 @@ module Capybara
##
#
# Checks if the page or current node has a radio button or
# checkbox with the given label, value or id, that is currently
# checkbox with the given label, value or id, or Capybara.test_id attribute that is currently
# unchecked.
#
# @param [String] locator The label, name or id of an unchecked field
@ -421,7 +421,7 @@ module Capybara
##
#
# Checks if the page or current node has no radio button or
# checkbox with the given label, value or id, that is currently
# checkbox with the given label, value or id, or Capybara.test_id attribute that is currently
# unchecked.
#
# @param [String] locator The label, name or id of an unchecked field

View File

@ -69,8 +69,10 @@ end
Capybara.add_selector(:fieldset) do
xpath(:legend) do |locator, legend: nil, **_options|
locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]
locator_matchers |= XPath.attr(Capybara.test_id) == locator if Capybara.test_id
xpath = XPath.descendant(:fieldset)
xpath = xpath[(XPath.attr(:id) == locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
xpath = xpath[locator_matchers] unless locator.nil?
xpath = xpath[XPath.child(:legend)[XPath.string.n.is(legend)]] if legend
xpath
end
@ -101,6 +103,7 @@ Capybara.add_selector(:link) do
XPath.attr(:title).is(locator),
XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
matchers << XPath.attr(:'aria-label').is(locator) if enable_aria_label
matchers << XPath.attr(Capybara.test_id) == locator if Capybara.test_id
xpath = xpath[matchers.reduce(:|)]
end
@ -138,12 +141,13 @@ Capybara.add_selector(:button) do
unless locator.nil?
locator = locator.to_s
locator_matches = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
locator_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
locator_matchers = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
locator_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
locator_matchers |= XPath.attr(Capybara.test_id) == locator if Capybara.test_id
input_btn_xpath = input_btn_xpath[locator_matches]
input_btn_xpath = input_btn_xpath[locator_matchers]
btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
btn_xpath = btn_xpath[locator_matchers | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
alt_matches = XPath.attr(:alt).is(locator)
alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
@ -380,7 +384,11 @@ Capybara.add_selector(:label) do
label "label"
xpath(:for) do |locator, options|
xpath = XPath.descendant(:label)
xpath = xpath[XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)] unless locator.nil?
unless locator.nil?
locator_matchers = XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)
locator_matchers |= XPath.attr(Capybara.test_id) == locator if Capybara.test_id
xpath = xpath[locator_matchers]
end
if options.key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
with_attr = XPath.attr(:for) == options[:for].to_s
labelable_elements = %i[button input keygen meter output progress select textarea]
@ -413,7 +421,11 @@ end
Capybara.add_selector(:table) do
xpath(:caption) do |locator, caption: nil, **_options|
xpath = XPath.descendant(:table)
xpath = xpath[(XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
unless locator.nil?
locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)
locator_matchers |= XPath.attr(Capybara.test_id) == locator if Capybara.test_id
xpath = xpath[locator_matchers]
end
xpath = xpath[XPath.descendant(:caption) == caption] if caption
xpath
end
@ -428,7 +440,11 @@ end
Capybara.add_selector(:frame) do
xpath(:name) do |locator, **options|
xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
xpath = xpath[(XPath.attr(:id) == locator.to_s) | (XPath.attr(:name) == locator.to_s)] unless locator.nil?
unless locator.nil?
locator_matchers = (XPath.attr(:id) == locator.to_s) | (XPath.attr(:name) == locator.to_s)
locator_matchers |= XPath.attr(Capybara.test_id) == locator if Capybara.test_id
xpath = xpath[locator_matchers]
end
xpath = expression_filters.keys.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
xpath
end

View File

@ -19,7 +19,7 @@ module Capybara
# * Locator: The id of the element to match
#
# * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
# * Locator: Matches against the id, name, or placeholder
# * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
# * Filters:
# * :id (String) — Matches the id attribute
# * :name (String) — Matches the name attribute
@ -50,7 +50,7 @@ module Capybara
# * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
#
# * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
# * Locator: Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
# * Locator: Matches the id, Capybara.test_id attribute, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
# * Filters:
# * :id (String) — Matches the id attribute
# * :title (String) — Matches the title attribute
@ -62,7 +62,7 @@ module Capybara
# * Locator: See :link and :button selectors
#
# * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
# * Locator: Matches against the id, name, or placeholder
# * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
# * Filters:
# * :id (String) — Matches the id attribute
# * :name (String) — Matches the name attribute
@ -74,7 +74,7 @@ module Capybara
# * :multiple (Boolean) — Match fields that accept multiple values
#
# * **:radio_button** - Find radio buttons
# * Locator: Match id, name, or associated label text
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
# * Filters:
# * :id (String) — Matches the id attribute
# * :name (String) — Matches the name attribute
@ -85,7 +85,7 @@ module Capybara
# * :option (String) — Match the value
#
# * **:checkbox** - Find checkboxes
# * Locator: Match id, name, or associated label text
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
# * Filters:
# * *:id (String) — Matches the id attribute
# * *:name (String) — Matches the name attribute
@ -96,7 +96,7 @@ module Capybara
# * *:option (String) — Match the value
#
# * **:select** - Find select elements
# * Locator: Match id, name, placeholder, or associated label text
# * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
# * Filters:
# * :id (String) — Matches the id attribute
# * :name (String) — Matches the name attribute
@ -126,7 +126,7 @@ module Capybara
# * Locator:
#
# * **:file_field** - Find file input elements
# * Locator: Match id, name, or associated label text
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
# * Filters:
# * :id (String) — Matches the id attribute
# * :name (String) — Matches the name attribute
@ -387,6 +387,7 @@ module Capybara
XPath.attr(:placeholder) == locator,
XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
attr_matchers |= XPath.attr(Capybara.test_id) == locator if Capybara.test_id
locate_xpath = locate_xpath[attr_matchers]
locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)

View File

@ -10,6 +10,7 @@ Capybara::SpecHelper.spec '#find_field' do
expect(@session.find_field('form_description').value).to eq('Descriptive text goes here')
expect(@session.find_field('Region')[:name]).to eq('form[region]')
expect(@session.find_field('With Asterisk*')).to be_truthy
expect(@session.find_field('my_test_id')).to be_truthy
end
context "aria_label attribute with Capybara.enable_aria_label" do

View File

@ -440,4 +440,20 @@ Capybara::SpecHelper.spec '#find' do
@session.find(:unknown, '//h1')
end.to raise_error(ArgumentError)
end
context "with Capybara.test_id" do
it "should not match when nil" do
Capybara.test_id = nil
expect(@session).not_to have_field('test_id')
end
it "should default to `data-test-id` attribute" do
expect(@session.find(:field, 'test_id')[:id]).to eq 'test_field'
end
it "should use a different attribute if set" do
Capybara.test_id = 'data-other-test-id'
expect(@session.find(:field, 'test_id')[:id]).to eq 'normal'
end
end
end

View File

@ -33,6 +33,7 @@ module Capybara
Capybara.enable_aria_label = false
Capybara.default_set_options = {}
Capybara.disable_animation = false
Capybara.test_id = 'data-test-id'
reset_threadsafe
end

View File

@ -395,7 +395,7 @@ New line after and before textarea tag
<input type="checkbox" name="form[disabled_fieldeset_legend_child]" id="form_disabled_fieldset_legend_child"/>
</legend>
<legend>
Another Legend
Another WLegend
<input type="checkbox" name="form[disabled_fieldeset_second_legend_child]" id="form_disabled_fieldset_second_legend_child"/>
</legend>
<fieldset>
@ -408,7 +408,7 @@ New line after and before textarea tag
</fieldset>
<p>
<select>
<select data-test-id="my_test_id">
<optgroup label="Level One">
<option> A.1 </option>
</optgroup>

View File

@ -36,9 +36,9 @@
</p>
<p>
<input type="text" id="test_field" value="monkey"/>
<input type="text" id="test_field" data-test-id="test_id" value="monkey"/>
<input type="text" readonly="readonly" value="should not change" />
<textarea id="normal">
<textarea id="normal" data-other-test-id="test_id">
banana</textarea>
<textarea id="additional_newline">