selenium-webdriver 3.142.7 → 4.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES +21 -43
- data/lib/selenium/webdriver.rb +2 -4
- data/lib/selenium/webdriver/chrome/bridge.rb +3 -21
- data/lib/selenium/webdriver/chrome/driver.rb +12 -39
- data/lib/selenium/webdriver/chrome/options.rb +3 -7
- data/lib/selenium/webdriver/chrome/profile.rb +2 -2
- data/lib/selenium/webdriver/chrome/service.rb +4 -9
- data/lib/selenium/webdriver/common.rb +7 -16
- data/lib/selenium/webdriver/common/action_builder.rb +97 -249
- data/lib/selenium/webdriver/common/driver.rb +2 -4
- data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common/element.rb +3 -6
- data/lib/selenium/webdriver/common/error.rb +27 -203
- data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -5
- data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +13 -13
- data/lib/selenium/webdriver/common/manager.rb +1 -1
- data/lib/selenium/webdriver/common/options.rb +148 -24
- data/lib/selenium/webdriver/common/service.rb +16 -34
- data/lib/selenium/webdriver/common/socket_poller.rb +2 -2
- data/lib/selenium/webdriver/common/w3c_options.rb +45 -0
- data/lib/selenium/webdriver/edge.rb +0 -1
- data/lib/selenium/webdriver/edge/driver.rb +14 -10
- data/lib/selenium/webdriver/edge/service.rb +6 -7
- data/lib/selenium/webdriver/firefox.rb +2 -6
- data/lib/selenium/webdriver/firefox/binary.rb +3 -80
- data/lib/selenium/webdriver/firefox/bridge.rb +47 -0
- data/lib/selenium/webdriver/firefox/driver.rb +44 -22
- data/lib/selenium/webdriver/firefox/marionette/driver.rb +1 -1
- data/lib/selenium/webdriver/firefox/options.rb +2 -2
- data/lib/selenium/webdriver/firefox/profile.rb +25 -14
- data/lib/selenium/webdriver/firefox/service.rb +4 -4
- data/lib/selenium/webdriver/ie/driver.rb +5 -18
- data/lib/selenium/webdriver/ie/options.rb +2 -2
- data/lib/selenium/webdriver/ie/service.rb +4 -4
- data/lib/selenium/webdriver/remote.rb +2 -6
- data/lib/selenium/webdriver/remote/bridge.rb +515 -69
- data/lib/selenium/webdriver/remote/capabilities.rb +77 -99
- data/lib/selenium/webdriver/remote/commands.rb +156 -0
- data/lib/selenium/webdriver/remote/driver.rb +12 -5
- data/lib/selenium/webdriver/remote/http/default.rb +0 -9
- data/lib/selenium/webdriver/remote/response.rb +16 -47
- data/lib/selenium/webdriver/safari.rb +1 -1
- data/lib/selenium/webdriver/safari/bridge.rb +3 -3
- data/lib/selenium/webdriver/safari/driver.rb +4 -1
- data/lib/selenium/webdriver/safari/service.rb +4 -4
- data/lib/selenium/webdriver/support/select.rb +1 -1
- data/lib/selenium/webdriver/version.rb +1 -1
- data/selenium-webdriver.gemspec +3 -3
- metadata +14 -5
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Licensed to the Software Freedom Conservancy (SFC) under one
         | 
| 4 | 
            +
            # or more contributor license agreements.  See the NOTICE file
         | 
| 5 | 
            +
            # distributed with this work for additional information
         | 
| 6 | 
            +
            # regarding copyright ownership.  The SFC licenses this file
         | 
| 7 | 
            +
            # to you under the Apache License, Version 2.0 (the
         | 
| 8 | 
            +
            # "License"); you may not use this file except in compliance
         | 
| 9 | 
            +
            # with the License.  You may obtain a copy of the License at
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            #   http://www.apache.org/licenses/LICENSE-2.0
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # Unless required by applicable law or agreed to in writing,
         | 
| 14 | 
            +
            # software distributed under the License is distributed on an
         | 
| 15 | 
            +
            # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
         | 
| 16 | 
            +
            # KIND, either express or implied.  See the License for the
         | 
| 17 | 
            +
            # specific language governing permissions and limitations
         | 
| 18 | 
            +
            # under the License.
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            module Selenium
         | 
| 21 | 
            +
              module WebDriver
         | 
| 22 | 
            +
                module Firefox
         | 
| 23 | 
            +
                  module Bridge
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    COMMANDS = {
         | 
| 26 | 
            +
                      install_addon: [:post, 'session/:session_id/moz/addon/install'],
         | 
| 27 | 
            +
                      uninstall_addon: [:post, 'session/:session_id/moz/addon/uninstall']
         | 
| 28 | 
            +
                    }.freeze
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    def commands(command)
         | 
| 31 | 
            +
                      COMMANDS[command] || super
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    def install_addon(path, temporary)
         | 
| 35 | 
            +
                      payload = {path: path}
         | 
| 36 | 
            +
                      payload[:temporary] = temporary unless temporary.nil?
         | 
| 37 | 
            +
                      execute :install_addon, {}, payload
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def uninstall_addon(id)
         | 
| 41 | 
            +
                      execute :uninstall_addon, {}, {id: id}
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  end # Bridge
         | 
| 45 | 
            +
                end # Firefox
         | 
| 46 | 
            +
              end # WebDriver
         | 
| 47 | 
            +
            end # Selenium
         | 
| @@ -20,30 +20,52 @@ | |
| 20 20 | 
             
            module Selenium
         | 
| 21 21 | 
             
              module WebDriver
         | 
| 22 22 | 
             
                module Firefox
         | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
                       | 
| 38 | 
            -
             | 
| 39 | 
            -
                       | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
                       | 
| 23 | 
            +
             | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # Driver implementation for Firefox using GeckoDriver.
         | 
| 26 | 
            +
                  # @api private
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  class Driver < WebDriver::Driver
         | 
| 30 | 
            +
                    include DriverExtensions::HasAddons
         | 
| 31 | 
            +
                    include DriverExtensions::HasWebStorage
         | 
| 32 | 
            +
                    include DriverExtensions::TakesScreenshot
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    def initialize(opts = {})
         | 
| 35 | 
            +
                      opts[:desired_capabilities] = create_capabilities(opts)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      opts[:url] ||= service_url(opts)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      listener = opts.delete(:listener)
         | 
| 40 | 
            +
                      desired_capabilities = opts.delete(:desired_capabilities)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      @bridge = Remote::Bridge.new(opts)
         | 
| 43 | 
            +
                      @bridge.extend Bridge
         | 
| 44 | 
            +
                      @bridge.create_session(desired_capabilities)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      super(@bridge, listener: listener)
         | 
| 45 47 | 
             
                    end
         | 
| 46 48 |  | 
| 49 | 
            +
                    def browser
         | 
| 50 | 
            +
                      :firefox
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    def quit
         | 
| 54 | 
            +
                      super
         | 
| 55 | 
            +
                    ensure
         | 
| 56 | 
            +
                      @service&.stop
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    private
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    def create_capabilities(opts)
         | 
| 62 | 
            +
                      caps = opts.delete(:desired_capabilities) { Remote::Capabilities.firefox }
         | 
| 63 | 
            +
                      options = opts.delete(:options) { Options.new }
         | 
| 64 | 
            +
                      options = options.as_json
         | 
| 65 | 
            +
                      caps.merge!(options) unless options.empty?
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                      caps
         | 
| 68 | 
            +
                    end
         | 
| 47 69 | 
             
                  end # Driver
         | 
| 48 70 | 
             
                end # Firefox
         | 
| 49 71 | 
             
              end # WebDriver
         | 
| @@ -42,7 +42,7 @@ module Selenium | |
| 42 42 | 
             
                        desired_capabilities = opts.delete(:desired_capabilities)
         | 
| 43 43 | 
             
                        bridge = Remote::Bridge.new(opts)
         | 
| 44 44 | 
             
                        capabilities = bridge.create_session(desired_capabilities)
         | 
| 45 | 
            -
                        @bridge = Remote::W3C::Bridge.new(capabilities, bridge.session_id,  | 
| 45 | 
            +
                        @bridge = Remote::W3C::Bridge.new(capabilities, bridge.session_id, opts)
         | 
| 46 46 | 
             
                        @bridge.extend Marionette::Bridge
         | 
| 47 47 |  | 
| 48 48 | 
             
                        super(@bridge, listener: listener)
         | 
| @@ -20,7 +20,7 @@ | |
| 20 20 | 
             
            module Selenium
         | 
| 21 21 | 
             
              module WebDriver
         | 
| 22 22 | 
             
                module Firefox
         | 
| 23 | 
            -
                  class Options | 
| 23 | 
            +
                  class Options
         | 
| 24 24 | 
             
                    attr_reader :args, :prefs, :options, :profile
         | 
| 25 25 | 
             
                    attr_accessor :binary, :log_level
         | 
| 26 26 |  | 
| @@ -139,7 +139,7 @@ module Selenium | |
| 139 139 | 
             
                      opts[:prefs] = @prefs unless @prefs.empty?
         | 
| 140 140 | 
             
                      opts[:log] = {level: @log_level} if @log_level
         | 
| 141 141 |  | 
| 142 | 
            -
                      {KEY =>  | 
| 142 | 
            +
                      {KEY => opts}
         | 
| 143 143 | 
             
                    end
         | 
| 144 144 |  | 
| 145 145 | 
             
                    private
         | 
| @@ -73,20 +73,9 @@ module Selenium | |
| 73 73 | 
             
                      model_prefs = read_model_prefs
         | 
| 74 74 |  | 
| 75 75 | 
             
                      if model_prefs.empty?
         | 
| 76 | 
            -
                         | 
| 77 | 
            -
                        @secure_ssl        = DEFAULT_SECURE_SSL
         | 
| 78 | 
            -
                        @untrusted_issuer  = DEFAULT_ASSUME_UNTRUSTED_ISSUER
         | 
| 79 | 
            -
                        @load_no_focus_lib = DEFAULT_LOAD_NO_FOCUS_LIB
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                        @additional_prefs  = {}
         | 
| 76 | 
            +
                        assign_default_preferences
         | 
| 82 77 | 
             
                      else
         | 
| 83 | 
            -
                         | 
| 84 | 
            -
                        @native_events     = model_prefs.delete(WEBDRIVER_PREFS[:native_events]) == 'true'
         | 
| 85 | 
            -
                        @secure_ssl        = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_certs]) != 'true'
         | 
| 86 | 
            -
                        @untrusted_issuer  = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_issuer]) == 'true'
         | 
| 87 | 
            -
                        # not stored in profile atm, so will always be false.
         | 
| 88 | 
            -
                        @load_no_focus_lib = model_prefs.delete(WEBDRIVER_PREFS[:load_no_focus_lib]) == 'true'
         | 
| 89 | 
            -
                        @additional_prefs  = model_prefs
         | 
| 78 | 
            +
                        assign_updated_preferences(model_prefs)
         | 
| 90 79 | 
             
                      end
         | 
| 91 80 |  | 
| 92 81 | 
             
                      @extensions = {}
         | 
| @@ -116,7 +105,7 @@ module Selenium | |
| 116 105 | 
             
                        raise TypeError, "expected one of #{VALID_PREFERENCE_TYPES.inspect}, got #{value.inspect}:#{value.class}"
         | 
| 117 106 | 
             
                      end
         | 
| 118 107 |  | 
| 119 | 
            -
                      if value.is_a?(String) &&  | 
| 108 | 
            +
                      if value.is_a?(String) && stringified?(value)
         | 
| 120 109 | 
             
                        raise ArgumentError, "preference values must be plain strings: #{key.inspect} => #{value.inspect}"
         | 
| 121 110 | 
             
                      end
         | 
| 122 111 |  | 
| @@ -195,6 +184,24 @@ module Selenium | |
| 195 184 |  | 
| 196 185 | 
             
                    private
         | 
| 197 186 |  | 
| 187 | 
            +
                    def assign_default_preferences
         | 
| 188 | 
            +
                      @native_events     = DEFAULT_ENABLE_NATIVE_EVENTS
         | 
| 189 | 
            +
                      @secure_ssl        = DEFAULT_SECURE_SSL
         | 
| 190 | 
            +
                      @untrusted_issuer  = DEFAULT_ASSUME_UNTRUSTED_ISSUER
         | 
| 191 | 
            +
                      @load_no_focus_lib = DEFAULT_LOAD_NO_FOCUS_LIB
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                      @additional_prefs  = {}
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    def assign_updated_preferences(model_prefs)
         | 
| 197 | 
            +
                      @native_events     = model_prefs.delete(WEBDRIVER_PREFS[:native_events]) == 'true'
         | 
| 198 | 
            +
                      @secure_ssl        = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_certs]) != 'true'
         | 
| 199 | 
            +
                      @untrusted_issuer  = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_issuer]) == 'true'
         | 
| 200 | 
            +
                      # not stored in profile atm, so will always be false.
         | 
| 201 | 
            +
                      @load_no_focus_lib = model_prefs.delete(WEBDRIVER_PREFS[:load_no_focus_lib]) == 'true'
         | 
| 202 | 
            +
                      @additional_prefs  = model_prefs
         | 
| 203 | 
            +
                    end
         | 
| 204 | 
            +
             | 
| 198 205 | 
             
                    def set_manual_proxy_preference(key, value)
         | 
| 199 206 | 
             
                      return unless value
         | 
| 200 207 |  | 
| @@ -275,6 +282,10 @@ module Selenium | |
| 275 282 | 
             
                        end
         | 
| 276 283 | 
             
                      end
         | 
| 277 284 | 
             
                    end
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                    def stringified?(str)
         | 
| 287 | 
            +
                      /^".*"$/.match?(str)
         | 
| 288 | 
            +
                    end
         | 
| 278 289 | 
             
                  end # Profile
         | 
| 279 290 | 
             
                end # Firefox
         | 
| 280 291 | 
             
              end # WebDriver
         | 
| @@ -25,14 +25,14 @@ module Selenium | |
| 25 25 | 
             
                  #
         | 
| 26 26 |  | 
| 27 27 | 
             
                  class Service < WebDriver::Service
         | 
| 28 | 
            -
                     | 
| 29 | 
            -
                     | 
| 30 | 
            -
                     | 
| 28 | 
            +
                    DEFAULT_PORT = 4444
         | 
| 29 | 
            +
                    EXECUTABLE = 'geckodriver'
         | 
| 30 | 
            +
                    MISSING_TEXT = <<~ERROR
         | 
| 31 31 | 
             
                      Unable to find Mozilla geckodriver. Please download the server from
         | 
| 32 32 | 
             
                      https://github.com/mozilla/geckodriver/releases and place it somewhere on your PATH.
         | 
| 33 33 | 
             
                      More info at https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver.
         | 
| 34 34 | 
             
                    ERROR
         | 
| 35 | 
            -
                     | 
| 35 | 
            +
                    SHUTDOWN_SUPPORTED = false
         | 
| 36 36 |  | 
| 37 37 | 
             
                    private
         | 
| 38 38 |  | 
| @@ -37,7 +37,11 @@ module Selenium | |
| 37 37 | 
             
                      opts[:url] ||= service_url(opts)
         | 
| 38 38 |  | 
| 39 39 | 
             
                      listener = opts.delete(:listener)
         | 
| 40 | 
            -
                       | 
| 40 | 
            +
                      desired_capabilities = opts.delete(:desired_capabilities)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      @bridge = Remote::Bridge.new(opts)
         | 
| 43 | 
            +
                      @bridge.create_session(desired_capabilities)
         | 
| 44 | 
            +
             | 
| 41 45 | 
             
                      super(@bridge, listener: listener)
         | 
| 42 46 | 
             
                    end
         | 
| 43 47 |  | 
| @@ -56,23 +60,6 @@ module Selenium | |
| 56 60 | 
             
                    def create_capabilities(opts)
         | 
| 57 61 | 
             
                      caps = opts.delete(:desired_capabilities) { Remote::Capabilities.internet_explorer }
         | 
| 58 62 | 
             
                      options = opts.delete(:options) { Options.new }
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                      if opts.delete(:introduce_flakiness_by_ignoring_security_domains)
         | 
| 61 | 
            -
                        WebDriver.logger.deprecate ':introduce_flakiness_by_ignoring_security_domains',
         | 
| 62 | 
            -
                                                   'Selenium::WebDriver::IE::Options#ignore_protected_mode_settings='
         | 
| 63 | 
            -
                        options.ignore_protected_mode_settings = true
         | 
| 64 | 
            -
                      end
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                      native_events = opts.delete(:native_events)
         | 
| 67 | 
            -
                      unless native_events.nil?
         | 
| 68 | 
            -
                        WebDriver.logger.deprecate ':native_events', 'Selenium::WebDriver::IE::Options#native_events='
         | 
| 69 | 
            -
                        options.native_events = native_events
         | 
| 70 | 
            -
                      end
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                      # Backward compatibility with older IEDriverServer versions
         | 
| 73 | 
            -
                      caps[:ignore_protected_mode_settings] = options.ignore_protected_mode_settings
         | 
| 74 | 
            -
                      caps[:native_events] = options.native_events
         | 
| 75 | 
            -
             | 
| 76 63 | 
             
                      options = options.as_json
         | 
| 77 64 | 
             
                      caps.merge!(options) unless options.empty?
         | 
| 78 65 |  | 
| @@ -20,7 +20,7 @@ | |
| 20 20 | 
             
            module Selenium
         | 
| 21 21 | 
             
              module WebDriver
         | 
| 22 22 | 
             
                module IE
         | 
| 23 | 
            -
                  class Options | 
| 23 | 
            +
                  class Options
         | 
| 24 24 | 
             
                    KEY = 'se:ieOptions'
         | 
| 25 25 | 
             
                    SCROLL_TOP = 0
         | 
| 26 26 | 
             
                    SCROLL_BOTTOM = 1
         | 
| @@ -130,7 +130,7 @@ module Selenium | |
| 130 130 | 
             
                      opts['ie.browserCommandLineSwitches'] = @args.to_a.join(' ') if @args.any?
         | 
| 131 131 | 
             
                      opts.merge!(@options)
         | 
| 132 132 |  | 
| 133 | 
            -
                      {KEY =>  | 
| 133 | 
            +
                      {KEY => opts}
         | 
| 134 134 | 
             
                    end
         | 
| 135 135 | 
             
                  end # Options
         | 
| 136 136 | 
             
                end # IE
         | 
| @@ -25,14 +25,14 @@ module Selenium | |
| 25 25 | 
             
                  #
         | 
| 26 26 |  | 
| 27 27 | 
             
                  class Service < WebDriver::Service
         | 
| 28 | 
            -
                     | 
| 29 | 
            -
                     | 
| 30 | 
            -
                     | 
| 28 | 
            +
                    DEFAULT_PORT = 5555
         | 
| 29 | 
            +
                    EXECUTABLE = 'IEDriverServer'
         | 
| 30 | 
            +
                    MISSING_TEXT = <<~ERROR
         | 
| 31 31 | 
             
                      Unable to find IEDriverServer. Please download the server from
         | 
| 32 32 | 
             
                      http://selenium-release.storage.googleapis.com/index.html and place it somewhere on your PATH.
         | 
| 33 33 | 
             
                      More info at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver.
         | 
| 34 34 | 
             
                    ERROR
         | 
| 35 | 
            -
                     | 
| 35 | 
            +
                    SHUTDOWN_SUPPORTED = true
         | 
| 36 36 |  | 
| 37 37 | 
             
                    private
         | 
| 38 38 |  | 
| @@ -26,10 +26,6 @@ require 'selenium/webdriver/remote/server_error' | |
| 26 26 | 
             
            require 'selenium/webdriver/remote/http/common'
         | 
| 27 27 | 
             
            require 'selenium/webdriver/remote/http/default'
         | 
| 28 28 |  | 
| 29 | 
            +
            require 'selenium/webdriver/remote/bridge'
         | 
| 29 30 | 
             
            require 'selenium/webdriver/remote/capabilities'
         | 
| 30 | 
            -
            require 'selenium/webdriver/remote/ | 
| 31 | 
            -
            require 'selenium/webdriver/remote/oss/commands'
         | 
| 32 | 
            -
             | 
| 33 | 
            -
            require 'selenium/webdriver/remote/w3c/bridge'
         | 
| 34 | 
            -
            require 'selenium/webdriver/remote/w3c/capabilities'
         | 
| 35 | 
            -
            require 'selenium/webdriver/remote/w3c/commands'
         | 
| 31 | 
            +
            require 'selenium/webdriver/remote/commands'
         | 
| @@ -22,48 +22,11 @@ module Selenium | |
| 22 22 | 
             
                module Remote
         | 
| 23 23 | 
             
                  class Bridge
         | 
| 24 24 | 
             
                    include Atoms
         | 
| 25 | 
            -
                    include BridgeHelper
         | 
| 26 25 |  | 
| 27 26 | 
             
                    PORT = 4444
         | 
| 28 | 
            -
                    COMMANDS = {
         | 
| 29 | 
            -
                      new_session: [:post, 'session']
         | 
| 30 | 
            -
                    }.freeze
         | 
| 31 27 |  | 
| 32 28 | 
             
                    attr_accessor :context, :http, :file_detector
         | 
| 33 | 
            -
                    attr_reader :capabilities | 
| 34 | 
            -
             | 
| 35 | 
            -
                    #
         | 
| 36 | 
            -
                    # Implements protocol handshake which:
         | 
| 37 | 
            -
                    #
         | 
| 38 | 
            -
                    #   1. Creates session with driver.
         | 
| 39 | 
            -
                    #   2. Sniffs response.
         | 
| 40 | 
            -
                    #   3. Based on the response, understands which dialect we should use.
         | 
| 41 | 
            -
                    #
         | 
| 42 | 
            -
                    # @return [OSS:Bridge, W3C::Bridge]
         | 
| 43 | 
            -
                    #
         | 
| 44 | 
            -
                    def self.handshake(**opts)
         | 
| 45 | 
            -
                      desired_capabilities = opts.delete(:desired_capabilities) { Capabilities.new }
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                      if desired_capabilities.is_a?(Symbol)
         | 
| 48 | 
            -
                        unless Capabilities.respond_to?(desired_capabilities)
         | 
| 49 | 
            -
                          raise Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
         | 
| 50 | 
            -
                        end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                        desired_capabilities = Capabilities.__send__(desired_capabilities)
         | 
| 53 | 
            -
                      end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                      bridge = new(opts)
         | 
| 56 | 
            -
                      capabilities = bridge.create_session(desired_capabilities, opts.delete(:options))
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                      case bridge.dialect
         | 
| 59 | 
            -
                      when :oss
         | 
| 60 | 
            -
                        Remote::OSS::Bridge.new(capabilities, bridge.session_id, **opts)
         | 
| 61 | 
            -
                      when :w3c
         | 
| 62 | 
            -
                        Remote::W3C::Bridge.new(capabilities, bridge.session_id, **opts)
         | 
| 63 | 
            -
                      else
         | 
| 64 | 
            -
                        raise WebDriverError, 'cannot understand dialect'
         | 
| 65 | 
            -
                      end
         | 
| 66 | 
            -
                    end
         | 
| 29 | 
            +
                    attr_reader :capabilities
         | 
| 67 30 |  | 
| 68 31 | 
             
                    #
         | 
| 69 32 | 
             
                    # Initializes the bridge with the given server URL
         | 
| @@ -86,7 +49,7 @@ module Selenium | |
| 86 49 | 
             
                      end
         | 
| 87 50 |  | 
| 88 51 | 
             
                      uri = url.is_a?(URI) ? url : URI.parse(url)
         | 
| 89 | 
            -
                      uri.path += '/' unless  | 
| 52 | 
            +
                      uri.path += '/' unless %r{\/$}.match?(uri.path)
         | 
| 90 53 |  | 
| 91 54 | 
             
                      http_client.server_url = uri
         | 
| 92 55 |  | 
| @@ -95,37 +58,18 @@ module Selenium | |
| 95 58 | 
             
                    end
         | 
| 96 59 |  | 
| 97 60 | 
             
                    #
         | 
| 98 | 
            -
                    # Creates session | 
| 61 | 
            +
                    # Creates session.
         | 
| 99 62 | 
             
                    #
         | 
| 100 63 |  | 
| 101 64 | 
             
                    def create_session(desired_capabilities, options = nil)
         | 
| 102 65 | 
             
                      response = execute(:new_session, {}, merged_capabilities(desired_capabilities, options))
         | 
| 103 66 |  | 
| 104 67 | 
             
                      @session_id = response['sessionId']
         | 
| 105 | 
            -
                       | 
| 106 | 
            -
                      value = response['value']
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                      if value.is_a?(Hash)
         | 
| 109 | 
            -
                        @session_id = value['sessionId'] if value.key?('sessionId')
         | 
| 110 | 
            -
             | 
| 111 | 
            -
                        if value.key?('capabilities')
         | 
| 112 | 
            -
                          value = value['capabilities']
         | 
| 113 | 
            -
                        elsif value.key?('value')
         | 
| 114 | 
            -
                          value = value['value']
         | 
| 115 | 
            -
                        end
         | 
| 116 | 
            -
                      end
         | 
| 68 | 
            +
                      capabilities = response['capabilities']
         | 
| 117 69 |  | 
| 118 70 | 
             
                      raise Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
         | 
| 119 71 |  | 
| 120 | 
            -
                       | 
| 121 | 
            -
                        WebDriver.logger.info 'Detected OSS dialect.'
         | 
| 122 | 
            -
                        @dialect = :oss
         | 
| 123 | 
            -
                        Capabilities.json_create(value)
         | 
| 124 | 
            -
                      else
         | 
| 125 | 
            -
                        WebDriver.logger.info 'Detected W3C dialect.'
         | 
| 126 | 
            -
                        @dialect = :w3c
         | 
| 127 | 
            -
                        W3C::Capabilities.json_create(value)
         | 
| 128 | 
            -
                      end
         | 
| 72 | 
            +
                      @capabilities = Capabilities.json_create(capabilities)
         | 
| 129 73 | 
             
                    end
         | 
| 130 74 |  | 
| 131 75 | 
             
                    #
         | 
| @@ -143,6 +87,466 @@ module Selenium | |
| 143 87 | 
             
                      end
         | 
| 144 88 | 
             
                    end
         | 
| 145 89 |  | 
| 90 | 
            +
                    def status
         | 
| 91 | 
            +
                      execute :status
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    def get(url)
         | 
| 95 | 
            +
                      execute :get, {}, {url: url}
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    def implicit_wait_timeout=(milliseconds)
         | 
| 99 | 
            +
                      timeout('implicit', milliseconds)
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    def script_timeout=(milliseconds)
         | 
| 103 | 
            +
                      timeout('script', milliseconds)
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    def timeout(type, milliseconds)
         | 
| 107 | 
            +
                      type = 'pageLoad' if type == 'page load'
         | 
| 108 | 
            +
                      execute :set_timeout, {}, {type => milliseconds}
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    #
         | 
| 112 | 
            +
                    # alerts
         | 
| 113 | 
            +
                    #
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    def accept_alert
         | 
| 116 | 
            +
                      execute :accept_alert
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    def dismiss_alert
         | 
| 120 | 
            +
                      execute :dismiss_alert
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    def alert=(keys)
         | 
| 124 | 
            +
                      execute :send_alert_text, {}, {value: keys.split(//), text: keys}
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                    def alert_text
         | 
| 128 | 
            +
                      execute :get_alert_text
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    #
         | 
| 132 | 
            +
                    # navigation
         | 
| 133 | 
            +
                    #
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    def go_back
         | 
| 136 | 
            +
                      execute :back
         | 
| 137 | 
            +
                    end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                    def go_forward
         | 
| 140 | 
            +
                      execute :forward
         | 
| 141 | 
            +
                    end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    def url
         | 
| 144 | 
            +
                      execute :get_current_url
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    def title
         | 
| 148 | 
            +
                      execute :get_title
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    def page_source
         | 
| 152 | 
            +
                      execute_script('var source = document.documentElement.outerHTML;' \
         | 
| 153 | 
            +
                                        'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
         | 
| 154 | 
            +
                                        'return source;')
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    #
         | 
| 158 | 
            +
                    # Create a new top-level browsing context
         | 
| 159 | 
            +
                    # https://w3c.github.io/webdriver/#new-window
         | 
| 160 | 
            +
                    # @param type [String] Supports two values: 'tab' and 'window'.
         | 
| 161 | 
            +
                    #  Use 'tab' if you'd like the new window to share an OS-level window
         | 
| 162 | 
            +
                    #  with the current browsing context.
         | 
| 163 | 
            +
                    #  Use 'window' otherwise
         | 
| 164 | 
            +
                    # @return [Hash] Containing 'handle' with the value of the window handle
         | 
| 165 | 
            +
                    #  and 'type' with the value of the created window type
         | 
| 166 | 
            +
                    #
         | 
| 167 | 
            +
                    def new_window(type)
         | 
| 168 | 
            +
                      execute :new_window, {}, {type: type}
         | 
| 169 | 
            +
                    end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                    def switch_to_window(name)
         | 
| 172 | 
            +
                      execute :switch_to_window, {}, {handle: name}
         | 
| 173 | 
            +
                    end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                    def switch_to_frame(id)
         | 
| 176 | 
            +
                      id = find_element_by('id', id) if id.is_a? String
         | 
| 177 | 
            +
                      execute :switch_to_frame, {}, {id: id}
         | 
| 178 | 
            +
                    end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                    def switch_to_parent_frame
         | 
| 181 | 
            +
                      execute :switch_to_parent_frame
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    def switch_to_default_content
         | 
| 185 | 
            +
                      switch_to_frame nil
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                    QUIT_ERRORS = [IOError].freeze
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    def quit
         | 
| 191 | 
            +
                      execute :delete_session
         | 
| 192 | 
            +
                      http.close
         | 
| 193 | 
            +
                    rescue *QUIT_ERRORS
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    def close
         | 
| 197 | 
            +
                      execute :close_window
         | 
| 198 | 
            +
                    end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                    def refresh
         | 
| 201 | 
            +
                      execute :refresh
         | 
| 202 | 
            +
                    end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                    #
         | 
| 205 | 
            +
                    # window handling
         | 
| 206 | 
            +
                    #
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                    def window_handles
         | 
| 209 | 
            +
                      execute :get_window_handles
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                    def window_handle
         | 
| 213 | 
            +
                      execute :get_window_handle
         | 
| 214 | 
            +
                    end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                    def resize_window(width, height, handle = :current)
         | 
| 217 | 
            +
                      raise Error::WebDriverError, 'Switch to desired window before changing its size' unless handle == :current
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                      set_window_rect(width: width, height: height)
         | 
| 220 | 
            +
                    end
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                    def window_size(handle = :current)
         | 
| 223 | 
            +
                      raise Error::UnsupportedOperationError, 'Switch to desired window before getting its size' unless handle == :current
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                      data = execute :get_window_rect
         | 
| 226 | 
            +
                      Dimension.new data['width'], data['height']
         | 
| 227 | 
            +
                    end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                    def minimize_window
         | 
| 230 | 
            +
                      execute :minimize_window
         | 
| 231 | 
            +
                    end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                    def maximize_window(handle = :current)
         | 
| 234 | 
            +
                      raise Error::UnsupportedOperationError, 'Switch to desired window before changing its size' unless handle == :current
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                      execute :maximize_window
         | 
| 237 | 
            +
                    end
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                    def full_screen_window
         | 
| 240 | 
            +
                      execute :fullscreen_window
         | 
| 241 | 
            +
                    end
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                    def reposition_window(x, y)
         | 
| 244 | 
            +
                      set_window_rect(x: x, y: y)
         | 
| 245 | 
            +
                    end
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                    def window_position
         | 
| 248 | 
            +
                      data = execute :get_window_rect
         | 
| 249 | 
            +
                      Point.new data['x'], data['y']
         | 
| 250 | 
            +
                    end
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                    def set_window_rect(x: nil, y: nil, width: nil, height: nil)
         | 
| 253 | 
            +
                      params = {x: x, y: y, width: width, height: height}
         | 
| 254 | 
            +
                      params.update(params) { |_k, v| Integer(v) unless v.nil? }
         | 
| 255 | 
            +
                      execute :set_window_rect, {}, params
         | 
| 256 | 
            +
                    end
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                    def window_rect
         | 
| 259 | 
            +
                      data = execute :get_window_rect
         | 
| 260 | 
            +
                      Rectangle.new data['x'], data['y'], data['width'], data['height']
         | 
| 261 | 
            +
                    end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                    def screenshot
         | 
| 264 | 
            +
                      execute :take_screenshot
         | 
| 265 | 
            +
                    end
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                    #
         | 
| 268 | 
            +
                    # HTML 5
         | 
| 269 | 
            +
                    #
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                    def local_storage_item(key, value = nil)
         | 
| 272 | 
            +
                      if value
         | 
| 273 | 
            +
                        execute_script("localStorage.setItem('#{key}', '#{value}')")
         | 
| 274 | 
            +
                      else
         | 
| 275 | 
            +
                        execute_script("return localStorage.getItem('#{key}')")
         | 
| 276 | 
            +
                      end
         | 
| 277 | 
            +
                    end
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                    def remove_local_storage_item(key)
         | 
| 280 | 
            +
                      execute_script("localStorage.removeItem('#{key}')")
         | 
| 281 | 
            +
                    end
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                    def local_storage_keys
         | 
| 284 | 
            +
                      execute_script('return Object.keys(localStorage)')
         | 
| 285 | 
            +
                    end
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                    def clear_local_storage
         | 
| 288 | 
            +
                      execute_script('localStorage.clear()')
         | 
| 289 | 
            +
                    end
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                    def local_storage_size
         | 
| 292 | 
            +
                      execute_script('return localStorage.length')
         | 
| 293 | 
            +
                    end
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                    def session_storage_item(key, value = nil)
         | 
| 296 | 
            +
                      if value
         | 
| 297 | 
            +
                        execute_script("sessionStorage.setItem('#{key}', '#{value}')")
         | 
| 298 | 
            +
                      else
         | 
| 299 | 
            +
                        execute_script("return sessionStorage.getItem('#{key}')")
         | 
| 300 | 
            +
                      end
         | 
| 301 | 
            +
                    end
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                    def remove_session_storage_item(key)
         | 
| 304 | 
            +
                      execute_script("sessionStorage.removeItem('#{key}')")
         | 
| 305 | 
            +
                    end
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                    def session_storage_keys
         | 
| 308 | 
            +
                      execute_script('return Object.keys(sessionStorage)')
         | 
| 309 | 
            +
                    end
         | 
| 310 | 
            +
             | 
| 311 | 
            +
                    def clear_session_storage
         | 
| 312 | 
            +
                      execute_script('sessionStorage.clear()')
         | 
| 313 | 
            +
                    end
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                    def session_storage_size
         | 
| 316 | 
            +
                      execute_script('return sessionStorage.length')
         | 
| 317 | 
            +
                    end
         | 
| 318 | 
            +
             | 
| 319 | 
            +
                    def location
         | 
| 320 | 
            +
                      raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting location'
         | 
| 321 | 
            +
                    end
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                    def set_location(_lat, _lon, _alt)
         | 
| 324 | 
            +
                      raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting location'
         | 
| 325 | 
            +
                    end
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                    def network_connection
         | 
| 328 | 
            +
                      raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting network connection'
         | 
| 329 | 
            +
                    end
         | 
| 330 | 
            +
             | 
| 331 | 
            +
                    def network_connection=(_type)
         | 
| 332 | 
            +
                      raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting network connection'
         | 
| 333 | 
            +
                    end
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                    #
         | 
| 336 | 
            +
                    # javascript execution
         | 
| 337 | 
            +
                    #
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                    def execute_script(script, *args)
         | 
| 340 | 
            +
                      result = execute :execute_script, {}, {script: script, args: args}
         | 
| 341 | 
            +
                      unwrap_script_result result
         | 
| 342 | 
            +
                    end
         | 
| 343 | 
            +
             | 
| 344 | 
            +
                    def execute_async_script(script, *args)
         | 
| 345 | 
            +
                      result = execute :execute_async_script, {}, {script: script, args: args}
         | 
| 346 | 
            +
                      unwrap_script_result result
         | 
| 347 | 
            +
                    end
         | 
| 348 | 
            +
             | 
| 349 | 
            +
                    #
         | 
| 350 | 
            +
                    # cookies
         | 
| 351 | 
            +
                    #
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                    def manage
         | 
| 354 | 
            +
                      @manage ||= WebDriver::Manager.new(self)
         | 
| 355 | 
            +
                    end
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                    def add_cookie(cookie)
         | 
| 358 | 
            +
                      execute :add_cookie, {}, {cookie: cookie}
         | 
| 359 | 
            +
                    end
         | 
| 360 | 
            +
             | 
| 361 | 
            +
                    def delete_cookie(name)
         | 
| 362 | 
            +
                      execute :delete_cookie, name: name
         | 
| 363 | 
            +
                    end
         | 
| 364 | 
            +
             | 
| 365 | 
            +
                    def cookie(name)
         | 
| 366 | 
            +
                      execute :get_cookie, name: name
         | 
| 367 | 
            +
                    end
         | 
| 368 | 
            +
             | 
| 369 | 
            +
                    def cookies
         | 
| 370 | 
            +
                      execute :get_all_cookies
         | 
| 371 | 
            +
                    end
         | 
| 372 | 
            +
             | 
| 373 | 
            +
                    def delete_all_cookies
         | 
| 374 | 
            +
                      execute :delete_all_cookies
         | 
| 375 | 
            +
                    end
         | 
| 376 | 
            +
             | 
| 377 | 
            +
                    #
         | 
| 378 | 
            +
                    # actions
         | 
| 379 | 
            +
                    #
         | 
| 380 | 
            +
             | 
| 381 | 
            +
                    def action(async = false)
         | 
| 382 | 
            +
                      ActionBuilder.new self,
         | 
| 383 | 
            +
                                        Interactions.pointer(:mouse, name: 'mouse'),
         | 
| 384 | 
            +
                                        Interactions.key('keyboard'),
         | 
| 385 | 
            +
                                        async
         | 
| 386 | 
            +
                    end
         | 
| 387 | 
            +
                    alias_method :actions, :action
         | 
| 388 | 
            +
             | 
| 389 | 
            +
                    def mouse
         | 
| 390 | 
            +
                      raise Error::UnsupportedOperationError, '#mouse is no longer supported, use #action instead'
         | 
| 391 | 
            +
                    end
         | 
| 392 | 
            +
             | 
| 393 | 
            +
                    def keyboard
         | 
| 394 | 
            +
                      raise Error::UnsupportedOperationError, '#keyboard is no longer supported, use #action instead'
         | 
| 395 | 
            +
                    end
         | 
| 396 | 
            +
             | 
| 397 | 
            +
                    def send_actions(data)
         | 
| 398 | 
            +
                      execute :actions, {}, {actions: data}
         | 
| 399 | 
            +
                    end
         | 
| 400 | 
            +
             | 
| 401 | 
            +
                    def release_actions
         | 
| 402 | 
            +
                      execute :release_actions
         | 
| 403 | 
            +
                    end
         | 
| 404 | 
            +
             | 
| 405 | 
            +
                    def click_element(element)
         | 
| 406 | 
            +
                      execute :element_click, id: element
         | 
| 407 | 
            +
                    end
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                    def send_keys_to_element(element, keys)
         | 
| 410 | 
            +
                      # TODO: rework file detectors before Selenium 4.0
         | 
| 411 | 
            +
                      if @file_detector
         | 
| 412 | 
            +
                        local_files = keys.first.split("\n").map { |key| @file_detector.call(Array(key)) }.compact
         | 
| 413 | 
            +
                        if local_files.any?
         | 
| 414 | 
            +
                          keys = local_files.map { |local_file| upload(local_file) }
         | 
| 415 | 
            +
                          keys = Array(keys.join("\n"))
         | 
| 416 | 
            +
                        end
         | 
| 417 | 
            +
                      end
         | 
| 418 | 
            +
             | 
| 419 | 
            +
                      # Keep .split(//) for backward compatibility for now
         | 
| 420 | 
            +
                      text = keys.join('')
         | 
| 421 | 
            +
                      execute :element_send_keys, {id: element}, {value: text.split(//), text: text}
         | 
| 422 | 
            +
                    end
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                    def upload(local_file)
         | 
| 425 | 
            +
                      unless File.file?(local_file)
         | 
| 426 | 
            +
                        WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!")
         | 
| 427 | 
            +
                        raise Error::WebDriverError, "You are trying to work with something that isn't a file."
         | 
| 428 | 
            +
                      end
         | 
| 429 | 
            +
             | 
| 430 | 
            +
                      execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
         | 
| 431 | 
            +
                    end
         | 
| 432 | 
            +
             | 
| 433 | 
            +
                    def clear_element(element)
         | 
| 434 | 
            +
                      execute :element_clear, id: element
         | 
| 435 | 
            +
                    end
         | 
| 436 | 
            +
             | 
| 437 | 
            +
                    def submit_element(element)
         | 
| 438 | 
            +
                      form = find_element_by('xpath', "./ancestor-or-self::form", element)
         | 
| 439 | 
            +
                      execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
         | 
| 440 | 
            +
                                        "e.initEvent('submit', true, true);" \
         | 
| 441 | 
            +
                                        'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
         | 
| 442 | 
            +
                    end
         | 
| 443 | 
            +
             | 
| 444 | 
            +
                    def screen_orientation=(orientation)
         | 
| 445 | 
            +
                      execute :set_screen_orientation, {}, {orientation: orientation}
         | 
| 446 | 
            +
                    end
         | 
| 447 | 
            +
             | 
| 448 | 
            +
                    def screen_orientation
         | 
| 449 | 
            +
                      execute :get_screen_orientation
         | 
| 450 | 
            +
                    end
         | 
| 451 | 
            +
             | 
| 452 | 
            +
                    #
         | 
| 453 | 
            +
                    # element properties
         | 
| 454 | 
            +
                    #
         | 
| 455 | 
            +
             | 
| 456 | 
            +
                    def element_tag_name(element)
         | 
| 457 | 
            +
                      execute :get_element_tag_name, id: element
         | 
| 458 | 
            +
                    end
         | 
| 459 | 
            +
             | 
| 460 | 
            +
                    def element_attribute(element, name)
         | 
| 461 | 
            +
                      WebDriver.logger.info "Using script for :getAttribute of #{name}"
         | 
| 462 | 
            +
                      execute_atom :getAttribute, element, name
         | 
| 463 | 
            +
                    end
         | 
| 464 | 
            +
             | 
| 465 | 
            +
                    def element_property(element, name)
         | 
| 466 | 
            +
                      execute :get_element_property, id: element.ref, name: name
         | 
| 467 | 
            +
                    end
         | 
| 468 | 
            +
             | 
| 469 | 
            +
                    def element_value(element)
         | 
| 470 | 
            +
                      element_property element, 'value'
         | 
| 471 | 
            +
                    end
         | 
| 472 | 
            +
             | 
| 473 | 
            +
                    def element_text(element)
         | 
| 474 | 
            +
                      execute :get_element_text, id: element
         | 
| 475 | 
            +
                    end
         | 
| 476 | 
            +
             | 
| 477 | 
            +
                    def element_location(element)
         | 
| 478 | 
            +
                      data = execute :get_element_rect, id: element
         | 
| 479 | 
            +
             | 
| 480 | 
            +
                      Point.new data['x'], data['y']
         | 
| 481 | 
            +
                    end
         | 
| 482 | 
            +
             | 
| 483 | 
            +
                    def element_rect(element)
         | 
| 484 | 
            +
                      data = execute :get_element_rect, id: element
         | 
| 485 | 
            +
             | 
| 486 | 
            +
                      Rectangle.new data['x'], data['y'], data['width'], data['height']
         | 
| 487 | 
            +
                    end
         | 
| 488 | 
            +
             | 
| 489 | 
            +
                    def element_location_once_scrolled_into_view(element)
         | 
| 490 | 
            +
                      send_keys_to_element(element, [''])
         | 
| 491 | 
            +
                      element_location(element)
         | 
| 492 | 
            +
                    end
         | 
| 493 | 
            +
             | 
| 494 | 
            +
                    def element_size(element)
         | 
| 495 | 
            +
                      data = execute :get_element_rect, id: element
         | 
| 496 | 
            +
             | 
| 497 | 
            +
                      Dimension.new data['width'], data['height']
         | 
| 498 | 
            +
                    end
         | 
| 499 | 
            +
             | 
| 500 | 
            +
                    def element_enabled?(element)
         | 
| 501 | 
            +
                      execute :is_element_enabled, id: element
         | 
| 502 | 
            +
                    end
         | 
| 503 | 
            +
             | 
| 504 | 
            +
                    def element_selected?(element)
         | 
| 505 | 
            +
                      execute :is_element_selected, id: element
         | 
| 506 | 
            +
                    end
         | 
| 507 | 
            +
             | 
| 508 | 
            +
                    def element_displayed?(element)
         | 
| 509 | 
            +
                      WebDriver.logger.info 'Using script for :isDisplayed'
         | 
| 510 | 
            +
                      execute_atom :isDisplayed, element
         | 
| 511 | 
            +
                    end
         | 
| 512 | 
            +
             | 
| 513 | 
            +
                    def element_value_of_css_property(element, prop)
         | 
| 514 | 
            +
                      execute :get_element_css_value, id: element, property_name: prop
         | 
| 515 | 
            +
                    end
         | 
| 516 | 
            +
             | 
| 517 | 
            +
                    #
         | 
| 518 | 
            +
                    # finding elements
         | 
| 519 | 
            +
                    #
         | 
| 520 | 
            +
             | 
| 521 | 
            +
                    def active_element
         | 
| 522 | 
            +
                      Element.new self, element_id_from(execute(:get_active_element))
         | 
| 523 | 
            +
                    end
         | 
| 524 | 
            +
             | 
| 525 | 
            +
                    alias_method :switch_to_active_element, :active_element
         | 
| 526 | 
            +
             | 
| 527 | 
            +
                    def find_element_by(how, what, parent = nil)
         | 
| 528 | 
            +
                      how, what = convert_locators(how, what)
         | 
| 529 | 
            +
             | 
| 530 | 
            +
                      id = if parent
         | 
| 531 | 
            +
                             execute :find_child_element, {id: parent}, {using: how, value: what}
         | 
| 532 | 
            +
                           else
         | 
| 533 | 
            +
                             execute :find_element, {}, {using: how, value: what}
         | 
| 534 | 
            +
                           end
         | 
| 535 | 
            +
                      Element.new self, element_id_from(id)
         | 
| 536 | 
            +
                    end
         | 
| 537 | 
            +
             | 
| 538 | 
            +
                    def find_elements_by(how, what, parent = nil)
         | 
| 539 | 
            +
                      how, what = convert_locators(how, what)
         | 
| 540 | 
            +
             | 
| 541 | 
            +
                      ids = if parent
         | 
| 542 | 
            +
                              execute :find_child_elements, {id: parent}, {using: how, value: what}
         | 
| 543 | 
            +
                            else
         | 
| 544 | 
            +
                              execute :find_elements, {}, {using: how, value: what}
         | 
| 545 | 
            +
                            end
         | 
| 546 | 
            +
             | 
| 547 | 
            +
                      ids.map { |id| Element.new self, element_id_from(id) }
         | 
| 548 | 
            +
                    end
         | 
| 549 | 
            +
             | 
| 146 550 | 
             
                    private
         | 
| 147 551 |  | 
| 148 552 | 
             
                    #
         | 
| @@ -164,7 +568,7 @@ module Selenium | |
| 164 568 | 
             
                      end
         | 
| 165 569 |  | 
| 166 570 | 
             
                      WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
         | 
| 167 | 
            -
                      http.call(verb, path, command_hash)
         | 
| 571 | 
            +
                      http.call(verb, path, command_hash)['value']
         | 
| 168 572 | 
             
                    end
         | 
| 169 573 |  | 
| 170 574 | 
             
                    def escaper
         | 
| @@ -172,23 +576,65 @@ module Selenium | |
| 172 576 | 
             
                    end
         | 
| 173 577 |  | 
| 174 578 | 
             
                    def commands(command)
         | 
| 175 | 
            -
                      raise NotImplementedError unless command == :new_session
         | 
| 176 | 
            -
             | 
| 177 579 | 
             
                      COMMANDS[command]
         | 
| 178 580 | 
             
                    end
         | 
| 179 581 |  | 
| 180 | 
            -
                    def merged_capabilities( | 
| 181 | 
            -
                       | 
| 182 | 
            -
                      w3c_capabilities.merge!(options.as_json) if options
         | 
| 582 | 
            +
                    def merged_capabilities(capabilities, options = nil)
         | 
| 583 | 
            +
                      capabilities.merge!(options.as_json) if options
         | 
| 183 584 |  | 
| 184 585 | 
             
                      {
         | 
| 185 | 
            -
                        desiredCapabilities: oss_capabilities,
         | 
| 186 586 | 
             
                        capabilities: {
         | 
| 187 | 
            -
                          firstMatch: [ | 
| 587 | 
            +
                          firstMatch: [capabilities]
         | 
| 188 588 | 
             
                        }
         | 
| 189 589 | 
             
                      }
         | 
| 190 590 | 
             
                    end
         | 
| 191 591 |  | 
| 592 | 
            +
                    def unwrap_script_result(arg)
         | 
| 593 | 
            +
                      case arg
         | 
| 594 | 
            +
                      when Array
         | 
| 595 | 
            +
                        arg.map { |e| unwrap_script_result(e) }
         | 
| 596 | 
            +
                      when Hash
         | 
| 597 | 
            +
                        element_id = element_id_from(arg)
         | 
| 598 | 
            +
                        return Element.new(self, element_id) if element_id
         | 
| 599 | 
            +
             | 
| 600 | 
            +
                        arg.each { |k, v| arg[k] = unwrap_script_result(v) }
         | 
| 601 | 
            +
                      else
         | 
| 602 | 
            +
                        arg
         | 
| 603 | 
            +
                      end
         | 
| 604 | 
            +
                    end
         | 
| 605 | 
            +
             | 
| 606 | 
            +
                    def element_id_from(id)
         | 
| 607 | 
            +
                      id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
         | 
| 608 | 
            +
                    end
         | 
| 609 | 
            +
             | 
| 610 | 
            +
                    def convert_locators(how, what)
         | 
| 611 | 
            +
                      case how
         | 
| 612 | 
            +
                      when 'class name'
         | 
| 613 | 
            +
                        how = 'css selector'
         | 
| 614 | 
            +
                        what = ".#{escape_css(what)}"
         | 
| 615 | 
            +
                      when 'id'
         | 
| 616 | 
            +
                        how = 'css selector'
         | 
| 617 | 
            +
                        what = "##{escape_css(what)}"
         | 
| 618 | 
            +
                      when 'name'
         | 
| 619 | 
            +
                        how = 'css selector'
         | 
| 620 | 
            +
                        what = "*[name='#{escape_css(what)}']"
         | 
| 621 | 
            +
                      when 'tag name'
         | 
| 622 | 
            +
                        how = 'css selector'
         | 
| 623 | 
            +
                      end
         | 
| 624 | 
            +
                      [how, what]
         | 
| 625 | 
            +
                    end
         | 
| 626 | 
            +
             | 
| 627 | 
            +
                    ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]\(\)])/.freeze
         | 
| 628 | 
            +
                    UNICODE_CODE_POINT = 30
         | 
| 629 | 
            +
             | 
| 630 | 
            +
                    # Escapes invalid characters in CSS selector.
         | 
| 631 | 
            +
                    # @see https://mathiasbynens.be/notes/css-escapes
         | 
| 632 | 
            +
                    def escape_css(string)
         | 
| 633 | 
            +
                      string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
         | 
| 634 | 
            +
                      string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..-1]}" if !string.empty? && string[0].match?(/[[:digit:]]/)
         | 
| 635 | 
            +
             | 
| 636 | 
            +
                      string
         | 
| 637 | 
            +
                    end
         | 
| 192 638 | 
             
                  end # Bridge
         | 
| 193 639 | 
             
                end # Remote
         | 
| 194 640 | 
             
              end # WebDriver
         |