selenium-webdriver 4.0.0.beta2 → 4.0.0.rc2
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 +1947 -0
- data/Gemfile +6 -0
- data/LICENSE +202 -0
- data/NOTICE +2 -0
- data/README.md +34 -0
- data/lib/selenium/webdriver/atoms/findElements.js +0 -0
- data/lib/selenium/webdriver/atoms/getAttribute.js +25 -25
- data/lib/selenium/webdriver/atoms/isDisplayed.js +0 -0
- data/lib/selenium/webdriver/chrome/driver.rb +16 -4
- data/lib/selenium/webdriver/chrome/features.rb +44 -4
- data/lib/selenium/webdriver/chrome/options.rb +25 -2
- data/lib/selenium/webdriver/chrome/profile.rb +5 -2
- data/lib/selenium/webdriver/common/driver.rb +5 -1
- data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +43 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +51 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +77 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +38 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +45 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -17
- data/lib/selenium/{devtools.rb → webdriver/common/driver_extensions/has_launching.rb} +16 -8
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -6
- data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +8 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +87 -18
- data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -11
- data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +77 -0
- data/lib/selenium/webdriver/common/driver_extensions/prints_page.rb +28 -1
- data/lib/selenium/webdriver/common/element.rb +35 -6
- data/lib/selenium/webdriver/common/error.rb +12 -0
- data/lib/selenium/webdriver/common/log_entry.rb +2 -2
- data/lib/selenium/webdriver/common/manager.rb +3 -13
- data/lib/selenium/webdriver/common/options.rb +31 -12
- data/lib/selenium/webdriver/common/proxy.rb +2 -2
- data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
- data/lib/selenium/webdriver/common/socket_poller.rb +19 -30
- data/lib/selenium/webdriver/common/takes_screenshot.rb +9 -6
- data/lib/selenium/webdriver/common/target_locator.rb +28 -0
- data/lib/selenium/webdriver/common/window.rb +0 -4
- data/lib/selenium/webdriver/common.rb +8 -0
- data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
- data/lib/selenium/webdriver/devtools/request.rb +27 -17
- data/lib/selenium/webdriver/devtools/response.rb +66 -0
- data/lib/selenium/webdriver/devtools.rb +50 -12
- data/lib/selenium/webdriver/edge/features.rb +5 -0
- data/lib/selenium/webdriver/edge/options.rb +0 -0
- data/lib/selenium/webdriver/edge/profile.rb +0 -0
- data/lib/selenium/webdriver/firefox/driver.rb +15 -2
- data/lib/selenium/webdriver/firefox/features.rb +20 -1
- data/lib/selenium/webdriver/firefox/options.rb +24 -1
- data/lib/selenium/webdriver/firefox.rb +0 -1
- data/lib/selenium/webdriver/ie/options.rb +3 -1
- data/lib/selenium/webdriver/ie/service.rb +1 -1
- data/lib/selenium/webdriver/remote/bridge.rb +43 -13
- data/lib/selenium/webdriver/remote/capabilities.rb +97 -53
- data/lib/selenium/webdriver/remote/commands.rb +5 -0
- data/lib/selenium/webdriver/remote/driver.rb +7 -7
- data/lib/selenium/webdriver/remote.rb +1 -1
- data/lib/selenium/webdriver/safari/driver.rb +1 -1
- data/lib/selenium/webdriver/safari/options.rb +7 -0
- data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
- data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
- data/lib/selenium/webdriver/support/event_firing_bridge.rb +2 -2
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +1 -1
- data/selenium-webdriver.gemspec +63 -0
- metadata +60 -44
@@ -0,0 +1,66 @@
|
|
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
|
+
class Response
|
24
|
+
|
25
|
+
attr_accessor :code, :body, :headers
|
26
|
+
attr_reader :id
|
27
|
+
|
28
|
+
#
|
29
|
+
# Creates response from DevTools message.
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
|
33
|
+
def self.from(id, encoded_body, params)
|
34
|
+
new(
|
35
|
+
id: id,
|
36
|
+
code: params['responseStatusCode'],
|
37
|
+
body: (Base64.strict_decode64(encoded_body) if encoded_body),
|
38
|
+
headers: params['responseHeaders'].each_with_object({}) do |header, hash|
|
39
|
+
hash[header['name']] = header['value']
|
40
|
+
end
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(id:, code:, body:, headers:)
|
45
|
+
@id = id
|
46
|
+
@code = code
|
47
|
+
@body = body
|
48
|
+
@headers = headers
|
49
|
+
end
|
50
|
+
|
51
|
+
def ==(other)
|
52
|
+
self.class == other.class &&
|
53
|
+
id == other.id &&
|
54
|
+
code == other.code &&
|
55
|
+
body == other.body &&
|
56
|
+
headers == other.headers
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect
|
60
|
+
%(#<#{self.class.name} @id="#{id}" @code="#{code}")
|
61
|
+
end
|
62
|
+
|
63
|
+
end # Response
|
64
|
+
end # DevTools
|
65
|
+
end # WebDriver
|
66
|
+
end # Selenium
|
@@ -20,21 +20,34 @@
|
|
20
20
|
module Selenium
|
21
21
|
module WebDriver
|
22
22
|
class DevTools
|
23
|
+
RESPONSE_WAIT_TIMEOUT = 30
|
24
|
+
RESPONSE_WAIT_INTERVAL = 0.1
|
25
|
+
|
23
26
|
autoload :ConsoleEvent, 'selenium/webdriver/devtools/console_event'
|
24
27
|
autoload :ExceptionEvent, 'selenium/webdriver/devtools/exception_event'
|
25
28
|
autoload :MutationEvent, 'selenium/webdriver/devtools/mutation_event'
|
29
|
+
autoload :PinnedScript, 'selenium/webdriver/devtools/pinned_script'
|
26
30
|
autoload :Request, 'selenium/webdriver/devtools/request'
|
31
|
+
autoload :Response, 'selenium/webdriver/devtools/response'
|
27
32
|
|
28
33
|
def initialize(url:)
|
34
|
+
@callback_threads = ThreadGroup.new
|
35
|
+
|
29
36
|
@messages = []
|
30
37
|
@session_id = nil
|
31
38
|
@url = url
|
32
39
|
|
33
40
|
process_handshake
|
34
|
-
attach_socket_listener
|
41
|
+
@socket_thread = attach_socket_listener
|
35
42
|
start_session
|
36
43
|
end
|
37
44
|
|
45
|
+
def close
|
46
|
+
@callback_threads.list.each(&:exit)
|
47
|
+
@socket_thread.exit
|
48
|
+
socket.close
|
49
|
+
end
|
50
|
+
|
38
51
|
def callbacks
|
39
52
|
@callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
|
40
53
|
end
|
@@ -84,27 +97,24 @@ module Selenium
|
|
84
97
|
end
|
85
98
|
|
86
99
|
def attach_socket_listener
|
87
|
-
|
100
|
+
Thread.new do
|
101
|
+
Thread.current.abort_on_exception = true
|
102
|
+
Thread.current.report_on_exception = false
|
103
|
+
|
88
104
|
until socket.eof?
|
89
105
|
incoming_frame << socket.readpartial(1024)
|
90
106
|
|
91
107
|
while (frame = incoming_frame.next)
|
92
|
-
|
93
|
-
break if frame.to_s.empty?
|
94
|
-
|
95
|
-
message = JSON.parse(frame.to_s)
|
96
|
-
@messages << message
|
97
|
-
WebDriver.logger.debug "DevTools <- #{message}"
|
108
|
+
message = process_frame(frame)
|
98
109
|
next unless message['method']
|
99
110
|
|
111
|
+
params = message['params']
|
100
112
|
callbacks[message['method']].each do |callback|
|
101
|
-
params
|
102
|
-
Thread.new { callback.call(params) }
|
113
|
+
@callback_threads.add(callback_thread(params, &callback))
|
103
114
|
end
|
104
115
|
end
|
105
116
|
end
|
106
117
|
end
|
107
|
-
socket_listener.abort_on_exception = true
|
108
118
|
end
|
109
119
|
|
110
120
|
def start_session
|
@@ -118,8 +128,36 @@ module Selenium
|
|
118
128
|
@incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
|
119
129
|
end
|
120
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
|
+
|
121
159
|
def wait
|
122
|
-
@wait ||= Wait.new(timeout:
|
160
|
+
@wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
|
123
161
|
end
|
124
162
|
|
125
163
|
def socket
|
@@ -27,6 +27,11 @@ module Selenium
|
|
27
27
|
include WebDriver::Chrome::Features
|
28
28
|
|
29
29
|
EDGE_COMMANDS = {
|
30
|
+
get_cast_sinks: [:get, 'session/:session_id/ms/cast/get_sinks'],
|
31
|
+
set_cast_sink_to_use: [:post, 'session/:session_id/ms/cast/set_sink_to_use'],
|
32
|
+
start_cast_tab_mirroring: [:post, 'session/:session_id/ms/cast/start_tab_mirroring'],
|
33
|
+
get_cast_issue_message: [:get, 'session/:session_id/ms/cast/get_issue_message'],
|
34
|
+
stop_casting: [:post, 'session/:session_id/ms/cast/stop_casting'],
|
30
35
|
send_command: [:post, 'session/:session_id/ms/cdp/execute']
|
31
36
|
}.freeze
|
32
37
|
|
File without changes
|
File without changes
|
@@ -28,6 +28,8 @@ module Selenium
|
|
28
28
|
|
29
29
|
class Driver < WebDriver::Driver
|
30
30
|
EXTENSIONS = [DriverExtensions::HasAddons,
|
31
|
+
DriverExtensions::FullPageScreenshot,
|
32
|
+
DriverExtensions::HasContext,
|
31
33
|
DriverExtensions::HasDevTools,
|
32
34
|
DriverExtensions::HasLogEvents,
|
33
35
|
DriverExtensions::HasNetworkInterception,
|
@@ -40,8 +42,19 @@ module Selenium
|
|
40
42
|
|
41
43
|
private
|
42
44
|
|
43
|
-
def
|
44
|
-
|
45
|
+
def devtools_url
|
46
|
+
if capabilities['moz:debuggerAddress'].nil?
|
47
|
+
raise(Error::WebDriverError, "DevTools is not supported by this version of Firefox; use v85 or higher")
|
48
|
+
end
|
49
|
+
|
50
|
+
uri = URI("http://#{capabilities['moz:debuggerAddress']}")
|
51
|
+
response = Net::HTTP.get(uri.hostname, '/json/version', uri.port)
|
52
|
+
|
53
|
+
JSON.parse(response)['webSocketDebuggerUrl']
|
54
|
+
end
|
55
|
+
|
56
|
+
def devtools_version
|
57
|
+
Firefox::DEVTOOLS_VERSION
|
45
58
|
end
|
46
59
|
end # Driver
|
47
60
|
end # Firefox
|
@@ -23,8 +23,11 @@ module Selenium
|
|
23
23
|
module Features
|
24
24
|
|
25
25
|
FIREFOX_COMMANDS = {
|
26
|
+
get_context: [:get, 'session/:session_id/moz/context'],
|
27
|
+
set_context: [:post, 'session/:session_id/moz/context'],
|
26
28
|
install_addon: [:post, 'session/:session_id/moz/addon/install'],
|
27
|
-
uninstall_addon: [:post, 'session/:session_id/moz/addon/uninstall']
|
29
|
+
uninstall_addon: [:post, 'session/:session_id/moz/addon/uninstall'],
|
30
|
+
full_page_screenshot: [:get, 'session/:session_id/moz/screenshot/full']
|
28
31
|
}.freeze
|
29
32
|
|
30
33
|
def commands(command)
|
@@ -32,6 +35,11 @@ module Selenium
|
|
32
35
|
end
|
33
36
|
|
34
37
|
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
|
42
|
+
|
35
43
|
payload = {path: path}
|
36
44
|
payload[:temporary] = temporary unless temporary.nil?
|
37
45
|
execute :install_addon, {}, payload
|
@@ -41,6 +49,17 @@ module Selenium
|
|
41
49
|
execute :uninstall_addon, {}, {id: id}
|
42
50
|
end
|
43
51
|
|
52
|
+
def full_screenshot
|
53
|
+
execute :full_page_screenshot
|
54
|
+
end
|
55
|
+
|
56
|
+
def context=(context)
|
57
|
+
execute :set_context, {}, {context: context}
|
58
|
+
end
|
59
|
+
|
60
|
+
def context
|
61
|
+
execute :get_context
|
62
|
+
end
|
44
63
|
end # Bridge
|
45
64
|
end # Firefox
|
46
65
|
end # WebDriver
|
@@ -29,7 +29,11 @@ module Selenium
|
|
29
29
|
CAPABILITIES = {binary: 'binary',
|
30
30
|
args: 'args',
|
31
31
|
log: 'log',
|
32
|
-
prefs: 'prefs'
|
32
|
+
prefs: 'prefs',
|
33
|
+
android_package: 'androidPackage',
|
34
|
+
android_activity: 'androidActivity',
|
35
|
+
android_device_serial: 'androidDeviceSerial',
|
36
|
+
android_intent_arguments: 'androidIntentArguments'}.freeze
|
33
37
|
BROWSER = 'firefox'
|
34
38
|
|
35
39
|
# NOTE: special handling of 'profile' to validate when set instead of when used
|
@@ -131,6 +135,25 @@ module Selenium
|
|
131
135
|
@options[:log] = {level: level}
|
132
136
|
end
|
133
137
|
|
138
|
+
#
|
139
|
+
# Enables mobile browser use on Android.
|
140
|
+
#
|
141
|
+
# @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#android
|
142
|
+
#
|
143
|
+
# @param [String] package The package name of the Chrome or WebView app.
|
144
|
+
# @param [String] serial_number The serial number of the device on which to launch the application.
|
145
|
+
# If not specified and multiple devices are attached, an error will be returned.
|
146
|
+
# @param [String] activity The fully qualified class name of the activity to be launched.
|
147
|
+
# @param [Array] intent_arguments Arguments to launch the intent with.
|
148
|
+
#
|
149
|
+
|
150
|
+
def enable_android(package: 'org.mozilla.firefox', serial_number: nil, activity: nil, intent_arguments: nil)
|
151
|
+
@options[:android_package] = package
|
152
|
+
@options[:android_activity] = activity unless activity.nil?
|
153
|
+
@options[:android_device_serial] = serial_number unless serial_number.nil?
|
154
|
+
@options[:android_intent_arguments] = intent_arguments unless intent_arguments.nil?
|
155
|
+
end
|
156
|
+
|
134
157
|
private
|
135
158
|
|
136
159
|
def process_browser_options(browser_options)
|
@@ -33,7 +33,6 @@ module Selenium
|
|
33
33
|
autoload :Service, 'selenium/webdriver/firefox/service'
|
34
34
|
|
35
35
|
DEFAULT_PORT = 7055
|
36
|
-
DEFAULT_ENABLE_NATIVE_EVENTS = Platform.os == :windows
|
37
36
|
DEFAULT_SECURE_SSL = false
|
38
37
|
DEFAULT_ASSUME_UNTRUSTED_ISSUER = true
|
39
38
|
DEFAULT_LOAD_NO_FOCUS_LIB = false
|
@@ -39,7 +39,9 @@ module Selenium
|
|
39
39
|
persistent_hover: 'enablePersistentHover',
|
40
40
|
require_window_focus: 'requireWindowFocus',
|
41
41
|
use_per_process_proxy: 'ie.usePerProcessProxy',
|
42
|
-
|
42
|
+
use_legacy_file_upload_dialog_handling: 'ie.useLegacyFileUploadDialogHandling',
|
43
|
+
attach_to_edge_chrome: 'ie.edgechromium',
|
44
|
+
edge_executable_path: 'ie.edgepath'
|
43
45
|
}.freeze
|
44
46
|
BROWSER = 'internet explorer'
|
45
47
|
|
@@ -25,7 +25,7 @@ module Selenium
|
|
25
25
|
EXECUTABLE = 'IEDriverServer'
|
26
26
|
MISSING_TEXT = <<~ERROR
|
27
27
|
Unable to find IEDriverServer. Please download the server from
|
28
|
-
|
28
|
+
https://www.selenium.dev/downloads/ and place it somewhere on your PATH.
|
29
29
|
More info at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver.
|
30
30
|
ERROR
|
31
31
|
SHUTDOWN_SUPPORTED = true
|
@@ -25,7 +25,7 @@ module Selenium
|
|
25
25
|
|
26
26
|
PORT = 4444
|
27
27
|
|
28
|
-
attr_accessor :
|
28
|
+
attr_accessor :http, :file_detector
|
29
29
|
attr_reader :capabilities
|
30
30
|
|
31
31
|
#
|
@@ -49,7 +49,7 @@ module Selenium
|
|
49
49
|
#
|
50
50
|
|
51
51
|
def create_session(capabilities)
|
52
|
-
response = execute(:new_session, {},
|
52
|
+
response = execute(:new_session, {}, prepare_capabilities_payload(capabilities))
|
53
53
|
|
54
54
|
@session_id = response['sessionId']
|
55
55
|
capabilities = response['capabilities']
|
@@ -269,7 +269,7 @@ module Selenium
|
|
269
269
|
end
|
270
270
|
|
271
271
|
def element_screenshot(element)
|
272
|
-
execute :take_element_screenshot, id: element
|
272
|
+
execute :take_element_screenshot, id: element
|
273
273
|
end
|
274
274
|
|
275
275
|
#
|
@@ -431,7 +431,7 @@ module Selenium
|
|
431
431
|
end
|
432
432
|
|
433
433
|
def submit_element(element)
|
434
|
-
form = find_element_by('xpath', "./ancestor-or-self::form", element)
|
434
|
+
form = find_element_by('xpath', "./ancestor-or-self::form", [:element, element])
|
435
435
|
execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
|
436
436
|
"e.initEvent('submit', true, true);" \
|
437
437
|
'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
|
@@ -451,11 +451,19 @@ module Selenium
|
|
451
451
|
end
|
452
452
|
|
453
453
|
def element_dom_attribute(element, name)
|
454
|
-
execute :get_element_attribute, id: element
|
454
|
+
execute :get_element_attribute, id: element, name: name
|
455
455
|
end
|
456
456
|
|
457
457
|
def element_property(element, name)
|
458
|
-
execute :get_element_property, id: element
|
458
|
+
execute :get_element_property, id: element, name: name
|
459
|
+
end
|
460
|
+
|
461
|
+
def element_aria_role(element)
|
462
|
+
execute :get_element_aria_role, id: element
|
463
|
+
end
|
464
|
+
|
465
|
+
def element_aria_label(element)
|
466
|
+
execute :get_element_aria_label, id: element
|
459
467
|
end
|
460
468
|
|
461
469
|
def element_value(element)
|
@@ -516,13 +524,17 @@ module Selenium
|
|
516
524
|
|
517
525
|
alias_method :switch_to_active_element, :active_element
|
518
526
|
|
519
|
-
def find_element_by(how, what,
|
527
|
+
def find_element_by(how, what, parent_ref = [])
|
520
528
|
how, what = convert_locator(how, what)
|
521
529
|
|
522
530
|
return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
|
523
531
|
|
524
|
-
|
525
|
-
|
532
|
+
parent_type, parent_id = parent_ref
|
533
|
+
id = case parent_type
|
534
|
+
when :element
|
535
|
+
execute :find_child_element, {id: parent_id}, {using: how, value: what.to_s}
|
536
|
+
when :shadow_root
|
537
|
+
execute :find_shadow_child_element, {id: parent_id}, {using: how, value: what.to_s}
|
526
538
|
else
|
527
539
|
execute :find_element, {}, {using: how, value: what.to_s}
|
528
540
|
end
|
@@ -530,13 +542,17 @@ module Selenium
|
|
530
542
|
Element.new self, element_id_from(id)
|
531
543
|
end
|
532
544
|
|
533
|
-
def find_elements_by(how, what,
|
545
|
+
def find_elements_by(how, what, parent_ref = [])
|
534
546
|
how, what = convert_locator(how, what)
|
535
547
|
|
536
548
|
return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
|
537
549
|
|
538
|
-
|
539
|
-
|
550
|
+
parent_type, parent_id = parent_ref
|
551
|
+
ids = case parent_type
|
552
|
+
when :element
|
553
|
+
execute :find_child_elements, {id: parent_id}, {using: how, value: what.to_s}
|
554
|
+
when :shadow_root
|
555
|
+
execute :find_shadow_child_elements, {id: parent_id}, {using: how, value: what.to_s}
|
540
556
|
else
|
541
557
|
execute :find_elements, {}, {using: how, value: what.to_s}
|
542
558
|
end
|
@@ -544,6 +560,11 @@ module Selenium
|
|
544
560
|
ids.map { |id| Element.new self, element_id_from(id) }
|
545
561
|
end
|
546
562
|
|
563
|
+
def shadow_root(element)
|
564
|
+
id = execute :get_element_shadow_root, id: element
|
565
|
+
ShadowRoot.new self, shadow_root_id_from(id)
|
566
|
+
end
|
567
|
+
|
547
568
|
private
|
548
569
|
|
549
570
|
#
|
@@ -591,7 +612,16 @@ module Selenium
|
|
591
612
|
end
|
592
613
|
|
593
614
|
def element_id_from(id)
|
594
|
-
id['ELEMENT'] || id[
|
615
|
+
id['ELEMENT'] || id[Element::ELEMENT_KEY]
|
616
|
+
end
|
617
|
+
|
618
|
+
def shadow_root_id_from(id)
|
619
|
+
id[ShadowRoot::ROOT_KEY]
|
620
|
+
end
|
621
|
+
|
622
|
+
def prepare_capabilities_payload(capabilities)
|
623
|
+
capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
|
624
|
+
{capabilities: capabilities}
|
595
625
|
end
|
596
626
|
|
597
627
|
def convert_locator(how, what)
|