selenium-webdriver 4.0.2 → 4.2.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 +70 -0
- data/LICENSE +1 -1
- data/NOTICE +1 -1
- data/lib/selenium/server.rb +14 -9
- data/lib/selenium/webdriver/bidi/session.rb +38 -0
- data/lib/selenium/webdriver/bidi.rb +55 -0
- data/lib/selenium/webdriver/chrome/features.rb +5 -0
- data/lib/selenium/webdriver/chrome/options.rb +20 -20
- data/lib/selenium/webdriver/common/action_builder.rb +108 -21
- data/lib/selenium/webdriver/common/driver.rb +2 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_bidi.rb +38 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -1
- data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -1
- data/lib/selenium/webdriver/common/element.rb +1 -1
- data/lib/selenium/webdriver/common/error.rb +1 -1
- 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 +49 -43
- 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/interactions/wheel_input.rb +42 -0
- data/lib/selenium/webdriver/common/keys.rb +1 -0
- data/lib/selenium/webdriver/common/options.rb +1 -3
- data/lib/selenium/webdriver/common/platform.rb +4 -4
- data/lib/selenium/webdriver/common/search_context.rb +0 -6
- data/lib/selenium/webdriver/common/service_manager.rb +2 -3
- data/lib/selenium/webdriver/common/shadow_root.rb +1 -1
- data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
- data/lib/selenium/webdriver/common/websocket_connection.rb +149 -0
- data/lib/selenium/webdriver/common/zipper.rb +3 -1
- data/lib/selenium/webdriver/common.rb +14 -2
- data/lib/selenium/webdriver/devtools/request.rb +1 -1
- data/lib/selenium/webdriver/devtools/response.rb +1 -1
- data/lib/selenium/webdriver/devtools.rb +5 -101
- data/lib/selenium/webdriver/edge/features.rb +1 -0
- data/lib/selenium/webdriver/firefox/driver.rb +1 -0
- data/lib/selenium/webdriver/firefox/features.rb +2 -5
- data/lib/selenium/webdriver/firefox/options.rb +5 -3
- data/lib/selenium/webdriver/firefox/profile.rb +1 -5
- data/lib/selenium/webdriver/firefox/util.rb +46 -0
- data/lib/selenium/webdriver/firefox.rb +1 -0
- data/lib/selenium/webdriver/ie/options.rb +2 -2
- data/lib/selenium/webdriver/remote/bridge.rb +25 -20
- data/lib/selenium/webdriver/remote/commands.rb +0 -5
- data/lib/selenium/webdriver/remote/http/default.rb +6 -12
- data/lib/selenium/webdriver/remote/response.rb +2 -2
- data/lib/selenium/webdriver/remote.rb +6 -5
- data/lib/selenium/webdriver/support/cdp_client_generator.rb +4 -4
- data/lib/selenium/webdriver/support/color.rb +7 -7
- data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -1
- data/lib/selenium/webdriver/support/guards.rb +1 -1
- data/lib/selenium/webdriver/support/select.rb +1 -1
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +2 -1
- data/selenium-webdriver.gemspec +7 -4
- metadata +55 -5
@@ -0,0 +1,149 @@
|
|
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
|
+
require 'websocket'
|
21
|
+
|
22
|
+
module Selenium
|
23
|
+
module WebDriver
|
24
|
+
class WebSocketConnection
|
25
|
+
RESPONSE_WAIT_TIMEOUT = 30
|
26
|
+
RESPONSE_WAIT_INTERVAL = 0.1
|
27
|
+
|
28
|
+
def initialize(url:)
|
29
|
+
@callback_threads = ThreadGroup.new
|
30
|
+
|
31
|
+
@messages = []
|
32
|
+
@session_id = nil
|
33
|
+
@url = url
|
34
|
+
|
35
|
+
process_handshake
|
36
|
+
@socket_thread = attach_socket_listener
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
@callback_threads.list.each(&:exit)
|
41
|
+
@socket_thread.exit
|
42
|
+
socket.close
|
43
|
+
end
|
44
|
+
|
45
|
+
def callbacks
|
46
|
+
@callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def send_cmd(**payload)
|
50
|
+
id = next_id
|
51
|
+
data = payload.merge(id: id)
|
52
|
+
data = JSON.generate(data)
|
53
|
+
WebDriver.logger.debug "WebSocket -> #{data}"
|
54
|
+
|
55
|
+
out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
|
56
|
+
socket.write(out_frame.to_s)
|
57
|
+
|
58
|
+
wait.until { @messages.find { |m| m['id'] == id } }
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def process_handshake
|
64
|
+
socket.print(ws.to_s)
|
65
|
+
ws << socket.readpartial(1024)
|
66
|
+
end
|
67
|
+
|
68
|
+
def attach_socket_listener
|
69
|
+
Thread.new do
|
70
|
+
Thread.current.abort_on_exception = true
|
71
|
+
Thread.current.report_on_exception = false
|
72
|
+
|
73
|
+
until socket.eof?
|
74
|
+
incoming_frame << socket.readpartial(1024)
|
75
|
+
|
76
|
+
while (frame = incoming_frame.next)
|
77
|
+
message = process_frame(frame)
|
78
|
+
next unless message['method']
|
79
|
+
|
80
|
+
params = message['params']
|
81
|
+
callbacks[message['method']].each do |callback|
|
82
|
+
@callback_threads.add(callback_thread(params, &callback))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def incoming_frame
|
90
|
+
@incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_frame(frame)
|
94
|
+
message = frame.to_s
|
95
|
+
|
96
|
+
# Firefox will periodically fail on unparsable empty frame
|
97
|
+
return {} if message.empty?
|
98
|
+
|
99
|
+
message = JSON.parse(message)
|
100
|
+
@messages << message
|
101
|
+
WebDriver.logger.debug "WebSocket <- #{message}"
|
102
|
+
|
103
|
+
message
|
104
|
+
end
|
105
|
+
|
106
|
+
def callback_thread(params)
|
107
|
+
Thread.new do
|
108
|
+
Thread.current.abort_on_exception = true
|
109
|
+
|
110
|
+
# We might end up blocked forever when we have an error in event.
|
111
|
+
# For example, if network interception event raises error,
|
112
|
+
# the browser will keep waiting for the request to be proceeded
|
113
|
+
# before returning back to the original thread. In this case,
|
114
|
+
# we should at least print the error.
|
115
|
+
Thread.current.report_on_exception = true
|
116
|
+
|
117
|
+
yield params
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def wait
|
122
|
+
@wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
|
123
|
+
end
|
124
|
+
|
125
|
+
def socket
|
126
|
+
@socket ||= if URI(@url).scheme == 'wss'
|
127
|
+
socket = TCPSocket.new(ws.host, ws.port)
|
128
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket, OpenSSL::SSL::SSLContext.new)
|
129
|
+
socket.sync_close = true
|
130
|
+
socket.connect
|
131
|
+
|
132
|
+
socket
|
133
|
+
else
|
134
|
+
TCPSocket.new(ws.host, ws.port)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def ws
|
139
|
+
@ws ||= WebSocket::Handshake::Client.new(url: @url)
|
140
|
+
end
|
141
|
+
|
142
|
+
def next_id
|
143
|
+
@id ||= 0
|
144
|
+
@id += 1
|
145
|
+
end
|
146
|
+
|
147
|
+
end # BiDi
|
148
|
+
end # WebDriver
|
149
|
+
end # Selenium
|
@@ -72,7 +72,9 @@ module Selenium
|
|
72
72
|
private
|
73
73
|
|
74
74
|
def with_tmp_zip(&blk)
|
75
|
-
Tempfile
|
75
|
+
# Don't use Tempfile since it lacks rb_file_s_rename permission on Windows.
|
76
|
+
Dir.mktmpdir do |tmp_dir|
|
77
|
+
zip_path = File.join(tmp_dir, 'webdriver-zip')
|
76
78
|
Zip::File.open(zip_path, Zip::File::CREATE, &blk)
|
77
79
|
end
|
78
80
|
end
|
@@ -38,14 +38,24 @@ require 'selenium/webdriver/common/logger'
|
|
38
38
|
require 'selenium/webdriver/common/logs'
|
39
39
|
require 'selenium/webdriver/common/manager'
|
40
40
|
require 'selenium/webdriver/common/search_context'
|
41
|
+
require 'selenium/webdriver/common/interactions/interaction'
|
42
|
+
require 'selenium/webdriver/common/interactions/interactions'
|
43
|
+
require 'selenium/webdriver/common/interactions/pointer_event_properties'
|
44
|
+
require 'selenium/webdriver/common/interactions/pointer_cancel'
|
45
|
+
require 'selenium/webdriver/common/interactions/pointer_move'
|
46
|
+
require 'selenium/webdriver/common/interactions/pointer_press'
|
47
|
+
require 'selenium/webdriver/common/interactions/typing_interaction'
|
48
|
+
require 'selenium/webdriver/common/interactions/pause'
|
41
49
|
require 'selenium/webdriver/common/interactions/key_actions'
|
42
50
|
require 'selenium/webdriver/common/interactions/pointer_actions'
|
43
|
-
require 'selenium/webdriver/common/interactions/interactions'
|
44
51
|
require 'selenium/webdriver/common/interactions/input_device'
|
45
|
-
require 'selenium/webdriver/common/interactions/interaction'
|
46
52
|
require 'selenium/webdriver/common/interactions/none_input'
|
47
53
|
require 'selenium/webdriver/common/interactions/key_input'
|
48
54
|
require 'selenium/webdriver/common/interactions/pointer_input'
|
55
|
+
require 'selenium/webdriver/common/interactions/scroll'
|
56
|
+
require 'selenium/webdriver/common/interactions/wheel_input'
|
57
|
+
require 'selenium/webdriver/common/interactions/scroll_origin'
|
58
|
+
require 'selenium/webdriver/common/interactions/wheel_actions'
|
49
59
|
require 'selenium/webdriver/common/action_builder'
|
50
60
|
require 'selenium/webdriver/common/html5/shared_web_storage'
|
51
61
|
require 'selenium/webdriver/common/html5/local_storage'
|
@@ -66,6 +76,7 @@ require 'selenium/webdriver/common/driver_extensions/prints_page'
|
|
66
76
|
require 'selenium/webdriver/common/driver_extensions/uploads_files'
|
67
77
|
require 'selenium/webdriver/common/driver_extensions/full_page_screenshot'
|
68
78
|
require 'selenium/webdriver/common/driver_extensions/has_addons'
|
79
|
+
require 'selenium/webdriver/common/driver_extensions/has_bidi'
|
69
80
|
require 'selenium/webdriver/common/driver_extensions/has_devtools'
|
70
81
|
require 'selenium/webdriver/common/driver_extensions/has_authentication'
|
71
82
|
require 'selenium/webdriver/common/driver_extensions/has_logs'
|
@@ -81,3 +92,4 @@ require 'selenium/webdriver/common/takes_screenshot'
|
|
81
92
|
require 'selenium/webdriver/common/driver'
|
82
93
|
require 'selenium/webdriver/common/element'
|
83
94
|
require 'selenium/webdriver/common/shadow_root'
|
95
|
+
require 'selenium/webdriver/common/websocket_connection'
|
@@ -35,7 +35,7 @@ module Selenium
|
|
35
35
|
id: id,
|
36
36
|
url: params.dig('request', 'url'),
|
37
37
|
method: params.dig('request', 'method'),
|
38
|
-
headers: params.dig('request', 'headers'),
|
38
|
+
headers: params.dig('request', 'headers').dup,
|
39
39
|
post_data: params.dig('request', 'postData')
|
40
40
|
)
|
41
41
|
end
|
@@ -35,7 +35,7 @@ module Selenium
|
|
35
35
|
id: id,
|
36
36
|
code: params['responseStatusCode'],
|
37
37
|
body: (Base64.strict_decode64(encoded_body) if encoded_body),
|
38
|
-
headers: params
|
38
|
+
headers: params.fetch('responseHeaders', []).each_with_object({}) do |header, hash|
|
39
39
|
hash[header['name']] = header['value']
|
40
40
|
end
|
41
41
|
)
|
@@ -20,9 +20,6 @@
|
|
20
20
|
module Selenium
|
21
21
|
module WebDriver
|
22
22
|
class DevTools
|
23
|
-
RESPONSE_WAIT_TIMEOUT = 30
|
24
|
-
RESPONSE_WAIT_INTERVAL = 0.1
|
25
|
-
|
26
23
|
autoload :ConsoleEvent, 'selenium/webdriver/devtools/console_event'
|
27
24
|
autoload :ExceptionEvent, 'selenium/webdriver/devtools/exception_event'
|
28
25
|
autoload :MutationEvent, 'selenium/webdriver/devtools/mutation_event'
|
@@ -31,41 +28,23 @@ module Selenium
|
|
31
28
|
autoload :Response, 'selenium/webdriver/devtools/response'
|
32
29
|
|
33
30
|
def initialize(url:)
|
34
|
-
@
|
35
|
-
|
36
|
-
@messages = []
|
31
|
+
@ws = WebSocketConnection.new(url: url)
|
37
32
|
@session_id = nil
|
38
|
-
@url = url
|
39
|
-
|
40
|
-
process_handshake
|
41
|
-
@socket_thread = attach_socket_listener
|
42
33
|
start_session
|
43
34
|
end
|
44
35
|
|
45
36
|
def close
|
46
|
-
@
|
47
|
-
@socket_thread.exit
|
48
|
-
socket.close
|
37
|
+
@ws.close
|
49
38
|
end
|
50
39
|
|
51
40
|
def callbacks
|
52
|
-
@
|
41
|
+
@ws.callbacks
|
53
42
|
end
|
54
43
|
|
55
44
|
def send_cmd(method, **params)
|
56
|
-
|
57
|
-
data = {id: id, method: method, params: params.reject { |_, v| v.nil? }}
|
45
|
+
data = {method: method, params: params.compact}
|
58
46
|
data[:sessionId] = @session_id if @session_id
|
59
|
-
|
60
|
-
WebDriver.logger.debug "DevTools -> #{data}"
|
61
|
-
|
62
|
-
out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
|
63
|
-
socket.write(out_frame.to_s)
|
64
|
-
|
65
|
-
message = wait.until do
|
66
|
-
@messages.find { |m| m['id'] == id }
|
67
|
-
end
|
68
|
-
|
47
|
+
message = @ws.send_cmd(**data)
|
69
48
|
raise Error::WebDriverError, error_message(message['error']) if message['error']
|
70
49
|
|
71
50
|
message
|
@@ -91,32 +70,6 @@ module Selenium
|
|
91
70
|
|
92
71
|
private
|
93
72
|
|
94
|
-
def process_handshake
|
95
|
-
socket.print(ws.to_s)
|
96
|
-
ws << socket.readpartial(1024)
|
97
|
-
end
|
98
|
-
|
99
|
-
def attach_socket_listener
|
100
|
-
Thread.new do
|
101
|
-
Thread.current.abort_on_exception = true
|
102
|
-
Thread.current.report_on_exception = false
|
103
|
-
|
104
|
-
until socket.eof?
|
105
|
-
incoming_frame << socket.readpartial(1024)
|
106
|
-
|
107
|
-
while (frame = incoming_frame.next)
|
108
|
-
message = process_frame(frame)
|
109
|
-
next unless message['method']
|
110
|
-
|
111
|
-
params = message['params']
|
112
|
-
callbacks[message['method']].each do |callback|
|
113
|
-
@callback_threads.add(callback_thread(params, &callback))
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
73
|
def start_session
|
121
74
|
targets = target.get_targets.dig('result', 'targetInfos')
|
122
75
|
page_target = targets.find { |target| target['type'] == 'page' }
|
@@ -124,55 +77,6 @@ module Selenium
|
|
124
77
|
@session_id = session.dig('result', 'sessionId')
|
125
78
|
end
|
126
79
|
|
127
|
-
def incoming_frame
|
128
|
-
@incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
|
129
|
-
end
|
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
|
-
|
159
|
-
def wait
|
160
|
-
@wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
|
161
|
-
end
|
162
|
-
|
163
|
-
def socket
|
164
|
-
@socket ||= TCPSocket.new(ws.host, ws.port)
|
165
|
-
end
|
166
|
-
|
167
|
-
def ws
|
168
|
-
@ws ||= WebSocket::Handshake::Client.new(url: @url)
|
169
|
-
end
|
170
|
-
|
171
|
-
def next_id
|
172
|
-
@id ||= 0
|
173
|
-
@id += 1
|
174
|
-
end
|
175
|
-
|
176
80
|
def error_message(error)
|
177
81
|
[error['code'], error['message'], error['data']].join(': ')
|
178
82
|
end
|
@@ -30,6 +30,7 @@ module Selenium
|
|
30
30
|
get_cast_sinks: [:get, 'session/:session_id/ms/cast/get_sinks'],
|
31
31
|
set_cast_sink_to_use: [:post, 'session/:session_id/ms/cast/set_sink_to_use'],
|
32
32
|
start_cast_tab_mirroring: [:post, 'session/:session_id/ms/cast/start_tab_mirroring'],
|
33
|
+
start_cast_desktop_mirroring: [:post, 'session/:session_id/ms/cast/start_desktop_mirroring'],
|
33
34
|
get_cast_issue_message: [:get, 'session/:session_id/ms/cast/get_issue_message'],
|
34
35
|
stop_casting: [:post, 'session/:session_id/ms/cast/stop_casting'],
|
35
36
|
send_command: [:post, 'session/:session_id/ms/cdp/execute']
|
@@ -30,6 +30,7 @@ module Selenium
|
|
30
30
|
EXTENSIONS = [DriverExtensions::HasAddons,
|
31
31
|
DriverExtensions::FullPageScreenshot,
|
32
32
|
DriverExtensions::HasContext,
|
33
|
+
DriverExtensions::HasBiDi,
|
33
34
|
DriverExtensions::HasDevTools,
|
34
35
|
DriverExtensions::HasLogEvents,
|
35
36
|
DriverExtensions::HasNetworkInterception,
|
@@ -35,12 +35,9 @@ module Selenium
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def install_addon(path, temporary)
|
38
|
-
|
39
|
-
local_file = @file_detector.call(path)
|
40
|
-
path = upload(local_file) if local_file
|
41
|
-
end
|
38
|
+
addon = File.open(path, 'rb') { |crx_file| Base64.strict_encode64 crx_file.read }
|
42
39
|
|
43
|
-
payload = {
|
40
|
+
payload = {addon: addon}
|
44
41
|
payload[:temporary] = temporary unless temporary.nil?
|
45
42
|
execute :install_addon, {}, payload
|
46
43
|
end
|
@@ -25,11 +25,12 @@ module Selenium
|
|
25
25
|
|
26
26
|
KEY = 'moz:firefoxOptions'
|
27
27
|
|
28
|
-
# see: https://
|
28
|
+
# see: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions
|
29
29
|
CAPABILITIES = {binary: 'binary',
|
30
30
|
args: 'args',
|
31
31
|
log: 'log',
|
32
32
|
prefs: 'prefs',
|
33
|
+
env: 'env',
|
33
34
|
android_package: 'androidPackage',
|
34
35
|
android_activity: 'androidActivity',
|
35
36
|
android_device_serial: 'androidDeviceSerial',
|
@@ -44,7 +45,7 @@ module Selenium
|
|
44
45
|
#
|
45
46
|
# @example
|
46
47
|
# options = Selenium::WebDriver::Firefox::Options.new(args: ['--host=127.0.0.1'])
|
47
|
-
# driver = Selenium::WebDriver.for :firefox,
|
48
|
+
# driver = Selenium::WebDriver.for :firefox, capabilities: options
|
48
49
|
#
|
49
50
|
# @param [Hash] opts the pre-defined options to create the Firefox::Options with
|
50
51
|
# @option opts [String] :binary Path to the Firefox executable to use
|
@@ -62,6 +63,7 @@ module Selenium
|
|
62
63
|
|
63
64
|
@options[:args] ||= []
|
64
65
|
@options[:prefs] ||= {}
|
66
|
+
@options[:env] ||= {}
|
65
67
|
@options[:log] ||= {level: log_level} if log_level
|
66
68
|
|
67
69
|
process_profile(@options.delete(:profile))
|
@@ -175,7 +177,7 @@ module Selenium
|
|
175
177
|
end
|
176
178
|
|
177
179
|
def camelize?(key)
|
178
|
-
key !=
|
180
|
+
key != "prefs"
|
179
181
|
end
|
180
182
|
end # Options
|
181
183
|
end # Firefox
|
@@ -96,7 +96,7 @@ module Selenium
|
|
96
96
|
raise TypeError, "expected one of #{VALID_PREFERENCE_TYPES.inspect}, got #{value.inspect}:#{value.class}"
|
97
97
|
end
|
98
98
|
|
99
|
-
if value.is_a?(String) && stringified?(value)
|
99
|
+
if value.is_a?(String) && Util.stringified?(value)
|
100
100
|
raise ArgumentError, "preference values must be plain strings: #{key.inspect} => #{value.inspect}"
|
101
101
|
end
|
102
102
|
|
@@ -221,10 +221,6 @@ module Selenium
|
|
221
221
|
end
|
222
222
|
end
|
223
223
|
end
|
224
|
-
|
225
|
-
def stringified?(str)
|
226
|
-
/^".*"$/.match?(str)
|
227
|
-
end
|
228
224
|
end # Profile
|
229
225
|
end # Firefox
|
230
226
|
end # WebDriver
|
@@ -0,0 +1,46 @@
|
|
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 Firefox
|
23
|
+
# @api private
|
24
|
+
module Util
|
25
|
+
module_function
|
26
|
+
|
27
|
+
def app_data_path
|
28
|
+
case Platform.os
|
29
|
+
when :windows
|
30
|
+
"#{ENV.fetch('APPDATA')}\\Mozilla\\Firefox"
|
31
|
+
when :macosx
|
32
|
+
"#{Platform.home}/Library/Application Support/Firefox"
|
33
|
+
when :unix, :linux
|
34
|
+
"#{Platform.home}/.mozilla/firefox"
|
35
|
+
else
|
36
|
+
raise "Unknown os: #{Platform.os}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def stringified?(str)
|
41
|
+
str =~ /^".*"$/
|
42
|
+
end
|
43
|
+
end # Util
|
44
|
+
end # Firefox
|
45
|
+
end # WebDriver
|
46
|
+
end # Selenium
|
@@ -24,6 +24,7 @@ require 'rexml/document'
|
|
24
24
|
module Selenium
|
25
25
|
module WebDriver
|
26
26
|
module Firefox
|
27
|
+
autoload :Util, 'selenium/webdriver/firefox/util'
|
27
28
|
autoload :Extension, 'selenium/webdriver/firefox/extension'
|
28
29
|
autoload :ProfilesIni, 'selenium/webdriver/firefox/profiles_ini'
|
29
30
|
autoload :Profile, 'selenium/webdriver/firefox/profile'
|
@@ -52,12 +52,12 @@ module Selenium
|
|
52
52
|
#
|
53
53
|
# @example
|
54
54
|
# options = Selenium::WebDriver::IE::Options.new(args: ['--host=127.0.0.1'])
|
55
|
-
# driver = Selenium::WebDriver.for(:ie,
|
55
|
+
# driver = Selenium::WebDriver.for(:ie, capabilities: options)
|
56
56
|
#
|
57
57
|
# @example
|
58
58
|
# options = Selenium::WebDriver::IE::Options.new
|
59
59
|
# options.element_scroll_behavior = Selenium::WebDriver::IE::Options::SCROLL_BOTTOM
|
60
|
-
# driver = Selenium::WebDriver.for(:ie,
|
60
|
+
# driver = Selenium::WebDriver.for(:ie, capabilities: options)
|
61
61
|
#
|
62
62
|
# @param [Hash] opts the pre-defined options
|
63
63
|
# @option opts [Array<String>] args
|
@@ -30,8 +30,8 @@ module Selenium
|
|
30
30
|
|
31
31
|
#
|
32
32
|
# Initializes the bridge with the given server URL
|
33
|
-
# @param [String, URI]
|
34
|
-
# @param [Object]
|
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
35
|
# @api private
|
36
36
|
#
|
37
37
|
|
@@ -118,7 +118,7 @@ module Selenium
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def alert=(keys)
|
121
|
-
execute :send_alert_text, {}, {value: keys.
|
121
|
+
execute :send_alert_text, {}, {value: keys.chars, text: keys}
|
122
122
|
end
|
123
123
|
|
124
124
|
def alert_text
|
@@ -146,9 +146,7 @@ module Selenium
|
|
146
146
|
end
|
147
147
|
|
148
148
|
def page_source
|
149
|
-
|
150
|
-
'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
|
151
|
-
'return source;')
|
149
|
+
execute :get_page_source
|
152
150
|
end
|
153
151
|
|
154
152
|
#
|
@@ -369,11 +367,8 @@ module Selenium
|
|
369
367
|
# actions
|
370
368
|
#
|
371
369
|
|
372
|
-
def action(
|
373
|
-
ActionBuilder.new self,
|
374
|
-
Interactions.pointer(:mouse, name: 'mouse'),
|
375
|
-
Interactions.key('keyboard'),
|
376
|
-
async
|
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
|
377
372
|
end
|
378
373
|
alias_method :actions, :action
|
379
374
|
|
@@ -404,7 +399,7 @@ module Selenium
|
|
404
399
|
def send_keys_to_element(element, keys)
|
405
400
|
# TODO: rework file detectors before Selenium 4.0
|
406
401
|
if @file_detector
|
407
|
-
local_files = keys.first
|
402
|
+
local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
|
408
403
|
if local_files.any?
|
409
404
|
keys = local_files.map { |local_file| upload(local_file) }
|
410
405
|
keys = Array(keys.join("\n"))
|
@@ -412,8 +407,8 @@ module Selenium
|
|
412
407
|
end
|
413
408
|
|
414
409
|
# Keep .split(//) for backward compatibility for now
|
415
|
-
text = keys.join
|
416
|
-
execute :element_send_keys, {id: element}, {value: text.
|
410
|
+
text = keys.join
|
411
|
+
execute :element_send_keys, {id: element}, {value: text.chars, text: text}
|
417
412
|
end
|
418
413
|
|
419
414
|
def upload(local_file)
|
@@ -430,10 +425,19 @@ module Selenium
|
|
430
425
|
end
|
431
426
|
|
432
427
|
def submit_element(element)
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
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"
|
437
441
|
end
|
438
442
|
|
439
443
|
#
|
@@ -604,6 +608,9 @@ module Selenium
|
|
604
608
|
element_id = element_id_from(arg)
|
605
609
|
return Element.new(self, element_id) if element_id
|
606
610
|
|
611
|
+
shadow_root_id = shadow_root_id_from(arg)
|
612
|
+
return ShadowRoot.new self, shadow_root_id if shadow_root_id
|
613
|
+
|
607
614
|
arg.each { |k, v| arg[k] = unwrap_script_result(v) }
|
608
615
|
else
|
609
616
|
arg
|
@@ -636,8 +643,6 @@ module Selenium
|
|
636
643
|
when 'name'
|
637
644
|
how = 'css selector'
|
638
645
|
what = "*[name='#{escape_css(what.to_s)}']"
|
639
|
-
when 'tag name'
|
640
|
-
how = 'css selector'
|
641
646
|
end
|
642
647
|
|
643
648
|
if what.is_a?(Hash)
|