selenium-webdriver 3.0.0.beta4.0 → 4.0.0.alpha5

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.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +679 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE +1 -1
  5. data/README.md +1 -1
  6. data/lib/selenium-webdriver.rb +2 -2
  7. data/lib/selenium/server.rb +23 -16
  8. data/lib/selenium/webdriver.rb +43 -25
  9. data/lib/selenium/webdriver/atoms.rb +20 -1
  10. data/lib/selenium/webdriver/atoms/findElements.js +122 -0
  11. data/lib/selenium/webdriver/atoms/getAttribute.js +84 -11
  12. data/lib/selenium/webdriver/atoms/isDisplayed.js +100 -0
  13. data/lib/selenium/webdriver/chrome.rb +15 -20
  14. data/lib/selenium/webdriver/chrome/bridge.rb +29 -66
  15. data/lib/selenium/webdriver/{edge/service.rb → chrome/driver.rb} +19 -22
  16. data/lib/selenium/webdriver/chrome/options.rb +221 -0
  17. data/lib/selenium/webdriver/chrome/profile.rb +6 -7
  18. data/lib/selenium/webdriver/chrome/service.rb +20 -20
  19. data/lib/selenium/webdriver/common.rb +19 -11
  20. data/lib/selenium/webdriver/common/action_builder.rb +98 -246
  21. data/lib/selenium/webdriver/common/alert.rb +2 -7
  22. data/lib/selenium/webdriver/common/driver.rb +89 -51
  23. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +45 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +50 -0
  25. data/lib/selenium/webdriver/common/driver_extensions/{has_input_devices.rb → has_debugger.rb} +12 -25
  26. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +38 -0
  27. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +3 -5
  28. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +51 -0
  29. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +3 -3
  30. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +51 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +2 -2
  32. data/lib/selenium/webdriver/common/driver_extensions/has_session_id.rb +2 -2
  33. data/lib/selenium/webdriver/common/driver_extensions/has_web_storage.rb +2 -2
  34. data/lib/selenium/webdriver/common/driver_extensions/rotatable.rb +4 -4
  35. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +9 -3
  36. data/lib/selenium/webdriver/common/driver_extensions/uploads_files.rb +3 -5
  37. data/lib/selenium/webdriver/common/element.rb +32 -10
  38. data/lib/selenium/webdriver/common/error.rb +103 -121
  39. data/lib/selenium/webdriver/common/file_reaper.rb +3 -5
  40. data/lib/selenium/webdriver/common/html5/local_storage.rb +2 -2
  41. data/lib/selenium/webdriver/common/html5/session_storage.rb +2 -2
  42. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +3 -2
  43. data/lib/selenium/webdriver/common/interactions/input_device.rb +54 -0
  44. data/lib/selenium/webdriver/common/interactions/interaction.rb +53 -0
  45. data/lib/selenium/webdriver/{phantomjs.rb → common/interactions/interactions.rb} +17 -20
  46. data/lib/selenium/webdriver/common/interactions/key_actions.rb +145 -0
  47. data/lib/selenium/webdriver/common/interactions/key_input.rb +66 -0
  48. data/lib/selenium/webdriver/common/interactions/none_input.rb +36 -0
  49. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +363 -0
  50. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +139 -0
  51. data/lib/selenium/webdriver/common/keys.rb +37 -15
  52. data/lib/selenium/webdriver/common/log_entry.rb +4 -4
  53. data/lib/selenium/webdriver/common/logger.rb +147 -0
  54. data/lib/selenium/webdriver/common/logs.rb +2 -2
  55. data/lib/selenium/webdriver/common/manager.rb +177 -0
  56. data/lib/selenium/webdriver/common/navigation.rb +2 -2
  57. data/lib/selenium/webdriver/common/options.rb +47 -105
  58. data/lib/selenium/webdriver/common/platform.rb +44 -38
  59. data/lib/selenium/webdriver/common/port_prober.rb +8 -19
  60. data/lib/selenium/webdriver/common/profile_helper.rb +13 -5
  61. data/lib/selenium/webdriver/common/proxy.rb +14 -8
  62. data/lib/selenium/webdriver/common/search_context.rb +16 -20
  63. data/lib/selenium/webdriver/common/service.rb +115 -30
  64. data/lib/selenium/webdriver/common/socket_lock.rb +12 -7
  65. data/lib/selenium/webdriver/common/socket_poller.rb +29 -22
  66. data/lib/selenium/webdriver/common/target_locator.rb +6 -6
  67. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  68. data/lib/selenium/webdriver/common/wait.rb +14 -8
  69. data/lib/selenium/webdriver/common/window.rb +38 -2
  70. data/lib/selenium/webdriver/common/zipper.rb +3 -5
  71. data/lib/selenium/webdriver/edge.rb +33 -20
  72. data/lib/selenium/webdriver/edge_chrome/bridge.rb +30 -0
  73. data/lib/selenium/webdriver/edge_chrome/driver.rb +38 -0
  74. data/lib/selenium/webdriver/{common/driver_extensions/has_touch_screen.rb → edge_chrome/options.rb} +10 -12
  75. data/lib/selenium/webdriver/edge_chrome/profile.rb +33 -0
  76. data/lib/selenium/webdriver/edge_chrome/service.rb +36 -0
  77. data/lib/selenium/webdriver/edge_html/driver.rb +39 -0
  78. data/lib/selenium/webdriver/edge_html/options.rb +91 -0
  79. data/lib/selenium/webdriver/edge_html/service.rb +47 -0
  80. data/lib/selenium/webdriver/firefox.rb +24 -29
  81. data/lib/selenium/webdriver/firefox/bridge.rb +15 -39
  82. data/lib/selenium/webdriver/firefox/{util.rb → driver.rb} +13 -19
  83. data/lib/selenium/webdriver/firefox/extension.rb +28 -8
  84. data/lib/selenium/webdriver/firefox/options.rb +162 -0
  85. data/lib/selenium/webdriver/firefox/profile.rb +23 -82
  86. data/lib/selenium/webdriver/firefox/profiles_ini.rb +5 -5
  87. data/lib/selenium/webdriver/firefox/service.rb +18 -37
  88. data/lib/selenium/webdriver/ie.rb +13 -19
  89. data/lib/selenium/webdriver/ie/driver.rb +40 -0
  90. data/lib/selenium/webdriver/ie/options.rb +118 -0
  91. data/lib/selenium/webdriver/ie/service.rb +20 -20
  92. data/lib/selenium/webdriver/remote.rb +15 -17
  93. data/lib/selenium/webdriver/remote/bridge.rb +281 -300
  94. data/lib/selenium/webdriver/remote/capabilities.rb +102 -83
  95. data/lib/selenium/webdriver/remote/commands.rb +132 -192
  96. data/lib/selenium/webdriver/remote/driver.rb +52 -0
  97. data/lib/selenium/webdriver/remote/http/common.rb +23 -13
  98. data/lib/selenium/webdriver/remote/http/curb.rb +10 -7
  99. data/lib/selenium/webdriver/remote/http/default.rb +52 -32
  100. data/lib/selenium/webdriver/remote/http/persistent.rb +8 -4
  101. data/lib/selenium/webdriver/remote/response.rb +32 -35
  102. data/lib/selenium/webdriver/remote/server_error.rb +2 -2
  103. data/lib/selenium/webdriver/safari.rb +24 -22
  104. data/lib/selenium/webdriver/safari/bridge.rb +21 -21
  105. data/lib/selenium/webdriver/safari/driver.rb +41 -0
  106. data/lib/selenium/webdriver/safari/options.rb +66 -0
  107. data/lib/selenium/webdriver/safari/service.rb +8 -24
  108. data/lib/selenium/webdriver/support.rb +3 -2
  109. data/lib/selenium/webdriver/support/abstract_event_listener.rb +2 -2
  110. data/lib/selenium/webdriver/support/block_event_listener.rb +3 -3
  111. data/lib/selenium/webdriver/support/color.rb +13 -13
  112. data/lib/selenium/webdriver/support/escaper.rb +2 -2
  113. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  114. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  115. data/lib/selenium/webdriver/support/select.rb +20 -21
  116. data/lib/selenium/webdriver/version.rb +24 -0
  117. data/selenium-webdriver.gemspec +31 -17
  118. metadata +331 -248
  119. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  120. data/lib/selenium/webdriver/common/keyboard.rb +0 -69
  121. data/lib/selenium/webdriver/common/mouse.rb +0 -88
  122. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -81
  123. data/lib/selenium/webdriver/common/touch_screen.rb +0 -121
  124. data/lib/selenium/webdriver/common/w3c_error.rb +0 -191
  125. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  126. data/lib/selenium/webdriver/edge/legacy_support.rb +0 -117
  127. data/lib/selenium/webdriver/firefox/binary.rb +0 -186
  128. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  129. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  130. data/lib/selenium/webdriver/firefox/launcher.rb +0 -114
  131. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  132. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  133. data/lib/selenium/webdriver/firefox/w3c_bridge.rb +0 -79
  134. data/lib/selenium/webdriver/ie/bridge.rb +0 -76
  135. data/lib/selenium/webdriver/phantomjs/bridge.rb +0 -65
  136. data/lib/selenium/webdriver/phantomjs/service.rb +0 -66
  137. data/lib/selenium/webdriver/remote/w3c_bridge.rb +0 -682
  138. data/lib/selenium/webdriver/remote/w3c_capabilities.rb +0 -228
  139. data/lib/selenium/webdriver/remote/w3c_commands.rb +0 -135
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -20,46 +20,27 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Firefox
23
- #
24
- # @api private
25
- #
26
-
27
23
  class Service < WebDriver::Service
28
24
  DEFAULT_PORT = 4444
25
+ EXECUTABLE = 'geckodriver'
26
+ MISSING_TEXT = <<~ERROR
27
+ Unable to find Mozilla geckodriver. Please download the server from
28
+ https://github.com/mozilla/geckodriver/releases and place it somewhere on your PATH.
29
+ More info at https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver.
30
+ ERROR
31
+ SHUTDOWN_SUPPORTED = false
29
32
 
30
33
  private
31
34
 
32
- def start_process
33
- server_command = [@executable_path, "--binary=#{Firefox::Binary.path}", "--port=#{@port}", *@extra_args]
34
- @process = ChildProcess.build(*server_command)
35
-
36
- if $DEBUG
37
- @process.io.inherit!
38
- elsif Platform.windows?
39
- # workaround stdio inheritance issue
40
- # https://github.com/mozilla/geckodriver/issues/48
41
- @process.io.stdout = @process.io.stderr = File.new(Platform.null_device, 'w')
42
- end
43
-
44
- @process.start
45
- end
46
-
47
- def stop_process
48
- super
49
- return unless Platform.windows? && !$DEBUG
50
- begin
51
- @process.io.close
52
- rescue
53
- nil
54
- end
55
- end
56
-
57
- def stop_server
58
- connect_to_server { |http| http.head('/shutdown') }
59
- end
60
-
61
- def cannot_connect_error_text
62
- "unable to connect to Mozilla geckodriver #{@host}:#{@port}"
35
+ # Note: This processing is deprecated
36
+ def extract_service_args(driver_opts)
37
+ driver_args = super
38
+ driver_opts = driver_opts.dup
39
+ driver_args << "--binary=#{driver_opts[:binary]}" if driver_opts.key?(:binary)
40
+ driver_args << "--log=#{driver_opts[:log]}" if driver_opts.key?(:log)
41
+ driver_args << "--marionette-port=#{driver_opts[:marionette_port]}" if driver_opts.key?(:marionette_port)
42
+ driver_args << "--host=#{driver_opts[:host]}" if driver_opts.key?(:host)
43
+ driver_args
63
44
  end
64
45
  end # Service
65
46
  end # Firefox
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -17,31 +17,25 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- require 'selenium/webdriver/ie/bridge'
21
- require 'selenium/webdriver/ie/service'
22
-
23
20
  module Selenium
24
21
  module WebDriver
25
22
  module IE
26
- MISSING_TEXT = <<-ERROR.tr("\n", '').freeze
27
- Unable to find IEDriverServer. Please download the server from
28
- http://selenium-release.storage.googleapis.com/index.html and place it
29
- somewhere on your PATH. More info at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver.
30
- ERROR
23
+ autoload :Driver, 'selenium/webdriver/ie/driver'
24
+ autoload :Options, 'selenium/webdriver/ie/options'
25
+ autoload :Service, 'selenium/webdriver/ie/service'
31
26
 
32
27
  def self.driver_path=(path)
33
- Platform.assert_executable path
34
- @driver_path = path
28
+ WebDriver.logger.deprecate 'Selenium::WebDriver::IE#driver_path=',
29
+ 'Selenium::WebDriver::IE::Service#driver_path=',
30
+ id: :driver_path
31
+ Selenium::WebDriver::IE::Service.driver_path = path
35
32
  end
36
33
 
37
34
  def self.driver_path
38
- @driver_path ||= begin
39
- path = Platform.find_binary('IEDriverServer')
40
- raise Error::WebDriverError, MISSING_TEXT unless path
41
- Platform.assert_executable path
42
-
43
- path
44
- end
35
+ WebDriver.logger.deprecate 'Selenium::WebDriver::IE#driver_path',
36
+ 'Selenium::WebDriver::IE::Service#driver_path',
37
+ id: :driver_path
38
+ Selenium::WebDriver::IE::Service.driver_path
45
39
  end
46
40
  end # IE
47
41
  end # WebDriver
@@ -0,0 +1,40 @@
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 IE
23
+
24
+ #
25
+ # Driver implementation for Internet Explorer supporting
26
+ # both OSS and W3C dialects of JSON wire protocol.
27
+ # @api private
28
+ #
29
+
30
+ class Driver < WebDriver::Driver
31
+ include DriverExtensions::HasWebStorage
32
+ include DriverExtensions::TakesScreenshot
33
+
34
+ def browser
35
+ :internet_explorer
36
+ end
37
+ end # Driver
38
+ end # IE
39
+ end # WebDriver
40
+ end # Selenium
@@ -0,0 +1,118 @@
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 IE
23
+ class Options < WebDriver::Options
24
+ KEY = 'se:ieOptions'
25
+ SCROLL_TOP = 0
26
+ SCROLL_BOTTOM = 1
27
+ CAPABILITIES = {
28
+ browser_attach_timeout: 'browserAttachTimeout',
29
+ element_scroll_behavior: 'elementScrollBehavior',
30
+ full_page_screenshot: 'ie.enableFullPageScreenshot',
31
+ ensure_clean_session: 'ie.ensureCleanSession',
32
+ file_upload_dialog_timeout: 'ie.fileUploadDialogTimeout',
33
+ force_create_process_api: 'ie.forceCreateProcessApi',
34
+ force_shell_windows_api: 'ie.forceShellWindowsApi',
35
+ ignore_protected_mode_settings: 'ignoreProtectedModeSettings',
36
+ ignore_zoom_level: 'ignoreZoomSetting',
37
+ initial_browser_url: 'initialBrowserUrl',
38
+ native_events: 'nativeEvents',
39
+ persistent_hover: 'enablePersistentHover',
40
+ require_window_focus: 'requireWindowFocus',
41
+ use_per_process_proxy: 'ie.usePerProcessProxy',
42
+ validate_cookie_document_type: 'ie.validateCookieDocumentType'
43
+ }.freeze
44
+
45
+ CAPABILITIES.each_key do |key|
46
+ define_method key do
47
+ @options[key]
48
+ end
49
+
50
+ define_method "#{key}=" do |value|
51
+ @options[key] = value
52
+ end
53
+ end
54
+
55
+ attr_reader :args
56
+
57
+ #
58
+ # Create a new Options instance
59
+ #
60
+ # @example
61
+ # options = Selenium::WebDriver::IE::Options.new(args: ['--host=127.0.0.1'])
62
+ # driver = Selenium::WebDriver.for(:ie, options: options)
63
+ #
64
+ # @example
65
+ # options = Selenium::WebDriver::IE::Options.new
66
+ # options.element_scroll_behavior = Selenium::WebDriver::IE::Options::SCROLL_BOTTOM
67
+ # driver = Selenium::WebDriver.for(:ie, options: options)
68
+ #
69
+ # @param [Hash] opts the pre-defined options
70
+ # @option opts [Array<String>] args
71
+ # @option opts [Integer] browser_attach_timeout
72
+ # @option opts [Integer] element_scroll_behavior Either SCROLL_TOP or SCROLL_BOTTOM
73
+ # @option opts [Boolean] full_page_screenshot
74
+ # @option opts [Boolean] ensure_clean_session
75
+ # @option opts [Integer] file_upload_dialog_timeout
76
+ # @option opts [Boolean] force_create_process_api
77
+ # @option opts [Boolean] force_shell_windows_api
78
+ # @option opts [Boolean] ignore_protected_mode_settings
79
+ # @option opts [Boolean] ignore_zoom_level
80
+ # @option opts [String] initial_browser_url
81
+ # @option opts [Boolean] native_events
82
+ # @option opts [Boolean] persistent_hover
83
+ # @option opts [Boolean] require_window_focus
84
+ # @option opts [Boolean] use_per_process_proxy
85
+ # @option opts [Boolean] validate_cookie_document_type
86
+ #
87
+
88
+ def initialize(args: nil, **opts)
89
+ super(opts)
90
+
91
+ @args = (args || []).to_set
92
+ @options[:native_events] = true if @options[:native_events].nil?
93
+ end
94
+
95
+ #
96
+ # Add a command-line argument to use when starting Internet Explorer.
97
+ #
98
+ # @param [String] arg The command-line argument to add
99
+ #
100
+
101
+ def add_argument(arg)
102
+ @args << arg
103
+ end
104
+
105
+ #
106
+ # @api private
107
+ #
108
+
109
+ def as_json(*)
110
+ options = super
111
+ options['ie.browserCommandLineSwitches'] = @args.to_a.join(' ') if @args.any?
112
+
113
+ {KEY => generate_as_json(options)}
114
+ end
115
+ end # Options
116
+ end # IE
117
+ end # WebDriver
118
+ end # Selenium
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -20,29 +20,29 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module IE
23
- #
24
- # @api private
25
- #
26
-
27
23
  class Service < WebDriver::Service
28
24
  DEFAULT_PORT = 5555
25
+ EXECUTABLE = 'IEDriverServer'
26
+ MISSING_TEXT = <<~ERROR
27
+ Unable to find IEDriverServer. Please download the server from
28
+ http://selenium-release.storage.googleapis.com/index.html and place it somewhere on your PATH.
29
+ More info at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver.
30
+ ERROR
31
+ SHUTDOWN_SUPPORTED = true
29
32
 
30
33
  private
31
34
 
32
- def stop_server
33
- # server can only be stopped as process
34
- end
35
-
36
- def start_process
37
- server_command = [@executable_path, "--port=#{@port}", *@extra_args]
38
- @process = ChildProcess.new(*server_command)
39
-
40
- @process.io.inherit! if $DEBUG
41
- @process.start
42
- end
43
-
44
- def cannot_connect_error_text
45
- "unable to connect to IE server #{@host}:#{@port}"
35
+ # Note: This processing is deprecated
36
+ def extract_service_args(driver_opts)
37
+ driver_args = super
38
+ driver_opts = driver_opts.dup
39
+ driver_args << "--log-level=#{driver_opts[:log_level].to_s.upcase}" if driver_opts.key?(:log_level)
40
+ driver_args << "--log-file=#{driver_opts[:log_file]}" if driver_opts.key?(:log_file)
41
+ driver_args << "--implementation=#{driver_opts[:implementation].to_s.upcase}" if driver_opts.key?(:implementation)
42
+ driver_args << "--host=#{driver_opts[:host]}" if driver_opts.key?(:host)
43
+ driver_args << "--extract_path=#{driver_opts[:extract_path]}" if driver_opts.key?(:extract_path)
44
+ driver_args << "--silent" if driver_opts[:silent] == true
45
+ driver_args
46
46
  end
47
47
  end # Server
48
48
  end # IE
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -19,21 +19,19 @@
19
19
 
20
20
  require 'uri'
21
21
 
22
- require 'selenium/webdriver/remote/capabilities'
23
- require 'selenium/webdriver/remote/w3c_capabilities'
24
- require 'selenium/webdriver/remote/bridge'
25
- require 'selenium/webdriver/remote/w3c_bridge'
26
- require 'selenium/webdriver/remote/server_error'
27
- require 'selenium/webdriver/remote/response'
28
- require 'selenium/webdriver/remote/commands'
29
- require 'selenium/webdriver/remote/w3c_commands'
30
- require 'selenium/webdriver/remote/http/common'
31
- require 'selenium/webdriver/remote/http/default'
32
-
33
22
  module Selenium
34
23
  module WebDriver
35
- # @api private
36
24
  module Remote
37
- end # Remote
38
- end # WebDriver
39
- end # Selenium
25
+ autoload :Bridge, 'selenium/webdriver/remote/bridge'
26
+ autoload :Driver, 'selenium/webdriver/remote/driver'
27
+ autoload :Response, 'selenium/webdriver/remote/response'
28
+ autoload :ServerError, 'selenium/webdriver/remote/server_error'
29
+ autoload :Capabilities, 'selenium/webdriver/remote/capabilities'
30
+ autoload :COMMANDS, 'selenium/webdriver/remote/commands'
31
+ module Http
32
+ autoload :Common, 'selenium/webdriver/remote/http/common'
33
+ autoload :Default, 'selenium/webdriver/remote/http/default'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # Licensed to the Software Freedom Conservancy (SFC) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -20,96 +20,43 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
- #
24
- # Low level bridge to the remote server, through which the rest of the API works.
25
- #
26
- # @api private
27
- #
28
-
29
23
  class Bridge
30
- include BridgeHelper
31
-
32
- # TODO: constant shouldn't be modified in class
33
- COMMANDS = {}
34
-
35
- #
36
- # Defines a wrapper method for a command, which ultimately calls #execute.
37
- #
38
- # @param name [Symbol]
39
- # name of the resulting method
40
- # @param url [String]
41
- # a URL template, which can include some arguments, much like the definitions on the server.
42
- # the :session_id parameter is implicitly handled, but the remainder will become required method arguments.
43
- # @param verb [Symbol]
44
- # the appropriate http verb, such as :get, :post, or :delete
45
- #
24
+ include Atoms
46
25
 
47
- def self.command(name, verb, url)
48
- COMMANDS[name] = [verb, url.freeze]
49
- end
26
+ PORT = 4444
50
27
 
51
28
  attr_accessor :context, :http, :file_detector
52
29
  attr_reader :capabilities
53
30
 
54
31
  #
55
- # Initializes the bridge with the given server URL.
56
- #
57
- # @param url [String] url for the remote server
58
- # @param http_client [Object] an HTTP client instance that implements the same protocol as Http::Default
59
- # @param desired_capabilities [Capabilities] an instance of Remote::Capabilities describing the capabilities you want
32
+ # Initializes the bridge with the given server URL
33
+ # @param [String, URI] :url url for the remote server
34
+ # @param [Object] :http_client an HTTP client instance that implements the same protocol as Http::Default
35
+ # @api private
60
36
  #
61
37
 
62
- def initialize(opts = {})
63
- opts = opts.dup
64
-
65
- port = opts.delete(:port) || 4444
66
- http_client = opts.delete(:http_client) { Http::Default.new }
67
- desired_capabilities = opts.delete(:desired_capabilities) { Capabilities.firefox }
68
- url = opts.delete(:url) { "http://#{Platform.localhost}:#{port}/wd/hub" }
69
-
70
- unless opts.empty?
71
- raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
72
- end
73
-
74
- if desired_capabilities.is_a?(Symbol)
75
- unless Capabilities.respond_to?(desired_capabilities)
76
- raise Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
77
- end
78
-
79
- desired_capabilities = Capabilities.send(desired_capabilities)
80
- end
81
-
38
+ def initialize(http_client: nil, url:)
82
39
  uri = url.is_a?(URI) ? url : URI.parse(url)
83
- uri.path += '/' unless uri.path =~ %r{\/$}
84
-
85
- http_client.server_url = uri
86
-
87
- @http = http_client
88
- @capabilities = create_session(desired_capabilities)
40
+ uri.path += '/' unless %r{\/$}.match?(uri.path)
89
41
 
42
+ @http = http_client || Http::Default.new
43
+ @http.server_url = uri
90
44
  @file_detector = nil
91
45
  end
92
46
 
93
- def browser
94
- @browser ||= (
95
- name = @capabilities.browser_name
96
- name ? name.tr(' ', '_').to_sym : 'unknown'
97
- )
98
- end
47
+ #
48
+ # Creates session.
49
+ #
50
+
51
+ def create_session(desired_capabilities, options = nil)
52
+ response = execute(:new_session, {}, merged_capabilities(desired_capabilities, options))
53
+
54
+ @session_id = response['sessionId']
55
+ capabilities = response['capabilities']
99
56
 
100
- def driver_extensions
101
- [
102
- DriverExtensions::HasInputDevices,
103
- DriverExtensions::UploadsFiles,
104
- DriverExtensions::TakesScreenshot,
105
- DriverExtensions::HasSessionId,
106
- DriverExtensions::Rotatable,
107
- DriverExtensions::HasTouchScreen,
108
- DriverExtensions::HasLocation,
109
- DriverExtensions::HasNetworkConnection,
110
- DriverExtensions::HasRemoteStatus,
111
- DriverExtensions::HasWebStorage
112
- ]
57
+ raise Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
58
+
59
+ @capabilities = Capabilities.json_create(capabilities)
113
60
  end
114
61
 
115
62
  #
@@ -120,12 +67,11 @@ module Selenium
120
67
  @session_id || raise(Error::WebDriverError, 'no current session exists')
121
68
  end
122
69
 
123
- def create_session(desired_capabilities)
124
- resp = raw_execute :newSession, {}, {desiredCapabilities: desired_capabilities}
125
- @session_id = resp['sessionId']
126
- return Capabilities.json_create resp['value'] if @session_id
127
-
128
- raise Error::WebDriverError, 'no sessionId in returned payload'
70
+ def browser
71
+ @browser ||= begin
72
+ name = @capabilities.browser_name
73
+ name ? name.tr(' ', '_').to_sym : 'unknown'
74
+ end
129
75
  end
130
76
 
131
77
  def status
@@ -136,20 +82,17 @@ module Selenium
136
82
  execute :get, {}, {url: url}
137
83
  end
138
84
 
139
- def session_capabilities
140
- Capabilities.json_create execute(:getCapabilities)
141
- end
142
-
143
85
  def implicit_wait_timeout=(milliseconds)
144
- execute :implicitlyWait, {}, {ms: milliseconds}
86
+ timeout('implicit', milliseconds)
145
87
  end
146
88
 
147
89
  def script_timeout=(milliseconds)
148
- execute :setScriptTimeout, {}, {ms: milliseconds}
90
+ timeout('script', milliseconds)
149
91
  end
150
92
 
151
93
  def timeout(type, milliseconds)
152
- execute :setTimeout, {}, {type: type, ms: milliseconds}
94
+ type = 'pageLoad' if type == 'page load'
95
+ execute :set_timeout, {}, {type => milliseconds}
153
96
  end
154
97
 
155
98
  #
@@ -157,23 +100,19 @@ module Selenium
157
100
  #
158
101
 
159
102
  def accept_alert
160
- execute :acceptAlert
103
+ execute :accept_alert
161
104
  end
162
105
 
163
106
  def dismiss_alert
164
- execute :dismissAlert
107
+ execute :dismiss_alert
165
108
  end
166
109
 
167
110
  def alert=(keys)
168
- execute :setAlertValue, {}, {text: keys.to_s}
111
+ execute :send_alert_text, {}, {value: keys.split(//), text: keys}
169
112
  end
170
113
 
171
114
  def alert_text
172
- execute :getAlertText
173
- end
174
-
175
- def authentication(credentials)
176
- execute :setAuthentication, {}, credentials
115
+ execute :get_alert_text
177
116
  end
178
117
 
179
118
  #
@@ -181,51 +120,68 @@ module Selenium
181
120
  #
182
121
 
183
122
  def go_back
184
- execute :goBack
123
+ execute :back
185
124
  end
186
125
 
187
126
  def go_forward
188
- execute :goForward
127
+ execute :forward
189
128
  end
190
129
 
191
130
  def url
192
- execute :getCurrentUrl
131
+ execute :get_current_url
193
132
  end
194
133
 
195
134
  def title
196
- execute :getTitle
135
+ execute :get_title
197
136
  end
198
137
 
199
138
  def page_source
200
- execute :getPageSource
139
+ execute_script('var source = document.documentElement.outerHTML;' \
140
+ 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
141
+ 'return source;')
142
+ end
143
+
144
+ #
145
+ # Create a new top-level browsing context
146
+ # https://w3c.github.io/webdriver/#new-window
147
+ # @param type [String] Supports two values: 'tab' and 'window'.
148
+ # Use 'tab' if you'd like the new window to share an OS-level window
149
+ # with the current browsing context.
150
+ # Use 'window' otherwise
151
+ # @return [Hash] Containing 'handle' with the value of the window handle
152
+ # and 'type' with the value of the created window type
153
+ #
154
+ def new_window(type)
155
+ execute :new_window, {}, {type: type}
201
156
  end
202
157
 
203
158
  def switch_to_window(name)
204
- execute :switchToWindow, {}, {name: name}
159
+ execute :switch_to_window, {}, {handle: name}
205
160
  end
206
161
 
207
162
  def switch_to_frame(id)
208
- execute :switchToFrame, {}, {id: id}
163
+ id = find_element_by('id', id) if id.is_a? String
164
+ execute :switch_to_frame, {}, {id: id}
209
165
  end
210
166
 
211
167
  def switch_to_parent_frame
212
- execute :switchToParentFrame
168
+ execute :switch_to_parent_frame
213
169
  end
214
170
 
215
171
  def switch_to_default_content
216
- switch_to_frame(nil)
172
+ switch_to_frame nil
217
173
  end
218
174
 
219
175
  QUIT_ERRORS = [IOError].freeze
220
176
 
221
177
  def quit
222
- execute :quit
178
+ execute :delete_session
223
179
  http.close
224
180
  rescue *QUIT_ERRORS
225
181
  end
226
182
 
227
183
  def close
228
- execute :close
184
+ execute :close_window
229
185
  end
230
186
 
231
187
  def refresh
@@ -237,42 +193,62 @@ module Selenium
237
193
  #
238
194
 
239
195
  def window_handles
240
- execute :getWindowHandles
196
+ execute :get_window_handles
241
197
  end
242
198
 
243
199
  def window_handle
244
- execute :getCurrentWindowHandle
200
+ execute :get_window_handle
245
201
  end
246
202
 
247
203
  def resize_window(width, height, handle = :current)
248
- execute :setWindowSize, {window_handle: handle},
249
- {width: width,
250
- height: height}
251
- end
204
+ raise Error::WebDriverError, 'Switch to desired window before changing its size' unless handle == :current
252
205
 
253
- def maximize_window(handle = :current)
254
- execute :maximizeWindow, window_handle: handle
206
+ set_window_rect(width: width, height: height)
255
207
  end
256
208
 
257
209
  def window_size(handle = :current)
258
- data = execute :getWindowSize, window_handle: handle
210
+ raise Error::UnsupportedOperationError, 'Switch to desired window before getting its size' unless handle == :current
259
211
 
212
+ data = execute :get_window_rect
260
213
  Dimension.new data['width'], data['height']
261
214
  end
262
215
 
263
- def reposition_window(x, y, handle = :current)
264
- execute :setWindowPosition, {window_handle: handle},
265
- {x: x, y: y}
216
+ def minimize_window
217
+ execute :minimize_window
266
218
  end
267
219
 
268
- def window_position(handle = :current)
269
- data = execute :getWindowPosition, window_handle: handle
220
+ def maximize_window(handle = :current)
221
+ raise Error::UnsupportedOperationError, 'Switch to desired window before changing its size' unless handle == :current
222
+
223
+ execute :maximize_window
224
+ end
270
225
 
226
+ def full_screen_window
227
+ execute :fullscreen_window
228
+ end
229
+
230
+ def reposition_window(x, y)
231
+ set_window_rect(x: x, y: y)
232
+ end
233
+
234
+ def window_position
235
+ data = execute :get_window_rect
271
236
  Point.new data['x'], data['y']
272
237
  end
273
238
 
239
+ def set_window_rect(x: nil, y: nil, width: nil, height: nil)
240
+ params = {x: x, y: y, width: width, height: height}
241
+ params.update(params) { |_k, v| Integer(v) unless v.nil? }
242
+ execute :set_window_rect, {}, params
243
+ end
244
+
245
+ def window_rect
246
+ data = execute :get_window_rect
247
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
248
+ end
249
+
274
250
  def screenshot
275
- execute :screenshot
251
+ execute :take_screenshot
276
252
  end
277
253
 
278
254
  #
@@ -281,68 +257,66 @@ module Selenium
281
257
 
282
258
  def local_storage_item(key, value = nil)
283
259
  if value
284
- execute :setLocalStorageItem, {}, {key: key, value: value}
260
+ execute_script("localStorage.setItem('#{key}', '#{value}')")
285
261
  else
286
- execute :getLocalStorageItem, key: key
262
+ execute_script("return localStorage.getItem('#{key}')")
287
263
  end
288
264
  end
289
265
 
290
266
  def remove_local_storage_item(key)
291
- execute :removeLocalStorageItem, key: key
267
+ execute_script("localStorage.removeItem('#{key}')")
292
268
  end
293
269
 
294
270
  def local_storage_keys
295
- execute :getLocalStorageKeys
271
+ execute_script('return Object.keys(localStorage)')
296
272
  end
297
273
 
298
274
  def clear_local_storage
299
- execute :clearLocalStorage
275
+ execute_script('localStorage.clear()')
300
276
  end
301
277
 
302
278
  def local_storage_size
303
- execute :getLocalStorageSize
279
+ execute_script('return localStorage.length')
304
280
  end
305
281
 
306
282
  def session_storage_item(key, value = nil)
307
283
  if value
308
- execute :setSessionStorageItem, {}, {key: key, value: value}
284
+ execute_script("sessionStorage.setItem('#{key}', '#{value}')")
309
285
  else
310
- execute :getSessionStorageItem, key: key
286
+ execute_script("return sessionStorage.getItem('#{key}')")
311
287
  end
312
288
  end
313
289
 
314
290
  def remove_session_storage_item(key)
315
- execute :removeSessionStorageItem, key: key
291
+ execute_script("sessionStorage.removeItem('#{key}')")
316
292
  end
317
293
 
318
294
  def session_storage_keys
319
- execute :getSessionStorageKeys
295
+ execute_script('return Object.keys(sessionStorage)')
320
296
  end
321
297
 
322
298
  def clear_session_storage
323
- execute :clearSessionStorage
299
+ execute_script('sessionStorage.clear()')
324
300
  end
325
301
 
326
302
  def session_storage_size
327
- execute :getSessionStorageSize
303
+ execute_script('return sessionStorage.length')
328
304
  end
329
305
 
330
306
  def location
331
- obj = execute(:getLocation) || {}
332
- Location.new obj['latitude'], obj['longitude'], obj['altitude']
307
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting location'
333
308
  end
334
309
 
335
- def set_location(lat, lon, alt)
336
- loc = {latitude: lat, longitude: lon, altitude: alt}
337
- execute :setLocation, {}, {location: loc}
310
+ def set_location(_lat, _lon, _alt)
311
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting location'
338
312
  end
339
313
 
340
314
  def network_connection
341
- execute :getNetworkConnection
315
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting network connection'
342
316
  end
343
317
 
344
- def network_connection=(type)
345
- execute :setNetworkConnection, {}, {parameters: {type: type}}
318
+ def network_connection=(_type)
319
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting network connection'
346
320
  end
347
321
 
348
322
  #
@@ -350,16 +324,12 @@ module Selenium
350
324
  #
351
325
 
352
326
  def execute_script(script, *args)
353
- assert_javascript_enabled
354
-
355
- result = execute :executeScript, {}, {script: script, args: args}
327
+ result = execute :execute_script, {}, {script: script, args: args}
356
328
  unwrap_script_result result
357
329
  end
358
330
 
359
331
  def execute_async_script(script, *args)
360
- assert_javascript_enabled
361
-
362
- result = execute :executeAsyncScript, {}, {script: script, args: args}
332
+ result = execute :execute_async_script, {}, {script: script, args: args}
363
333
  unwrap_script_result result
364
334
  end
365
335
 
@@ -367,166 +337,103 @@ module Selenium
367
337
  # cookies
368
338
  #
369
339
 
340
+ def manage
341
+ @manage ||= WebDriver::Manager.new(self)
342
+ end
343
+
370
344
  def add_cookie(cookie)
371
- execute :addCookie, {}, {cookie: cookie}
345
+ execute :add_cookie, {}, {cookie: cookie}
372
346
  end
373
347
 
374
348
  def delete_cookie(name)
375
- execute :deleteCookie, name: name
349
+ execute :delete_cookie, name: name
350
+ end
351
+
352
+ def cookie(name)
353
+ execute :get_cookie, name: name
376
354
  end
377
355
 
378
356
  def cookies
379
- execute :getCookies
357
+ execute :get_all_cookies
380
358
  end
381
359
 
382
360
  def delete_all_cookies
383
- execute :deleteAllCookies
361
+ execute :delete_all_cookies
384
362
  end
385
363
 
386
364
  #
387
365
  # actions
388
366
  #
389
367
 
390
- def click_element(element)
391
- execute :clickElement, id: element
392
- end
393
-
394
- def click
395
- execute :click, {}, {button: 0}
396
- end
397
-
398
- def double_click
399
- execute :doubleClick
368
+ def action(async = false)
369
+ ActionBuilder.new self,
370
+ Interactions.pointer(:mouse, name: 'mouse'),
371
+ Interactions.key('keyboard'),
372
+ async
400
373
  end
374
+ alias_method :actions, :action
401
375
 
402
- def context_click
403
- execute :click, {}, {button: 2}
376
+ def mouse
377
+ raise Error::UnsupportedOperationError, '#mouse is no longer supported, use #action instead'
404
378
  end
405
379
 
406
- def mouse_down
407
- execute :mouseDown
380
+ def keyboard
381
+ raise Error::UnsupportedOperationError, '#keyboard is no longer supported, use #action instead'
408
382
  end
409
383
 
410
- def mouse_up
411
- execute :mouseUp
384
+ def send_actions(data)
385
+ execute :actions, {}, {actions: data}
412
386
  end
413
387
 
414
- def mouse_move_to(element, x = nil, y = nil)
415
- params = {element: element}
416
-
417
- if x && y
418
- params[:xoffset] = x
419
- params[:yoffset] = y
420
- end
421
-
422
- execute :mouseMoveTo, {}, params
388
+ def release_actions
389
+ execute :release_actions
423
390
  end
424
391
 
425
- def send_keys_to_active_element(key)
426
- execute :sendKeysToActiveElement, {}, {value: key}
392
+ def click_element(element)
393
+ execute :element_click, id: element
427
394
  end
428
395
 
429
396
  def send_keys_to_element(element, keys)
397
+ # TODO: rework file detectors before Selenium 4.0
430
398
  if @file_detector
431
- local_file = @file_detector.call(keys)
432
- keys = upload(local_file) if local_file
399
+ local_files = keys.first.split("\n").map { |key| @file_detector.call(Array(key)) }.compact
400
+ if local_files.any?
401
+ keys = local_files.map { |local_file| upload(local_file) }
402
+ keys = Array(keys.join("\n"))
403
+ end
433
404
  end
434
405
 
435
- execute :sendKeysToElement, {id: element}, {value: Array(keys)}
406
+ # Keep .split(//) for backward compatibility for now
407
+ text = keys.join('')
408
+ execute :element_send_keys, {id: element}, {value: text.split(//), text: text}
436
409
  end
437
410
 
438
411
  def upload(local_file)
439
412
  unless File.file?(local_file)
440
- raise Error::WebDriverError, "you may only upload files: #{local_file.inspect}"
413
+ WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!")
414
+ raise Error::WebDriverError, "You are trying to work with something that isn't a file."
441
415
  end
442
416
 
443
- execute :uploadFile, {}, {file: Zipper.zip_file(local_file)}
417
+ execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
444
418
  end
445
419
 
446
420
  def clear_element(element)
447
- execute :clearElement, id: element
421
+ execute :element_clear, id: element
448
422
  end
449
423
 
450
424
  def submit_element(element)
451
- execute :submitElement, id: element
452
- end
453
-
454
- def drag_element(element, right_by, down_by)
455
- execute :dragElement, {id: element}, {x: right_by, y: down_by}
456
- end
457
-
458
- def touch_single_tap(element)
459
- execute :touchSingleTap, {}, {element: element}
460
- end
461
-
462
- def touch_double_tap(element)
463
- execute :touchDoubleTap, {}, {element: element}
464
- end
465
-
466
- def touch_long_press(element)
467
- execute :touchLongPress, {}, {element: element}
468
- end
469
-
470
- def touch_down(x, y)
471
- execute :touchDown, {}, {x: x, y: y}
472
- end
473
-
474
- def touch_up(x, y)
475
- execute :touchUp, {}, {x: x, y: y}
476
- end
477
-
478
- def touch_move(x, y)
479
- execute :touchMove, {}, {x: x, y: y}
480
- end
481
-
482
- def touch_scroll(element, x, y)
483
- if element
484
- execute :touchScroll, {}, {element: element,
485
- xoffset: x,
486
- yoffset: y}
487
- else
488
- execute :touchScroll, {}, {xoffset: x, yoffset: y}
489
- end
490
- end
491
-
492
- def touch_flick(xspeed, yspeed)
493
- execute :touchFlick, {}, {xspeed: xspeed, yspeed: yspeed}
494
- end
495
-
496
- def touch_element_flick(element, right_by, down_by, speed)
497
- execute :touchFlick, {}, {element: element,
498
- xoffset: right_by,
499
- yoffset: down_by,
500
- speed: speed}
425
+ form = find_element_by('xpath', "./ancestor-or-self::form", element)
426
+ execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
427
+ "e.initEvent('submit', true, true);" \
428
+ 'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
501
429
  end
502
430
 
503
431
  def screen_orientation=(orientation)
504
- execute :setScreenOrientation, {}, {orientation: orientation}
432
+ execute :set_screen_orientation, {}, {orientation: orientation}
505
433
  end
506
434
 
507
435
  def screen_orientation
508
- execute :getScreenOrientation
509
- end
510
-
511
- #
512
- # logs
513
- #
514
-
515
- def available_log_types
516
- types = execute :getAvailableLogTypes
517
- Array(types).map(&:to_sym)
518
- end
519
-
520
- def log(type)
521
- data = execute :getLog, {}, {type: type.to_s}
522
-
523
- Array(data).map do |l|
524
- begin
525
- LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
526
- rescue KeyError
527
- next
528
- end
529
- end
436
+ execute :get_screen_orientation
530
437
  end
531
438
 
532
439
  #
@@ -534,53 +441,64 @@ module Selenium
534
441
  #
535
442
 
536
443
  def element_tag_name(element)
537
- execute :getElementTagName, id: element
444
+ execute :get_element_tag_name, id: element
538
445
  end
539
446
 
540
447
  def element_attribute(element, name)
541
- execute :getElementAttribute, id: element.ref, name: name
448
+ WebDriver.logger.info "Using script for :getAttribute of #{name}"
449
+ execute_atom :getAttribute, element, name
450
+ end
451
+
452
+ def element_property(element, name)
453
+ execute :get_element_property, id: element.ref, name: name
542
454
  end
543
455
 
544
456
  def element_value(element)
545
- execute :getElementValue, id: element
457
+ element_property element, 'value'
546
458
  end
547
459
 
548
460
  def element_text(element)
549
- execute :getElementText, id: element
461
+ execute :get_element_text, id: element
550
462
  end
551
463
 
552
464
  def element_location(element)
553
- data = execute :getElementLocation, id: element
465
+ data = execute :get_element_rect, id: element
554
466
 
555
467
  Point.new data['x'], data['y']
556
468
  end
557
469
 
558
- def element_location_once_scrolled_into_view(element)
559
- data = execute :getElementLocationOnceScrolledIntoView, id: element
470
+ def element_rect(element)
471
+ data = execute :get_element_rect, id: element
560
472
 
561
- Point.new data['x'], data['y']
473
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
474
+ end
475
+
476
+ def element_location_once_scrolled_into_view(element)
477
+ send_keys_to_element(element, [''])
478
+ element_location(element)
562
479
  end
563
480
 
564
481
  def element_size(element)
565
- data = execute :getElementSize, id: element
482
+ data = execute :get_element_rect, id: element
566
483
 
567
484
  Dimension.new data['width'], data['height']
568
485
  end
569
486
 
570
487
  def element_enabled?(element)
571
- execute :isElementEnabled, id: element
488
+ execute :is_element_enabled, id: element
572
489
  end
573
490
 
574
491
  def element_selected?(element)
575
- execute :isElementSelected, id: element
492
+ execute :is_element_selected, id: element
576
493
  end
577
494
 
578
495
  def element_displayed?(element)
579
- execute :isElementDisplayed, id: element
496
+ WebDriver.logger.info 'Using script for :isDisplayed'
497
+ execute_atom :isDisplayed, element
580
498
  end
581
499
 
582
500
  def element_value_of_css_property(element, prop)
583
- execute :getElementValueOfCssProperty, id: element, property_name: prop
501
+ execute :get_element_css_value, id: element, property_name: prop
584
502
  end
585
503
 
586
504
  #
@@ -588,26 +506,34 @@ module Selenium
588
506
  #
589
507
 
590
508
  def active_element
591
- Element.new self, element_id_from(execute(:getActiveElement))
509
+ Element.new self, element_id_from(execute(:get_active_element))
592
510
  end
593
511
 
594
512
  alias_method :switch_to_active_element, :active_element
595
513
 
596
514
  def find_element_by(how, what, parent = nil)
515
+ how, what = convert_locator(how, what)
516
+
517
+ return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
518
+
597
519
  id = if parent
598
- execute :findChildElement, {id: parent}, {using: how, value: what}
520
+ execute :find_child_element, {id: parent}, {using: how, value: what.to_s}
599
521
  else
600
- execute :findElement, {}, {using: how, value: what}
522
+ execute :find_element, {}, {using: how, value: what.to_s}
601
523
  end
602
524
 
603
525
  Element.new self, element_id_from(id)
604
526
  end
605
527
 
606
528
  def find_elements_by(how, what, parent = nil)
529
+ how, what = convert_locator(how, what)
530
+
531
+ return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
532
+
607
533
  ids = if parent
608
- execute :findChildElements, {id: parent}, {using: how, value: what}
534
+ execute :find_child_elements, {id: parent}, {using: how, value: what.to_s}
609
535
  else
610
- execute :findElements, {}, {using: how, value: what}
536
+ execute :find_elements, {}, {using: how, value: what.to_s}
611
537
  end
612
538
 
613
539
  ids.map { |id| Element.new self, element_id_from(id) }
@@ -615,33 +541,17 @@ module Selenium
615
541
 
616
542
  private
617
543
 
618
- def assert_javascript_enabled
619
- return if capabilities.javascript_enabled?
620
- raise Error::UnsupportedOperationError, 'underlying webdriver instance does not support javascript'
621
- end
622
-
623
- #
624
- # executes a command on the remote server.
625
- #
626
- #
627
- # Returns the 'value' of the returned payload
628
- #
629
-
630
- def execute(*args)
631
- raw_execute(*args)['value']
632
- end
633
-
634
544
  #
635
545
  # executes a command on the remote server.
636
546
  #
637
547
  # @return [WebDriver::Remote::Response]
638
548
  #
639
549
 
640
- def raw_execute(command, opts = {}, command_hash = nil)
641
- verb, path = COMMANDS[command] || raise(ArgumentError, "unknown command: #{command.inspect}")
550
+ def execute(command, opts = {}, command_hash = nil)
551
+ verb, path = commands(command) || raise(ArgumentError, "unknown command: #{command.inspect}")
642
552
  path = path.dup
643
553
 
644
- path[':session_id'] = @session_id if path.include?(':session_id')
554
+ path[':session_id'] = session_id if path.include?(':session_id')
645
555
 
646
556
  begin
647
557
  opts.each { |key, value| path[key.inspect] = escaper.escape(value.to_s) }
@@ -649,12 +559,83 @@ module Selenium
649
559
  raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
650
560
  end
651
561
 
652
- puts "-> #{verb.to_s.upcase} #{path}" if $DEBUG
653
- http.call verb, path, command_hash
562
+ WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
563
+ http.call(verb, path, command_hash)['value']
654
564
  end
655
565
 
656
566
  def escaper
657
- @escaper ||= defined?(URI::Parser) ? URI::Parser.new : URI
567
+ @escaper ||= defined?(URI::Parser) ? URI::DEFAULT_PARSER : URI
568
+ end
569
+
570
+ def commands(command)
571
+ COMMANDS[command]
572
+ end
573
+
574
+ def merged_capabilities(capabilities, options = nil)
575
+ capabilities.merge!(options.as_json) if options
576
+
577
+ {
578
+ capabilities: {
579
+ firstMatch: [capabilities]
580
+ }
581
+ }
582
+ end
583
+
584
+ def unwrap_script_result(arg)
585
+ case arg
586
+ when Array
587
+ arg.map { |e| unwrap_script_result(e) }
588
+ when Hash
589
+ element_id = element_id_from(arg)
590
+ return Element.new(self, element_id) if element_id
591
+
592
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
593
+ else
594
+ arg
595
+ end
596
+ end
597
+
598
+ def element_id_from(id)
599
+ id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
600
+ end
601
+
602
+ def convert_locator(how, what)
603
+ how = SearchContext::FINDERS[how.to_sym] || how
604
+
605
+ case how
606
+ when 'class name'
607
+ how = 'css selector'
608
+ what = ".#{escape_css(what.to_s)}"
609
+ when 'id'
610
+ how = 'css selector'
611
+ what = "##{escape_css(what.to_s)}"
612
+ when 'name'
613
+ how = 'css selector'
614
+ what = "*[name='#{escape_css(what.to_s)}']"
615
+ when 'tag name'
616
+ how = 'css selector'
617
+ end
618
+
619
+ if what.is_a?(Hash)
620
+ what = what.each_with_object({}) do |(h, w), hash|
621
+ h, w = convert_locator(h.to_s, w)
622
+ hash[h] = w
623
+ end
624
+ end
625
+
626
+ [how, what]
627
+ end
628
+
629
+ ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]\(\)])/.freeze
630
+ UNICODE_CODE_POINT = 30
631
+
632
+ # Escapes invalid characters in CSS selector.
633
+ # @see https://mathiasbynens.be/notes/css-escapes
634
+ def escape_css(string)
635
+ string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
636
+ string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..-1]}" if string[0]&.match?(/[[:digit:]]/)
637
+
638
+ string
658
639
  end
659
640
  end # Bridge
660
641
  end # Remote