Skip to content

Commit

Permalink
Add Capybara.test_id option defaulting to data-test-id
Browse files Browse the repository at this point in the history
  • Loading branch information
twalpole committed Jul 6, 2018
1 parent 9903dd2 commit 5b76480
Show file tree
Hide file tree
Showing 12 changed files with 84 additions and 34 deletions.
2 changes: 2 additions & 0 deletions lib/capybara.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class << self
# [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
#
Expand Down Expand Up @@ -483,6 +484,7 @@ module Selenium; end
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|
Expand Down
15 changes: 14 additions & 1 deletion lib/capybara/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down Expand Up @@ -79,6 +80,18 @@ def javascript_driver
@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]
Expand Down
12 changes: 6 additions & 6 deletions lib/capybara/node/actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ def click_link_or_button(locator = nil, **options)

##
#
# 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
Expand All @@ -45,7 +45,7 @@ def click_link(locator = nil, **options)
#
# 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
Expand All @@ -61,7 +61,7 @@ def click_button(locator = nil, **options)
##
#
# 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'
#
Expand Down Expand Up @@ -170,7 +170,7 @@ def uncheck(locator = nil, **options)
# @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)
Expand All @@ -194,7 +194,7 @@ def select(value = nil, from: nil, **options)
# @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)
Expand Down
8 changes: 4 additions & 4 deletions lib/capybara/node/finders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def sibling(*args, **options, &optional_filter_block)
# 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
#
Expand Down Expand Up @@ -120,7 +120,7 @@ def find_field(locator = nil, **options, &optional_filter_block)
# 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
#
Expand All @@ -139,11 +139,11 @@ def find_link(locator = nil, **options, &optional_filter_block)
#
# 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)
#
Expand Down
8 changes: 4 additions & 4 deletions lib/capybara/node/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ def has_no_field?(locator = nil, **options, &optional_filter_block)
##
#
# 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
Expand All @@ -395,7 +395,7 @@ def has_checked_field?(locator = nil, **options, &optional_filter_block)
##
#
# 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
Expand All @@ -408,7 +408,7 @@ def has_no_checked_field?(locator = nil, **options, &optional_filter_block)
##
#
# 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
Expand All @@ -421,7 +421,7 @@ def has_unchecked_field?(locator = nil, **options, &optional_filter_block)
##
#
# 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
Expand Down
32 changes: 24 additions & 8 deletions lib/capybara/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@

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
Expand Down Expand Up @@ -101,6 +103,7 @@
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

Expand Down Expand Up @@ -138,12 +141,13 @@

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
Expand Down Expand Up @@ -380,7 +384,11 @@
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]
Expand Down Expand Up @@ -413,7 +421,11 @@
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
Expand All @@ -428,7 +440,11 @@
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
Expand Down
15 changes: 8 additions & 7 deletions lib/capybara/selector/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -387,6 +387,7 @@ def locate_field(xpath, locator, enable_aria_label: false, **_options)
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)
Expand Down
1 change: 1 addition & 0 deletions lib/capybara/spec/session/find_field_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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
Expand Down
16 changes: 16 additions & 0 deletions lib/capybara/spec/session/find_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,20 @@
@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
1 change: 1 addition & 0 deletions lib/capybara/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def reset!
Capybara.enable_aria_label = false
Capybara.default_set_options = {}
Capybara.disable_animation = false
Capybara.test_id = 'data-test-id'
reset_threadsafe
end

Expand Down
4 changes: 2 additions & 2 deletions lib/capybara/spec/views/form.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand Down
4 changes: 2 additions & 2 deletions lib/capybara/spec/views/with_html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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">

Expand Down

0 comments on commit 5b76480

Please sign in to comment.