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