selenium-webdriver 4.1.0 → 4.23.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 +405 -1
- data/Gemfile +3 -0
- data/LICENSE +1 -1
- data/NOTICE +1 -1
- data/README.md +3 -3
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/server.rb +38 -40
- data/lib/selenium/webdriver/atoms/findElements.js +27 -27
- data/lib/selenium/webdriver/atoms/getAttribute.js +6 -100
- data/lib/selenium/webdriver/atoms/isDisplayed.js +24 -96
- data/lib/selenium/webdriver/atoms/mutationListener.js +0 -0
- data/lib/selenium/webdriver/atoms.rb +5 -3
- data/lib/selenium/webdriver/bidi/browsing_context.rb +88 -0
- data/lib/selenium/webdriver/bidi/browsing_context_info.rb +35 -0
- data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +35 -0
- data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +35 -0
- data/lib/selenium/webdriver/{common/driver_extensions/has_location.rb → bidi/log/filter_by.rb} +14 -11
- data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +33 -0
- data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +33 -0
- data/lib/selenium/webdriver/bidi/log_handler.rb +63 -0
- data/lib/selenium/webdriver/bidi/log_inspector.rb +147 -0
- data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
- data/lib/selenium/webdriver/bidi/session.rb +51 -0
- data/lib/selenium/webdriver/bidi/struct.rb +44 -0
- data/lib/selenium/webdriver/bidi.rb +66 -0
- data/lib/selenium/webdriver/chrome/driver.rb +9 -29
- data/lib/selenium/webdriver/chrome/features.rb +9 -67
- data/lib/selenium/webdriver/chrome/options.rb +3 -223
- data/lib/selenium/webdriver/chrome/profile.rb +3 -83
- data/lib/selenium/webdriver/chrome/service.rb +4 -19
- data/lib/selenium/webdriver/chrome.rb +0 -16
- data/lib/selenium/webdriver/chromium/driver.rb +61 -0
- data/lib/selenium/webdriver/chromium/features.rb +99 -0
- data/lib/selenium/webdriver/chromium/options.rb +243 -0
- data/lib/selenium/webdriver/chromium/profile.rb +113 -0
- data/lib/selenium/webdriver/chromium.rb +29 -0
- data/lib/selenium/webdriver/common/action_builder.rb +60 -22
- data/lib/selenium/webdriver/common/child_process.rb +130 -0
- data/lib/selenium/webdriver/common/driver.rb +46 -90
- data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -1
- data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_bidi.rb} +10 -5
- data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -1
- data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -4
- data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_fedcm_dialog.rb +55 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +2 -3
- data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -69
- data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -3
- data/lib/selenium/webdriver/common/driver_finder.rb +97 -0
- data/lib/selenium/webdriver/common/element.rb +8 -8
- data/lib/selenium/webdriver/common/error.rb +27 -4
- data/lib/selenium/webdriver/common/fedcm/account.rb +50 -0
- data/lib/selenium/webdriver/common/fedcm/dialog.rb +74 -0
- data/lib/selenium/webdriver/common/fedcm.rb +27 -0
- data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
- data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
- data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -25
- data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
- data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -1
- data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
- data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
- data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
- data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +59 -71
- data/lib/selenium/webdriver/common/{driver_extensions/has_network_connection.rb → interactions/pointer_cancel.rb} +19 -11
- data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
- data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
- data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
- data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
- data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
- data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
- data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
- data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +114 -0
- data/lib/selenium/webdriver/common/interactions/wheel_input.rb +42 -0
- data/lib/selenium/webdriver/common/keys.rb +1 -0
- data/lib/selenium/webdriver/common/local_driver.rb +53 -0
- data/lib/selenium/webdriver/common/logger.rb +93 -28
- data/lib/selenium/webdriver/common/manager.rb +1 -28
- data/lib/selenium/webdriver/common/options.rb +19 -19
- data/lib/selenium/webdriver/common/platform.rb +12 -52
- data/lib/selenium/webdriver/common/port_prober.rb +1 -1
- data/lib/selenium/webdriver/common/profile_helper.rb +1 -1
- data/lib/selenium/webdriver/common/proxy.rb +4 -4
- data/lib/selenium/webdriver/common/script.rb +45 -0
- data/lib/selenium/webdriver/common/search_context.rb +10 -8
- data/lib/selenium/webdriver/common/selenium_manager.rb +104 -0
- data/lib/selenium/webdriver/common/service.rb +21 -30
- data/lib/selenium/webdriver/common/service_manager.rb +8 -15
- data/lib/selenium/webdriver/common/shadow_root.rb +2 -3
- data/lib/selenium/webdriver/common/socket_lock.rb +3 -3
- data/lib/selenium/webdriver/common/socket_poller.rb +3 -3
- data/lib/selenium/webdriver/common/takes_screenshot.rb +6 -5
- data/lib/selenium/webdriver/common/target_locator.rb +2 -3
- data/lib/selenium/webdriver/common/timeouts.rb +2 -2
- data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
- data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
- data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
- data/lib/selenium/webdriver/common/websocket_connection.rb +176 -0
- data/lib/selenium/webdriver/common/window.rb +6 -6
- data/lib/selenium/webdriver/common/zipper.rb +1 -1
- data/lib/selenium/webdriver/common.rb +26 -5
- data/lib/selenium/webdriver/devtools/console_event.rb +0 -2
- data/lib/selenium/webdriver/devtools/exception_event.rb +0 -2
- data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -2
- data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
- data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -2
- data/lib/selenium/webdriver/devtools/request.rb +1 -3
- data/lib/selenium/webdriver/devtools/response.rb +1 -3
- data/lib/selenium/webdriver/devtools.rb +17 -114
- data/lib/selenium/webdriver/edge/driver.rb +9 -3
- data/lib/selenium/webdriver/edge/features.rb +8 -4
- data/lib/selenium/webdriver/edge/options.rb +17 -5
- data/lib/selenium/webdriver/edge/profile.rb +2 -2
- data/lib/selenium/webdriver/edge/service.rb +8 -7
- data/lib/selenium/webdriver/edge.rb +0 -2
- data/lib/selenium/webdriver/firefox/driver.rb +9 -2
- data/lib/selenium/webdriver/firefox/features.rb +11 -7
- data/lib/selenium/webdriver/firefox/options.rb +10 -16
- data/lib/selenium/webdriver/firefox/profile.rb +21 -17
- data/lib/selenium/webdriver/firefox/profiles_ini.rb +1 -1
- data/lib/selenium/webdriver/firefox/service.rb +0 -18
- data/lib/selenium/webdriver/firefox/util.rb +46 -0
- data/lib/selenium/webdriver/firefox.rb +1 -14
- data/lib/selenium/webdriver/ie/driver.rb +7 -1
- data/lib/selenium/webdriver/ie/features.rb +34 -0
- data/lib/selenium/webdriver/ie/options.rb +6 -4
- data/lib/selenium/webdriver/ie/service.rb +0 -22
- data/lib/selenium/webdriver/ie.rb +4 -17
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -0
- data/lib/selenium/webdriver/remote/{commands.rb → bridge/commands.rb} +22 -9
- data/lib/selenium/webdriver/remote/bridge/locator_converter.rb +76 -0
- data/lib/selenium/webdriver/remote/bridge.rb +147 -99
- data/lib/selenium/webdriver/remote/capabilities.rb +5 -55
- data/lib/selenium/webdriver/remote/driver.rb +35 -14
- data/lib/selenium/webdriver/remote/features.rb +75 -0
- data/lib/selenium/webdriver/remote/http/common.rb +24 -6
- data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
- data/lib/selenium/webdriver/remote/http/default.rb +8 -14
- data/lib/selenium/webdriver/remote/response.rb +8 -34
- data/lib/selenium/webdriver/remote/server_error.rb +2 -2
- data/lib/selenium/webdriver/remote.rb +2 -1
- data/lib/selenium/webdriver/safari/driver.rb +7 -1
- data/lib/selenium/webdriver/safari/features.rb +5 -3
- data/lib/selenium/webdriver/safari/options.rb +5 -1
- data/lib/selenium/webdriver/safari/service.rb +10 -4
- data/lib/selenium/webdriver/safari.rb +1 -15
- data/lib/selenium/webdriver/support/color.rb +22 -22
- data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
- data/lib/selenium/webdriver/support/guards/guard.rb +14 -14
- data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -3
- data/lib/selenium/webdriver/support/guards.rb +2 -2
- data/lib/selenium/webdriver/support/relative_locator.rb +0 -1
- data/lib/selenium/webdriver/support/select.rb +3 -1
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +7 -5
- data/selenium-webdriver.gemspec +20 -16
- metadata +135 -47
- data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
- data/lib/selenium/webdriver/support/cdp/domain.rb.erb +0 -63
- data/lib/selenium/webdriver/support/cdp_client_generator.rb +0 -108
|
@@ -21,6 +21,9 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
module Remote
|
|
23
23
|
class Bridge
|
|
24
|
+
autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands'
|
|
25
|
+
autoload :LocatorConverter, 'selenium/webdriver/remote/bridge/locator_converter'
|
|
26
|
+
|
|
24
27
|
include Atoms
|
|
25
28
|
|
|
26
29
|
PORT = 4444
|
|
@@ -28,10 +31,29 @@ module Selenium
|
|
|
28
31
|
attr_accessor :http, :file_detector
|
|
29
32
|
attr_reader :capabilities
|
|
30
33
|
|
|
34
|
+
class << self
|
|
35
|
+
attr_reader :extra_commands
|
|
36
|
+
attr_writer :element_class, :locator_converter
|
|
37
|
+
|
|
38
|
+
def add_command(name, verb, url, &block)
|
|
39
|
+
@extra_commands ||= {}
|
|
40
|
+
@extra_commands[name] = [verb, url]
|
|
41
|
+
define_method(name, &block)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def locator_converter
|
|
45
|
+
@locator_converter ||= LocatorConverter.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def element_class
|
|
49
|
+
@element_class ||= Element
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
31
53
|
#
|
|
32
54
|
# Initializes the bridge with the given server URL
|
|
33
|
-
# @param [String, URI]
|
|
34
|
-
# @param [Object]
|
|
55
|
+
# @param [String, URI] url url for the remote server
|
|
56
|
+
# @param [Object] http_client an HTTP client instance that implements the same protocol as Http::Default
|
|
35
57
|
# @api private
|
|
36
58
|
#
|
|
37
59
|
|
|
@@ -42,6 +64,8 @@ module Selenium
|
|
|
42
64
|
@http = http_client || Http::Default.new
|
|
43
65
|
@http.server_url = uri
|
|
44
66
|
@file_detector = nil
|
|
67
|
+
|
|
68
|
+
@locator_converter = self.class.locator_converter
|
|
45
69
|
end
|
|
46
70
|
|
|
47
71
|
#
|
|
@@ -59,14 +83,16 @@ module Selenium
|
|
|
59
83
|
@capabilities = Capabilities.json_create(capabilities)
|
|
60
84
|
|
|
61
85
|
case @capabilities[:browser_name]
|
|
62
|
-
when 'chrome'
|
|
86
|
+
when 'chrome', 'chrome-headless-shell'
|
|
63
87
|
extend(WebDriver::Chrome::Features)
|
|
64
88
|
when 'firefox'
|
|
65
89
|
extend(WebDriver::Firefox::Features)
|
|
66
|
-
when 'msedge'
|
|
90
|
+
when 'msedge', 'MicrosoftEdge'
|
|
67
91
|
extend(WebDriver::Edge::Features)
|
|
68
92
|
when 'Safari', 'Safari Technology Preview'
|
|
69
93
|
extend(WebDriver::Safari::Features)
|
|
94
|
+
when 'internet explorer'
|
|
95
|
+
extend(WebDriver::IE::Features)
|
|
70
96
|
end
|
|
71
97
|
end
|
|
72
98
|
|
|
@@ -81,7 +107,7 @@ module Selenium
|
|
|
81
107
|
def browser
|
|
82
108
|
@browser ||= begin
|
|
83
109
|
name = @capabilities.browser_name
|
|
84
|
-
name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
|
|
110
|
+
name ? name.tr(' -', '_').downcase.to_sym : 'unknown'
|
|
85
111
|
end
|
|
86
112
|
end
|
|
87
113
|
|
|
@@ -118,7 +144,7 @@ module Selenium
|
|
|
118
144
|
end
|
|
119
145
|
|
|
120
146
|
def alert=(keys)
|
|
121
|
-
execute :send_alert_text, {}, {value: keys.
|
|
147
|
+
execute :send_alert_text, {}, {value: keys.chars, text: keys}
|
|
122
148
|
end
|
|
123
149
|
|
|
124
150
|
def alert_text
|
|
@@ -146,9 +172,7 @@ module Selenium
|
|
|
146
172
|
end
|
|
147
173
|
|
|
148
174
|
def page_source
|
|
149
|
-
|
|
150
|
-
'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
|
|
151
|
-
'return source;')
|
|
175
|
+
execute :get_page_source
|
|
152
176
|
end
|
|
153
177
|
|
|
154
178
|
#
|
|
@@ -188,6 +212,7 @@ module Selenium
|
|
|
188
212
|
execute :delete_session
|
|
189
213
|
http.close
|
|
190
214
|
rescue *QUIT_ERRORS
|
|
215
|
+
nil
|
|
191
216
|
end
|
|
192
217
|
|
|
193
218
|
def close
|
|
@@ -369,21 +394,10 @@ module Selenium
|
|
|
369
394
|
# actions
|
|
370
395
|
#
|
|
371
396
|
|
|
372
|
-
def action(async
|
|
373
|
-
ActionBuilder.new self,
|
|
374
|
-
Interactions.pointer(:mouse, name: 'mouse'),
|
|
375
|
-
Interactions.key('keyboard'),
|
|
376
|
-
async
|
|
377
|
-
end
|
|
378
|
-
alias_method :actions, :action
|
|
379
|
-
|
|
380
|
-
def mouse
|
|
381
|
-
raise Error::UnsupportedOperationError, '#mouse is no longer supported, use #action instead'
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
def keyboard
|
|
385
|
-
raise Error::UnsupportedOperationError, '#keyboard is no longer supported, use #action instead'
|
|
397
|
+
def action(async: false, devices: [], duration: 250)
|
|
398
|
+
ActionBuilder.new self, async: async, devices: devices, duration: duration
|
|
386
399
|
end
|
|
400
|
+
alias actions action
|
|
387
401
|
|
|
388
402
|
def send_actions(data)
|
|
389
403
|
execute :actions, {}, {actions: data}
|
|
@@ -402,27 +416,9 @@ module Selenium
|
|
|
402
416
|
end
|
|
403
417
|
|
|
404
418
|
def send_keys_to_element(element, keys)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if local_files.any?
|
|
409
|
-
keys = local_files.map { |local_file| upload(local_file) }
|
|
410
|
-
keys = Array(keys.join("\n"))
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
# Keep .split(//) for backward compatibility for now
|
|
415
|
-
text = keys.join('')
|
|
416
|
-
execute :element_send_keys, {id: element}, {value: text.split(//), text: text}
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
def upload(local_file)
|
|
420
|
-
unless File.file?(local_file)
|
|
421
|
-
WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!")
|
|
422
|
-
raise Error::WebDriverError, "You are trying to work with something that isn't a file."
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
|
|
419
|
+
keys = upload_if_necessary(keys) if @file_detector
|
|
420
|
+
text = keys.join
|
|
421
|
+
execute :element_send_keys, {id: element}, {value: text.chars, text: text}
|
|
426
422
|
end
|
|
427
423
|
|
|
428
424
|
def clear_element(element)
|
|
@@ -430,10 +426,19 @@ module Selenium
|
|
|
430
426
|
end
|
|
431
427
|
|
|
432
428
|
def submit_element(element)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
429
|
+
script = "/* submitForm */ var form = arguments[0];\n" \
|
|
430
|
+
"while (form.nodeName != \"FORM\" && form.parentNode) {\n " \
|
|
431
|
+
"form = form.parentNode;\n" \
|
|
432
|
+
"}\n" \
|
|
433
|
+
"if (!form) { throw Error('Unable to find containing form element'); }\n" \
|
|
434
|
+
"if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" \
|
|
435
|
+
"var e = form.ownerDocument.createEvent('Event');\n" \
|
|
436
|
+
"e.initEvent('submit', true, true);\n" \
|
|
437
|
+
"if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
|
|
438
|
+
|
|
439
|
+
execute_script(script, Bridge.element_class::ELEMENT_KEY => element)
|
|
440
|
+
rescue Error::JavascriptError
|
|
441
|
+
raise Error::UnsupportedOperationError, 'To submit an element, it must be nested inside a form element'
|
|
437
442
|
end
|
|
438
443
|
|
|
439
444
|
#
|
|
@@ -445,7 +450,7 @@ module Selenium
|
|
|
445
450
|
end
|
|
446
451
|
|
|
447
452
|
def element_attribute(element, name)
|
|
448
|
-
WebDriver.logger.
|
|
453
|
+
WebDriver.logger.debug "Using script for :getAttribute of #{name}", id: :script
|
|
449
454
|
execute_atom :getAttribute, element, name
|
|
450
455
|
end
|
|
451
456
|
|
|
@@ -505,7 +510,7 @@ module Selenium
|
|
|
505
510
|
end
|
|
506
511
|
|
|
507
512
|
def element_displayed?(element)
|
|
508
|
-
WebDriver.logger.
|
|
513
|
+
WebDriver.logger.debug 'Using script for :isDisplayed', id: :script
|
|
509
514
|
execute_atom :isDisplayed, element
|
|
510
515
|
end
|
|
511
516
|
|
|
@@ -518,13 +523,13 @@ module Selenium
|
|
|
518
523
|
#
|
|
519
524
|
|
|
520
525
|
def active_element
|
|
521
|
-
|
|
526
|
+
Bridge.element_class.new self, element_id_from(execute(:get_active_element))
|
|
522
527
|
end
|
|
523
528
|
|
|
524
|
-
|
|
529
|
+
alias switch_to_active_element active_element
|
|
525
530
|
|
|
526
531
|
def find_element_by(how, what, parent_ref = [])
|
|
527
|
-
how, what =
|
|
532
|
+
how, what = @locator_converter.convert(how, what)
|
|
528
533
|
|
|
529
534
|
return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
|
|
530
535
|
|
|
@@ -538,11 +543,11 @@ module Selenium
|
|
|
538
543
|
execute :find_element, {}, {using: how, value: what.to_s}
|
|
539
544
|
end
|
|
540
545
|
|
|
541
|
-
|
|
546
|
+
Bridge.element_class.new self, element_id_from(id)
|
|
542
547
|
end
|
|
543
548
|
|
|
544
549
|
def find_elements_by(how, what, parent_ref = [])
|
|
545
|
-
how, what =
|
|
550
|
+
how, what = @locator_converter.convert(how, what)
|
|
546
551
|
|
|
547
552
|
return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
|
|
548
553
|
|
|
@@ -556,7 +561,7 @@ module Selenium
|
|
|
556
561
|
execute :find_elements, {}, {using: how, value: what.to_s}
|
|
557
562
|
end
|
|
558
563
|
|
|
559
|
-
ids.map { |id|
|
|
564
|
+
ids.map { |id| Bridge.element_class.new self, element_id_from(id) }
|
|
560
565
|
end
|
|
561
566
|
|
|
562
567
|
def shadow_root(element)
|
|
@@ -564,6 +569,88 @@ module Selenium
|
|
|
564
569
|
ShadowRoot.new self, shadow_root_id_from(id)
|
|
565
570
|
end
|
|
566
571
|
|
|
572
|
+
#
|
|
573
|
+
# virtual-authenticator
|
|
574
|
+
#
|
|
575
|
+
|
|
576
|
+
def add_virtual_authenticator(options)
|
|
577
|
+
authenticator_id = execute :add_virtual_authenticator, {}, options.as_json
|
|
578
|
+
VirtualAuthenticator.new(self, authenticator_id, options)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def remove_virtual_authenticator(id)
|
|
582
|
+
execute :remove_virtual_authenticator, {authenticatorId: id}
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def add_credential(credential, id)
|
|
586
|
+
execute :add_credential, {authenticatorId: id}, credential
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def credentials(authenticator_id)
|
|
590
|
+
execute :get_credentials, {authenticatorId: authenticator_id}
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def remove_credential(credential_id, authenticator_id)
|
|
594
|
+
execute :remove_credential, {credentialId: credential_id, authenticatorId: authenticator_id}
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def remove_all_credentials(authenticator_id)
|
|
598
|
+
execute :remove_all_credentials, {authenticatorId: authenticator_id}
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def user_verified(verified, authenticator_id)
|
|
602
|
+
execute :set_user_verified, {authenticatorId: authenticator_id}, {isUserVerified: verified}
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
#
|
|
606
|
+
# federated-credential management
|
|
607
|
+
#
|
|
608
|
+
|
|
609
|
+
def cancel_fedcm_dialog
|
|
610
|
+
execute :cancel_fedcm_dialog
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def select_fedcm_account(index)
|
|
614
|
+
execute :select_fedcm_account, {}, {accountIndex: index}
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def fedcm_dialog_type
|
|
618
|
+
execute :get_fedcm_dialog_type
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def fedcm_title
|
|
622
|
+
execute(:get_fedcm_title).fetch('title')
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
def fedcm_subtitle
|
|
626
|
+
execute(:get_fedcm_title).fetch('subtitle', nil)
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
def fedcm_account_list
|
|
630
|
+
execute :get_fedcm_account_list
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def fedcm_delay(enabled)
|
|
634
|
+
execute :set_fedcm_delay, {}, {enabled: enabled}
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def reset_fedcm_cooldown
|
|
638
|
+
execute :reset_fedcm_cooldown
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def click_fedcm_dialog_button
|
|
642
|
+
execute :click_fedcm_dialog_button, {}, {dialogButton: 'ConfirmIdpLoginContinue'}
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def bidi
|
|
646
|
+
msg = 'BiDi must be enabled by setting #web_socket_url to true in options class'
|
|
647
|
+
raise(WebDriver::Error::WebDriverError, msg)
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
def command_list
|
|
651
|
+
COMMANDS
|
|
652
|
+
end
|
|
653
|
+
|
|
567
654
|
private
|
|
568
655
|
|
|
569
656
|
#
|
|
@@ -584,7 +671,7 @@ module Selenium
|
|
|
584
671
|
raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
|
|
585
672
|
end
|
|
586
673
|
|
|
587
|
-
WebDriver.logger.
|
|
674
|
+
WebDriver.logger.debug("-> #{verb.to_s.upcase} #{path}", id: :command)
|
|
588
675
|
http.call(verb, path, command_hash)['value']
|
|
589
676
|
end
|
|
590
677
|
|
|
@@ -593,7 +680,7 @@ module Selenium
|
|
|
593
680
|
end
|
|
594
681
|
|
|
595
682
|
def commands(command)
|
|
596
|
-
|
|
683
|
+
command_list[command] || Bridge.extra_commands[command]
|
|
597
684
|
end
|
|
598
685
|
|
|
599
686
|
def unwrap_script_result(arg)
|
|
@@ -602,7 +689,7 @@ module Selenium
|
|
|
602
689
|
arg.map { |e| unwrap_script_result(e) }
|
|
603
690
|
when Hash
|
|
604
691
|
element_id = element_id_from(arg)
|
|
605
|
-
return
|
|
692
|
+
return Bridge.element_class.new(self, element_id) if element_id
|
|
606
693
|
|
|
607
694
|
shadow_root_id = shadow_root_id_from(arg)
|
|
608
695
|
return ShadowRoot.new self, shadow_root_id if shadow_root_id
|
|
@@ -614,7 +701,7 @@ module Selenium
|
|
|
614
701
|
end
|
|
615
702
|
|
|
616
703
|
def element_id_from(id)
|
|
617
|
-
id['ELEMENT'] || id[
|
|
704
|
+
id['ELEMENT'] || id[Bridge.element_class::ELEMENT_KEY]
|
|
618
705
|
end
|
|
619
706
|
|
|
620
707
|
def shadow_root_id_from(id)
|
|
@@ -625,45 +712,6 @@ module Selenium
|
|
|
625
712
|
capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
|
|
626
713
|
{capabilities: capabilities}
|
|
627
714
|
end
|
|
628
|
-
|
|
629
|
-
def convert_locator(how, what)
|
|
630
|
-
how = SearchContext::FINDERS[how.to_sym] || how
|
|
631
|
-
|
|
632
|
-
case how
|
|
633
|
-
when 'class name'
|
|
634
|
-
how = 'css selector'
|
|
635
|
-
what = ".#{escape_css(what.to_s)}"
|
|
636
|
-
when 'id'
|
|
637
|
-
how = 'css selector'
|
|
638
|
-
what = "##{escape_css(what.to_s)}"
|
|
639
|
-
when 'name'
|
|
640
|
-
how = 'css selector'
|
|
641
|
-
what = "*[name='#{escape_css(what.to_s)}']"
|
|
642
|
-
when 'tag name'
|
|
643
|
-
how = 'css selector'
|
|
644
|
-
end
|
|
645
|
-
|
|
646
|
-
if what.is_a?(Hash)
|
|
647
|
-
what = what.each_with_object({}) do |(h, w), hash|
|
|
648
|
-
h, w = convert_locator(h.to_s, w)
|
|
649
|
-
hash[h] = w
|
|
650
|
-
end
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
[how, what]
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/.freeze
|
|
657
|
-
UNICODE_CODE_POINT = 30
|
|
658
|
-
|
|
659
|
-
# Escapes invalid characters in CSS selector.
|
|
660
|
-
# @see https://mathiasbynens.be/notes/css-escapes
|
|
661
|
-
def escape_css(string)
|
|
662
|
-
string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
|
|
663
|
-
string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
|
|
664
|
-
|
|
665
|
-
string
|
|
666
|
-
end
|
|
667
715
|
end # Bridge
|
|
668
716
|
end # Remote
|
|
669
717
|
end # WebDriver
|
|
@@ -20,14 +20,12 @@
|
|
|
20
20
|
module Selenium
|
|
21
21
|
module WebDriver
|
|
22
22
|
module Remote
|
|
23
|
-
|
|
24
23
|
#
|
|
25
24
|
# Specification of the desired and/or actual capabilities of the browser that the
|
|
26
25
|
# server is being asked to create.
|
|
27
26
|
#
|
|
28
27
|
|
|
29
28
|
class Capabilities
|
|
30
|
-
|
|
31
29
|
KNOWN = [
|
|
32
30
|
:browser_name,
|
|
33
31
|
:browser_version,
|
|
@@ -50,65 +48,16 @@ module Selenium
|
|
|
50
48
|
@capabilities[key]
|
|
51
49
|
end
|
|
52
50
|
|
|
53
|
-
define_method "#{key}=" do |value|
|
|
51
|
+
define_method :"#{key}=" do |value|
|
|
54
52
|
@capabilities[key] = value
|
|
55
53
|
end
|
|
56
54
|
end
|
|
57
55
|
|
|
58
|
-
#
|
|
59
|
-
# Backward compatibility
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
alias_method :version, :browser_version
|
|
63
|
-
alias_method :version=, :browser_version=
|
|
64
|
-
alias_method :platform, :platform_name
|
|
65
|
-
alias_method :platform=, :platform_name=
|
|
66
|
-
|
|
67
56
|
#
|
|
68
57
|
# Convenience methods for the common choices.
|
|
69
58
|
#
|
|
70
59
|
|
|
71
60
|
class << self
|
|
72
|
-
def chrome(opts = {})
|
|
73
|
-
new({
|
|
74
|
-
browser_name: 'chrome'
|
|
75
|
-
}.merge(opts))
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def edge(opts = {})
|
|
79
|
-
new({
|
|
80
|
-
browser_name: 'MicrosoftEdge'
|
|
81
|
-
}.merge(opts))
|
|
82
|
-
end
|
|
83
|
-
alias_method :microsoftedge, :edge
|
|
84
|
-
|
|
85
|
-
def firefox(opts = {})
|
|
86
|
-
new({
|
|
87
|
-
browser_name: 'firefox'
|
|
88
|
-
}.merge(opts))
|
|
89
|
-
end
|
|
90
|
-
alias_method :ff, :firefox
|
|
91
|
-
|
|
92
|
-
def safari(opts = {})
|
|
93
|
-
new({
|
|
94
|
-
browser_name: Selenium::WebDriver::Safari.technology_preview? ? "Safari Technology Preview" : 'safari'
|
|
95
|
-
}.merge(opts))
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def htmlunit(opts = {})
|
|
99
|
-
new({
|
|
100
|
-
browser_name: 'htmlunit'
|
|
101
|
-
}.merge(opts))
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def internet_explorer(opts = {})
|
|
105
|
-
new({
|
|
106
|
-
browser_name: 'internet explorer',
|
|
107
|
-
platform_name: :windows
|
|
108
|
-
}.merge(opts))
|
|
109
|
-
end
|
|
110
|
-
alias_method :ie, :internet_explorer
|
|
111
|
-
|
|
112
61
|
def always_match(capabilities)
|
|
113
62
|
new(always_match: capabilities)
|
|
114
63
|
end
|
|
@@ -134,7 +83,8 @@ module Selenium
|
|
|
134
83
|
|
|
135
84
|
# Remote Server Specific
|
|
136
85
|
if data.key?('webdriver.remote.sessionid')
|
|
137
|
-
caps[:remote_session_id] =
|
|
86
|
+
caps[:remote_session_id] =
|
|
87
|
+
data.delete('webdriver.remote.sessionid')
|
|
138
88
|
end
|
|
139
89
|
|
|
140
90
|
KNOWN.each do |cap|
|
|
@@ -149,7 +99,7 @@ module Selenium
|
|
|
149
99
|
end
|
|
150
100
|
|
|
151
101
|
def camel_case(str_or_sym)
|
|
152
|
-
str_or_sym.to_s.gsub(/_([a-z])/) { Regexp.last_match(1)
|
|
102
|
+
str_or_sym.to_s.gsub(/_([a-z])/) { Regexp.last_match(1)&.upcase }
|
|
153
103
|
end
|
|
154
104
|
|
|
155
105
|
private
|
|
@@ -269,7 +219,7 @@ module Selenium
|
|
|
269
219
|
as_json == other.as_json
|
|
270
220
|
end
|
|
271
221
|
|
|
272
|
-
|
|
222
|
+
alias eql? ==
|
|
273
223
|
|
|
274
224
|
protected
|
|
275
225
|
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
module Selenium
|
|
21
21
|
module WebDriver
|
|
22
22
|
module Remote
|
|
23
|
-
|
|
24
23
|
#
|
|
25
24
|
# Driver implementation for remote server.
|
|
26
25
|
# @api private
|
|
@@ -29,20 +28,18 @@ module Selenium
|
|
|
29
28
|
class Driver < WebDriver::Driver
|
|
30
29
|
include DriverExtensions::UploadsFiles
|
|
31
30
|
include DriverExtensions::HasSessionId
|
|
32
|
-
include DriverExtensions::
|
|
31
|
+
include DriverExtensions::HasFileDownloads
|
|
33
32
|
|
|
34
|
-
def initialize(
|
|
35
|
-
|
|
36
|
-
if desired_capabilities.is_a?(Symbol)
|
|
37
|
-
unless Remote::Capabilities.respond_to?(desired_capabilities)
|
|
38
|
-
raise Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
|
|
39
|
-
end
|
|
33
|
+
def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
|
|
34
|
+
raise ArgumentError, "Can not set :service object on #{self.class}" if service
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
super
|
|
36
|
+
url ||= "http://#{Platform.localhost}:4444/wd/hub"
|
|
37
|
+
caps = process_options(options, capabilities)
|
|
38
|
+
super(caps: caps, url: url, **opts)
|
|
45
39
|
@bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
|
|
40
|
+
command_list = @bridge.command_list
|
|
41
|
+
@bridge.extend(WebDriver::Remote::Features)
|
|
42
|
+
@bridge.add_commands(command_list)
|
|
46
43
|
end
|
|
47
44
|
|
|
48
45
|
private
|
|
@@ -52,8 +49,32 @@ module Selenium
|
|
|
52
49
|
end
|
|
53
50
|
|
|
54
51
|
def devtools_version
|
|
55
|
-
capabilities['se:cdpVersion']&.split('.')&.first
|
|
56
|
-
|
|
52
|
+
cdp_version = capabilities['se:cdpVersion']&.split('.')&.first
|
|
53
|
+
raise Error::WebDriverError, 'DevTools is not supported by the Remote Server' unless cdp_version
|
|
54
|
+
|
|
55
|
+
Integer(cdp_version)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def process_options(options, capabilities)
|
|
59
|
+
if options && capabilities
|
|
60
|
+
msg = "Don't use both :options and :capabilities when initializing #{self.class}, prefer :options"
|
|
61
|
+
raise ArgumentError, msg
|
|
62
|
+
elsif options.nil? && capabilities.nil?
|
|
63
|
+
raise ArgumentError, "#{self.class} needs :options to be set"
|
|
64
|
+
end
|
|
65
|
+
options ? options.as_json : generate_capabilities(capabilities)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def generate_capabilities(capabilities)
|
|
69
|
+
Array(capabilities).map { |cap|
|
|
70
|
+
if cap.is_a? Symbol
|
|
71
|
+
cap = WebDriver::Options.send(cap)
|
|
72
|
+
elsif !cap.respond_to? :as_json
|
|
73
|
+
msg = ":capabilities parameter only accepts objects responding to #as_json which #{cap.class} does not"
|
|
74
|
+
raise ArgumentError, msg
|
|
75
|
+
end
|
|
76
|
+
cap.as_json
|
|
77
|
+
}.inject(:merge)
|
|
57
78
|
end
|
|
58
79
|
end # Driver
|
|
59
80
|
end # Remote
|
|
@@ -0,0 +1,75 @@
|
|
|
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 Remote
|
|
23
|
+
module Features
|
|
24
|
+
REMOTE_COMMANDS = {
|
|
25
|
+
upload_file: [:post, 'session/:session_id/se/file'],
|
|
26
|
+
get_downloadable_files: [:get, 'session/:session_id/se/files'],
|
|
27
|
+
download_file: [:post, 'session/:session_id/se/files'],
|
|
28
|
+
delete_downloadable_files: [:delete, 'session/:session_id/se/files']
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def add_commands(commands)
|
|
32
|
+
@command_list = command_list.merge(commands)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def command_list
|
|
36
|
+
@command_list ||= REMOTE_COMMANDS
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def commands(command)
|
|
40
|
+
command_list[command]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def upload(local_file)
|
|
44
|
+
unless File.file?(local_file)
|
|
45
|
+
WebDriver.logger.error("File detector only works with files. #{local_file.inspect} isn`t a file!",
|
|
46
|
+
id: :file_detector)
|
|
47
|
+
raise Error::WebDriverError, "You are trying to upload something that isn't a file."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def upload_if_necessary(keys)
|
|
54
|
+
local_files = keys.first&.split("\n")&.filter_map { |key| @file_detector.call(Array(key)) }
|
|
55
|
+
return keys unless local_files&.any?
|
|
56
|
+
|
|
57
|
+
keys = local_files.map { |local_file| upload(local_file) }
|
|
58
|
+
Array(keys.join("\n"))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def downloadable_files
|
|
62
|
+
execute :get_downloadable_files
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def download_file(name)
|
|
66
|
+
execute :download_file, {}, {name: name}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def delete_downloadable_files
|
|
70
|
+
execute :delete_downloadable_files
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end # Remote
|
|
74
|
+
end # WebDriver
|
|
75
|
+
end # Selenium
|