selenium-webdriver 3.142.7 → 4.3.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 +5 -5
- data/CHANGES +432 -5
- data/Gemfile +3 -1
- data/LICENSE +1 -1
- data/NOTICE +2 -0
- data/README.md +4 -5
- data/lib/selenium/server.rb +75 -64
- data/lib/selenium/webdriver/atoms/findElements.js +122 -0
- data/lib/selenium/webdriver/atoms/getAttribute.js +100 -7
- data/lib/selenium/webdriver/atoms/isDisplayed.js +76 -78
- data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
- data/lib/selenium/webdriver/bidi/session.rb +38 -0
- data/lib/selenium/webdriver/bidi.rb +55 -0
- data/lib/selenium/webdriver/chrome/driver.rb +26 -83
- data/lib/selenium/webdriver/chrome/{bridge.rb → features.rb} +55 -12
- data/lib/selenium/webdriver/chrome/options.rb +138 -67
- data/lib/selenium/webdriver/chrome/profile.rb +6 -3
- data/lib/selenium/webdriver/chrome/service.rb +8 -15
- data/lib/selenium/webdriver/chrome.rb +5 -18
- data/lib/selenium/webdriver/common/action_builder.rb +171 -236
- data/lib/selenium/webdriver/common/driver.rb +76 -29
- 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_authentication.rb +89 -0
- data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_bidi.rb} +10 -8
- data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +87 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +38 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +44 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +43 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +38 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +5 -8
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +144 -0
- data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_logs.rb} +4 -4
- data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +17 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +6 -27
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +136 -0
- 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/{rotatable.rb → prints_page.rb} +18 -20
- data/lib/selenium/webdriver/common/element.rb +83 -23
- data/lib/selenium/webdriver/common/error.rb +32 -196
- data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
- data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -22
- data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
- data/lib/selenium/webdriver/common/interactions/key_actions.rb +10 -6
- 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 +68 -78
- data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
- 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 +57 -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 +113 -0
- data/lib/selenium/webdriver/common/{w3c_manager.rb → interactions/wheel_input.rb} +14 -17
- data/lib/selenium/webdriver/common/keys.rb +1 -0
- data/lib/selenium/webdriver/common/log_entry.rb +2 -2
- data/lib/selenium/webdriver/common/logger.rb +50 -15
- data/lib/selenium/webdriver/common/manager.rb +11 -38
- data/lib/selenium/webdriver/common/options.rb +147 -23
- data/lib/selenium/webdriver/common/platform.rb +10 -5
- data/lib/selenium/webdriver/common/port_prober.rb +4 -6
- data/lib/selenium/webdriver/common/profile_helper.rb +11 -9
- data/lib/selenium/webdriver/common/proxy.rb +6 -3
- data/lib/selenium/webdriver/common/search_context.rb +7 -9
- data/lib/selenium/webdriver/common/service.rb +17 -125
- data/lib/selenium/webdriver/common/service_manager.rb +150 -0
- data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
- data/lib/selenium/webdriver/common/socket_lock.rb +2 -2
- data/lib/selenium/webdriver/common/socket_poller.rb +3 -3
- data/lib/selenium/webdriver/common/takes_screenshot.rb +66 -0
- data/lib/selenium/webdriver/common/target_locator.rb +32 -4
- data/lib/selenium/webdriver/common/timeouts.rb +31 -4
- data/lib/selenium/webdriver/common/wait.rb +1 -1
- data/lib/selenium/webdriver/common/websocket_connection.rb +149 -0
- data/lib/selenium/webdriver/common/window.rb +0 -4
- data/lib/selenium/webdriver/common/zipper.rb +3 -9
- data/lib/selenium/webdriver/common.rb +35 -18
- data/lib/selenium/webdriver/devtools/console_event.rb +38 -0
- data/lib/selenium/webdriver/devtools/exception_event.rb +36 -0
- data/lib/selenium/webdriver/devtools/mutation_event.rb +37 -0
- data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
- data/lib/selenium/webdriver/devtools/request.rb +67 -0
- data/lib/selenium/webdriver/devtools/response.rb +66 -0
- data/lib/selenium/webdriver/devtools.rb +86 -0
- data/lib/selenium/webdriver/edge/driver.rb +7 -29
- data/lib/selenium/webdriver/edge/features.rb +45 -0
- data/lib/selenium/webdriver/edge/options.rb +11 -48
- data/lib/selenium/webdriver/edge/profile.rb +33 -0
- data/lib/selenium/webdriver/edge/service.rb +10 -26
- data/lib/selenium/webdriver/edge.rb +11 -14
- data/lib/selenium/webdriver/firefox/driver.rb +32 -19
- data/lib/selenium/webdriver/firefox/extension.rb +8 -0
- data/lib/selenium/webdriver/firefox/features.rb +63 -0
- data/lib/selenium/webdriver/firefox/options.rb +73 -50
- data/lib/selenium/webdriver/firefox/profile.rb +16 -70
- data/lib/selenium/webdriver/firefox/service.rb +5 -9
- data/lib/selenium/webdriver/firefox/util.rb +1 -1
- data/lib/selenium/webdriver/firefox.rb +17 -28
- data/lib/selenium/webdriver/ie/driver.rb +1 -47
- data/lib/selenium/webdriver/ie/options.rb +15 -46
- data/lib/selenium/webdriver/ie/service.rb +13 -15
- data/lib/selenium/webdriver/ie.rb +3 -16
- data/lib/selenium/webdriver/remote/bridge.rb +563 -86
- data/lib/selenium/webdriver/remote/capabilities.rb +159 -123
- data/lib/selenium/webdriver/remote/commands.rb +158 -0
- data/lib/selenium/webdriver/remote/driver.rb +22 -13
- data/lib/selenium/webdriver/remote/http/common.rb +0 -5
- data/lib/selenium/webdriver/remote/http/default.rb +22 -31
- data/lib/selenium/webdriver/remote/response.rb +18 -49
- data/lib/selenium/webdriver/remote.rb +15 -12
- data/lib/selenium/webdriver/safari/driver.rb +3 -31
- data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -3
- data/lib/selenium/webdriver/safari/options.rb +10 -29
- data/lib/selenium/webdriver/safari/service.rb +4 -8
- data/lib/selenium/webdriver/safari.rb +13 -19
- data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
- 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/color.rb +9 -9
- data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
- data/lib/selenium/webdriver/support/guards/guard.rb +89 -0
- data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +22 -19
- data/lib/selenium/webdriver/support/guards.rb +95 -0
- data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
- data/lib/selenium/webdriver/support/select.rb +3 -3
- data/lib/selenium/webdriver/support.rb +1 -0
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +14 -13
- data/selenium-webdriver.gemspec +32 -13
- metadata +176 -69
- data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
- data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
- data/lib/selenium/webdriver/common/keyboard.rb +0 -70
- data/lib/selenium/webdriver/common/mouse.rb +0 -89
- data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
- data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
- data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
- data/lib/selenium/webdriver/edge/bridge.rb +0 -76
- data/lib/selenium/webdriver/firefox/binary.rb +0 -187
- data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
- data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
- data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
- data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
- data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
- data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
- data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
- data/lib/selenium/webdriver/remote/http/persistent.rb +0 -60
- data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
- data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
- data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
- data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
- data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
|
@@ -22,125 +22,550 @@ module Selenium
|
|
|
22
22
|
module Remote
|
|
23
23
|
class Bridge
|
|
24
24
|
include Atoms
|
|
25
|
-
include BridgeHelper
|
|
26
25
|
|
|
27
26
|
PORT = 4444
|
|
28
|
-
COMMANDS = {
|
|
29
|
-
new_session: [:post, 'session']
|
|
30
|
-
}.freeze
|
|
31
27
|
|
|
32
|
-
attr_accessor :
|
|
33
|
-
attr_reader :capabilities
|
|
28
|
+
attr_accessor :http, :file_detector
|
|
29
|
+
attr_reader :capabilities
|
|
34
30
|
|
|
35
31
|
#
|
|
36
|
-
#
|
|
32
|
+
# Initializes the bridge with the given server URL
|
|
33
|
+
# @param [String, URI] url url for the remote server
|
|
34
|
+
# @param [Object] http_client an HTTP client instance that implements the same protocol as Http::Default
|
|
35
|
+
# @api private
|
|
37
36
|
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
|
|
38
|
+
def initialize(url:, http_client: nil)
|
|
39
|
+
uri = url.is_a?(URI) ? url : URI.parse(url)
|
|
40
|
+
uri.path += '/' unless uri.path.end_with?('/')
|
|
41
|
+
|
|
42
|
+
@http = http_client || Http::Default.new
|
|
43
|
+
@http.server_url = uri
|
|
44
|
+
@file_detector = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
41
47
|
#
|
|
42
|
-
#
|
|
48
|
+
# Creates session.
|
|
43
49
|
#
|
|
44
|
-
def self.handshake(**opts)
|
|
45
|
-
desired_capabilities = opts.delete(:desired_capabilities) { Capabilities.new }
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
def create_session(capabilities)
|
|
52
|
+
response = execute(:new_session, {}, prepare_capabilities_payload(capabilities))
|
|
53
|
+
|
|
54
|
+
@session_id = response['sessionId']
|
|
55
|
+
capabilities = response['capabilities']
|
|
56
|
+
|
|
57
|
+
raise Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
@capabilities = Capabilities.json_create(capabilities)
|
|
60
|
+
|
|
61
|
+
case @capabilities[:browser_name]
|
|
62
|
+
when 'chrome'
|
|
63
|
+
extend(WebDriver::Chrome::Features)
|
|
64
|
+
when 'firefox'
|
|
65
|
+
extend(WebDriver::Firefox::Features)
|
|
66
|
+
when 'msedge'
|
|
67
|
+
extend(WebDriver::Edge::Features)
|
|
68
|
+
when 'Safari', 'Safari Technology Preview'
|
|
69
|
+
extend(WebDriver::Safari::Features)
|
|
53
70
|
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# Returns the current session ID.
|
|
75
|
+
#
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
def session_id
|
|
78
|
+
@session_id || raise(Error::WebDriverError, 'no current session exists')
|
|
79
|
+
end
|
|
57
80
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Remote::W3C::Bridge.new(capabilities, bridge.session_id, **opts)
|
|
63
|
-
else
|
|
64
|
-
raise WebDriverError, 'cannot understand dialect'
|
|
81
|
+
def browser
|
|
82
|
+
@browser ||= begin
|
|
83
|
+
name = @capabilities.browser_name
|
|
84
|
+
name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
|
|
65
85
|
end
|
|
66
86
|
end
|
|
67
87
|
|
|
88
|
+
def status
|
|
89
|
+
execute :status
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def get(url)
|
|
93
|
+
execute :get, {}, {url: url}
|
|
94
|
+
end
|
|
95
|
+
|
|
68
96
|
#
|
|
69
|
-
#
|
|
70
|
-
# @param [Hash] opts options for the driver
|
|
71
|
-
# @option opts [String] :url url for the remote server
|
|
72
|
-
# @option opts [Object] :http_client an HTTP client instance that implements the same protocol as Http::Default
|
|
73
|
-
# @option opts [Capabilities] :desired_capabilities an instance of Remote::Capabilities describing the capabilities you want
|
|
74
|
-
# @api private
|
|
97
|
+
# timeouts
|
|
75
98
|
#
|
|
76
99
|
|
|
77
|
-
def
|
|
78
|
-
|
|
100
|
+
def timeouts
|
|
101
|
+
execute :get_timeouts, {}
|
|
102
|
+
end
|
|
79
103
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
def timeouts=(timeouts)
|
|
105
|
+
execute :set_timeout, {}, timeouts
|
|
106
|
+
end
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
#
|
|
109
|
+
# alerts
|
|
110
|
+
#
|
|
87
111
|
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
def accept_alert
|
|
113
|
+
execute :accept_alert
|
|
114
|
+
end
|
|
90
115
|
|
|
91
|
-
|
|
116
|
+
def dismiss_alert
|
|
117
|
+
execute :dismiss_alert
|
|
118
|
+
end
|
|
92
119
|
|
|
93
|
-
|
|
94
|
-
|
|
120
|
+
def alert=(keys)
|
|
121
|
+
execute :send_alert_text, {}, {value: keys.chars, text: keys}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def alert_text
|
|
125
|
+
execute :get_alert_text
|
|
95
126
|
end
|
|
96
127
|
|
|
97
128
|
#
|
|
98
|
-
#
|
|
129
|
+
# navigation
|
|
99
130
|
#
|
|
100
131
|
|
|
101
|
-
def
|
|
102
|
-
|
|
132
|
+
def go_back
|
|
133
|
+
execute :back
|
|
134
|
+
end
|
|
103
135
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
136
|
+
def go_forward
|
|
137
|
+
execute :forward
|
|
138
|
+
end
|
|
107
139
|
|
|
108
|
-
|
|
109
|
-
|
|
140
|
+
def url
|
|
141
|
+
execute :get_current_url
|
|
142
|
+
end
|
|
110
143
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
144
|
+
def title
|
|
145
|
+
execute :get_title
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def page_source
|
|
149
|
+
execute :get_page_source
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
#
|
|
153
|
+
# Create a new top-level browsing context
|
|
154
|
+
# https://w3c.github.io/webdriver/#new-window
|
|
155
|
+
# @param type [String] Supports two values: 'tab' and 'window'.
|
|
156
|
+
# Use 'tab' if you'd like the new window to share an OS-level window
|
|
157
|
+
# with the current browsing context.
|
|
158
|
+
# Use 'window' otherwise
|
|
159
|
+
# @return [Hash] Containing 'handle' with the value of the window handle
|
|
160
|
+
# and 'type' with the value of the created window type
|
|
161
|
+
#
|
|
162
|
+
def new_window(type)
|
|
163
|
+
execute :new_window, {}, {type: type}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def switch_to_window(name)
|
|
167
|
+
execute :switch_to_window, {}, {handle: name}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def switch_to_frame(id)
|
|
171
|
+
id = find_element_by('id', id) if id.is_a? String
|
|
172
|
+
execute :switch_to_frame, {}, {id: id}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def switch_to_parent_frame
|
|
176
|
+
execute :switch_to_parent_frame
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def switch_to_default_content
|
|
180
|
+
switch_to_frame nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
QUIT_ERRORS = [IOError].freeze
|
|
184
|
+
|
|
185
|
+
def quit
|
|
186
|
+
execute :delete_session
|
|
187
|
+
http.close
|
|
188
|
+
rescue *QUIT_ERRORS
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def close
|
|
192
|
+
execute :close_window
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def refresh
|
|
196
|
+
execute :refresh
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
#
|
|
200
|
+
# window handling
|
|
201
|
+
#
|
|
202
|
+
|
|
203
|
+
def window_handles
|
|
204
|
+
execute :get_window_handles
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def window_handle
|
|
208
|
+
execute :get_window_handle
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def resize_window(width, height, handle = :current)
|
|
212
|
+
raise Error::WebDriverError, 'Switch to desired window before changing its size' unless handle == :current
|
|
213
|
+
|
|
214
|
+
set_window_rect(width: width, height: height)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def window_size(handle = :current)
|
|
218
|
+
unless handle == :current
|
|
219
|
+
raise Error::UnsupportedOperationError,
|
|
220
|
+
'Switch to desired window before getting its size'
|
|
116
221
|
end
|
|
117
222
|
|
|
118
|
-
|
|
223
|
+
data = execute :get_window_rect
|
|
224
|
+
Dimension.new data['width'], data['height']
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def minimize_window
|
|
228
|
+
execute :minimize_window
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def maximize_window(handle = :current)
|
|
232
|
+
unless handle == :current
|
|
233
|
+
raise Error::UnsupportedOperationError,
|
|
234
|
+
'Switch to desired window before changing its size'
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
execute :maximize_window
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def full_screen_window
|
|
241
|
+
execute :fullscreen_window
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def reposition_window(x, y)
|
|
245
|
+
set_window_rect(x: x, y: y)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def window_position
|
|
249
|
+
data = execute :get_window_rect
|
|
250
|
+
Point.new data['x'], data['y']
|
|
251
|
+
end
|
|
119
252
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
253
|
+
def set_window_rect(x: nil, y: nil, width: nil, height: nil)
|
|
254
|
+
params = {x: x, y: y, width: width, height: height}
|
|
255
|
+
params.update(params) { |_k, v| Integer(v) unless v.nil? }
|
|
256
|
+
execute :set_window_rect, {}, params
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def window_rect
|
|
260
|
+
data = execute :get_window_rect
|
|
261
|
+
Rectangle.new data['x'], data['y'], data['width'], data['height']
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def screenshot
|
|
265
|
+
execute :take_screenshot
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def element_screenshot(element)
|
|
269
|
+
execute :take_element_screenshot, id: element
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
#
|
|
273
|
+
# HTML 5
|
|
274
|
+
#
|
|
275
|
+
|
|
276
|
+
def local_storage_item(key, value = nil)
|
|
277
|
+
if value
|
|
278
|
+
execute_script("localStorage.setItem('#{key}', '#{value}')")
|
|
124
279
|
else
|
|
125
|
-
|
|
126
|
-
@dialect = :w3c
|
|
127
|
-
W3C::Capabilities.json_create(value)
|
|
280
|
+
execute_script("return localStorage.getItem('#{key}')")
|
|
128
281
|
end
|
|
129
282
|
end
|
|
130
283
|
|
|
284
|
+
def remove_local_storage_item(key)
|
|
285
|
+
execute_script("localStorage.removeItem('#{key}')")
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def local_storage_keys
|
|
289
|
+
execute_script('return Object.keys(localStorage)')
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def clear_local_storage
|
|
293
|
+
execute_script('localStorage.clear()')
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def local_storage_size
|
|
297
|
+
execute_script('return localStorage.length')
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def session_storage_item(key, value = nil)
|
|
301
|
+
if value
|
|
302
|
+
execute_script("sessionStorage.setItem('#{key}', '#{value}')")
|
|
303
|
+
else
|
|
304
|
+
execute_script("return sessionStorage.getItem('#{key}')")
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def remove_session_storage_item(key)
|
|
309
|
+
execute_script("sessionStorage.removeItem('#{key}')")
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def session_storage_keys
|
|
313
|
+
execute_script('return Object.keys(sessionStorage)')
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def clear_session_storage
|
|
317
|
+
execute_script('sessionStorage.clear()')
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def session_storage_size
|
|
321
|
+
execute_script('return sessionStorage.length')
|
|
322
|
+
end
|
|
323
|
+
|
|
131
324
|
#
|
|
132
|
-
#
|
|
325
|
+
# javascript execution
|
|
133
326
|
#
|
|
134
327
|
|
|
135
|
-
def
|
|
136
|
-
|
|
328
|
+
def execute_script(script, *args)
|
|
329
|
+
result = execute :execute_script, {}, {script: script, args: args}
|
|
330
|
+
unwrap_script_result result
|
|
137
331
|
end
|
|
138
332
|
|
|
139
|
-
def
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
333
|
+
def execute_async_script(script, *args)
|
|
334
|
+
result = execute :execute_async_script, {}, {script: script, args: args}
|
|
335
|
+
unwrap_script_result result
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
#
|
|
339
|
+
# cookies
|
|
340
|
+
#
|
|
341
|
+
|
|
342
|
+
def manage
|
|
343
|
+
@manage ||= WebDriver::Manager.new(self)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def add_cookie(cookie)
|
|
347
|
+
execute :add_cookie, {}, {cookie: cookie}
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def delete_cookie(name)
|
|
351
|
+
execute :delete_cookie, name: name
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def cookie(name)
|
|
355
|
+
execute :get_cookie, name: name
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def cookies
|
|
359
|
+
execute :get_all_cookies
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def delete_all_cookies
|
|
363
|
+
execute :delete_all_cookies
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
#
|
|
367
|
+
# actions
|
|
368
|
+
#
|
|
369
|
+
|
|
370
|
+
def action(deprecated_async = nil, async: false, devices: [], duration: 250)
|
|
371
|
+
ActionBuilder.new self, nil, nil, deprecated_async, async: async, devices: devices, duration: duration
|
|
372
|
+
end
|
|
373
|
+
alias_method :actions, :action
|
|
374
|
+
|
|
375
|
+
def mouse
|
|
376
|
+
raise Error::UnsupportedOperationError, '#mouse is no longer supported, use #action instead'
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def keyboard
|
|
380
|
+
raise Error::UnsupportedOperationError, '#keyboard is no longer supported, use #action instead'
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def send_actions(data)
|
|
384
|
+
execute :actions, {}, {actions: data}
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def release_actions
|
|
388
|
+
execute :release_actions
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def print_page(options = {})
|
|
392
|
+
execute :print_page, {}, {options: options}
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def click_element(element)
|
|
396
|
+
execute :element_click, id: element
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def send_keys_to_element(element, keys)
|
|
400
|
+
# TODO: rework file detectors before Selenium 4.0
|
|
401
|
+
if @file_detector
|
|
402
|
+
local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
|
|
403
|
+
if local_files.any?
|
|
404
|
+
keys = local_files.map { |local_file| upload(local_file) }
|
|
405
|
+
keys = Array(keys.join("\n"))
|
|
406
|
+
end
|
|
143
407
|
end
|
|
408
|
+
|
|
409
|
+
# Keep .split(//) for backward compatibility for now
|
|
410
|
+
text = keys.join
|
|
411
|
+
execute :element_send_keys, {id: element}, {value: text.chars, text: text}
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def upload(local_file)
|
|
415
|
+
unless File.file?(local_file)
|
|
416
|
+
WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!")
|
|
417
|
+
raise Error::WebDriverError, "You are trying to work with something that isn't a file."
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def clear_element(element)
|
|
424
|
+
execute :element_clear, id: element
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def submit_element(element)
|
|
428
|
+
script = "var form = arguments[0];\n" \
|
|
429
|
+
"while (form.nodeName != \"FORM\" && form.parentNode) {\n" \
|
|
430
|
+
" form = form.parentNode;\n" \
|
|
431
|
+
"}\n" \
|
|
432
|
+
"if (!form) { throw Error('Unable to find containing form element'); }\n" \
|
|
433
|
+
"if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" \
|
|
434
|
+
"var e = form.ownerDocument.createEvent('Event');\n" \
|
|
435
|
+
"e.initEvent('submit', true, true);\n" \
|
|
436
|
+
"if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
|
|
437
|
+
|
|
438
|
+
execute_script(script, Element::ELEMENT_KEY => element)
|
|
439
|
+
rescue Error::JavascriptError
|
|
440
|
+
raise Error::UnsupportedOperationError, "To submit an element, it must be nested inside a form element"
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
#
|
|
444
|
+
# element properties
|
|
445
|
+
#
|
|
446
|
+
|
|
447
|
+
def element_tag_name(element)
|
|
448
|
+
execute :get_element_tag_name, id: element
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def element_attribute(element, name)
|
|
452
|
+
WebDriver.logger.info "Using script for :getAttribute of #{name}"
|
|
453
|
+
execute_atom :getAttribute, element, name
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def element_dom_attribute(element, name)
|
|
457
|
+
execute :get_element_attribute, id: element, name: name
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def element_property(element, name)
|
|
461
|
+
execute :get_element_property, id: element, name: name
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def element_aria_role(element)
|
|
465
|
+
execute :get_element_aria_role, id: element
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def element_aria_label(element)
|
|
469
|
+
execute :get_element_aria_label, id: element
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def element_value(element)
|
|
473
|
+
element_property element, 'value'
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def element_text(element)
|
|
477
|
+
execute :get_element_text, id: element
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def element_location(element)
|
|
481
|
+
data = execute :get_element_rect, id: element
|
|
482
|
+
|
|
483
|
+
Point.new data['x'], data['y']
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def element_rect(element)
|
|
487
|
+
data = execute :get_element_rect, id: element
|
|
488
|
+
|
|
489
|
+
Rectangle.new data['x'], data['y'], data['width'], data['height']
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def element_location_once_scrolled_into_view(element)
|
|
493
|
+
send_keys_to_element(element, [''])
|
|
494
|
+
element_location(element)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def element_size(element)
|
|
498
|
+
data = execute :get_element_rect, id: element
|
|
499
|
+
|
|
500
|
+
Dimension.new data['width'], data['height']
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def element_enabled?(element)
|
|
504
|
+
execute :is_element_enabled, id: element
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def element_selected?(element)
|
|
508
|
+
execute :is_element_selected, id: element
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def element_displayed?(element)
|
|
512
|
+
WebDriver.logger.info 'Using script for :isDisplayed'
|
|
513
|
+
execute_atom :isDisplayed, element
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def element_value_of_css_property(element, prop)
|
|
517
|
+
execute :get_element_css_value, id: element, property_name: prop
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
#
|
|
521
|
+
# finding elements
|
|
522
|
+
#
|
|
523
|
+
|
|
524
|
+
def active_element
|
|
525
|
+
Element.new self, element_id_from(execute(:get_active_element))
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
alias_method :switch_to_active_element, :active_element
|
|
529
|
+
|
|
530
|
+
def find_element_by(how, what, parent_ref = [])
|
|
531
|
+
how, what = convert_locator(how, what)
|
|
532
|
+
|
|
533
|
+
return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
|
|
534
|
+
|
|
535
|
+
parent_type, parent_id = parent_ref
|
|
536
|
+
id = case parent_type
|
|
537
|
+
when :element
|
|
538
|
+
execute :find_child_element, {id: parent_id}, {using: how, value: what.to_s}
|
|
539
|
+
when :shadow_root
|
|
540
|
+
execute :find_shadow_child_element, {id: parent_id}, {using: how, value: what.to_s}
|
|
541
|
+
else
|
|
542
|
+
execute :find_element, {}, {using: how, value: what.to_s}
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
Element.new self, element_id_from(id)
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def find_elements_by(how, what, parent_ref = [])
|
|
549
|
+
how, what = convert_locator(how, what)
|
|
550
|
+
|
|
551
|
+
return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
|
|
552
|
+
|
|
553
|
+
parent_type, parent_id = parent_ref
|
|
554
|
+
ids = case parent_type
|
|
555
|
+
when :element
|
|
556
|
+
execute :find_child_elements, {id: parent_id}, {using: how, value: what.to_s}
|
|
557
|
+
when :shadow_root
|
|
558
|
+
execute :find_shadow_child_elements, {id: parent_id}, {using: how, value: what.to_s}
|
|
559
|
+
else
|
|
560
|
+
execute :find_elements, {}, {using: how, value: what.to_s}
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
ids.map { |id| Element.new self, element_id_from(id) }
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def shadow_root(element)
|
|
567
|
+
id = execute :get_element_shadow_root, id: element
|
|
568
|
+
ShadowRoot.new self, shadow_root_id_from(id)
|
|
144
569
|
end
|
|
145
570
|
|
|
146
571
|
private
|
|
@@ -164,7 +589,7 @@ module Selenium
|
|
|
164
589
|
end
|
|
165
590
|
|
|
166
591
|
WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
|
|
167
|
-
http.call(verb, path, command_hash)
|
|
592
|
+
http.call(verb, path, command_hash)['value']
|
|
168
593
|
end
|
|
169
594
|
|
|
170
595
|
def escaper
|
|
@@ -172,23 +597,75 @@ module Selenium
|
|
|
172
597
|
end
|
|
173
598
|
|
|
174
599
|
def commands(command)
|
|
175
|
-
raise NotImplementedError unless command == :new_session
|
|
176
|
-
|
|
177
600
|
COMMANDS[command]
|
|
178
601
|
end
|
|
179
602
|
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
|
|
603
|
+
def unwrap_script_result(arg)
|
|
604
|
+
case arg
|
|
605
|
+
when Array
|
|
606
|
+
arg.map { |e| unwrap_script_result(e) }
|
|
607
|
+
when Hash
|
|
608
|
+
element_id = element_id_from(arg)
|
|
609
|
+
return Element.new(self, element_id) if element_id
|
|
183
610
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
611
|
+
shadow_root_id = shadow_root_id_from(arg)
|
|
612
|
+
return ShadowRoot.new self, shadow_root_id if shadow_root_id
|
|
613
|
+
|
|
614
|
+
arg.each { |k, v| arg[k] = unwrap_script_result(v) }
|
|
615
|
+
else
|
|
616
|
+
arg
|
|
617
|
+
end
|
|
190
618
|
end
|
|
191
619
|
|
|
620
|
+
def element_id_from(id)
|
|
621
|
+
id['ELEMENT'] || id[Element::ELEMENT_KEY]
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def shadow_root_id_from(id)
|
|
625
|
+
id[ShadowRoot::ROOT_KEY]
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def prepare_capabilities_payload(capabilities)
|
|
629
|
+
capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
|
|
630
|
+
{capabilities: capabilities}
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def convert_locator(how, what)
|
|
634
|
+
how = SearchContext::FINDERS[how.to_sym] || how
|
|
635
|
+
|
|
636
|
+
case how
|
|
637
|
+
when 'class name'
|
|
638
|
+
how = 'css selector'
|
|
639
|
+
what = ".#{escape_css(what.to_s)}"
|
|
640
|
+
when 'id'
|
|
641
|
+
how = 'css selector'
|
|
642
|
+
what = "##{escape_css(what.to_s)}"
|
|
643
|
+
when 'name'
|
|
644
|
+
how = 'css selector'
|
|
645
|
+
what = "*[name='#{escape_css(what.to_s)}']"
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
if what.is_a?(Hash)
|
|
649
|
+
what = what.each_with_object({}) do |(h, w), hash|
|
|
650
|
+
h, w = convert_locator(h.to_s, w)
|
|
651
|
+
hash[h] = w
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
[how, what]
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/.freeze
|
|
659
|
+
UNICODE_CODE_POINT = 30
|
|
660
|
+
|
|
661
|
+
# Escapes invalid characters in CSS selector.
|
|
662
|
+
# @see https://mathiasbynens.be/notes/css-escapes
|
|
663
|
+
def escape_css(string)
|
|
664
|
+
string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
|
|
665
|
+
string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
|
|
666
|
+
|
|
667
|
+
string
|
|
668
|
+
end
|
|
192
669
|
end # Bridge
|
|
193
670
|
end # Remote
|
|
194
671
|
end # WebDriver
|