selenium-webdriver 4.1.0 → 4.8.1

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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +182 -1
  3. data/LICENSE +1 -1
  4. data/NOTICE +1 -1
  5. data/bin/linux/selenium-manager +0 -0
  6. data/bin/macos/selenium-manager +0 -0
  7. data/bin/windows/selenium-manager.exe +0 -0
  8. data/lib/selenium/server.rb +35 -26
  9. data/lib/selenium/webdriver/atoms/findElements.js +0 -0
  10. data/lib/selenium/webdriver/atoms/getAttribute.js +0 -0
  11. data/lib/selenium/webdriver/atoms/isDisplayed.js +0 -0
  12. data/lib/selenium/webdriver/atoms/mutationListener.js +0 -0
  13. data/lib/selenium/webdriver/atoms.rb +2 -3
  14. data/lib/selenium/webdriver/bidi/browsing_context.rb +88 -0
  15. data/lib/selenium/webdriver/bidi/browsing_context_info.rb +35 -0
  16. data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +35 -0
  17. data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +35 -0
  18. data/lib/selenium/webdriver/bidi/log/filter_by.rb +40 -0
  19. data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +33 -0
  20. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +33 -0
  21. data/lib/selenium/webdriver/bidi/log_inspector.rb +143 -0
  22. data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
  23. data/lib/selenium/webdriver/bidi/session.rb +51 -0
  24. data/lib/selenium/webdriver/bidi.rb +56 -0
  25. data/lib/selenium/webdriver/chrome/driver.rb +19 -28
  26. data/lib/selenium/webdriver/chrome/features.rb +6 -68
  27. data/lib/selenium/webdriver/chrome/options.rb +3 -223
  28. data/lib/selenium/webdriver/chrome/profile.rb +3 -83
  29. data/lib/selenium/webdriver/chrome/service.rb +4 -19
  30. data/lib/selenium/webdriver/chrome.rb +0 -14
  31. data/lib/selenium/webdriver/chromium/driver.rb +61 -0
  32. data/lib/selenium/webdriver/chromium/features.rb +103 -0
  33. data/lib/selenium/webdriver/chromium/options.rb +261 -0
  34. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  35. data/lib/selenium/webdriver/chromium/service.rb +42 -0
  36. data/lib/selenium/webdriver/chromium.rb +32 -0
  37. data/lib/selenium/webdriver/common/action_builder.rb +72 -22
  38. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  39. data/lib/selenium/webdriver/common/driver.rb +50 -70
  40. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  41. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -1
  42. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  43. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -2
  45. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_bidi.rb} +10 -5
  46. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -1
  47. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -2
  48. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -4
  49. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  50. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -2
  52. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +1 -2
  53. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -2
  54. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -2
  55. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -69
  56. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -2
  57. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -3
  58. data/lib/selenium/webdriver/common/element.rb +8 -8
  59. data/lib/selenium/webdriver/common/error.rb +1 -3
  60. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  61. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  62. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -25
  63. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  64. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -1
  65. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  66. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  67. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  68. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +59 -70
  69. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  70. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  71. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  72. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  73. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  74. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  75. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  76. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  77. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  78. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +42 -0
  79. data/lib/selenium/webdriver/common/keys.rb +1 -0
  80. data/lib/selenium/webdriver/common/logger.rb +10 -2
  81. data/lib/selenium/webdriver/common/manager.rb +0 -27
  82. data/lib/selenium/webdriver/common/options.rb +32 -17
  83. data/lib/selenium/webdriver/common/platform.rb +8 -5
  84. data/lib/selenium/webdriver/common/profile_helper.rb +1 -1
  85. data/lib/selenium/webdriver/common/proxy.rb +1 -1
  86. data/lib/selenium/webdriver/common/search_context.rb +0 -6
  87. data/lib/selenium/webdriver/common/selenium_manager.rb +89 -0
  88. data/lib/selenium/webdriver/common/service.rb +16 -8
  89. data/lib/selenium/webdriver/common/service_manager.rb +4 -13
  90. data/lib/selenium/webdriver/common/shadow_root.rb +2 -3
  91. data/lib/selenium/webdriver/common/socket_lock.rb +2 -2
  92. data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
  93. data/lib/selenium/webdriver/common/takes_screenshot.rb +2 -3
  94. data/lib/selenium/webdriver/common/target_locator.rb +2 -3
  95. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  96. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  97. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  98. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  99. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  100. data/lib/selenium/webdriver/common/window.rb +6 -6
  101. data/lib/selenium/webdriver/common/zipper.rb +1 -1
  102. data/lib/selenium/webdriver/common.rb +19 -3
  103. data/lib/selenium/webdriver/devtools/console_event.rb +0 -2
  104. data/lib/selenium/webdriver/devtools/exception_event.rb +0 -2
  105. data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -2
  106. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  107. data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -2
  108. data/lib/selenium/webdriver/devtools/request.rb +1 -3
  109. data/lib/selenium/webdriver/devtools/response.rb +1 -3
  110. data/lib/selenium/webdriver/devtools.rb +6 -113
  111. data/lib/selenium/webdriver/edge/driver.rb +20 -3
  112. data/lib/selenium/webdriver/edge/features.rb +4 -4
  113. data/lib/selenium/webdriver/edge/options.rb +3 -5
  114. data/lib/selenium/webdriver/edge/profile.rb +2 -2
  115. data/lib/selenium/webdriver/edge/service.rb +2 -2
  116. data/lib/selenium/webdriver/firefox/driver.rb +20 -2
  117. data/lib/selenium/webdriver/firefox/features.rb +6 -6
  118. data/lib/selenium/webdriver/firefox/options.rb +9 -3
  119. data/lib/selenium/webdriver/firefox/profile.rb +7 -11
  120. data/lib/selenium/webdriver/firefox/service.rb +0 -1
  121. data/lib/selenium/webdriver/firefox/util.rb +46 -0
  122. data/lib/selenium/webdriver/firefox.rb +1 -14
  123. data/lib/selenium/webdriver/ie/driver.rb +20 -1
  124. data/lib/selenium/webdriver/ie/service.rb +1 -2
  125. data/lib/selenium/webdriver/ie.rb +0 -14
  126. data/lib/selenium/webdriver/remote/{commands.rb → bridge/commands.rb} +15 -8
  127. data/lib/selenium/webdriver/remote/bridge.rb +59 -30
  128. data/lib/selenium/webdriver/remote/capabilities.rb +34 -12
  129. data/lib/selenium/webdriver/remote/driver.rb +14 -15
  130. data/lib/selenium/webdriver/remote/http/curb.rb +0 -2
  131. data/lib/selenium/webdriver/remote/http/default.rb +7 -12
  132. data/lib/selenium/webdriver/remote/response.rb +2 -3
  133. data/lib/selenium/webdriver/remote.rb +0 -1
  134. data/lib/selenium/webdriver/safari/driver.rb +20 -1
  135. data/lib/selenium/webdriver/safari/features.rb +0 -2
  136. data/lib/selenium/webdriver/safari/options.rb +5 -1
  137. data/lib/selenium/webdriver/safari.rb +1 -15
  138. data/lib/selenium/webdriver/support/color.rb +20 -20
  139. data/lib/selenium/webdriver/support/guards/guard.rb +0 -2
  140. data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -3
  141. data/lib/selenium/webdriver/support/guards.rb +1 -1
  142. data/lib/selenium/webdriver/support/relative_locator.rb +0 -1
  143. data/lib/selenium/webdriver/support/select.rb +3 -1
  144. data/lib/selenium/webdriver/version.rb +1 -1
  145. data/lib/selenium/webdriver.rb +4 -4
  146. data/selenium-webdriver.gemspec +13 -11
  147. metadata +68 -59
  148. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
  149. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +0 -63
  150. data/lib/selenium/webdriver/support/cdp_client_generator.rb +0 -108
@@ -0,0 +1,173 @@
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
+ class DevTools
23
+ #
24
+ # Wraps the network request/response interception, providing
25
+ # thread-safety guarantees and handling special cases such as browser
26
+ # canceling requests midst interception.
27
+ #
28
+ # You should not be using this class directly, use Driver#intercept instead.
29
+ # @api private
30
+ #
31
+
32
+ class NetworkInterceptor
33
+ # CDP fails to get body on certain responses (301) and raises:
34
+ # "Can only get response body on requests captured after headers received."
35
+ CANNOT_GET_BODY_ON_REDIRECT_ERROR_CODE = '-32000'
36
+
37
+ # CDP fails to operate with intercepted requests.
38
+ # Typical reason is browser cancelling intercepted requests/responses.
39
+ INVALID_INTERCEPTION_ID_ERROR_CODE = '-32602'
40
+
41
+ def initialize(devtools)
42
+ @devtools = devtools
43
+ @lock = Mutex.new
44
+ end
45
+
46
+ def intercept(&block)
47
+ devtools.network.on(:loading_failed) { |params| track_cancelled_request(params) }
48
+ devtools.fetch.on(:request_paused) { |params| request_paused(params, &block) }
49
+
50
+ devtools.network.set_cache_disabled(cache_disabled: true)
51
+ devtools.network.enable
52
+ devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}])
53
+ end
54
+
55
+ private
56
+
57
+ attr_accessor :devtools, :lock
58
+
59
+ # We should be thread-safe to use the hash without synchronization
60
+ # because its keys are interception job identifiers and they should be
61
+ # unique within a devtools session.
62
+ def pending_response_requests
63
+ @pending_response_requests ||= {}
64
+ end
65
+
66
+ # Ensure usage of cancelled_requests is thread-safe via synchronization!
67
+ def cancelled_requests
68
+ @cancelled_requests ||= []
69
+ end
70
+
71
+ def track_cancelled_request(data)
72
+ return unless data['canceled']
73
+
74
+ lock.synchronize { cancelled_requests << data['requestId'] }
75
+ end
76
+
77
+ def request_paused(data, &block)
78
+ id = data['requestId']
79
+ network_id = data['networkId']
80
+
81
+ with_cancellable_request(network_id) do
82
+ if response?(data)
83
+ block = pending_response_requests.delete(id)
84
+ intercept_response(id, data, &block)
85
+ else
86
+ intercept_request(id, data, &block)
87
+ end
88
+ end
89
+ end
90
+
91
+ # The presence of any of these fields indicate we're at the response stage.
92
+ # @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused
93
+ def response?(params)
94
+ params.key?('responseStatusCode') || params.key?('responseErrorReason')
95
+ end
96
+
97
+ def intercept_request(id, params, &block)
98
+ original = DevTools::Request.from(id, params)
99
+ mutable = DevTools::Request.from(id, params)
100
+
101
+ block.call(mutable) do |&continue| # rubocop:disable Performance/RedundantBlockCall
102
+ pending_response_requests[id] = continue
103
+
104
+ if original == mutable
105
+ continue_request(original.id)
106
+ else
107
+ mutate_request(mutable)
108
+ end
109
+ end
110
+ end
111
+
112
+ def intercept_response(id, params)
113
+ return continue_response(id) unless block_given?
114
+
115
+ body = fetch_response_body(id)
116
+ original = DevTools::Response.from(id, body, params)
117
+ mutable = DevTools::Response.from(id, body, params)
118
+ yield mutable
119
+
120
+ if original == mutable
121
+ continue_response(id)
122
+ else
123
+ mutate_response(mutable)
124
+ end
125
+ end
126
+
127
+ def continue_request(id)
128
+ devtools.fetch.continue_request(request_id: id)
129
+ end
130
+ alias continue_response continue_request
131
+
132
+ def mutate_request(request)
133
+ devtools.fetch.continue_request(
134
+ request_id: request.id,
135
+ url: request.url,
136
+ method: request.method,
137
+ post_data: request.post_data,
138
+ headers: request.headers.map do |k, v|
139
+ {name: k, value: v}
140
+ end
141
+ )
142
+ end
143
+
144
+ def mutate_response(response)
145
+ devtools.fetch.fulfill_request(
146
+ request_id: response.id,
147
+ body: (Base64.strict_encode64(response.body) if response.body),
148
+ response_code: response.code,
149
+ response_headers: response.headers.map do |k, v|
150
+ {name: k, value: v}
151
+ end
152
+ )
153
+ end
154
+
155
+ def fetch_response_body(id)
156
+ devtools.fetch.get_response_body(request_id: id).dig('result', 'body')
157
+ rescue Error::WebDriverError => e
158
+ raise unless e.message.start_with?(CANNOT_GET_BODY_ON_REDIRECT_ERROR_CODE)
159
+ end
160
+
161
+ def with_cancellable_request(network_id)
162
+ yield
163
+ rescue Error::WebDriverError => e
164
+ raise if e.message.start_with?(INVALID_INTERCEPTION_ID_ERROR_CODE) && !cancelled?(network_id)
165
+ end
166
+
167
+ def cancelled?(network_id)
168
+ lock.synchronize { !!cancelled_requests.delete(network_id) }
169
+ end
170
+ end # NetworkInterceptor
171
+ end # DevTools
172
+ end # WebDriver
173
+ end # Selenium
@@ -21,7 +21,6 @@ module Selenium
21
21
  module WebDriver
22
22
  class DevTools
23
23
  class PinnedScript
24
-
25
24
  attr_accessor :key, :devtools_identifier, :script
26
25
 
27
26
  def initialize(script)
@@ -52,7 +51,6 @@ module Selenium
52
51
  def remove
53
52
  "__webdriver_#{key} = undefined"
54
53
  end
55
-
56
54
  end # PinnedScript
57
55
  end # DevTools
58
56
  end # WebDriver
@@ -21,7 +21,6 @@ module Selenium
21
21
  module WebDriver
22
22
  class DevTools
23
23
  class Request
24
-
25
24
  attr_accessor :url, :method, :headers, :post_data
26
25
  attr_reader :id
27
26
 
@@ -35,7 +34,7 @@ module Selenium
35
34
  id: id,
36
35
  url: params.dig('request', 'url'),
37
36
  method: params.dig('request', 'method'),
38
- headers: params.dig('request', 'headers'),
37
+ headers: params.dig('request', 'headers').dup,
39
38
  post_data: params.dig('request', 'postData')
40
39
  )
41
40
  end
@@ -60,7 +59,6 @@ module Selenium
60
59
  def inspect
61
60
  %(#<#{self.class.name} @id="#{id}" @method="#{method}" @url="#{url}")
62
61
  end
63
-
64
62
  end # Request
65
63
  end # DevTools
66
64
  end # WebDriver
@@ -21,7 +21,6 @@ module Selenium
21
21
  module WebDriver
22
22
  class DevTools
23
23
  class Response
24
-
25
24
  attr_accessor :code, :body, :headers
26
25
  attr_reader :id
27
26
 
@@ -35,7 +34,7 @@ module Selenium
35
34
  id: id,
36
35
  code: params['responseStatusCode'],
37
36
  body: (Base64.strict_decode64(encoded_body) if encoded_body),
38
- headers: params['responseHeaders'].each_with_object({}) do |header, hash|
37
+ headers: params.fetch('responseHeaders', []).each_with_object({}) do |header, hash|
39
38
  hash[header['name']] = header['value']
40
39
  end
41
40
  )
@@ -59,7 +58,6 @@ module Selenium
59
58
  def inspect
60
59
  %(#<#{self.class.name} @id="#{id}" @code="#{code}")
61
60
  end
62
-
63
61
  end # Response
64
62
  end # DevTools
65
63
  end # WebDriver
@@ -20,52 +20,32 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  class DevTools
23
- RESPONSE_WAIT_TIMEOUT = 30
24
- RESPONSE_WAIT_INTERVAL = 0.1
25
-
26
23
  autoload :ConsoleEvent, 'selenium/webdriver/devtools/console_event'
27
24
  autoload :ExceptionEvent, 'selenium/webdriver/devtools/exception_event'
28
25
  autoload :MutationEvent, 'selenium/webdriver/devtools/mutation_event'
26
+ autoload :NetworkInterceptor, 'selenium/webdriver/devtools/network_interceptor'
29
27
  autoload :PinnedScript, 'selenium/webdriver/devtools/pinned_script'
30
28
  autoload :Request, 'selenium/webdriver/devtools/request'
31
29
  autoload :Response, 'selenium/webdriver/devtools/response'
32
30
 
33
31
  def initialize(url:)
34
- @callback_threads = ThreadGroup.new
35
-
36
- @messages = []
32
+ @ws = WebSocketConnection.new(url: url)
37
33
  @session_id = nil
38
- @url = url
39
-
40
- process_handshake
41
- @socket_thread = attach_socket_listener
42
34
  start_session
43
35
  end
44
36
 
45
37
  def close
46
- @callback_threads.list.each(&:exit)
47
- @socket_thread.exit
48
- socket.close
38
+ @ws.close
49
39
  end
50
40
 
51
41
  def callbacks
52
- @callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
42
+ @ws.callbacks
53
43
  end
54
44
 
55
45
  def send_cmd(method, **params)
56
- id = next_id
57
- data = {id: id, method: method, params: params.reject { |_, v| v.nil? }}
46
+ data = {method: method, params: params.compact}
58
47
  data[:sessionId] = @session_id if @session_id
59
- data = JSON.generate(data)
60
- WebDriver.logger.debug "DevTools -> #{data}"
61
-
62
- out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
63
- socket.write(out_frame.to_s)
64
-
65
- message = wait.until do
66
- @messages.find { |m| m['id'] == id }
67
- end
68
-
48
+ message = @ws.send_cmd(**data)
69
49
  raise Error::WebDriverError, error_message(message['error']) if message['error']
70
50
 
71
51
  message
@@ -91,32 +71,6 @@ module Selenium
91
71
 
92
72
  private
93
73
 
94
- def process_handshake
95
- socket.print(ws.to_s)
96
- ws << socket.readpartial(1024)
97
- end
98
-
99
- def attach_socket_listener
100
- Thread.new do
101
- Thread.current.abort_on_exception = true
102
- Thread.current.report_on_exception = false
103
-
104
- until socket.eof?
105
- incoming_frame << socket.readpartial(1024)
106
-
107
- while (frame = incoming_frame.next)
108
- message = process_frame(frame)
109
- next unless message['method']
110
-
111
- params = message['params']
112
- callbacks[message['method']].each do |callback|
113
- @callback_threads.add(callback_thread(params, &callback))
114
- end
115
- end
116
- end
117
- end
118
- end
119
-
120
74
  def start_session
121
75
  targets = target.get_targets.dig('result', 'targetInfos')
122
76
  page_target = targets.find { |target| target['type'] == 'page' }
@@ -124,70 +78,9 @@ module Selenium
124
78
  @session_id = session.dig('result', 'sessionId')
125
79
  end
126
80
 
127
- def incoming_frame
128
- @incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
129
- end
130
-
131
- def process_frame(frame)
132
- message = frame.to_s
133
-
134
- # Firefox will periodically fail on unparsable empty frame
135
- return {} if message.empty?
136
-
137
- message = JSON.parse(message)
138
- @messages << message
139
- WebDriver.logger.debug "DevTools <- #{message}"
140
-
141
- message
142
- end
143
-
144
- def callback_thread(params)
145
- Thread.new do
146
- Thread.current.abort_on_exception = true
147
-
148
- # We might end up blocked forever when we have an error in event.
149
- # For example, if network interception event raises error,
150
- # the browser will keep waiting for the request to be proceeded
151
- # before returning back to the original thread. In this case,
152
- # we should at least print the error.
153
- Thread.current.report_on_exception = true
154
-
155
- yield params
156
- end
157
- end
158
-
159
- def wait
160
- @wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
161
- end
162
-
163
- def socket
164
- @socket ||= begin
165
- if URI(@url).scheme == 'wss'
166
- socket = TCPSocket.new(ws.host, ws.port)
167
- socket = OpenSSL::SSL::SSLSocket.new(socket, OpenSSL::SSL::SSLContext.new)
168
- socket.sync_close = true
169
- socket.connect
170
-
171
- socket
172
- else
173
- TCPSocket.new(ws.host, ws.port)
174
- end
175
- end
176
- end
177
-
178
- def ws
179
- @ws ||= WebSocket::Handshake::Client.new(url: @url)
180
- end
181
-
182
- def next_id
183
- @id ||= 0
184
- @id += 1
185
- end
186
-
187
81
  def error_message(error)
188
82
  [error['code'], error['message'], error['data']].join(': ')
189
83
  end
190
-
191
84
  end # DevTools
192
85
  end # WebDriver
193
86
  end # Selenium
@@ -17,18 +17,25 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- require 'selenium/webdriver/chrome/driver'
20
+ require 'selenium/webdriver/chromium/driver'
21
21
 
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Edge
25
-
26
25
  #
27
26
  # Driver implementation for Microsoft Edge.
28
27
  # @api private
29
28
  #
30
29
 
31
- class Driver < Selenium::WebDriver::Chrome::Driver
30
+ class Driver < Chromium::Driver
31
+ def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
32
+ raise ArgumentError, "Can't initialize #{self.class} with :url" if url
33
+
34
+ caps = process_options(options, capabilities)
35
+ url = service_url(service || Service.edge)
36
+ super(caps: caps, url: url, **opts)
37
+ end
38
+
32
39
  def browser
33
40
  :edge
34
41
  end
@@ -38,6 +45,16 @@ module Selenium
38
45
  def devtools_address
39
46
  "http://#{capabilities['ms:edgeOptions']['debuggerAddress']}"
40
47
  end
48
+
49
+ def process_options(options, capabilities)
50
+ if options && !options.is_a?(Options)
51
+ raise ArgumentError, ":options must be an instance of #{Options}"
52
+ elsif options.nil? && capabilities.nil?
53
+ options = Options.new
54
+ end
55
+
56
+ super(options, capabilities)
57
+ end
41
58
  end # Driver
42
59
  end # Edge
43
60
  end # WebDriver
@@ -17,26 +17,26 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- require 'selenium/webdriver/chrome/features'
20
+ require 'selenium/webdriver/chromium/features'
21
21
 
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Edge
25
25
  module Features
26
-
27
- include WebDriver::Chrome::Features
26
+ include WebDriver::Chromium::Features
28
27
 
29
28
  EDGE_COMMANDS = {
30
29
  get_cast_sinks: [:get, 'session/:session_id/ms/cast/get_sinks'],
31
30
  set_cast_sink_to_use: [:post, 'session/:session_id/ms/cast/set_sink_to_use'],
32
31
  start_cast_tab_mirroring: [:post, 'session/:session_id/ms/cast/start_tab_mirroring'],
32
+ start_cast_desktop_mirroring: [:post, 'session/:session_id/ms/cast/start_desktop_mirroring'],
33
33
  get_cast_issue_message: [:get, 'session/:session_id/ms/cast/get_issue_message'],
34
34
  stop_casting: [:post, 'session/:session_id/ms/cast/stop_casting'],
35
35
  send_command: [:post, 'session/:session_id/ms/cdp/execute']
36
36
  }.freeze
37
37
 
38
38
  def commands(command)
39
- EDGE_COMMANDS[command] || Chrome::Features::CHROME_COMMANDS[command] || self.class::COMMANDS[command]
39
+ EDGE_COMMANDS[command] || CHROMIUM_COMMANDS[command] || self.class::COMMANDS[command]
40
40
  end
41
41
  end # Bridge
42
42
  end # Edge
@@ -17,23 +17,21 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- require 'selenium/webdriver/chrome/options'
20
+ require 'selenium/webdriver/chromium/options'
21
21
 
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Edge
25
- class Options < Selenium::WebDriver::Chrome::Options
25
+ class Options < Chromium::Options
26
26
  KEY = 'ms:edgeOptions'
27
27
  BROWSER = 'MicrosoftEdge'
28
28
 
29
- protected
29
+ private
30
30
 
31
31
  def enable_logging(browser_options)
32
32
  browser_options['ms:loggingPrefs'] = @logging_prefs
33
33
  end
34
34
 
35
- private
36
-
37
35
  def binary_path
38
36
  Edge.path
39
37
  end
@@ -17,7 +17,7 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- require 'selenium/webdriver/chrome/profile'
20
+ require 'selenium/webdriver/chromium/profile'
21
21
 
22
22
  module Selenium
23
23
  module WebDriver
@@ -26,7 +26,7 @@ module Selenium
26
26
  # @private
27
27
  #
28
28
 
29
- class Profile < Selenium::WebDriver::Chrome::Profile
29
+ class Profile < Chromium::Profile
30
30
  end # Profile
31
31
  end # Edge
32
32
  end # WebDriver
@@ -17,12 +17,12 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- require 'selenium/webdriver/chrome/service'
20
+ require 'selenium/webdriver/chromium/service'
21
21
 
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Edge
25
- class Service < Selenium::WebDriver::Chrome::Service
25
+ class Service < Chromium::Service
26
26
  DEFAULT_PORT = 9515
27
27
  EXECUTABLE = 'msedgedriver'
28
28
  MISSING_TEXT = <<~ERROR
@@ -20,7 +20,6 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Firefox
23
-
24
23
  #
25
24
  # Driver implementation for Firefox using GeckoDriver.
26
25
  # @api private
@@ -30,12 +29,21 @@ module Selenium
30
29
  EXTENSIONS = [DriverExtensions::HasAddons,
31
30
  DriverExtensions::FullPageScreenshot,
32
31
  DriverExtensions::HasContext,
32
+ DriverExtensions::HasBiDi,
33
33
  DriverExtensions::HasDevTools,
34
34
  DriverExtensions::HasLogEvents,
35
35
  DriverExtensions::HasNetworkInterception,
36
36
  DriverExtensions::HasWebStorage,
37
37
  DriverExtensions::PrintsPage].freeze
38
38
 
39
+ def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
40
+ raise ArgumentError, "Can't initialize #{self.class} with :url" if url
41
+
42
+ caps = process_options(options, capabilities)
43
+ url = service_url(service || Service.firefox)
44
+ super(caps: caps, url: url, **opts)
45
+ end
46
+
39
47
  def browser
40
48
  :firefox
41
49
  end
@@ -44,7 +52,7 @@ module Selenium
44
52
 
45
53
  def devtools_url
46
54
  if capabilities['moz:debuggerAddress'].nil?
47
- raise(Error::WebDriverError, "DevTools is not supported by this version of Firefox; use v85 or higher")
55
+ raise(Error::WebDriverError, 'DevTools is not supported by this version of Firefox; use v85 or higher')
48
56
  end
49
57
 
50
58
  uri = URI("http://#{capabilities['moz:debuggerAddress']}")
@@ -56,6 +64,16 @@ module Selenium
56
64
  def devtools_version
57
65
  Firefox::DEVTOOLS_VERSION
58
66
  end
67
+
68
+ def process_options(options, capabilities)
69
+ if options && !options.is_a?(Options)
70
+ raise ArgumentError, ":options must be an instance of #{Options}"
71
+ elsif options.nil? && capabilities.nil?
72
+ options = Options.new
73
+ end
74
+
75
+ super(options, capabilities)
76
+ end
59
77
  end # Driver
60
78
  end # Firefox
61
79
  end # WebDriver
@@ -21,7 +21,6 @@ module Selenium
21
21
  module WebDriver
22
22
  module Firefox
23
23
  module Features
24
-
25
24
  FIREFOX_COMMANDS = {
26
25
  get_context: [:get, 'session/:session_id/moz/context'],
27
26
  set_context: [:post, 'session/:session_id/moz/context'],
@@ -35,12 +34,13 @@ module Selenium
35
34
  end
36
35
 
37
36
  def install_addon(path, temporary)
38
- if @file_detector
39
- local_file = @file_detector.call(path)
40
- path = upload(local_file) if local_file
41
- end
37
+ addon = if File.directory?(path)
38
+ Zipper.zip(path)
39
+ else
40
+ File.open(path, 'rb') { |crx_file| Base64.strict_encode64 crx_file.read }
41
+ end
42
42
 
43
- payload = {path: path}
43
+ payload = {addon: addon}
44
44
  payload[:temporary] = temporary unless temporary.nil?
45
45
  execute :install_addon, {}, payload
46
46
  end
@@ -25,11 +25,12 @@ module Selenium
25
25
 
26
26
  KEY = 'moz:firefoxOptions'
27
27
 
28
- # see: https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html
28
+ # see: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions
29
29
  CAPABILITIES = {binary: 'binary',
30
30
  args: 'args',
31
31
  log: 'log',
32
32
  prefs: 'prefs',
33
+ env: 'env',
33
34
  android_package: 'androidPackage',
34
35
  android_activity: 'androidActivity',
35
36
  android_device_serial: 'androidDeviceSerial',
@@ -56,12 +57,14 @@ module Selenium
56
57
  #
57
58
 
58
59
  def initialize(log_level: nil, **opts)
59
- @debugger_address = opts.delete(:debugger_address)
60
+ @debugger_address = opts.delete(:debugger_address) { true }
61
+ opts[:accept_insecure_certs] = true unless opts.key?(:accept_insecure_certs)
60
62
 
61
63
  super(**opts)
62
64
 
63
65
  @options[:args] ||= []
64
66
  @options[:prefs] ||= {}
67
+ @options[:env] ||= {}
65
68
  @options[:log] ||= {level: log_level} if log_level
66
69
 
67
70
  process_profile(@options.delete(:profile))
@@ -105,6 +108,9 @@ module Selenium
105
108
  #
106
109
 
107
110
  def headless!
111
+ WebDriver.logger.deprecate('`Options#headless!`',
112
+ "`Options#add_argument('-headless')`",
113
+ id: :headless)
108
114
  add_argument '-headless'
109
115
  end
110
116
 
@@ -175,7 +181,7 @@ module Selenium
175
181
  end
176
182
 
177
183
  def camelize?(key)
178
- key != "prefs"
184
+ key != 'prefs'
179
185
  end
180
186
  end # Options
181
187
  end # Firefox