selenium-webdriver 4.2.1 → 4.15.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 +252 -0
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/NOTICE +1 -1
- data/README.md +2 -2
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/server.rb +22 -30
- data/lib/selenium/webdriver/atoms/findElements.js +3 -4
- data/lib/selenium/webdriver/atoms/getAttribute.js +0 -0
- data/lib/selenium/webdriver/atoms/isDisplayed.js +0 -0
- data/lib/selenium/webdriver/atoms/mutationListener.js +0 -0
- data/lib/selenium/webdriver/atoms.rb +5 -3
- data/lib/selenium/webdriver/bidi/browsing_context.rb +88 -0
- data/lib/selenium/webdriver/{common/driver_extensions/has_network_connection.rb → bidi/browsing_context_info.rb} +10 -12
- data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +35 -0
- data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +35 -0
- data/lib/selenium/webdriver/{common/driver_extensions/has_location.rb → bidi/log/filter_by.rb} +14 -11
- data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +33 -0
- data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +33 -0
- data/lib/selenium/webdriver/bidi/log_inspector.rb +143 -0
- data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
- data/lib/selenium/webdriver/bidi/session.rb +13 -0
- data/lib/selenium/webdriver/bidi.rb +3 -2
- data/lib/selenium/webdriver/chrome/driver.rb +9 -29
- data/lib/selenium/webdriver/chrome/features.rb +8 -71
- data/lib/selenium/webdriver/chrome/options.rb +3 -223
- data/lib/selenium/webdriver/chrome/profile.rb +3 -83
- data/lib/selenium/webdriver/chrome/service.rb +4 -19
- data/lib/selenium/webdriver/chrome.rb +0 -16
- data/lib/selenium/webdriver/chromium/driver.rb +60 -0
- data/lib/selenium/webdriver/chromium/features.rb +99 -0
- data/lib/selenium/webdriver/chromium/options.rb +243 -0
- data/lib/selenium/webdriver/chromium/profile.rb +113 -0
- data/lib/selenium/webdriver/chromium.rb +29 -0
- data/lib/selenium/webdriver/common/action_builder.rb +11 -58
- data/lib/selenium/webdriver/common/child_process.rb +124 -0
- data/lib/selenium/webdriver/common/driver.rb +29 -79
- data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -1
- data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_bidi.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +0 -1
- data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
- data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -1
- data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -69
- data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +0 -2
- data/lib/selenium/webdriver/common/driver_finder.rb +45 -0
- data/lib/selenium/webdriver/common/element.rb +7 -7
- data/lib/selenium/webdriver/common/error.rb +27 -4
- data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
- data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +19 -36
- data/lib/selenium/webdriver/common/interactions/pointer_move.rb +2 -2
- data/lib/selenium/webdriver/common/interactions/scroll.rb +7 -5
- data/lib/selenium/webdriver/common/local_driver.rb +46 -0
- data/lib/selenium/webdriver/common/logger.rb +90 -25
- data/lib/selenium/webdriver/common/manager.rb +0 -27
- data/lib/selenium/webdriver/common/options.rb +17 -17
- data/lib/selenium/webdriver/common/platform.rb +4 -50
- data/lib/selenium/webdriver/common/port_prober.rb +1 -1
- data/lib/selenium/webdriver/common/profile_helper.rb +1 -1
- data/lib/selenium/webdriver/common/proxy.rb +2 -2
- data/lib/selenium/webdriver/common/selenium_manager.rb +134 -0
- data/lib/selenium/webdriver/common/service.rb +21 -30
- data/lib/selenium/webdriver/common/service_manager.rb +6 -12
- data/lib/selenium/webdriver/common/shadow_root.rb +1 -2
- data/lib/selenium/webdriver/common/socket_lock.rb +3 -3
- data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
- data/lib/selenium/webdriver/common/takes_screenshot.rb +2 -3
- data/lib/selenium/webdriver/common/target_locator.rb +2 -3
- data/lib/selenium/webdriver/common/timeouts.rb +2 -2
- data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
- data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
- data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
- data/lib/selenium/webdriver/common/websocket_connection.rb +22 -7
- data/lib/selenium/webdriver/common/window.rb +6 -6
- data/lib/selenium/webdriver/common/zipper.rb +1 -1
- data/lib/selenium/webdriver/common.rb +8 -3
- data/lib/selenium/webdriver/devtools/console_event.rb +0 -2
- data/lib/selenium/webdriver/devtools/exception_event.rb +0 -2
- data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -2
- data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
- data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -2
- data/lib/selenium/webdriver/devtools/request.rb +0 -2
- data/lib/selenium/webdriver/devtools/response.rb +0 -2
- data/lib/selenium/webdriver/devtools.rb +12 -2
- data/lib/selenium/webdriver/edge/driver.rb +9 -3
- data/lib/selenium/webdriver/edge/features.rb +7 -4
- data/lib/selenium/webdriver/edge/options.rb +17 -5
- data/lib/selenium/webdriver/edge/profile.rb +2 -2
- data/lib/selenium/webdriver/edge/service.rb +8 -7
- data/lib/selenium/webdriver/edge.rb +0 -2
- data/lib/selenium/webdriver/firefox/driver.rb +8 -2
- data/lib/selenium/webdriver/firefox/features.rb +10 -3
- data/lib/selenium/webdriver/firefox/options.rb +4 -15
- data/lib/selenium/webdriver/firefox/profile.rb +7 -7
- data/lib/selenium/webdriver/firefox/service.rb +0 -18
- data/lib/selenium/webdriver/firefox.rb +0 -14
- data/lib/selenium/webdriver/ie/driver.rb +7 -1
- data/lib/selenium/webdriver/{common/driver_extensions/has_remote_status.rb → ie/features.rb} +10 -7
- data/lib/selenium/webdriver/ie/options.rb +4 -3
- data/lib/selenium/webdriver/ie/service.rb +0 -22
- data/lib/selenium/webdriver/ie.rb +4 -17
- data/lib/selenium/webdriver/remote/{commands.rb → bridge/commands.rb} +10 -4
- data/lib/selenium/webdriver/remote/bridge.rb +48 -39
- data/lib/selenium/webdriver/remote/capabilities.rb +3 -53
- data/lib/selenium/webdriver/remote/driver.rb +35 -14
- data/lib/selenium/webdriver/remote/features.rb +75 -0
- data/lib/selenium/webdriver/remote/http/common.rb +3 -3
- data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
- data/lib/selenium/webdriver/remote/http/default.rb +2 -2
- data/lib/selenium/webdriver/remote/response.rb +0 -1
- data/lib/selenium/webdriver/remote/server_error.rb +1 -1
- data/lib/selenium/webdriver/remote.rb +1 -1
- data/lib/selenium/webdriver/safari/driver.rb +7 -1
- data/lib/selenium/webdriver/safari/features.rb +5 -3
- data/lib/selenium/webdriver/safari/options.rb +5 -1
- data/lib/selenium/webdriver/safari/service.rb +10 -4
- data/lib/selenium/webdriver/safari.rb +1 -15
- data/lib/selenium/webdriver/support/color.rb +17 -17
- data/lib/selenium/webdriver/support/guards/guard.rb +6 -5
- data/lib/selenium/webdriver/support/guards/guard_condition.rb +0 -2
- data/lib/selenium/webdriver/support/guards.rb +1 -1
- data/lib/selenium/webdriver/support/relative_locator.rb +0 -1
- data/lib/selenium/webdriver/support/select.rb +3 -1
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +5 -4
- data/selenium-webdriver.gemspec +10 -11
- metadata +51 -90
- data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
- data/lib/selenium/webdriver/support/cdp/domain.rb.erb +0 -63
- data/lib/selenium/webdriver/support/cdp_client_generator.rb +0 -108
|
@@ -22,13 +22,19 @@ require 'websocket'
|
|
|
22
22
|
module Selenium
|
|
23
23
|
module WebDriver
|
|
24
24
|
class WebSocketConnection
|
|
25
|
+
CONNECTION_ERRORS = [
|
|
26
|
+
Errno::ECONNRESET, # connection is aborted (browser process was killed)
|
|
27
|
+
Errno::EPIPE # broken pipe (browser process was killed)
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
25
30
|
RESPONSE_WAIT_TIMEOUT = 30
|
|
26
31
|
RESPONSE_WAIT_INTERVAL = 0.1
|
|
27
32
|
|
|
33
|
+
MAX_LOG_MESSAGE_SIZE = 9999
|
|
34
|
+
|
|
28
35
|
def initialize(url:)
|
|
29
36
|
@callback_threads = ThreadGroup.new
|
|
30
37
|
|
|
31
|
-
@messages = []
|
|
32
38
|
@session_id = nil
|
|
33
39
|
@url = url
|
|
34
40
|
|
|
@@ -49,17 +55,23 @@ module Selenium
|
|
|
49
55
|
def send_cmd(**payload)
|
|
50
56
|
id = next_id
|
|
51
57
|
data = payload.merge(id: id)
|
|
58
|
+
WebDriver.logger.debug "WebSocket -> #{data}"[...MAX_LOG_MESSAGE_SIZE], id: :bidi
|
|
52
59
|
data = JSON.generate(data)
|
|
53
|
-
WebDriver.logger.debug "WebSocket -> #{data}"
|
|
54
|
-
|
|
55
60
|
out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
|
|
56
61
|
socket.write(out_frame.to_s)
|
|
57
62
|
|
|
58
|
-
wait.until {
|
|
63
|
+
wait.until { messages.delete(id) }
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
private
|
|
62
67
|
|
|
68
|
+
# We should be thread-safe to use the hash without synchronization
|
|
69
|
+
# because its keys are WebSocket message identifiers and they should be
|
|
70
|
+
# unique within a devtools session.
|
|
71
|
+
def messages
|
|
72
|
+
@messages ||= {}
|
|
73
|
+
end
|
|
74
|
+
|
|
63
75
|
def process_handshake
|
|
64
76
|
socket.print(ws.to_s)
|
|
65
77
|
ws << socket.readpartial(1024)
|
|
@@ -83,6 +95,8 @@ module Selenium
|
|
|
83
95
|
end
|
|
84
96
|
end
|
|
85
97
|
end
|
|
98
|
+
rescue *CONNECTION_ERRORS
|
|
99
|
+
Thread.stop
|
|
86
100
|
end
|
|
87
101
|
end
|
|
88
102
|
|
|
@@ -97,8 +111,8 @@ module Selenium
|
|
|
97
111
|
return {} if message.empty?
|
|
98
112
|
|
|
99
113
|
message = JSON.parse(message)
|
|
100
|
-
|
|
101
|
-
WebDriver.logger.debug "WebSocket <- #{message}"
|
|
114
|
+
messages[message['id']] = message
|
|
115
|
+
WebDriver.logger.debug "WebSocket <- #{message}"[...MAX_LOG_MESSAGE_SIZE], id: :bidi
|
|
102
116
|
|
|
103
117
|
message
|
|
104
118
|
end
|
|
@@ -115,6 +129,8 @@ module Selenium
|
|
|
115
129
|
Thread.current.report_on_exception = true
|
|
116
130
|
|
|
117
131
|
yield params
|
|
132
|
+
rescue Error::WebDriverError, *CONNECTION_ERRORS
|
|
133
|
+
Thread.stop
|
|
118
134
|
end
|
|
119
135
|
end
|
|
120
136
|
|
|
@@ -143,7 +159,6 @@ module Selenium
|
|
|
143
159
|
@id ||= 0
|
|
144
160
|
@id += 1
|
|
145
161
|
end
|
|
146
|
-
|
|
147
162
|
end # BiDi
|
|
148
163
|
end # WebDriver
|
|
149
164
|
end # Selenium
|
|
@@ -36,8 +36,8 @@ module Selenium
|
|
|
36
36
|
|
|
37
37
|
def size=(dimension)
|
|
38
38
|
unless dimension.respond_to?(:width) && dimension.respond_to?(:height)
|
|
39
|
-
raise ArgumentError, "expected #{dimension.inspect}:#{dimension.class}" \
|
|
40
|
-
'
|
|
39
|
+
raise ArgumentError, "expected #{dimension.inspect}:#{dimension.class} " \
|
|
40
|
+
'to respond to #width and #height'
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
@bridge.resize_window dimension.width, dimension.height
|
|
@@ -61,8 +61,8 @@ module Selenium
|
|
|
61
61
|
|
|
62
62
|
def position=(point)
|
|
63
63
|
unless point.respond_to?(:x) && point.respond_to?(:y)
|
|
64
|
-
raise ArgumentError, "expected #{point.inspect}:#{point.class}" \
|
|
65
|
-
'
|
|
64
|
+
raise ArgumentError, "expected #{point.inspect}:#{point.class} " \
|
|
65
|
+
'to respond to #x and #y'
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
@bridge.reposition_window point.x, point.y
|
|
@@ -86,8 +86,8 @@ module Selenium
|
|
|
86
86
|
|
|
87
87
|
def rect=(rectangle)
|
|
88
88
|
unless %w[x y width height].all? { |val| rectangle.respond_to? val }
|
|
89
|
-
raise ArgumentError, "expected #{rectangle.inspect}:#{rectangle.class}" \
|
|
90
|
-
'
|
|
89
|
+
raise ArgumentError, "expected #{rectangle.inspect}:#{rectangle.class} " \
|
|
90
|
+
'to respond to #x, #y, #width, and #height'
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
@bridge.set_window_rect(x: rectangle.x,
|
|
@@ -18,10 +18,13 @@
|
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
20
|
require 'selenium/webdriver/common/error'
|
|
21
|
+
require 'selenium/webdriver/common/local_driver'
|
|
22
|
+
require 'selenium/webdriver/common/driver_finder'
|
|
21
23
|
require 'selenium/webdriver/common/platform'
|
|
22
24
|
require 'selenium/webdriver/common/proxy'
|
|
23
25
|
require 'selenium/webdriver/common/log_entry'
|
|
24
26
|
require 'selenium/webdriver/common/file_reaper'
|
|
27
|
+
require 'selenium/webdriver/common/selenium_manager'
|
|
25
28
|
require 'selenium/webdriver/common/service'
|
|
26
29
|
require 'selenium/webdriver/common/service_manager'
|
|
27
30
|
require 'selenium/webdriver/common/socket_lock'
|
|
@@ -57,16 +60,16 @@ require 'selenium/webdriver/common/interactions/wheel_input'
|
|
|
57
60
|
require 'selenium/webdriver/common/interactions/scroll_origin'
|
|
58
61
|
require 'selenium/webdriver/common/interactions/wheel_actions'
|
|
59
62
|
require 'selenium/webdriver/common/action_builder'
|
|
63
|
+
require 'selenium/webdriver/common/virtual_authenticator/credential'
|
|
64
|
+
require 'selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options'
|
|
65
|
+
require 'selenium/webdriver/common/virtual_authenticator/virtual_authenticator'
|
|
60
66
|
require 'selenium/webdriver/common/html5/shared_web_storage'
|
|
61
67
|
require 'selenium/webdriver/common/html5/local_storage'
|
|
62
68
|
require 'selenium/webdriver/common/html5/session_storage'
|
|
63
69
|
require 'selenium/webdriver/common/driver_extensions/has_web_storage'
|
|
64
70
|
require 'selenium/webdriver/common/driver_extensions/downloads_files'
|
|
65
|
-
require 'selenium/webdriver/common/driver_extensions/has_location'
|
|
66
71
|
require 'selenium/webdriver/common/driver_extensions/has_session_id'
|
|
67
|
-
require 'selenium/webdriver/common/driver_extensions/has_remote_status'
|
|
68
72
|
require 'selenium/webdriver/common/driver_extensions/has_network_conditions'
|
|
69
|
-
require 'selenium/webdriver/common/driver_extensions/has_network_connection'
|
|
70
73
|
require 'selenium/webdriver/common/driver_extensions/has_network_interception'
|
|
71
74
|
require 'selenium/webdriver/common/driver_extensions/has_apple_permissions'
|
|
72
75
|
require 'selenium/webdriver/common/driver_extensions/has_permissions'
|
|
@@ -78,6 +81,7 @@ require 'selenium/webdriver/common/driver_extensions/full_page_screenshot'
|
|
|
78
81
|
require 'selenium/webdriver/common/driver_extensions/has_addons'
|
|
79
82
|
require 'selenium/webdriver/common/driver_extensions/has_bidi'
|
|
80
83
|
require 'selenium/webdriver/common/driver_extensions/has_devtools'
|
|
84
|
+
require 'selenium/webdriver/common/driver_extensions/has_file_downloads'
|
|
81
85
|
require 'selenium/webdriver/common/driver_extensions/has_authentication'
|
|
82
86
|
require 'selenium/webdriver/common/driver_extensions/has_logs'
|
|
83
87
|
require 'selenium/webdriver/common/driver_extensions/has_log_events'
|
|
@@ -93,3 +97,4 @@ require 'selenium/webdriver/common/driver'
|
|
|
93
97
|
require 'selenium/webdriver/common/element'
|
|
94
98
|
require 'selenium/webdriver/common/shadow_root'
|
|
95
99
|
require 'selenium/webdriver/common/websocket_connection'
|
|
100
|
+
require 'selenium/webdriver/common/child_process'
|
|
@@ -21,7 +21,6 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class DevTools
|
|
23
23
|
class ConsoleEvent
|
|
24
|
-
|
|
25
24
|
attr_accessor :type, :timestamp, :args
|
|
26
25
|
|
|
27
26
|
def initialize(type:, timestamp:, args:)
|
|
@@ -31,7 +30,6 @@ module Selenium
|
|
|
31
30
|
arg.key?('value') ? arg['value'] : arg
|
|
32
31
|
end
|
|
33
32
|
end
|
|
34
|
-
|
|
35
33
|
end # ConsoleEvent
|
|
36
34
|
end # DevTools
|
|
37
35
|
end # WebDriver
|
|
@@ -21,7 +21,6 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class DevTools
|
|
23
23
|
class ExceptionEvent
|
|
24
|
-
|
|
25
24
|
attr_accessor :description, :timestamp, :stacktrace
|
|
26
25
|
|
|
27
26
|
def initialize(description:, timestamp:, stacktrace:)
|
|
@@ -29,7 +28,6 @@ module Selenium
|
|
|
29
28
|
@timestamp = Time.at(timestamp / 1000)
|
|
30
29
|
@stacktrace = stacktrace
|
|
31
30
|
end
|
|
32
|
-
|
|
33
31
|
end # ExceptionEvent
|
|
34
32
|
end # DevTools
|
|
35
33
|
end # WebDriver
|
|
@@ -21,7 +21,6 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class DevTools
|
|
23
23
|
class MutationEvent
|
|
24
|
-
|
|
25
24
|
attr_accessor :element, :attribute_name, :current_value, :old_value
|
|
26
25
|
|
|
27
26
|
def initialize(element:, attribute_name:, current_value:, old_value:)
|
|
@@ -30,7 +29,6 @@ module Selenium
|
|
|
30
29
|
@current_value = current_value
|
|
31
30
|
@old_value = old_value
|
|
32
31
|
end
|
|
33
|
-
|
|
34
32
|
end # MutationEvent
|
|
35
33
|
end # DevTools
|
|
36
34
|
end # WebDriver
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Licensed to the Software Freedom Conservancy (SFC) under one
|
|
4
|
+
# or more contributor license agreements. See the NOTICE file
|
|
5
|
+
# distributed with this work for additional information
|
|
6
|
+
# regarding copyright ownership. The SFC licenses this file
|
|
7
|
+
# to you under the Apache License, Version 2.0 (the
|
|
8
|
+
# "License"); you may not use this file except in compliance
|
|
9
|
+
# with the License. You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing,
|
|
14
|
+
# software distributed under the License is distributed on an
|
|
15
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
# KIND, either express or implied. See the License for the
|
|
17
|
+
# specific language governing permissions and limitations
|
|
18
|
+
# under the License.
|
|
19
|
+
|
|
20
|
+
module Selenium
|
|
21
|
+
module WebDriver
|
|
22
|
+
class DevTools
|
|
23
|
+
#
|
|
24
|
+
# Wraps the network request/response interception, providing
|
|
25
|
+
# thread-safety guarantees and handling special cases such as browser
|
|
26
|
+
# canceling requests midst interception.
|
|
27
|
+
#
|
|
28
|
+
# You should not be using this class directly, use Driver#intercept instead.
|
|
29
|
+
# @api private
|
|
30
|
+
#
|
|
31
|
+
|
|
32
|
+
class NetworkInterceptor
|
|
33
|
+
# CDP fails to get body on certain responses (301) and raises:
|
|
34
|
+
# "Can only get response body on requests captured after headers received."
|
|
35
|
+
CANNOT_GET_BODY_ON_REDIRECT_ERROR_CODE = '-32000'
|
|
36
|
+
|
|
37
|
+
# CDP fails to operate with intercepted requests.
|
|
38
|
+
# Typical reason is browser cancelling intercepted requests/responses.
|
|
39
|
+
INVALID_INTERCEPTION_ID_ERROR_CODE = '-32602'
|
|
40
|
+
|
|
41
|
+
def initialize(devtools)
|
|
42
|
+
@devtools = devtools
|
|
43
|
+
@lock = Mutex.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def intercept(&block)
|
|
47
|
+
devtools.network.on(:loading_failed) { |params| track_cancelled_request(params) }
|
|
48
|
+
devtools.fetch.on(:request_paused) { |params| request_paused(params, &block) }
|
|
49
|
+
|
|
50
|
+
devtools.network.set_cache_disabled(cache_disabled: true)
|
|
51
|
+
devtools.network.enable
|
|
52
|
+
devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
attr_accessor :devtools, :lock
|
|
58
|
+
|
|
59
|
+
# We should be thread-safe to use the hash without synchronization
|
|
60
|
+
# because its keys are interception job identifiers and they should be
|
|
61
|
+
# unique within a devtools session.
|
|
62
|
+
def pending_response_requests
|
|
63
|
+
@pending_response_requests ||= {}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Ensure usage of cancelled_requests is thread-safe via synchronization!
|
|
67
|
+
def cancelled_requests
|
|
68
|
+
@cancelled_requests ||= []
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def track_cancelled_request(data)
|
|
72
|
+
return unless data['canceled']
|
|
73
|
+
|
|
74
|
+
lock.synchronize { cancelled_requests << data['requestId'] }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def request_paused(data, &block)
|
|
78
|
+
id = data['requestId']
|
|
79
|
+
network_id = data['networkId']
|
|
80
|
+
|
|
81
|
+
with_cancellable_request(network_id) do
|
|
82
|
+
if response?(data)
|
|
83
|
+
block = pending_response_requests.delete(id)
|
|
84
|
+
intercept_response(id, data, &block)
|
|
85
|
+
else
|
|
86
|
+
intercept_request(id, data, &block)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The presence of any of these fields indicate we're at the response stage.
|
|
92
|
+
# @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused
|
|
93
|
+
def response?(params)
|
|
94
|
+
params.key?('responseStatusCode') || params.key?('responseErrorReason')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def intercept_request(id, params, &block)
|
|
98
|
+
original = DevTools::Request.from(id, params)
|
|
99
|
+
mutable = DevTools::Request.from(id, params)
|
|
100
|
+
|
|
101
|
+
block.call(mutable) do |&continue| # rubocop:disable Performance/RedundantBlockCall
|
|
102
|
+
pending_response_requests[id] = continue
|
|
103
|
+
|
|
104
|
+
if original == mutable
|
|
105
|
+
continue_request(original.id)
|
|
106
|
+
else
|
|
107
|
+
mutate_request(mutable)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def intercept_response(id, params)
|
|
113
|
+
return continue_response(id) unless block_given?
|
|
114
|
+
|
|
115
|
+
body = fetch_response_body(id)
|
|
116
|
+
original = DevTools::Response.from(id, body, params)
|
|
117
|
+
mutable = DevTools::Response.from(id, body, params)
|
|
118
|
+
yield mutable
|
|
119
|
+
|
|
120
|
+
if original == mutable
|
|
121
|
+
continue_response(id)
|
|
122
|
+
else
|
|
123
|
+
mutate_response(mutable)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def continue_request(id)
|
|
128
|
+
devtools.fetch.continue_request(request_id: id)
|
|
129
|
+
end
|
|
130
|
+
alias continue_response continue_request
|
|
131
|
+
|
|
132
|
+
def mutate_request(request)
|
|
133
|
+
devtools.fetch.continue_request(
|
|
134
|
+
request_id: request.id,
|
|
135
|
+
url: request.url,
|
|
136
|
+
method: request.method,
|
|
137
|
+
post_data: (Base64.strict_encode64(request.post_data) if request.post_data),
|
|
138
|
+
headers: request.headers.map do |k, v|
|
|
139
|
+
{name: k, value: v}
|
|
140
|
+
end
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def mutate_response(response)
|
|
145
|
+
devtools.fetch.fulfill_request(
|
|
146
|
+
request_id: response.id,
|
|
147
|
+
body: (Base64.strict_encode64(response.body) if response.body),
|
|
148
|
+
response_code: response.code,
|
|
149
|
+
response_headers: response.headers.map do |k, v|
|
|
150
|
+
{name: k, value: v}
|
|
151
|
+
end
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def fetch_response_body(id)
|
|
156
|
+
devtools.fetch.get_response_body(request_id: id).dig('result', 'body')
|
|
157
|
+
rescue Error::WebDriverError => e
|
|
158
|
+
raise unless e.message.start_with?(CANNOT_GET_BODY_ON_REDIRECT_ERROR_CODE)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def with_cancellable_request(network_id)
|
|
162
|
+
yield
|
|
163
|
+
rescue Error::WebDriverError => e
|
|
164
|
+
raise if e.message.start_with?(INVALID_INTERCEPTION_ID_ERROR_CODE) && !cancelled?(network_id)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def cancelled?(network_id)
|
|
168
|
+
lock.synchronize { !!cancelled_requests.delete(network_id) }
|
|
169
|
+
end
|
|
170
|
+
end # NetworkInterceptor
|
|
171
|
+
end # DevTools
|
|
172
|
+
end # WebDriver
|
|
173
|
+
end # Selenium
|
|
@@ -21,7 +21,6 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class DevTools
|
|
23
23
|
class PinnedScript
|
|
24
|
-
|
|
25
24
|
attr_accessor :key, :devtools_identifier, :script
|
|
26
25
|
|
|
27
26
|
def initialize(script)
|
|
@@ -52,7 +51,6 @@ module Selenium
|
|
|
52
51
|
def remove
|
|
53
52
|
"__webdriver_#{key} = undefined"
|
|
54
53
|
end
|
|
55
|
-
|
|
56
54
|
end # PinnedScript
|
|
57
55
|
end # DevTools
|
|
58
56
|
end # WebDriver
|
|
@@ -21,7 +21,6 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class DevTools
|
|
23
23
|
class Request
|
|
24
|
-
|
|
25
24
|
attr_accessor :url, :method, :headers, :post_data
|
|
26
25
|
attr_reader :id
|
|
27
26
|
|
|
@@ -60,7 +59,6 @@ module Selenium
|
|
|
60
59
|
def inspect
|
|
61
60
|
%(#<#{self.class.name} @id="#{id}" @method="#{method}" @url="#{url}")
|
|
62
61
|
end
|
|
63
|
-
|
|
64
62
|
end # Request
|
|
65
63
|
end # DevTools
|
|
66
64
|
end # WebDriver
|
|
@@ -21,7 +21,6 @@ module Selenium
|
|
|
21
21
|
module WebDriver
|
|
22
22
|
class DevTools
|
|
23
23
|
class Response
|
|
24
|
-
|
|
25
24
|
attr_accessor :code, :body, :headers
|
|
26
25
|
attr_reader :id
|
|
27
26
|
|
|
@@ -59,7 +58,6 @@ module Selenium
|
|
|
59
58
|
def inspect
|
|
60
59
|
%(#<#{self.class.name} @id="#{id}" @code="#{code}")
|
|
61
60
|
end
|
|
62
|
-
|
|
63
61
|
end # Response
|
|
64
62
|
end # DevTools
|
|
65
63
|
end # WebDriver
|
|
@@ -23,6 +23,7 @@ module Selenium
|
|
|
23
23
|
autoload :ConsoleEvent, 'selenium/webdriver/devtools/console_event'
|
|
24
24
|
autoload :ExceptionEvent, 'selenium/webdriver/devtools/exception_event'
|
|
25
25
|
autoload :MutationEvent, 'selenium/webdriver/devtools/mutation_event'
|
|
26
|
+
autoload :NetworkInterceptor, 'selenium/webdriver/devtools/network_interceptor'
|
|
26
27
|
autoload :PinnedScript, 'selenium/webdriver/devtools/pinned_script'
|
|
27
28
|
autoload :Request, 'selenium/webdriver/devtools/request'
|
|
28
29
|
autoload :Response, 'selenium/webdriver/devtools/response'
|
|
@@ -51,7 +52,17 @@ module Selenium
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def method_missing(method, *_args)
|
|
54
|
-
|
|
55
|
+
namespace = "Selenium::DevTools::V#{Selenium::DevTools.version}"
|
|
56
|
+
methods_to_classes = "#{namespace}::METHODS_TO_CLASSES"
|
|
57
|
+
|
|
58
|
+
desired_class = if Object.const_defined?(methods_to_classes)
|
|
59
|
+
# selenium-devtools 0.113 and newer
|
|
60
|
+
"#{namespace}::#{Object.const_get(methods_to_classes)[method]}"
|
|
61
|
+
else
|
|
62
|
+
# selenium-devtools 0.112 and older
|
|
63
|
+
"#{namespace}::#{method.capitalize}"
|
|
64
|
+
end
|
|
65
|
+
|
|
55
66
|
return unless Object.const_defined?(desired_class)
|
|
56
67
|
|
|
57
68
|
self.class.class_eval do
|
|
@@ -80,7 +91,6 @@ module Selenium
|
|
|
80
91
|
def error_message(error)
|
|
81
92
|
[error['code'], error['message'], error['data']].join(': ')
|
|
82
93
|
end
|
|
83
|
-
|
|
84
94
|
end # DevTools
|
|
85
95
|
end # WebDriver
|
|
86
96
|
end # Selenium
|
|
@@ -17,18 +17,24 @@
|
|
|
17
17
|
# specific language governing permissions and limitations
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
|
-
require 'selenium/webdriver/
|
|
20
|
+
require 'selenium/webdriver/chromium/driver'
|
|
21
21
|
|
|
22
22
|
module Selenium
|
|
23
23
|
module WebDriver
|
|
24
24
|
module Edge
|
|
25
|
-
|
|
26
25
|
#
|
|
27
26
|
# Driver implementation for Microsoft Edge.
|
|
28
27
|
# @api private
|
|
29
28
|
#
|
|
30
29
|
|
|
31
|
-
class Driver <
|
|
30
|
+
class Driver < Chromium::Driver
|
|
31
|
+
include LocalDriver
|
|
32
|
+
|
|
33
|
+
def initialize(options: nil, service: nil, url: nil, **opts)
|
|
34
|
+
caps, url = initialize_local_driver(options, service, url)
|
|
35
|
+
super(caps: caps, url: url, **opts)
|
|
36
|
+
end
|
|
37
|
+
|
|
32
38
|
def browser
|
|
33
39
|
:edge
|
|
34
40
|
end
|
|
@@ -17,14 +17,13 @@
|
|
|
17
17
|
# specific language governing permissions and limitations
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
|
-
require 'selenium/webdriver/
|
|
20
|
+
require 'selenium/webdriver/chromium/features'
|
|
21
21
|
|
|
22
22
|
module Selenium
|
|
23
23
|
module WebDriver
|
|
24
24
|
module Edge
|
|
25
25
|
module Features
|
|
26
|
-
|
|
27
|
-
include WebDriver::Chrome::Features
|
|
26
|
+
include WebDriver::Chromium::Features
|
|
28
27
|
|
|
29
28
|
EDGE_COMMANDS = {
|
|
30
29
|
get_cast_sinks: [:get, 'session/:session_id/ms/cast/get_sinks'],
|
|
@@ -36,8 +35,12 @@ module Selenium
|
|
|
36
35
|
send_command: [:post, 'session/:session_id/ms/cdp/execute']
|
|
37
36
|
}.freeze
|
|
38
37
|
|
|
38
|
+
def command_list
|
|
39
|
+
EDGE_COMMANDS.merge(CHROMIUM_COMMANDS).merge(self.class::COMMANDS)
|
|
40
|
+
end
|
|
41
|
+
|
|
39
42
|
def commands(command)
|
|
40
|
-
|
|
43
|
+
command_list[command]
|
|
41
44
|
end
|
|
42
45
|
end # Bridge
|
|
43
46
|
end # Edge
|
|
@@ -17,23 +17,35 @@
|
|
|
17
17
|
# specific language governing permissions and limitations
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
|
-
require 'selenium/webdriver/
|
|
20
|
+
require 'selenium/webdriver/chromium/options'
|
|
21
21
|
|
|
22
22
|
module Selenium
|
|
23
23
|
module WebDriver
|
|
24
24
|
module Edge
|
|
25
|
-
class Options <
|
|
25
|
+
class Options < Chromium::Options
|
|
26
26
|
KEY = 'ms:edgeOptions'
|
|
27
27
|
BROWSER = 'MicrosoftEdge'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
#
|
|
30
|
+
# Changes the browser name enable webview2
|
|
31
|
+
# see: https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/webdriver
|
|
32
|
+
# Automation of WebView2 apps with Microsoft Edge WebDriver
|
|
33
|
+
#
|
|
34
|
+
# @example Enable webview2
|
|
35
|
+
# options = Selenium::WebDriver::Edge::Options.new
|
|
36
|
+
# options.webview2!
|
|
37
|
+
#
|
|
30
38
|
|
|
31
|
-
def
|
|
32
|
-
|
|
39
|
+
def webview2!
|
|
40
|
+
@options[:browser_name] = 'webview2'
|
|
33
41
|
end
|
|
34
42
|
|
|
35
43
|
private
|
|
36
44
|
|
|
45
|
+
def enable_logging(browser_options)
|
|
46
|
+
browser_options['ms:loggingPrefs'] = @logging_prefs
|
|
47
|
+
end
|
|
48
|
+
|
|
37
49
|
def binary_path
|
|
38
50
|
Edge.path
|
|
39
51
|
end
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# specific language governing permissions and limitations
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
|
-
require 'selenium/webdriver/
|
|
20
|
+
require 'selenium/webdriver/chromium/profile'
|
|
21
21
|
|
|
22
22
|
module Selenium
|
|
23
23
|
module WebDriver
|
|
@@ -26,7 +26,7 @@ module Selenium
|
|
|
26
26
|
# @private
|
|
27
27
|
#
|
|
28
28
|
|
|
29
|
-
class Profile <
|
|
29
|
+
class Profile < Chromium::Profile
|
|
30
30
|
end # Profile
|
|
31
31
|
end # Edge
|
|
32
32
|
end # WebDriver
|
|
@@ -17,19 +17,20 @@
|
|
|
17
17
|
# specific language governing permissions and limitations
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
|
-
require 'selenium/webdriver/chrome/service'
|
|
21
|
-
|
|
22
20
|
module Selenium
|
|
23
21
|
module WebDriver
|
|
24
22
|
module Edge
|
|
25
|
-
class Service <
|
|
23
|
+
class Service < WebDriver::Service
|
|
26
24
|
DEFAULT_PORT = 9515
|
|
27
25
|
EXECUTABLE = 'msedgedriver'
|
|
28
|
-
MISSING_TEXT = <<~ERROR
|
|
29
|
-
Unable to find msedgedriver. Please download the server from
|
|
30
|
-
https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ and place it somewhere on your PATH.
|
|
31
|
-
ERROR
|
|
32
26
|
SHUTDOWN_SUPPORTED = true
|
|
27
|
+
|
|
28
|
+
def log
|
|
29
|
+
return @log unless @log.is_a? String
|
|
30
|
+
|
|
31
|
+
@args << "--log-path=#{@log}"
|
|
32
|
+
@log = nil
|
|
33
|
+
end
|
|
33
34
|
end # Service
|
|
34
35
|
end # Edge
|
|
35
36
|
end # WebDriver
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
module Selenium
|
|
21
21
|
module WebDriver
|
|
22
22
|
module Firefox
|
|
23
|
-
|
|
24
23
|
#
|
|
25
24
|
# Driver implementation for Firefox using GeckoDriver.
|
|
26
25
|
# @api private
|
|
@@ -37,6 +36,13 @@ module Selenium
|
|
|
37
36
|
DriverExtensions::HasWebStorage,
|
|
38
37
|
DriverExtensions::PrintsPage].freeze
|
|
39
38
|
|
|
39
|
+
include LocalDriver
|
|
40
|
+
|
|
41
|
+
def initialize(options: nil, service: nil, url: nil, **opts)
|
|
42
|
+
caps, url = initialize_local_driver(options, service, url)
|
|
43
|
+
super(caps: caps, url: url, **opts)
|
|
44
|
+
end
|
|
45
|
+
|
|
40
46
|
def browser
|
|
41
47
|
:firefox
|
|
42
48
|
end
|
|
@@ -45,7 +51,7 @@ module Selenium
|
|
|
45
51
|
|
|
46
52
|
def devtools_url
|
|
47
53
|
if capabilities['moz:debuggerAddress'].nil?
|
|
48
|
-
raise(Error::WebDriverError,
|
|
54
|
+
raise(Error::WebDriverError, 'DevTools is not supported by this version of Firefox; use v85 or higher')
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
uri = URI("http://#{capabilities['moz:debuggerAddress']}")
|