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

Sign up to get free protection for your applications and to get access to all the features.
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