diff --git a/Gemfile b/Gemfile index b1dd4737..b842c217 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ gemspec gem 'xpath', github: 'teamcapybara/xpath' +# gem 'rack-test', github: 'rack/rack-test' + group :doc do gem 'redcarpet', platforms: :mri end diff --git a/lib/capybara/rack_test/browser.rb b/lib/capybara/rack_test/browser.rb index f29e4966..af666bc9 100644 --- a/lib/capybara/rack_test/browser.rb +++ b/lib/capybara/rack_test/browser.rb @@ -31,11 +31,17 @@ class Capybara::RackTest::Browser request(last_request.fullpath, last_request.env) end - def submit(method, path, attributes) + def submit(method, path, attributes, content_type: nil) path = request_path if path.nil? || path.empty? uri = build_uri(path) uri.query = '' if method.to_s.casecmp('get').zero? - process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => referer_url) + process_and_follow_redirects( + method, + uri.to_s, + attributes, + 'HTTP_REFERER' => referer_url, + 'CONTENT_TYPE' => content_type + ) end def follow(method, path, **attributes) diff --git a/lib/capybara/rack_test/form.rb b/lib/capybara/rack_test/form.rb index fa291039..b1a92be6 100644 --- a/lib/capybara/rack_test/form.rb +++ b/lib/capybara/rack_test/form.rb @@ -30,19 +30,31 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) } - form_elements.each_with_object(make_params) do |field, params| + form_params = form_elements.each_with_object({}.compare_by_identity) do |field, params| case field.name when 'input', 'button' then add_input_param(field, params) when 'select' then add_select_param(field, params) when 'textarea' then add_textarea_param(field, params) end + end + + form_params.each_with_object(make_params) do |(name, value), params| + merge_param!(params, name, value) end.to_params_hash + + # form_elements.each_with_object(make_params) do |field, params| + # case field.name + # when 'input', 'button' then add_input_param(field, params) + # when 'select' then add_select_param(field, params) + # when 'textarea' then add_textarea_param(field, params) + # end + # end.to_params_hash end def submit(button) action = button&.[]('formaction') || native['action'] method = button&.[]('formmethod') || request_method - driver.submit(method, action.to_s, params(button)) + driver.submit(method, action.to_s, params(button), content_type: native['enctype']) end def multipart? @@ -88,6 +100,8 @@ private Capybara::RackTest::Node.new(driver, field).value.to_s when 'file' + return if value.empty? && params.keys.include?(name) && Rack::Test::VERSION.to_f >= 2.0 # rubocop:disable Performance/InefficientHashSearch + if multipart? file_to_upload(value) else @@ -96,7 +110,8 @@ private else value end - merge_param!(params, name, value) + # merge_param!(params, name, value) + params[name] = value end def file_to_upload(filename) @@ -109,18 +124,23 @@ private end def add_select_param(field, params) + name = field['name'] if field.has_attribute?('multiple') - field.xpath('.//option[@selected]').each do |option| - merge_param!(params, field['name'], (option['value'] || option.text).to_s) + value = field.xpath('.//option[@selected]').map do |option| + # merge_param!(params, field['name'], (option['value'] || option.text).to_s) + (option['value'] || option.text).to_s end + params[name] = value unless value.empty? else option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first - merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option + # merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option + params[name] = (option['value'] || option.text).to_s if option end end def add_textarea_param(field, params) - merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n")) + # merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n")) + params[field['name']] = field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n") end def submitter?(el) diff --git a/lib/capybara/spec/session/attach_file_spec.rb b/lib/capybara/spec/session/attach_file_spec.rb index be773ad7..4ed90603 100644 --- a/lib/capybara/spec/session/attach_file_spec.rb +++ b/lib/capybara/spec/session/attach_file_spec.rb @@ -55,6 +55,12 @@ Capybara::SpecHelper.spec '#attach_file' do expect(@session).to have_content('No file uploaded') end + it 'should send prior hidden field if no file submitted' do + @session.click_button('Upload Empty With Hidden') + expect(extract_results(@session)['document2']).to eq('hidden_field') + expect(extract_content_type(@session)).to start_with('multipart/form-data;') + end + it 'should send content type text/plain when uploading a text file' do @session.attach_file 'Single Document', with_os_path_separators(test_file_path) @session.click_button 'Upload Single' diff --git a/lib/capybara/spec/spec_helper.rb b/lib/capybara/spec/spec_helper.rb index 0b649eaa..2802d2b0 100644 --- a/lib/capybara/spec/spec_helper.rb +++ b/lib/capybara/spec/spec_helper.rb @@ -122,6 +122,11 @@ module Capybara YAML.safe_load results, permitted_classes: perms end + def extract_content_type(session) + expect(session).to have_xpath("//pre[@id='content_type']") + Capybara::HTML(session.body).xpath("//pre[@id='content_type']").first.text + end + def be_an_invalid_element_error(session) satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } } end diff --git a/lib/capybara/spec/test_app.rb b/lib/capybara/spec/test_app.rb index 18b97f2a..c900e092 100644 --- a/lib/capybara/spec/test_app.rb +++ b/lib/capybara/spec/test_app.rb @@ -256,12 +256,15 @@ class TestApp < Sinatra::Base post '/form' do self.class.form_post_count += 1 - %(
#{params[:form].merge('post_count' => self.class.form_post_count).to_yaml}
) + %( +
#{request.content_type}
+
#{params.fetch(:form, {}).merge('post_count' => self.class.form_post_count).to_yaml}
+ ) end post '/upload_empty' do if params[:form][:file].nil? - 'Successfully ignored empty file field.' + "Successfully ignored empty file field. Content type was #{request.content_type}" else 'Something went wrong.' end diff --git a/lib/capybara/spec/views/form.erb b/lib/capybara/spec/views/form.erb index 8b01f295..0a9b2008 100644 --- a/lib/capybara/spec/views/form.erb +++ b/lib/capybara/spec/views/form.erb @@ -586,6 +586,19 @@ New line after and before textarea tag

+

+ + +

+ + +

+ +

+ +

+

+

diff --git a/spec/dsl_spec.rb b/spec/dsl_spec.rb index fab8067b..952cfa71 100644 --- a/spec/dsl_spec.rb +++ b/spec/dsl_spec.rb @@ -15,6 +15,8 @@ Capybara::SpecHelper.run_specs TestClass.new, 'DSL', capybara_skip: %i[ pending "Nokogiri doesn't support case insensitive CSS attribute matchers" when /#click_button should follow permanent redirects that maintain method/ pending "Rack < 2 doesn't support 308" if Gem.loaded_specs['rack'].version < Gem::Version.new('2.0.0') + when /#attach_file with multipart form should send prior hidden field if no file submitted/ + skip 'Rack-test < 2 needs an empty file to detect multipart form' if Gem.loaded_specs['rack-test'].version < Gem::Version.new('2.0.0') end end diff --git a/spec/rack_test_spec.rb b/spec/rack_test_spec.rb index 296f879c..24bf807d 100644 --- a/spec/rack_test_spec.rb +++ b/spec/rack_test_spec.rb @@ -30,6 +30,8 @@ Capybara::SpecHelper.run_specs TestSessions::RackTest, 'RackTest', capybara_skip skip "Nokogiri doesn't support case insensitive CSS attribute matchers" when /#click_button should follow permanent redirects that maintain method/ skip "Rack < 2 doesn't support 308" if Gem.loaded_specs['rack'].version < Gem::Version.new('2.0.0') + when /#attach_file with multipart form should send prior hidden field if no file submitted/ + skip 'Rack-test < 2 needs an empty file to detect multipart form' if Gem.loaded_specs['rack-test'].version < Gem::Version.new('2.0.0') end end @@ -93,6 +95,12 @@ RSpec.describe Capybara::Session do # rubocop:disable RSpec/MultipleDescribes session.click_button('Upload Empty') expect(session.html).to include('Successfully ignored empty file field.') end + + it 'should submit multipart even if no file is submitted' do + session.visit('/form') + session.click_button('Upload Empty') + expect(session.html).to include('Content type was multipart/form-data;') + end end it 'should not submit an obsolete mime type' do