class Capybara::Selector
rubocop:disable Lint/EmptyClass
“‘
page.find :element, role: /checkbox/, ’aria-checked’: ‘true’
page.find :element, role: ‘menuitemcheckbox’
page.find :element, type: ‘button’, text: ‘Check me’
page.find :element, ‘button’
page.html # => ‘<button type=“button” role=“menuitemcheckbox” aria-checked=“true”>Check me</button>
“`ruby
* :<any> (String, Regexp) - Match on any specified element attribute
* Filters:
* Locator: Type of element (’div’, ‘a’, etc) - if not specified defaults to ‘*’
* :element
“‘
page.find :frame, name: ’embed’
page.find :frame, ‘embed’
page.find :frame, ‘embed_frame’
page.html # => ‘<iframe id=“embed_frame” name=“embed” src=“example.com/embed”></iframe>’
“‘ruby
* :name (String) - Match name attribute
* Filters:
* Locator: Match id, {Capybara.configure test_id} attribute, or name
* :frame - Find frame/iframe elements
“`
page.find :table_row, ’A’ => ‘3’, ‘B’ => ‘4’
page.find :table_row, ‘A’ => ‘1’, ‘B’ => ‘2’
</table>‘
</tr>
<td>4</td>
<td>3</td>
<tr>
</tr>
<td>2</td>
<td>1</td>
<tr>
</tr>
<th>B</th>
<th>A</th>
<tr>
page.html # => ’<table>
“‘ruby
* Locator: Array<String>, Hash<String, String> table row `<td>` contents - visibility of `<td>` elements is not considered
* :table_row - Find table row
“`
page.find :table, rows: [ [’1’, ‘2’] ] # => raises Capybara::ElementNotFound
]
[‘3’, ‘4’],
[‘1’, ‘2’],
page.find :table, rows: [
]
{ ‘A’ => ‘3’, ‘B’ => ‘4’ },
{ ‘A’ => ‘1’, ‘B’ => ‘2’ },
page.find :table, rows: [
]
[‘3’, ‘4’],
[‘1’, ‘2’],
page.find :table, with_rows: [
]
{ ‘A’ => ‘3’, ‘B’ => ‘4’ },
{ ‘A’ => ‘1’, ‘B’ => ‘2’ },
page.find :table, with_rows: [
page.find :table, ‘A table’
</table>‘
</tr>
<td>4</td>
<td>3</td>
<tr>
</tr>
<td>2</td>
<td>1</td>
<tr>
</tr>
<th>B</th>
<th>A</th>
<tr>
<caption>A table</caption>
page.html # => ’<table>
“‘ruby
* :cols (Array<Array<String>>) - Match all `<td>`s - visibility of `<td>` elements is not considered
* :with_cols (Array<Array<String>>, Array<Hash<String, String>>) - Partial match `<td>` data - visibility of `<td>` elements is not considered
* :rows (Array<Array<String>>) - Match all `<td>`s - visibility of `<td>` elements is not considered
* :with_rows (Array<Array<String>>, Array<Hash<String, String>>) - Partial match `<td>` data - visibility of `<td>` elements is not considered
* :caption (String) - Match text of associated caption
* Filters:
* Locator: id, {Capybara.configure test_id}, or caption text of table
* :table - Find table elements
“`
page.find :label, ’Title’, for: page.find(‘article’)
page.find :label, ‘Title’, for: ‘article_title’
page.find :label, ‘Title’
<input id=“article_title” name=“article”>‘
page.html # => ’<label for=“article_title”>Title</label>
“‘ruby
* :for (Element, String, Regexp) - The element or id of the element associated with the label
* Filters:
* Locator: Match id, {Capybara.configure test_id}, or text contents
* :label - Find label elements
“`
page.find :field, ’Banner Image’, type: ‘file’
page.find :file_field, ‘Banner Image’, name: ‘article’
page.find :file_field, ‘Banner Image’
page.find :file_field, ‘article’
page.find :file_field, ‘article_banner_image’
<input type=“file” id=“article_banner_image” name=“article”>‘
page.html # => ’<label for=“article_banner_image”>Banner Image</label>
“‘ruby
* :multiple (Boolean) - Match field that accepts multiple values
* :disabled (Boolean, :all) - Match disabled field? (Default: false)
* :name (String, Regexp) - Matches the name attribute
* Filters:
* Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
* :file_field - Find file input elements
“`
page.find :datalist_option, ’Forbidden’, disabled: true
page.find :datalist_option, ‘Vanilla’
page.find :datalist_option, ‘Strawberry’
page.find :datalist_option, ‘Chocolate’
</datalist>‘
<option value=“Forbidden” disabled></option>
<option value=“Vanilla”></option>
<option value=“Strawberry”></option>
<option value=“Chocolate”></option>
page.html # => ’<datalist>
“‘ruby
* :disabled (Boolean) - Match disabled option
* Filters:
* Locator: Match text or value of option
* :datalist_option - Find datalist option
“`
page.find :datalist_input, options: [’Chocolate’] # => raises Capybara::ElementNotFound
page.find :datalist_input, options: [‘Chocolate’, ‘Strawberry’, ‘Vanilla’]
page.find :datalist_input, with_options: [‘Chocolate’, ‘Strawberry’]
page.find :datalist_input, ‘Flavor’
page.find :datalist_input, ‘ice_cream’
page.find :datalist_input, ‘ice_cream_flavor’
</datalist>‘
<option value=“Vanilla”></option>
<option value=“Strawberry”></option>
<option value=“Chocolate”></option>
<datalist id=“ice_cream_flavors”>
<input list=“ice_cream_flavors” id=“ice_cream_flavor” name=“ice_cream”>
page.html # => ’<label for=“ice_cream_flavor”>Flavor</label>
“‘ruby
* :with_options (Array<String>) - Partial match options
* :options (Array<String>) - Exact match options
* :disabled (Boolean, :all) - Match disabled field? (Default: false)
* :placeholder (String, Regexp) - Matches the placeholder attribute
* :name (String, Regexp) - Matches the name attribute
* Filters:
placeholder, or associated label text
* Locator: Matches against the id, {Capybara.configure test_id} attribute, name,
* :datalist_input - Find input field with datalist completion
“`
page.find :option, ’Other’, selected: false
page.find :option, ‘Disabled’, disabled: true
page.find :option, ‘General’, selected: true
page.find :option, ‘General’
<option value=“Other”></option>‘
<option value=“Disabled” disabled></option>
page.html # => ’<option value=“General” checked></option>
“‘ruby
* :selected (Boolean) - Match selected option
* :disabled (Boolean) - Match disabled option
* Filters:
* Locator: Match text of option
* :option - Find option elements
“`
page.find :select, options: [’General’] # => raises Capybara::ElementNotFound
page.find :select, options: [‘General’, ‘Other’]
page.find :select, with_options: [‘Other’]
page.find :select, with_options: [‘General’]
page.find :select, ‘Category’, selected: ‘General’
page.find :select, ‘Category’
page.find :select, ‘article’
page.find :select, ‘article_category’
</select>‘
<option value=“Other”></option>
<option value=“General” checked></option>
<select id=“article_category” name=“article”>
page.html # => ’<label for=“article_category”>Category</label>
“‘ruby
* :with_selected (String, Array<String>) - Partial match the selection(s)
* :selected (String, Array<String>) - Match the selection(s)
* :with_options (Array<String>) - Partial match options
* :disabled_options (Array<String>) - Exact match disabled options
* :enabled_options (Array<String>) - Exact match enabled options
* :options (Array<String>) - Exact match options
* :multiple (Boolean) - Match fields that accept multiple values
* :disabled (Boolean, :all) - Match disabled field? (Default: false)
* :placeholder (String, Placeholder) - Matches the placeholder attribute
* :name (String, Regexp) - Matches the name attribute
* Filters:
* Locator: Match id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
* :select - Find select elements
“`
page.find :checkbox, ’I agree to terms and conditions’, unchecked: true
page.find :checkbox, ‘registration’
page.find :checkbox, ‘registration_terms’
<label for=“registration_terms”>I agree to terms and conditions</label>‘
page.html # => ’<input type=“checkbox” id=“registration_terms” name=“registration” value=“true”>
“‘ruby
* :option - Alias of :with
* :with (String, Regexp) - Match the current value
* :disabled (Boolean, :all) - Match disabled field? (Default: false)
* :unchecked (Boolean) - Match unchecked fields?
* :checked (Boolean) - Match checked fields?
* :name (String, Regexp) - Matches the name attribute
* Filters:
* Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
* :checkbox - Find checkboxes
“`
page.find :radio_button, ’Draft’, unchecked: true
page.find :radio_button, ‘Published’, checked: true
page.find :radio_button, ‘article’, option: ‘published’
page.find :radio_button, ‘article_state_published’
<label for=“article_state_draft”>Draft</label>‘
<input type=“radio” id=“article_state_draft” name=“article” value=“draft”>
<label for=“article_state_published”>Published</label>
page.html # => ’<input type=“radio” id=“article_state_published” name=“article” value=“published” checked>
“‘ruby
* :with - Alias of :option
* :option (String, Regexp) - Match the current value
* :disabled (Boolean, :all) - Match disabled field? (Default: false)
* :unchecked (Boolean) - Match unchecked fields?
* :checked (Boolean) - Match checked fields?
* :name (String, Regexp) - Matches the name attribute
* Filters:
* Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
* :radio_button - Find radio buttons
“`
page.find :field, ’Body’, type: ‘textarea’
page.find :fillable_field, ‘Body’
page.find :fillable_field, ‘article’
page.find :fillable_field, ‘article_body’
<textarea id=“article_body” name=“article”></textarea>‘
page.html # => ’<label for=“article_body”>Body</label>
“‘ruby
* :validation_message (String, Regexp) - Matches the elements current validationMessage
* :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
* :multiple (Boolean) - Match fields that accept multiple values
* :disabled (Boolean, :all) - Match disabled field? (Default: false)
* :type (String) - Matches the type attribute of the field or element type for ’textarea’
* :with (String, Regexp) - Matches the current value of the field
* :placeholder (String, Regexp) - Matches the placeholder attribute
* :name (String, Regexp) - Matches the name attribute
* Filters:
* Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
* :fillable_field - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
“‘
page.find :link_or_button, ’Submit’
page.html # => ‘<button>Submit</button>’
page.find :link_or_button, ‘Home’
page.html # => ‘<a href=“/”>Home</a>’
“‘ruby
* :disabled (Boolean, :all) - Match disabled buttons? (Default: false)
* Filters:
* Locator: See :link and :button selectors
* :link_or_button - Find links or buttons
“`
page.find :button, ’Save as draft’, name: ‘article’, value: ‘draft’
page.html # => ‘<button name=“article” value=“draft”>Save as draft</button>’
page.find :button, ‘Submit’
page.html # => ‘<button>Submit</button>’
“‘ruby
* :disabled (Boolean, :all) - Match disabled buttons (Default: false)
* :type (String) - Matches the type attribute
* :value (String) - Matches the value of an input button
* :title (String) - Matches the title attribute
* :name (String, Regexp) - Matches the name attribute
* Filters:
* Locator: Matches the id, {Capybara.configure test_id} attribute, name, 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
* :button - Find buttons ( input [of type submit, reset, image, button] or button elements )
“`
page.find :link, alt: ’The logo’, href: ‘/’
page.find :link, ‘The logo’, href: ‘/’
page.html # => ‘<a href=“/”><img src=“/logo.png” alt=“The logo”></a>’
page.find :link, ‘Home’, href: ‘/’
page.html # => ‘<a href=“/”>Home</a>’
“‘ruby
* :href (String, Regexp, nil, false) - Matches the normalized href of the link, if nil will find `<a>` elements with no href attribute, if false ignores href presence
* :alt (String) - Matches the alt attribute of a contained img element
* :title (String) - Matches the title attribute
* Filters:
or the alt attribute of a contained img element. By default this selector requires a link to have an href attribute.
* Locator: Matches the id, {Capybara.configure test_id}, or title attributes, or the string content of the link,
* :link - Find links (`<a>` elements with an href attribute)
“`
page.find :fieldset, ’Fields (disabled)‘, disabled: true
</fieldset>’
<legend>Fields (disabled)</legend>
page.html # => ‘<fieldset disabled>
“`ruby
* :disabled (Boolean) - Match disabled fieldset?
* :legend (String) - Matches contents of wrapped legend
* Filters:
* Locator: Matches id, {Capybara.configure test_id}, or contents of wrapped legend
* :fieldset - Select fieldset elements
“`
page.find :field, ’Title’, type: ‘text’, with: ‘Hello world’
page.find :field, ‘Title’
page.find :field, ‘article’
page.find :field, ‘article_title’
<input id=“article_title” name=“article” value=“Hello world”>‘
page.html # => ’<label for=“article_title”>Title</label>
“‘ruby
* :validation_message (String, Regexp) - Matches the elements current validationMessage
* :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
* :multiple (Boolean) - Match fields that accept multiple values
* :disabled (Boolean, :all) - Match disabled field? (Default: false)
* :unchecked (Boolean) - Match unchecked fields?
* :checked (Boolean) - Match checked fields?
* :with (String, Regexp) - Matches the current value of the field
* :readonly (Boolean) - Match on the element being readonly
* :type (String) - Matches the type attribute of the field or element type for ’textarea’ and ‘select’
* :placeholder (String, Regexp) - Matches the placeholder attribute
* :name (String, Regexp) - Matches the name attribute
* Filters:
associated label text
* Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or
* :field - Select field elements (input [not of type submit, image, or hidden], textarea, select)
“‘
page.find :id, ’content’
page.html # => ‘<input id=“field”>’
“‘ruby
* Locator: (String, Regexp, XPath::Expression) The id of the element to match
* :id - Select element by id
“`
page.find :css, ’input’
page.html # => ‘<input>’
“‘ruby
* Locator: A CSS selector
* :css - Select elements by CSS selector
“`
page.find :xpath, ’.//input’
page.html # => ‘<input>’
“‘ruby
* Locator: An XPath expression
* :xpath - Select elements by XPath expression
### Built-in Selectors
* :focused (Boolean) - Match elements with focus (requires driver support)
* :near (Element) - Match elements near (within 50px) the passed element on the page
* :right_of (Element) - Match elements right of the passed element on the page
* :left_of (Element) - Match elements left of the passed element on the page
* :below (Element) - Match elements below the passed element on the page
* :above (Element) - Match elements above the passed element on the page
* :style (String, Regexp, Hash<String, String>) - Match on elements style
* :class (String, Array<String | Regexp>, Regexp, XPath::Expression) - Matches the class(es) provided
* :id (String, Regexp, XPath::Expression) - Matches the id attribute
All Selectors below support the listed selector specific filters in addition to the following system-wide filters
def [](name)
def [](name) all.fetch(name.to_sym) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" } end
def add(name, **options, &block)
def add(name, **options, &block) all[name.to_sym] = Definition.new(name.to_sym, **options, &block) end
def add_error(error_msg)
def add_error(error_msg) errors << error_msg end
def all
def all @definitions ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName end
def builder(expr = nil)
- Api: - private
def builder(expr = nil) case format when :css Capybara::Selector::CSSBuilder when :xpath Capybara::Selector::XPathBuilder else raise NotImplementedError, "No builder exists for selector of type #{default_format}" end.new(expr) end
def call(locator, **options)
def call(locator, **options) if format raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format) instance_exec(locator, **options, &expressions[format]) else warn 'Selector has no format' end ensure unless locator_valid?(locator) Capybara::Helpers.warn( "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. " \ 'This will raise an error in a future version of Capybara. ' \ "Called from: #{Capybara::Helpers.filter_backtrace(caller)}" ) end end
def enable_aria_label
def enable_aria_label @config[:enable_aria_label] end
def enable_aria_role
def enable_aria_role @config[:enable_aria_role] end
def expression_for(name, locator, config: @config, format: current_format, **options)
def expression_for(name, locator, config: @config, format: current_format, **options) Selector.new(name, config: config, format: format).call(locator, **options) end
def find_by_attr(attribute, value)
def find_by_attr(attribute, value) finder_name = "find_by_#{attribute}_attr" if respond_to?(finder_name, true) send(finder_name, value) else value ? XPath.attr(attribute) == value : nil end end
def find_by_class_attr(classes)
def find_by_class_attr(classes) Array(classes).map { |klass| XPath.attr(:class).contains_word(klass) }.reduce(:&) end
def for(locator)
def for(locator) all.values.find { |sel| sel.match?(locator) } end
def format
def format @format || @definition.default_format end
def initialize(definition, config:, format:)
def initialize(definition, config:, format:) definition = self.class[definition] unless definition.is_a? Definition super(definition) @definition = definition @config = config @format = format @errors = [] end
def locate_field(xpath, locator, **_options)
def locate_field(xpath, locator, **_options) return xpath if locator.nil? locate_xpath = xpath # Need to save original xpath for the label wrap locator = locator.to_s attr_matchers = [XPath.attr(:id) == locator, XPath.attr(:name) == locator, 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(test_id) == locator if test_id locate_xpath = locate_xpath[attr_matchers] locate_xpath + locate_label(locator).descendant(xpath) end
def locate_label(locator)
def locate_label(locator) XPath.descendant(:label)[XPath.string.n.is(locator)] end
def locator_description
def locator_description locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods| if symbol "respond to #{types_or_methods.join(' or ')}" else "be an instance of #{types_or_methods.join(' or ')}" end end.join(' or ') end
def locator_valid?(locator)
def locator_valid?(locator) return true unless locator && locator_types locator_types&.any? do |type_or_method| type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality end end
def remove(name)
def remove(name) all.delete(name.to_sym) end
def test_id
def test_id @config[:test_id] end
def update(name, &block)
def update(name, &block) self[name].instance_eval(&block) end
def with_filter_errors(errors)
- Api: - private
def with_filter_errors(errors) old_errors = @errors @errors = errors yield ensure @errors = old_errors end