selenium-webdriver 4.1.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES +70 -1
- 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 +19 -19
- data/lib/selenium/webdriver/chrome.rb +0 -14
- data/lib/selenium/webdriver/common/action_builder.rb +108 -21
- data/lib/selenium/webdriver/common/driver.rb +13 -55
- data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_bidi.rb} +12 -5
- 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 +56 -66
- 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/manager.rb +0 -27
- data/lib/selenium/webdriver/common/options.rb +2 -9
- 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.rb +14 -3
- 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 -112
- 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 +3 -1
- 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 -14
- data/lib/selenium/webdriver/ie.rb +0 -14
- data/lib/selenium/webdriver/remote/bridge.rb +21 -19
- data/lib/selenium/webdriver/remote/commands.rb +0 -5
- data/lib/selenium/webdriver/remote/driver.rb +0 -1
- data/lib/selenium/webdriver/remote/http/default.rb +6 -12
- data/lib/selenium/webdriver/remote/response.rb +2 -2
- data/lib/selenium/webdriver/safari.rb +0 -14
- 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/version.rb +1 -1
- data/lib/selenium/webdriver.rb +1 -0
- data/selenium-webdriver.gemspec +7 -4
- metadata +56 -8
- data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
@@ -104,33 +104,6 @@ module Selenium
|
|
104
104
|
@timeouts ||= Timeouts.new(@bridge)
|
105
105
|
end
|
106
106
|
|
107
|
-
def logs
|
108
|
-
WebDriver.logger.deprecate('Manager#logs', 'Chrome::Driver#logs')
|
109
|
-
@logs ||= Logs.new(@bridge)
|
110
|
-
end
|
111
|
-
|
112
|
-
#
|
113
|
-
# @param type [Symbol] Supports two values: :tab and :window.
|
114
|
-
# @return [String] The value of the window handle
|
115
|
-
#
|
116
|
-
def new_window(type = :tab)
|
117
|
-
WebDriver.logger.deprecate('Manager#new_window', 'TargetLocator#new_window', id: :new_window) do
|
118
|
-
'e.g., `driver.switch_to.new_window(:tab)`'
|
119
|
-
end
|
120
|
-
case type
|
121
|
-
when :tab, :window
|
122
|
-
result = @bridge.new_window(type)
|
123
|
-
unless result.key?('handle')
|
124
|
-
raise UnknownError, "the driver did not return a handle. " \
|
125
|
-
"The returned result: #{result.inspect}"
|
126
|
-
end
|
127
|
-
result['handle']
|
128
|
-
else
|
129
|
-
raise ArgumentError, "invalid argument for type. Got: '#{type.inspect}'. " \
|
130
|
-
"Try :tab or :window"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
107
|
def window
|
135
108
|
@window ||= Window.new(@bridge)
|
136
109
|
end
|
@@ -66,17 +66,10 @@ module Selenium
|
|
66
66
|
|
67
67
|
attr_accessor :options
|
68
68
|
|
69
|
-
def initialize(
|
69
|
+
def initialize(**opts)
|
70
70
|
self.class.set_capabilities
|
71
71
|
|
72
|
-
@options =
|
73
|
-
WebDriver.logger.deprecate(":options as keyword for initializing #{self.class}",
|
74
|
-
"custom values directly in #new constructor",
|
75
|
-
id: :options_options)
|
76
|
-
opts.merge(options)
|
77
|
-
else
|
78
|
-
opts
|
79
|
-
end
|
72
|
+
@options = opts
|
80
73
|
@options[:browser_name] = self.class::BROWSER
|
81
74
|
end
|
82
75
|
|
@@ -104,7 +104,7 @@ module Selenium
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def cygwin?
|
107
|
-
RUBY_PLATFORM
|
107
|
+
RUBY_PLATFORM.include?('cygwin')
|
108
108
|
!Regexp.last_match.nil?
|
109
109
|
end
|
110
110
|
|
@@ -177,9 +177,9 @@ module Selenium
|
|
177
177
|
|
178
178
|
def find_in_program_files(*binary_names)
|
179
179
|
paths = [
|
180
|
-
ENV
|
181
|
-
ENV
|
182
|
-
ENV
|
180
|
+
ENV.fetch('PROGRAMFILES', '\\Program Files'),
|
181
|
+
ENV.fetch('ProgramFiles(x86)', '\\Program Files (x86)'),
|
182
|
+
ENV.fetch('ProgramW6432', '\\Program Files')
|
183
183
|
]
|
184
184
|
|
185
185
|
paths.each do |root|
|
@@ -61,9 +61,6 @@ module Selenium
|
|
61
61
|
raise ArgumentError, "cannot find element by #{how.inspect}" unless by
|
62
62
|
|
63
63
|
bridge.find_element_by by, what, ref
|
64
|
-
rescue Selenium::WebDriver::Error::TimeoutError
|
65
|
-
# Implicit Wait times out in Edge
|
66
|
-
raise Selenium::WebDriver::Error::NoSuchElementError
|
67
64
|
end
|
68
65
|
|
69
66
|
#
|
@@ -79,9 +76,6 @@ module Selenium
|
|
79
76
|
raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
|
80
77
|
|
81
78
|
bridge.find_elements_by by, what, ref
|
82
|
-
rescue Selenium::WebDriver::Error::TimeoutError
|
83
|
-
# Implicit Wait times out in Edge
|
84
|
-
[]
|
85
79
|
end
|
86
80
|
|
87
81
|
private
|
@@ -49,7 +49,7 @@ module Selenium
|
|
49
49
|
def start
|
50
50
|
raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?
|
51
51
|
|
52
|
-
Platform.exit_hook
|
52
|
+
Platform.exit_hook { stop } # make sure we don't leave the server running
|
53
53
|
|
54
54
|
socket_lock.locked do
|
55
55
|
find_free_port
|
@@ -60,6 +60,7 @@ module Selenium
|
|
60
60
|
|
61
61
|
def stop
|
62
62
|
return unless @shutdown_supported
|
63
|
+
return if process_exited?
|
63
64
|
|
64
65
|
stop_server
|
65
66
|
@process.poll_for_exit STOP_TIMEOUT
|
@@ -116,8 +117,6 @@ module Selenium
|
|
116
117
|
end
|
117
118
|
|
118
119
|
def stop_server
|
119
|
-
return if process_exited?
|
120
|
-
|
121
120
|
connect_to_server do |http|
|
122
121
|
headers = WebDriver::Remote::Http::Common::DEFAULT_HEADERS.dup
|
123
122
|
http.get('/shutdown', headers)
|
@@ -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
|
@@ -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'
|
@@ -54,7 +64,6 @@ require 'selenium/webdriver/common/driver_extensions/has_web_storage'
|
|
54
64
|
require 'selenium/webdriver/common/driver_extensions/downloads_files'
|
55
65
|
require 'selenium/webdriver/common/driver_extensions/has_location'
|
56
66
|
require 'selenium/webdriver/common/driver_extensions/has_session_id'
|
57
|
-
require 'selenium/webdriver/common/driver_extensions/has_remote_status'
|
58
67
|
require 'selenium/webdriver/common/driver_extensions/has_network_conditions'
|
59
68
|
require 'selenium/webdriver/common/driver_extensions/has_network_connection'
|
60
69
|
require 'selenium/webdriver/common/driver_extensions/has_network_interception'
|
@@ -66,6 +75,7 @@ require 'selenium/webdriver/common/driver_extensions/prints_page'
|
|
66
75
|
require 'selenium/webdriver/common/driver_extensions/uploads_files'
|
67
76
|
require 'selenium/webdriver/common/driver_extensions/full_page_screenshot'
|
68
77
|
require 'selenium/webdriver/common/driver_extensions/has_addons'
|
78
|
+
require 'selenium/webdriver/common/driver_extensions/has_bidi'
|
69
79
|
require 'selenium/webdriver/common/driver_extensions/has_devtools'
|
70
80
|
require 'selenium/webdriver/common/driver_extensions/has_authentication'
|
71
81
|
require 'selenium/webdriver/common/driver_extensions/has_logs'
|
@@ -81,3 +91,4 @@ require 'selenium/webdriver/common/takes_screenshot'
|
|
81
91
|
require 'selenium/webdriver/common/driver'
|
82
92
|
require 'selenium/webdriver/common/element'
|
83
93
|
require 'selenium/webdriver/common/shadow_root'
|
94
|
+
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,66 +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 ||= begin
|
165
|
-
if URI(@url).scheme == 'wss'
|
166
|
-
socket = TCPSocket.new(ws.host, ws.port)
|
167
|
-
socket = OpenSSL::SSL::SSLSocket.new(socket, OpenSSL::SSL::SSLContext.new)
|
168
|
-
socket.sync_close = true
|
169
|
-
socket.connect
|
170
|
-
|
171
|
-
socket
|
172
|
-
else
|
173
|
-
TCPSocket.new(ws.host, ws.port)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
def ws
|
179
|
-
@ws ||= WebSocket::Handshake::Client.new(url: @url)
|
180
|
-
end
|
181
|
-
|
182
|
-
def next_id
|
183
|
-
@id ||= 0
|
184
|
-
@id += 1
|
185
|
-
end
|
186
|
-
|
187
80
|
def error_message(error)
|
188
81
|
[error['code'], error['message'], error['data']].join(': ')
|
189
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',
|
@@ -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))
|
@@ -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'
|
@@ -41,20 +42,6 @@ module Selenium
|
|
41
42
|
# until WebDriver Bidi is available.
|
42
43
|
DEVTOOLS_VERSION = 85
|
43
44
|
|
44
|
-
def self.driver_path=(path)
|
45
|
-
WebDriver.logger.deprecate 'Selenium::WebDriver::Firefox#driver_path=',
|
46
|
-
'Selenium::WebDriver::Firefox::Service#driver_path=',
|
47
|
-
id: :driver_path
|
48
|
-
Selenium::WebDriver::Firefox::Service.driver_path = path
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.driver_path
|
52
|
-
WebDriver.logger.deprecate 'Selenium::WebDriver::Firefox#driver_path',
|
53
|
-
'Selenium::WebDriver::Firefox::Service#driver_path',
|
54
|
-
id: :driver_path
|
55
|
-
Selenium::WebDriver::Firefox::Service.driver_path
|
56
|
-
end
|
57
|
-
|
58
45
|
def self.path=(path)
|
59
46
|
Platform.assert_executable path
|
60
47
|
@path = path
|