diff --git a/lib/capybara/node/actions.rb b/lib/capybara/node/actions.rb index aa9faa35..d0f1f165 100644 --- a/lib/capybara/node/actions.rb +++ b/lib/capybara/node/actions.rb @@ -62,6 +62,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, Capybara.test_id attribute, or label text. + # If no locator is provided will operate on self or a descendant # # page.fill_in 'Name', with: 'Bob' # @@ -82,6 +83,7 @@ module Capybara # @return [Capybara::Node::Element] The element filled_in def fill_in(locator = nil, with:, currently_with: nil, fill_options: {}, **find_options) find_options[:with] = currently_with if currently_with + find_options[:allow_self] = true if locator.nil? find(:fillable_field, locator, find_options).set(with, fill_options) end @@ -90,8 +92,9 @@ module Capybara ## # - # Find a radio button and mark it as checked. The radio button can be found - # via name, id or label text. + # Find a descendant radio button and mark it as checked. The radio button can be found + # via name, id or label text. If no locator is provided this will match against self or + # a descendant. # # page.choose('Male') # @@ -112,8 +115,9 @@ module Capybara ## # - # Find a check box and mark it as checked. The check box can be found - # via name, id or label text. + # Find a descendant check box and mark it as checked. The check box can be found + # via name, id or label text. If no locator is provided this will match against + # self or a descendant. # # page.check('German') # @@ -135,8 +139,9 @@ module Capybara ## # - # Find a check box and mark uncheck it. The check box can be found - # via name, id or label text. + # Find a descendant check box and mark uncheck it. The check box can be found + # via name, id or label text. If no locator is provided this will match against + # self or a descendant. # # page.uncheck('German') # @@ -204,10 +209,11 @@ module Capybara ## # - # Find a file field on the page and attach a file given its path. The file field can + # Find a descendant file field on the page and attach a file given its path. The file field can # be found via its name, id or label text. In the case of the file field being hidden for # styling reasons the `make_visible` option can be used to temporarily change the CSS of - # the file field, attach the file, and then revert the CSS back to original. + # the file field, attach the file, and then revert the CSS back to original. If no locator is + # passed this will match self or a descendant. # # page.attach_file(locator, '/path/to/file.png') # @@ -230,6 +236,7 @@ module Capybara Array(paths).each do |path| raise Capybara::FileNotFound, "cannot attach file, #{path} does not exist" unless File.exist?(path.to_s) end + options[:allow_self] = true if locator.nil? # Allow user to update the CSS style of the file input since they are so often hidden on a page if make_visible ff = find(:file_field, locator, options.merge(visible: :all)) @@ -291,6 +298,8 @@ module Capybara end def _check_with_label(selector, checked, locator, allow_label_click: session_options.automatic_label_click, **options) + options[:allow_self] = true if locator.nil? + synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do begin el = find(selector, locator, options) @@ -299,7 +308,7 @@ module Capybara raise unless allow_label_click && catch_error?(err) begin el ||= find(selector, locator, options.merge(visible: :all)) - find(:label, for: el, visible: true).click unless el.checked? == checked + el.session.find(:label, for: el, visible: true).click unless el.checked? == checked rescue StandardError # swallow extra errors - raise original raise err end diff --git a/lib/capybara/queries/selector_query.rb b/lib/capybara/queries/selector_query.rb index 28b67bf3..23565dee 100644 --- a/lib/capybara/queries/selector_query.rb +++ b/lib/capybara/queries/selector_query.rb @@ -58,6 +58,7 @@ module Capybara end def matches_filters?(node) + return true if (@resolved_node&.== node) && options[:allow_self] @applied_filters ||= :system return false unless matches_text_filter?(node) && matches_exact_text_filter?(node) && matches_visible_filter?(node) @applied_filters = :node @@ -210,7 +211,7 @@ module Capybara return if unhandled_options.empty? invalid_names = unhandled_options.map(&:inspect).join(', ') - valid_names = valid_keys.map(&:inspect).join(', ') + valid_names = (valid_keys - [:allow_self]).map(&:inspect).join(', ') raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}" end diff --git a/lib/capybara/selector.rb b/lib/capybara/selector.rb index c2d17865..067b4b64 100644 --- a/lib/capybara/selector.rb +++ b/lib/capybara/selector.rb @@ -194,8 +194,12 @@ end Capybara.add_selector(:fillable_field) do label 'field' - xpath do |locator, **options| - xpath = XPath.descendant(:input, :textarea)[!XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')] + xpath(:allow_self) do |locator, **options| + xpath = if options[:allow_self] + XPath.descendant_or_self(:input, :textarea) + else + XPath.descendant(:input, :textarea) + end[!XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')] locate_field(xpath, locator, options) end @@ -223,8 +227,12 @@ end Capybara.add_selector(:radio_button) do label 'radio button' - xpath do |locator, **options| - xpath = XPath.descendant(:input)[XPath.attr(:type) == 'radio'] + xpath(:allow_self) do |locator, **options| + xpath = if options[:allow_self] + XPath.descendant_or_self(:input) + else + XPath.descendant(:input) + end[XPath.attr(:type) == 'radio'] locate_field(xpath, locator, options) end @@ -239,8 +247,12 @@ Capybara.add_selector(:radio_button) do end Capybara.add_selector(:checkbox) do - xpath do |locator, **options| - xpath = XPath.descendant(:input)[XPath.attr(:type) == 'checkbox'] + xpath(:allow_self) do |locator, **options| + xpath = if options[:allow_self] + XPath.descendant_or_self(:input) + else + XPath.descendant(:input) + end[XPath.attr(:type) == 'checkbox'] locate_field(xpath, locator, options) end @@ -375,8 +387,12 @@ end Capybara.add_selector(:file_field) do label 'file field' - xpath do |locator, options| - xpath = XPath.descendant(:input)[XPath.attr(:type) == 'file'] + xpath(:allow_self) do |locator, options| + xpath = if options[:allow_self] + XPath.descendant_or_self(:input) + else + XPath.descendant(:input) + end[XPath.attr(:type) == 'file'] locate_field(xpath, locator, options) end diff --git a/lib/capybara/selenium/nodes/marionette_node.rb b/lib/capybara/selenium/nodes/marionette_node.rb index 48ca81a9..52afad0a 100644 --- a/lib/capybara/selenium/nodes/marionette_node.rb +++ b/lib/capybara/selenium/nodes/marionette_node.rb @@ -27,7 +27,7 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node def set_file(value) # rubocop:disable Naming/AccessorMethodName # By default files are appended so we have to clear here if its multiple and already set - native.clear if multiple? && driver.evaluate_script("arguments[0].files", self).any? + native.clear if multiple? && driver.evaluate_script('arguments[0].files', self).any? return super if browser_version >= 62.0 # Workaround lack of support for multiple upload by uploading one at a time diff --git a/lib/capybara/spec/session/attach_file_spec.rb b/lib/capybara/spec/session/attach_file_spec.rb index 6f2bf0a6..541301cc 100644 --- a/lib/capybara/spec/session/attach_file_spec.rb +++ b/lib/capybara/spec/session/attach_file_spec.rb @@ -22,6 +22,13 @@ Capybara::SpecHelper.spec '#attach_file' do expect(extract_results(@session)['image']).to eq(File.basename(__FILE__)) end + it 'should be able to set on element if no locator passed' do + ff = @session.find(:file_field, 'Image') + ff.attach_file(with_os_path_separators(__FILE__)) + @session.click_button('awesome') + expect(extract_results(@session)['image']).to eq(File.basename(__FILE__)) + end + it 'casts to string' do @session.attach_file :form_image, with_os_path_separators(__FILE__) @session.click_button('awesome') diff --git a/lib/capybara/spec/session/check_spec.rb b/lib/capybara/spec/session/check_spec.rb index 7bdee312..e76caee3 100644 --- a/lib/capybara/spec/session/check_spec.rb +++ b/lib/capybara/spec/session/check_spec.rb @@ -68,6 +68,13 @@ Capybara::SpecHelper.spec '#check' do expect(extract_results(@session)['pets']).to include('dog', 'cat', 'hamster') end + it 'should be able to check itself if no locator specified' do + cb = @session.find(:id, 'form_pets_cat') + cb.check + @session.click_button('awesome') + expect(extract_results(@session)['pets']).to include('dog', 'cat', 'hamster') + end + it 'casts to string' do @session.check(:form_pets_cat) @session.click_button('awesome') @@ -142,6 +149,20 @@ Capybara::SpecHelper.spec '#check' do expect(extract_results(@session)['cars']).to include('mclaren') end + it 'should check via clicking the label with :for attribute if locator nil' do + cb = @session.find(:checkbox, 'form_cars_tesla', unchecked: true, visible: :hidden) + cb.check + @session.click_button('awesome') + expect(extract_results(@session)['cars']).to include('tesla') + end + + it 'should check self via clicking the wrapping label if locator nil' do + cb = @session.find(:checkbox, 'form_cars_mclaren', unchecked: true, visible: :hidden) + cb.check + @session.click_button('awesome') + expect(extract_results(@session)['cars']).to include('mclaren') + end + it 'should not click the label if unneeded' do expect(@session.find(:checkbox, 'form_cars_jaguar', checked: true, visible: :hidden)).to be_truthy @session.check('form_cars_jaguar') diff --git a/lib/capybara/spec/session/choose_spec.rb b/lib/capybara/spec/session/choose_spec.rb index 89006339..435c92a2 100644 --- a/lib/capybara/spec/session/choose_spec.rb +++ b/lib/capybara/spec/session/choose_spec.rb @@ -23,6 +23,13 @@ Capybara::SpecHelper.spec '#choose' do expect(extract_results(@session)['gender']).to eq('male') end + it 'should be able to choose self when no locator string specified' do + rb = @session.find(:id, 'gender_male') + rb.choose + @session.click_button('awesome') + expect(extract_results(@session)['gender']).to eq('male') + end + it 'casts to string' do @session.choose('Both') @session.click_button(:awesome) @@ -82,12 +89,19 @@ Capybara::SpecHelper.spec '#choose' do Capybara.automatic_label_click = old_click_label end - it 'should select by clicking the link if available' do + it 'should select by clicking the label if available' do @session.choose('party_democrat') @session.click_button('awesome') expect(extract_results(@session)['party']).to eq('democrat') end + it 'should select self by clicking the label if no locator specified' do + cb = @session.find(:id, 'party_democrat', visible: :hidden) + cb.choose + @session.click_button('awesome') + expect(extract_results(@session)['party']).to eq('democrat') + end + it 'should raise error if not allowed to click label' do expect { @session.choose('party_democrat', allow_label_click: false) }.to raise_error(Capybara::ElementNotFound, 'Unable to find visible radio button "party_democrat"') end diff --git a/lib/capybara/spec/session/fill_in_spec.rb b/lib/capybara/spec/session/fill_in_spec.rb index 22e3aa2d..d60c3b00 100644 --- a/lib/capybara/spec/session/fill_in_spec.rb +++ b/lib/capybara/spec/session/fill_in_spec.rb @@ -137,6 +137,13 @@ Capybara::SpecHelper.spec '#fill_in' do expect(extract_results(@session)['schmooo']).to eq('Schmooo for all') end + it 'should be able to fill in element called on when no locator passed' do + field = @session.find(:fillable_field, 'form[password]') + field.fill_in(with: 'supasikrit') + @session.click_button('awesome') + expect(extract_results(@session)['password']).to eq('supasikrit') + end + it "should throw an exception if a hash containing 'with' is not provided" do expect { @session.fill_in 'Name' }.to raise_error(ArgumentError, /with/) end diff --git a/lib/capybara/spec/session/uncheck_spec.rb b/lib/capybara/spec/session/uncheck_spec.rb index 7890e214..23066878 100644 --- a/lib/capybara/spec/session/uncheck_spec.rb +++ b/lib/capybara/spec/session/uncheck_spec.rb @@ -26,6 +26,14 @@ Capybara::SpecHelper.spec '#uncheck' do expect(extract_results(@session)['pets']).not_to include('hamster') end + it 'should be able to uncheck itself if no locator specified' do + cb = @session.find(:id, 'form_pets_hamster') + cb.uncheck + @session.click_button('awesome') + expect(extract_results(@session)['pets']).to include('dog') + expect(extract_results(@session)['pets']).not_to include('hamster') + end + it 'casts to string' do @session.uncheck(:form_pets_hamster) @session.click_button('awesome')