From 3e5f82bb2360f11077e26318db05e97618062602 Mon Sep 17 00:00:00 2001 From: Thomas Walpole Date: Thu, 2 Aug 2018 12:56:16 -0700 Subject: [PATCH] Workaround geckodriver/firefox deficient send_keys implementation as much as possible --- lib/capybara/selenium/node.rb | 9 ++-- .../selenium/nodes/marionette_node.rb | 43 +++++++++++++++++++ spec/selenium_spec_firefox_remote.rb | 4 -- spec/selenium_spec_marionette.rb | 4 -- spec/shared_selenium_session.rb | 1 - 5 files changed, 47 insertions(+), 14 deletions(-) diff --git a/lib/capybara/selenium/node.rb b/lib/capybara/selenium/node.rb index 8c9e9417..233f7d82 100644 --- a/lib/capybara/selenium/node.rb +++ b/lib/capybara/selenium/node.rb @@ -47,7 +47,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node # Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace] def set(value, **options) raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" if value.is_a?(Array) && !multiple? - case tag_name when 'input' case self[:type] @@ -212,17 +211,17 @@ private elsif clear == :backspace # Clear field by sending the correct number of backspace keys. backspaces = [:backspace] * self.value.to_s.length - native.send_keys(*([:end] + backspaces + [value.to_s])) + send_keys(*([:end] + backspaces + [value.to_s])) elsif clear == :none - native.send_keys(value.to_s) + send_keys(value.to_s) elsif clear.is_a? Array - native.send_keys(*clear, value.to_s) + send_keys(*clear, value.to_s) else # Clear field by JavaScript assignment of the value property. # Script can change a readonly element which user input cannot, so # don't execute if readonly. driver.execute_script "arguments[0].value = ''", self - native.send_keys(value.to_s) + send_keys(value.to_s) end end diff --git a/lib/capybara/selenium/nodes/marionette_node.rb b/lib/capybara/selenium/nodes/marionette_node.rb index c4aec9d4..8f821bb6 100644 --- a/lib/capybara/selenium/nodes/marionette_node.rb +++ b/lib/capybara/selenium/nodes/marionette_node.rb @@ -42,8 +42,51 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node end end + def send_keys(*args) + # https://github.com/mozilla/geckodriver/issues/846 + return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none? { |s| s.is_a? Array } + + native.click + actions = driver.browser.action + args.each do |keys| + _send_keys(keys, actions) + end + actions.perform + end + private + def _send_keys(keys, actions, down_keys = nil) + case keys + when String + keys = keys.upcase if down_keys&.include?(:shift) # https://bugzilla.mozilla.org/show_bug.cgi?id=1405370 + actions.send_keys(keys) + when :space + actions.send_keys(' ') # https://github.com/mozilla/geckodriver/issues/846 + when :control, :left_control, :right_control, + :alt, :left_alt, :right_alt, + :shift, :left_shift, :right_shift, + :meta, :left_meta, :right_meta, + :command + if down_keys.nil? + actions.send_keys(keys) + else + down_keys << keys + actions.key_down(keys) + end + when Symbol + actions.send_keys(keys) + when Array + local_down_keys = [] + keys.each do |sub_keys| + _send_keys(sub_keys, actions, local_down_keys) + end + local_down_keys.each { |key| actions.key_up(key) } + else + raise ArgumentError, 'Unknown keys type' + end + end + def bridge driver.browser.send(:bridge) end diff --git a/spec/selenium_spec_firefox_remote.rb b/spec/selenium_spec_firefox_remote.rb index d3a36a12..029f4461 100644 --- a/spec/selenium_spec_firefox_remote.rb +++ b/spec/selenium_spec_firefox_remote.rb @@ -60,10 +60,6 @@ skipped_tests << :windows if ENV['TRAVIS'] && (ENV['SKIP_WINDOW'] || ENV['HEADLE Capybara::SpecHelper.run_specs TestSessions::RemoteFirefox, FIREFOX_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example| case example.metadata[:full_description] - when 'Capybara::Session selenium_firefox_remote node #send_keys should generate key events', - 'Capybara::Session selenium_firefox_remote node #send_keys should allow for multiple simultaneous keys', - 'Capybara::Session selenium_firefox_remote node #send_keys should send special characters' - pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" when 'Capybara::Session selenium_firefox_remote node #click should allow multiple modifiers' pending "Firefox doesn't generate an event for shift+control+click" if marionette_gte?(62, @session) when /^Capybara::Session selenium node #double_click/ diff --git a/spec/selenium_spec_marionette.rb b/spec/selenium_spec_marionette.rb index 4ad9bed6..23100048 100644 --- a/spec/selenium_spec_marionette.rb +++ b/spec/selenium_spec_marionette.rb @@ -50,10 +50,6 @@ $stdout.puts `#{Selenium::WebDriver::Firefox.driver_path} --version` if ENV['CI' Capybara::SpecHelper.run_specs TestSessions::SeleniumMarionette, 'selenium', capybara_skip: skipped_tests do |example| case example.metadata[:full_description] - when 'Capybara::Session selenium node #send_keys should generate key events', - 'Capybara::Session selenium node #send_keys should allow for multiple simultaneous keys', - 'Capybara::Session selenium node #send_keys should send special characters' - pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" when 'Capybara::Session selenium node #click should allow multiple modifiers' pending "Firefox doesn't generate an event for shift+control+click" if marionette_gte?(62, @session) when /^Capybara::Session selenium node #double_click/ diff --git a/spec/shared_selenium_session.rb b/spec/shared_selenium_session.rb index 765304fe..fe7e2146 100644 --- a/spec/shared_selenium_session.rb +++ b/spec/shared_selenium_session.rb @@ -185,7 +185,6 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode| context '#fill_in with { clear: Array } fill_options' do it 'should pass the array through to the element' do - pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" if marionette?(session) # this is mainly for use with [[:control, 'a'], :backspace] - however since that is platform dependant I'm testing with something less useful session.visit('/form') session.fill_in('form_first_name',