selenium-webdriver 4.1.0 → 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 +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
|