selenium-webdriver 4.2.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES +40 -1
- data/lib/selenium/server.rb +1 -1
- data/lib/selenium/webdriver/chrome/options.rb +14 -0
- data/lib/selenium/webdriver/chrome.rb +0 -14
- data/lib/selenium/webdriver/common/driver.rb +20 -53
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -67
- data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +17 -34
- data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +1 -1
- data/lib/selenium/webdriver/common/manager.rb +0 -27
- data/lib/selenium/webdriver/common/options.rb +2 -9
- data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +83 -0
- data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +73 -0
- data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
- data/lib/selenium/webdriver/common/websocket_connection.rb +13 -6
- data/lib/selenium/webdriver/common/window.rb +6 -6
- data/lib/selenium/webdriver/common/zipper.rb +1 -1
- data/lib/selenium/webdriver/common.rb +3 -1
- data/lib/selenium/webdriver/devtools/network_interceptor.rb +176 -0
- data/lib/selenium/webdriver/devtools.rb +1 -0
- data/lib/selenium/webdriver/firefox.rb +0 -14
- data/lib/selenium/webdriver/ie.rb +0 -14
- data/lib/selenium/webdriver/remote/bridge.rb +35 -2
- data/lib/selenium/webdriver/remote/commands.rb +15 -1
- data/lib/selenium/webdriver/remote/driver.rb +0 -1
- data/lib/selenium/webdriver/safari.rb +0 -14
- data/lib/selenium/webdriver/version.rb +1 -1
- data/selenium-webdriver.gemspec +3 -3
- metadata +17 -15
- data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +0 -31
- data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2be9fb14d69ce82170e3ddbe0cbcf2f2428e1978544d8a7c11191ebd37cf0241
|
4
|
+
data.tar.gz: 2e40d6ad67ccfb269ee1ae9d3f47d024e90081da9881f757f22170751875fa69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7f419d9b1349a1afbf68999eecaa53391503cc793026228577fbcb40ac4958a35c539286f7c4c3c913992336dc797481f7c57f6aec9d58813671e24d1cd09ad
|
7
|
+
data.tar.gz: '0093bd2d0fbb0c1c8e7abee9d1a632f80d458b1dc0d1b22635f403230ed7c40a2be498915d8abed40bd4992295a1444a69c3e653ebf1d4cf106886c8a9d5a499'
|
data/CHANGES
CHANGED
@@ -1,4 +1,43 @@
|
|
1
|
-
4.
|
1
|
+
4.4.0 (Unreleased)
|
2
|
+
=========================
|
3
|
+
|
4
|
+
BiDi:
|
5
|
+
* Released selenium-devtools 0.103.1 to fix websocket dependency requirement
|
6
|
+
* Released selenium-devtools 0.104.0 (supports CDP v85, v102, v103, v104)
|
7
|
+
* Have network interceptor ignore cancelled requests (#10856)
|
8
|
+
* Improve websocket message handling
|
9
|
+
|
10
|
+
Chromium:
|
11
|
+
* Prevent users from setting w3c: false and warn for true (thanks Tamsil Amani!)
|
12
|
+
|
13
|
+
Ruby:
|
14
|
+
* Implement Virtual Authenticator (#10903, #10541) (thanks Tamsil Amani!)
|
15
|
+
|
16
|
+
4.3.0 (2022-06-23)
|
17
|
+
=========================
|
18
|
+
|
19
|
+
BiDi:
|
20
|
+
* Released selenium-devtools 0.103.0 (supports CDP v85, v101, v102, v103)
|
21
|
+
|
22
|
+
Ruby:
|
23
|
+
* Allow specifying which button is clicked in pointer action class methods
|
24
|
+
* Remove deprecated `Persistent` http class
|
25
|
+
* Remove deprecated HasRemoteStatus module
|
26
|
+
* Remove deprecated `Manager#new_window` and `Manager#logs`
|
27
|
+
* `ActionBuilder#move_to` no longer attempts to move to top left corner of element
|
28
|
+
* Remove deprecated support for sending Service parameters directly to Driver constructor
|
29
|
+
* Remove deprecated setters and getters for driver path on Browser modules
|
30
|
+
* Remove deprecated support for passing in options argument to Options class
|
31
|
+
* Allow `:options` parameter to take `Options` instance argument like other languages
|
32
|
+
* Remove deprecated support for `:desired_capabilities` & `:options` with `Hash` argument
|
33
|
+
|
34
|
+
4.2.1 (2022-05-31)
|
35
|
+
=========================
|
36
|
+
|
37
|
+
Ruby
|
38
|
+
* Fix bug in setting default duration in Actions constructor
|
39
|
+
|
40
|
+
4.2.0 (2022-05-27)
|
2
41
|
=========================
|
3
42
|
|
4
43
|
BiDi:
|
data/lib/selenium/server.rb
CHANGED
@@ -227,6 +227,9 @@ module Selenium
|
|
227
227
|
|
228
228
|
options = browser_options[self.class::KEY]
|
229
229
|
options['binary'] ||= binary_path if binary_path
|
230
|
+
|
231
|
+
check_w3c(options[:w3c]) if options.key?(:w3c)
|
232
|
+
|
230
233
|
if @profile
|
231
234
|
options['args'] ||= []
|
232
235
|
options['args'] << "--user-data-dir=#{@profile.directory}"
|
@@ -237,6 +240,17 @@ module Selenium
|
|
237
240
|
options['extensions'] = @encoded_extensions + @extensions.map { |ext| encode_extension(ext) }
|
238
241
|
end
|
239
242
|
|
243
|
+
def check_w3c(w3c)
|
244
|
+
if w3c
|
245
|
+
WebDriver.logger.warn("Setting 'w3c: true' is redundant and will no longer be allowed", id: :w3c)
|
246
|
+
return
|
247
|
+
end
|
248
|
+
|
249
|
+
raise Error::InvalidArgumentError,
|
250
|
+
"Setting 'w3c: false' is not allowed.\n" \
|
251
|
+
"Please update to W3C Syntax: https://www.selenium.dev/blog/2022/legacy-protocol-support/"
|
252
|
+
end
|
253
|
+
|
240
254
|
def binary_path
|
241
255
|
Chrome.path
|
242
256
|
end
|
@@ -28,20 +28,6 @@ module Selenium
|
|
28
28
|
autoload :Options, 'selenium/webdriver/chrome/options'
|
29
29
|
autoload :Service, 'selenium/webdriver/chrome/service'
|
30
30
|
|
31
|
-
def self.driver_path=(path)
|
32
|
-
WebDriver.logger.deprecate 'Selenium::WebDriver::Chrome#driver_path=',
|
33
|
-
'Selenium::WebDriver::Chrome::Service#driver_path=',
|
34
|
-
id: :driver_path
|
35
|
-
Selenium::WebDriver::Chrome::Service.driver_path = path
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.driver_path
|
39
|
-
WebDriver.logger.deprecate 'Selenium::WebDriver::Chrome#driver_path',
|
40
|
-
'Selenium::WebDriver::Chrome::Service#driver_path',
|
41
|
-
id: :driver_path
|
42
|
-
Selenium::WebDriver::Chrome::Service.driver_path
|
43
|
-
end
|
44
|
-
|
45
31
|
def self.path=(path)
|
46
32
|
Platform.assert_executable path
|
47
33
|
@path = path
|
@@ -248,6 +248,15 @@ module Selenium
|
|
248
248
|
bridge.execute_async_script(script, *args)
|
249
249
|
end
|
250
250
|
|
251
|
+
#
|
252
|
+
# @return [VirtualAuthenticator]
|
253
|
+
# @see VirtualAuthenticator
|
254
|
+
#
|
255
|
+
|
256
|
+
def add_virtual_authenticator(options)
|
257
|
+
bridge.add_virtual_authenticator(options)
|
258
|
+
end
|
259
|
+
|
251
260
|
#-------------------------------- sugar --------------------------------
|
252
261
|
|
253
262
|
#
|
@@ -307,71 +316,29 @@ module Selenium
|
|
307
316
|
|
308
317
|
attr_reader :bridge
|
309
318
|
|
310
|
-
def create_bridge(
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
desired_capabilities = opts.delete(:desired_capabilities)
|
317
|
-
if desired_capabilities
|
318
|
-
WebDriver.logger.deprecate(':desired_capabilities as a parameter for driver initialization',
|
319
|
-
':capabilities with an Array value of capabilities/options if necessary',
|
320
|
-
id: :desired_capabilities)
|
321
|
-
desired_capabilities = Remote::Capabilities.new(desired_capabilities) if desired_capabilities.is_a?(Hash)
|
322
|
-
cap_array << desired_capabilities
|
323
|
-
end
|
324
|
-
|
325
|
-
options = opts.delete(:options)
|
326
|
-
if options
|
327
|
-
WebDriver.logger.deprecate(':options as a parameter for driver initialization',
|
328
|
-
':capabilities with an Array of value capabilities/options if necessary',
|
329
|
-
id: :browser_options)
|
330
|
-
cap_array << options
|
319
|
+
def create_bridge(capabilities: nil, options: nil, url: nil, service: nil, http_client: nil)
|
320
|
+
Remote::Bridge.new(http_client: http_client,
|
321
|
+
url: url || service_url(service)).tap do |bridge|
|
322
|
+
generated_caps = options ? options.as_json : generate_capabilities(capabilities)
|
323
|
+
bridge.create_session(generated_caps)
|
331
324
|
end
|
332
|
-
|
333
|
-
capabilities = generate_capabilities(cap_array)
|
334
|
-
|
335
|
-
bridge_opts = {http_client: opts.delete(:http_client), url: opts.delete(:url)}
|
336
|
-
raise ArgumentError, "Unable to create a driver with parameters: #{opts}" unless opts.empty?
|
337
|
-
|
338
|
-
bridge = Remote::Bridge.new(**bridge_opts)
|
339
|
-
|
340
|
-
bridge.create_session(capabilities)
|
341
|
-
bridge
|
342
325
|
end
|
343
326
|
|
344
|
-
def generate_capabilities(
|
345
|
-
|
327
|
+
def generate_capabilities(capabilities)
|
328
|
+
Array(capabilities).map { |cap|
|
346
329
|
if cap.is_a? Symbol
|
347
330
|
cap = Remote::Capabilities.send(cap)
|
348
|
-
elsif cap.is_a? Hash
|
349
|
-
new_message = 'Capabilities instance initialized with the Hash, or build values with Options class'
|
350
|
-
WebDriver.logger.deprecate("passing a Hash value to :capabilities",
|
351
|
-
new_message,
|
352
|
-
id: :capabilities_hash)
|
353
|
-
cap = Remote::Capabilities.new(cap)
|
354
331
|
elsif !cap.respond_to? :as_json
|
355
332
|
msg = ":capabilities parameter only accepts objects responding to #as_json which #{cap.class} does not"
|
356
333
|
raise ArgumentError, msg
|
357
334
|
end
|
358
|
-
cap
|
335
|
+
cap.as_json
|
359
336
|
}.inject(:merge) || Remote::Capabilities.send(browser || :new)
|
360
337
|
end
|
361
338
|
|
362
|
-
def service_url(
|
363
|
-
|
364
|
-
|
365
|
-
next unless opts.key? key
|
366
|
-
|
367
|
-
WebDriver.logger.deprecate(":#{key}", ':service with an instance of Selenium::WebDriver::Service',
|
368
|
-
id: "service_#{key}".to_sym)
|
369
|
-
end
|
370
|
-
service_config ||= Service.send(browser,
|
371
|
-
args: opts.delete(:driver_opts),
|
372
|
-
path: opts.delete(:driver_path),
|
373
|
-
port: opts.delete(:port))
|
374
|
-
@service = service_config.launch
|
339
|
+
def service_url(service)
|
340
|
+
service ||= Service.send(browser)
|
341
|
+
@service = service.launch
|
375
342
|
@service.uri
|
376
343
|
end
|
377
344
|
|
@@ -61,75 +61,10 @@ module Selenium
|
|
61
61
|
#
|
62
62
|
|
63
63
|
def intercept(&block)
|
64
|
-
|
65
|
-
|
66
|
-
id = params['requestId']
|
67
|
-
if params.key?('responseStatusCode') || params.key?('responseErrorReason')
|
68
|
-
intercept_response(id, params, &pending_response_requests.delete(id))
|
69
|
-
else
|
70
|
-
intercept_request(id, params, &block)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}])
|
64
|
+
@interceptor ||= DevTools::NetworkInterceptor.new(devtools)
|
65
|
+
@interceptor.intercept(&block)
|
74
66
|
end
|
75
67
|
|
76
|
-
private
|
77
|
-
|
78
|
-
def pending_response_requests
|
79
|
-
@pending_response_requests ||= {}
|
80
|
-
end
|
81
|
-
|
82
|
-
def intercept_request(id, params, &block)
|
83
|
-
original = DevTools::Request.from(id, params)
|
84
|
-
mutable = DevTools::Request.from(id, params)
|
85
|
-
|
86
|
-
block.call(mutable) do |&continue| # rubocop:disable Performance/RedundantBlockCall
|
87
|
-
pending_response_requests[id] = continue
|
88
|
-
|
89
|
-
if original == mutable
|
90
|
-
devtools.fetch.continue_request(request_id: id)
|
91
|
-
else
|
92
|
-
devtools.fetch.continue_request(
|
93
|
-
request_id: id,
|
94
|
-
url: mutable.url,
|
95
|
-
method: mutable.method,
|
96
|
-
post_data: mutable.post_data,
|
97
|
-
headers: mutable.headers.map do |k, v|
|
98
|
-
{name: k, value: v}
|
99
|
-
end
|
100
|
-
)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def intercept_response(id, params)
|
106
|
-
return devtools.fetch.continue_request(request_id: id) unless block_given?
|
107
|
-
|
108
|
-
body = fetch_response_body(id)
|
109
|
-
original = DevTools::Response.from(id, body, params)
|
110
|
-
mutable = DevTools::Response.from(id, body, params)
|
111
|
-
yield mutable
|
112
|
-
|
113
|
-
if original == mutable
|
114
|
-
devtools.fetch.continue_request(request_id: id)
|
115
|
-
else
|
116
|
-
devtools.fetch.fulfill_request(
|
117
|
-
request_id: id,
|
118
|
-
body: (Base64.strict_encode64(mutable.body) if mutable.body),
|
119
|
-
response_code: mutable.code,
|
120
|
-
response_headers: mutable.headers.map do |k, v|
|
121
|
-
{name: k, value: v}
|
122
|
-
end
|
123
|
-
)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def fetch_response_body(id)
|
128
|
-
devtools.fetch.get_response_body(request_id: id).dig('result', 'body')
|
129
|
-
rescue Error::WebDriverError
|
130
|
-
# CDP fails to get body on certain responses (301) and raises:
|
131
|
-
# Can only get response body on requests captured after headers received.
|
132
|
-
end
|
133
68
|
end # HasNetworkInterception
|
134
69
|
end # DriverExtensions
|
135
70
|
end # WebDriver
|
@@ -28,7 +28,7 @@ module Selenium
|
|
28
28
|
#
|
29
29
|
|
30
30
|
def default_move_duration
|
31
|
-
@default_move_duration ||= @duration / 1000 # convert ms to seconds
|
31
|
+
@default_move_duration ||= @duration / 1000.0 # convert ms to seconds
|
32
32
|
end
|
33
33
|
|
34
34
|
#
|
@@ -68,9 +68,8 @@ module Selenium
|
|
68
68
|
end
|
69
69
|
|
70
70
|
#
|
71
|
-
# Moves the pointer to the
|
72
|
-
#
|
73
|
-
# Then the pointer is moved to optional offset coordinates from the element.
|
71
|
+
# Moves the pointer to the in-view center point of the given element.
|
72
|
+
# Then the pointer is moved to optional offset coordinates.
|
74
73
|
#
|
75
74
|
# The element is not scrolled into view.
|
76
75
|
# MoveTargetOutOfBoundsError will be raised if element with offset is outside the viewport
|
@@ -88,32 +87,19 @@ module Selenium
|
|
88
87
|
# driver.action.move_to(el, 100, 100).perform
|
89
88
|
#
|
90
89
|
# @param [Selenium::WebDriver::Element] element to move to.
|
91
|
-
# @param [Integer] right_by Optional offset from the
|
92
|
-
# coordinates to the left of the
|
93
|
-
# @param [Integer] down_by Optional offset from the
|
94
|
-
# coordinates
|
90
|
+
# @param [Integer] right_by Optional offset from the in-view center of the
|
91
|
+
# element. A negative value means coordinates to the left of the center.
|
92
|
+
# @param [Integer] down_by Optional offset from the in-view center of the
|
93
|
+
# element. A negative value means coordinates to the top of the center.
|
95
94
|
# @param [Symbol || String] device optional name of the PointerInput device to move.
|
96
95
|
# @return [ActionBuilder] A self reference.
|
97
96
|
#
|
98
97
|
|
99
98
|
def move_to(element, right_by = nil, down_by = nil, device: nil, duration: default_move_duration, **opts)
|
100
99
|
pointer = pointer_input(device)
|
101
|
-
if right_by || down_by
|
102
|
-
WebDriver.logger.warn("moving to an element with offset currently tries to use
|
103
|
-
the top left corner of the element as the origin; in Selenium 4.3 it will use the in-view
|
104
|
-
center point of the element as the origin.")
|
105
|
-
size = element.size
|
106
|
-
left_offset = (size[:width] / 2).to_i
|
107
|
-
top_offset = (size[:height] / 2).to_i
|
108
|
-
left = -left_offset + (right_by || 0)
|
109
|
-
top = -top_offset + (down_by || 0)
|
110
|
-
else
|
111
|
-
left = 0
|
112
|
-
top = 0
|
113
|
-
end
|
114
100
|
pointer.create_pointer_move(duration: duration,
|
115
|
-
x:
|
116
|
-
y:
|
101
|
+
x: right_by || 0,
|
102
|
+
y: down_by || 0,
|
117
103
|
origin: element,
|
118
104
|
**opts)
|
119
105
|
tick(pointer)
|
@@ -192,9 +178,9 @@ center point of the element as the origin.")
|
|
192
178
|
# @return [ActionBuilder] A self reference.
|
193
179
|
#
|
194
180
|
|
195
|
-
def click_and_hold(element = nil, device: nil)
|
181
|
+
def click_and_hold(element = nil, button: nil, device: nil)
|
196
182
|
move_to(element, device: device) if element
|
197
|
-
pointer_down(:left, device: device)
|
183
|
+
pointer_down(button || :left, device: device)
|
198
184
|
self
|
199
185
|
end
|
200
186
|
|
@@ -211,8 +197,8 @@ center point of the element as the origin.")
|
|
211
197
|
# @return [ActionBuilder] A self reference.
|
212
198
|
#
|
213
199
|
|
214
|
-
def release(device: nil)
|
215
|
-
pointer_up(:left, device: device)
|
200
|
+
def release(button: nil, device: nil)
|
201
|
+
pointer_up(button || :left, device: device)
|
216
202
|
self
|
217
203
|
end
|
218
204
|
|
@@ -238,10 +224,10 @@ center point of the element as the origin.")
|
|
238
224
|
# @return [ActionBuilder] A self reference.
|
239
225
|
#
|
240
226
|
|
241
|
-
def click(element = nil, device: nil)
|
227
|
+
def click(element = nil, button: nil, device: nil)
|
242
228
|
move_to(element, device: device) if element
|
243
|
-
pointer_down(:left, device: device)
|
244
|
-
pointer_up(:left, device: device)
|
229
|
+
pointer_down(button || :left, device: device)
|
230
|
+
pointer_up(button || :left, device: device)
|
245
231
|
self
|
246
232
|
end
|
247
233
|
|
@@ -296,10 +282,7 @@ center point of the element as the origin.")
|
|
296
282
|
#
|
297
283
|
|
298
284
|
def context_click(element = nil, device: nil)
|
299
|
-
|
300
|
-
pointer_down(:right, device: device)
|
301
|
-
pointer_up(:right, device: device)
|
302
|
-
self
|
285
|
+
click(element, button: :right, device: device)
|
303
286
|
end
|
304
287
|
|
305
288
|
#
|
@@ -104,33 +104,6 @@ module Selenium
|
|
104
104
|
@timeouts ||= Timeouts.new(@bridge)
|
105
105
|
end
|
106
106
|
|
107
|
-
def logs
|
108
|
-
WebDriver.logger.deprecate('Manager#logs', 'Chrome::Driver#logs')
|
109
|
-
@logs ||= Logs.new(@bridge)
|
110
|
-
end
|
111
|
-
|
112
|
-
#
|
113
|
-
# @param type [Symbol] Supports two values: :tab and :window.
|
114
|
-
# @return [String] The value of the window handle
|
115
|
-
#
|
116
|
-
def new_window(type = :tab)
|
117
|
-
WebDriver.logger.deprecate('Manager#new_window', 'TargetLocator#new_window', id: :new_window) do
|
118
|
-
'e.g., `driver.switch_to.new_window(:tab)`'
|
119
|
-
end
|
120
|
-
case type
|
121
|
-
when :tab, :window
|
122
|
-
result = @bridge.new_window(type)
|
123
|
-
unless result.key?('handle')
|
124
|
-
raise UnknownError, "the driver did not return a handle. " \
|
125
|
-
"The returned result: #{result.inspect}"
|
126
|
-
end
|
127
|
-
result['handle']
|
128
|
-
else
|
129
|
-
raise ArgumentError, "invalid argument for type. Got: '#{type.inspect}'. " \
|
130
|
-
"Try :tab or :window"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
107
|
def window
|
135
108
|
@window ||= Window.new(@bridge)
|
136
109
|
end
|
@@ -66,17 +66,10 @@ module Selenium
|
|
66
66
|
|
67
67
|
attr_accessor :options
|
68
68
|
|
69
|
-
def initialize(
|
69
|
+
def initialize(**opts)
|
70
70
|
self.class.set_capabilities
|
71
71
|
|
72
|
-
@options =
|
73
|
-
WebDriver.logger.deprecate(":options as keyword for initializing #{self.class}",
|
74
|
-
"custom values directly in #new constructor",
|
75
|
-
id: :options_options)
|
76
|
-
opts.merge(options)
|
77
|
-
else
|
78
|
-
opts
|
79
|
-
end
|
72
|
+
@options = opts
|
80
73
|
@options[:browser_name] = self.class::BROWSER
|
81
74
|
end
|
82
75
|
|
@@ -32,7 +32,7 @@ module Selenium
|
|
32
32
|
def save_screenshot(png_path, full_page: false)
|
33
33
|
extension = File.extname(png_path).downcase
|
34
34
|
if extension != '.png'
|
35
|
-
WebDriver.logger.warn "name used for saved screenshot does not match file type. "\
|
35
|
+
WebDriver.logger.warn "name used for saved screenshot does not match file type. " \
|
36
36
|
"It should end with .png extension",
|
37
37
|
id: :screenshot
|
38
38
|
end
|
@@ -0,0 +1,83 @@
|
|
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
|
+
#
|
21
|
+
# A credential stored in a virtual authenticator.
|
22
|
+
# @see https://w3c.github.io/webauthn/#credential-parameters
|
23
|
+
#
|
24
|
+
|
25
|
+
module Selenium
|
26
|
+
module WebDriver
|
27
|
+
class Credential
|
28
|
+
class << self
|
29
|
+
def resident(**opts)
|
30
|
+
Credential.new(resident_credential: true, **opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def non_resident(**opts)
|
34
|
+
Credential.new(resident_credential: false, **opts)
|
35
|
+
end
|
36
|
+
|
37
|
+
def encode(byte_array)
|
38
|
+
Base64.urlsafe_encode64(byte_array&.pack('C*'))
|
39
|
+
end
|
40
|
+
|
41
|
+
def decode(base64)
|
42
|
+
Base64.urlsafe_decode64(base64).unpack('C*')
|
43
|
+
end
|
44
|
+
|
45
|
+
def from_json(opts)
|
46
|
+
user_handle = opts['userHandle'] ? decode(opts['userHandle']) : nil
|
47
|
+
new(id: decode(opts["credentialId"]),
|
48
|
+
resident_credential: opts["isResidentCredential"],
|
49
|
+
rp_id: opts['rpId'],
|
50
|
+
private_key: opts['privateKey'],
|
51
|
+
sign_count: opts['signCount'],
|
52
|
+
user_handle: user_handle)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :id, :resident_credential, :rp_id, :user_handle, :private_key, :sign_count
|
57
|
+
alias_method :resident_credential?, :resident_credential
|
58
|
+
|
59
|
+
def initialize(id:, resident_credential:, rp_id:, private_key:, user_handle: nil, sign_count: 0)
|
60
|
+
@id = id
|
61
|
+
@resident_credential = resident_credential
|
62
|
+
@rp_id = rp_id
|
63
|
+
@user_handle = user_handle
|
64
|
+
@private_key = private_key
|
65
|
+
@sign_count = sign_count
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
#
|
71
|
+
|
72
|
+
def as_json(*)
|
73
|
+
credential_data = {'credentialId' => Credential.encode(id),
|
74
|
+
'isResidentCredential' => resident_credential?,
|
75
|
+
'rpId' => rp_id,
|
76
|
+
'privateKey' => Credential.encode(private_key),
|
77
|
+
'signCount' => sign_count}
|
78
|
+
credential_data['userHandle'] = Credential.encode(user_handle) if user_handle
|
79
|
+
credential_data
|
80
|
+
end
|
81
|
+
end # Credential
|
82
|
+
end # WebDriver
|
83
|
+
end # Selenium
|
@@ -0,0 +1,73 @@
|
|
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 VirtualAuthenticator
|
23
|
+
|
24
|
+
attr_reader :options
|
25
|
+
|
26
|
+
#
|
27
|
+
# api private
|
28
|
+
# Use `Driver#add_virtual_authenticator`
|
29
|
+
#
|
30
|
+
|
31
|
+
def initialize(bridge, authenticator_id, options)
|
32
|
+
@id = authenticator_id
|
33
|
+
@bridge = bridge
|
34
|
+
@options = options
|
35
|
+
@valid = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_credential(credential)
|
39
|
+
credential = credential.as_json
|
40
|
+
@bridge.add_credential credential, @id
|
41
|
+
end
|
42
|
+
|
43
|
+
def credentials
|
44
|
+
credential_data = @bridge.credentials @id
|
45
|
+
credential_data.map do |cred|
|
46
|
+
Credential.from_json(cred)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_credential(credential_id)
|
51
|
+
credential_id = Credential.encode(credential_id) if credential_id.instance_of?(Array)
|
52
|
+
@bridge.remove_credential credential_id, @id
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove_all_credentials
|
56
|
+
@bridge.remove_all_credentials @id
|
57
|
+
end
|
58
|
+
|
59
|
+
def user_verified=(verified)
|
60
|
+
@bridge.user_verified verified, @id
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove!
|
64
|
+
@bridge.remove_virtual_authenticator(@id)
|
65
|
+
@valid = false
|
66
|
+
end
|
67
|
+
|
68
|
+
def valid?
|
69
|
+
@valid
|
70
|
+
end
|
71
|
+
end # VirtualAuthenticator
|
72
|
+
end # WebDriver
|
73
|
+
end # Selenium
|