selenium-webdriver 4.35.0 → 4.47.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 +111 -0
- data/Gemfile +1 -4
- data/LICENSE +1 -1
- data/NOTICE +1 -1
- data/README.md +43 -23
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +5971 -0
- data/bin/selenium-manager.cdx.json +11629 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/server.rb +30 -5
- data/lib/selenium/webdriver/atoms/findElements.js +272 -54
- data/lib/selenium/webdriver/atoms/getAttribute.js +152 -6
- data/lib/selenium/webdriver/atoms/isDisplayed.js +385 -28
- data/lib/selenium/webdriver/bidi/browser.rb +71 -0
- data/lib/selenium/webdriver/bidi/browsing_context.rb +3 -2
- data/lib/selenium/webdriver/bidi/{log/filter_by.rb → error.rb} +20 -16
- data/lib/selenium/webdriver/bidi/log_handler.rb +5 -0
- data/lib/selenium/webdriver/bidi/network/cookies.rb +13 -9
- data/lib/selenium/webdriver/bidi/network/credentials.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/headers.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_auth.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_item.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_request.rb +20 -4
- data/lib/selenium/webdriver/bidi/network/intercepted_response.rb +24 -6
- data/lib/selenium/webdriver/bidi/network/url_pattern.rb +4 -0
- data/lib/selenium/webdriver/bidi/network.rb +18 -9
- data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +495 -0
- data/lib/selenium/webdriver/bidi/protocol/browser.rb +250 -0
- data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +798 -0
- data/lib/selenium/webdriver/bidi/{log/base_log_entry.rb → protocol/domain.rb} +22 -11
- data/lib/selenium/webdriver/bidi/protocol/emulation.rb +448 -0
- data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
- data/lib/selenium/webdriver/bidi/protocol/input.rb +354 -0
- data/lib/selenium/webdriver/bidi/protocol/log.rb +115 -0
- data/lib/selenium/webdriver/bidi/protocol/network.rb +720 -0
- data/lib/selenium/webdriver/bidi/protocol/permissions.rb +75 -0
- data/lib/selenium/webdriver/bidi/protocol/script.rb +1052 -0
- data/lib/selenium/webdriver/bidi/protocol/session.rb +286 -0
- data/lib/selenium/webdriver/bidi/protocol/speculation.rb +58 -0
- data/lib/selenium/webdriver/bidi/protocol/storage.rb +183 -0
- data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +88 -0
- data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +132 -0
- data/lib/selenium/webdriver/bidi/protocol.rb +42 -0
- data/lib/selenium/webdriver/bidi/serialization/record.rb +402 -0
- data/lib/selenium/webdriver/bidi/serialization/union.rb +156 -0
- data/lib/selenium/webdriver/bidi/serialization.rb +90 -0
- data/lib/selenium/webdriver/bidi/session.rb +4 -0
- data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +1420 -0
- data/lib/selenium/webdriver/bidi/support/check_generated.rb +63 -0
- data/lib/selenium/webdriver/bidi/transport.rb +57 -0
- data/lib/selenium/webdriver/bidi.rb +4 -1
- data/lib/selenium/webdriver/chrome/driver.rb +4 -3
- data/lib/selenium/webdriver/chrome/service.rb +13 -0
- data/lib/selenium/webdriver/chromium/driver.rb +0 -1
- data/lib/selenium/webdriver/chromium/options.rb +23 -1
- data/lib/selenium/webdriver/chromium/profile.rb +6 -0
- data/lib/selenium/webdriver/common/child_process.rb +2 -1
- data/lib/selenium/webdriver/common/client_config.rb +97 -0
- data/lib/selenium/webdriver/common/driver.rb +13 -7
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -7
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -7
- data/lib/selenium/webdriver/common/driver_extensions/{has_bidi.rb → has_session_events.rb} +19 -7
- data/lib/selenium/webdriver/common/driver_finder.rb +35 -25
- data/lib/selenium/webdriver/common/error.rb +10 -3
- data/lib/selenium/webdriver/common/local_driver.rb +29 -12
- data/lib/selenium/webdriver/common/logger.rb +28 -0
- data/lib/selenium/webdriver/common/manager.rb +2 -0
- data/lib/selenium/webdriver/common/options.rb +41 -2
- data/lib/selenium/webdriver/common/platform.rb +1 -3
- data/lib/selenium/webdriver/common/proxy.rb +1 -9
- data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
- data/lib/selenium/webdriver/common/service.rb +4 -8
- data/lib/selenium/webdriver/common/service_manager.rb +36 -4
- data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
- data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +1 -1
- data/lib/selenium/webdriver/common/wait.rb +4 -1
- data/lib/selenium/webdriver/common/websocket_connection.rb +73 -37
- data/lib/selenium/webdriver/common.rb +2 -1
- data/lib/selenium/webdriver/devtools/response.rb +3 -3
- data/lib/selenium/webdriver/devtools.rb +1 -1
- data/lib/selenium/webdriver/edge/driver.rb +4 -3
- data/lib/selenium/webdriver/edge/service.rb +14 -0
- data/lib/selenium/webdriver/firefox/driver.rb +4 -6
- data/lib/selenium/webdriver/firefox/options.rb +19 -0
- data/lib/selenium/webdriver/firefox/profile.rb +13 -6
- data/lib/selenium/webdriver/firefox/service.rb +31 -2
- data/lib/selenium/webdriver/ie/driver.rb +4 -3
- data/lib/selenium/webdriver/ie/service.rb +10 -0
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -10
- data/lib/selenium/webdriver/remote/bridge.rb +20 -14
- data/lib/selenium/webdriver/remote/driver.rb +15 -4
- data/lib/selenium/webdriver/remote/features.rb +26 -1
- data/lib/selenium/webdriver/remote/http/common.rb +57 -12
- data/lib/selenium/webdriver/remote/http/curb.rb +6 -8
- data/lib/selenium/webdriver/remote/http/default.rb +56 -39
- data/lib/selenium/webdriver/safari/driver.rb +4 -3
- data/lib/selenium/webdriver/safari/options.rb +23 -4
- data/lib/selenium/webdriver/safari.rb +1 -6
- data/lib/selenium/webdriver/support/block_event_listener.rb +5 -1
- data/lib/selenium/webdriver/support/color.rb +14 -14
- data/lib/selenium/webdriver/support/event_firing_bridge.rb +5 -1
- data/lib/selenium/webdriver/support/guards/guard.rb +30 -14
- data/lib/selenium/webdriver/support/guards.rb +11 -4
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +7 -2
- data/selenium-webdriver.gemspec +1 -1
- metadata +33 -11
- data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +0 -35
- data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +0 -33
- data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +0 -33
- data/lib/selenium/webdriver/bidi/log_inspector.rb +0 -147
|
@@ -40,7 +40,6 @@ module Selenium
|
|
|
40
40
|
LOCK_FILES = %w[.parentlock parent.lock lock].freeze
|
|
41
41
|
|
|
42
42
|
attr_reader :name, :log_file
|
|
43
|
-
attr_writer :secure_ssl, :load_no_focus_lib
|
|
44
43
|
|
|
45
44
|
class << self
|
|
46
45
|
def ini
|
|
@@ -110,19 +109,28 @@ module Selenium
|
|
|
110
109
|
end
|
|
111
110
|
|
|
112
111
|
def port=(port)
|
|
112
|
+
WebDriver.logger.deprecate('Firefox::Profile#port=', 'the Service class', id: :firefox_profile)
|
|
113
113
|
self[WEBDRIVER_PREFS[:port]] = port
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
def secure_ssl=(value)
|
|
117
|
+
WebDriver.logger.deprecate('Firefox::Profile#secure_ssl=', id: :firefox_profile)
|
|
118
|
+
@secure_ssl = value
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def load_no_focus_lib=(value)
|
|
122
|
+
WebDriver.logger.deprecate('Firefox::Profile#load_no_focus_lib=', id: :firefox_profile)
|
|
123
|
+
@load_no_focus_lib = value
|
|
124
|
+
end
|
|
125
|
+
|
|
116
126
|
def log_file=(file)
|
|
117
127
|
@log_file = file
|
|
118
128
|
self[WEBDRIVER_PREFS[:log_file]] = file
|
|
119
129
|
end
|
|
120
130
|
|
|
121
|
-
#
|
|
122
|
-
# Add the extension (directory, .zip or .xpi) at the given path to the profile.
|
|
123
|
-
#
|
|
124
|
-
|
|
125
131
|
def add_extension(path, name = extension_name_for(path))
|
|
132
|
+
WebDriver.logger.deprecate('Firefox::Profile#add_extension', 'Driver#install_addon',
|
|
133
|
+
id: :firefox_profile)
|
|
126
134
|
@extensions[name] = Extension.new(path)
|
|
127
135
|
end
|
|
128
136
|
|
|
@@ -133,7 +141,6 @@ module Selenium
|
|
|
133
141
|
when :manual
|
|
134
142
|
self['network.proxy.type'] = 1
|
|
135
143
|
|
|
136
|
-
set_manual_proxy_preference 'ftp', proxy.ftp
|
|
137
144
|
set_manual_proxy_preference 'http', proxy.http
|
|
138
145
|
set_manual_proxy_preference 'ssl', proxy.ssl
|
|
139
146
|
set_manual_proxy_preference 'socks', proxy.socks
|
|
@@ -26,14 +26,43 @@ module Selenium
|
|
|
26
26
|
SHUTDOWN_SUPPORTED = false
|
|
27
27
|
DRIVER_PATH_ENV_KEY = 'SE_GECKODRIVER'
|
|
28
28
|
|
|
29
|
-
def initialize(
|
|
30
|
-
args
|
|
29
|
+
def initialize(args: nil, **)
|
|
30
|
+
args = Array(args.dup)
|
|
31
31
|
unless args.any? { |arg| arg.include?('--connect-existing') || arg.include?('--websocket-port') }
|
|
32
32
|
args << '--websocket-port'
|
|
33
33
|
args << '0'
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
configure_debug_args(args) if ENV.key?('SE_DEBUG')
|
|
37
|
+
|
|
35
38
|
super
|
|
36
39
|
end
|
|
40
|
+
|
|
41
|
+
def launch
|
|
42
|
+
configure_debug_args(args) if ENV.key?('SE_DEBUG')
|
|
43
|
+
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def configure_debug_args(args)
|
|
50
|
+
# An explicit geckodriver log setting is more specific than Selenium's
|
|
51
|
+
# generic SE_DEBUG fallback, so preserve it and skip adding `-v`.
|
|
52
|
+
if args.any? { |arg| arg.start_with?('--log') }
|
|
53
|
+
warn_explicit_log_preference unless @log_preference_warned
|
|
54
|
+
@log_preference_warned = true
|
|
55
|
+
args.reject! { |arg| arg.start_with?('-v') }
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
args << '-v' unless args.any? { |arg| /\A-v+\z/.match?(arg) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def warn_explicit_log_preference
|
|
63
|
+
WebDriver.logger.warn('SE_DEBUG is set; preserving user-specified geckodriver --log setting instead of ' \
|
|
64
|
+
'adding -v', id: :se_debug)
|
|
65
|
+
end
|
|
37
66
|
end # Service
|
|
38
67
|
end # Firefox
|
|
39
68
|
end # WebDriver
|
|
@@ -31,9 +31,10 @@ module Selenium
|
|
|
31
31
|
|
|
32
32
|
include LocalDriver
|
|
33
33
|
|
|
34
|
-
def initialize(options: nil, service: nil, url: nil, **)
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
|
|
35
|
+
initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
|
|
36
|
+
super(caps: caps, http_client: client, **)
|
|
37
|
+
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def browser
|
|
@@ -25,6 +25,16 @@ module Selenium
|
|
|
25
25
|
EXECUTABLE = 'IEDriverServer'
|
|
26
26
|
SHUTDOWN_SUPPORTED = true
|
|
27
27
|
DRIVER_PATH_ENV_KEY = 'SE_IEDRIVER'
|
|
28
|
+
|
|
29
|
+
def initialize(args: nil, **)
|
|
30
|
+
if ENV.key?('SE_DEBUG')
|
|
31
|
+
args = Array(args.dup)
|
|
32
|
+
warn_driver_log_override if args.reject! { |arg| arg.include?('log-level') || arg.include?('silent') }
|
|
33
|
+
args << '--log-level=DEBUG'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
super
|
|
37
|
+
end
|
|
28
38
|
end # Server
|
|
29
39
|
end # IE
|
|
30
40
|
end # WebDriver
|
|
@@ -17,38 +17,60 @@
|
|
|
17
17
|
# specific language governing permissions and limitations
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
|
+
require 'selenium/webdriver/bidi'
|
|
21
|
+
require 'selenium/webdriver/bidi/protocol'
|
|
22
|
+
|
|
20
23
|
module Selenium
|
|
21
24
|
module WebDriver
|
|
22
25
|
module Remote
|
|
23
26
|
class BiDiBridge < Bridge
|
|
24
|
-
attr_reader :bidi
|
|
27
|
+
attr_reader :bidi, :connection
|
|
28
|
+
|
|
29
|
+
READINESS_STATE = {
|
|
30
|
+
'none' => :none,
|
|
31
|
+
'eager' => :interactive,
|
|
32
|
+
'normal' => :complete
|
|
33
|
+
}.freeze
|
|
25
34
|
|
|
26
35
|
def create_session(capabilities)
|
|
27
36
|
super
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
@bidi = Selenium::WebDriver::BiDi.new(url: validated_socket_url)
|
|
40
|
+
# Reuse the BiDi object's socket as the connection until the bridge owns it directly.
|
|
41
|
+
@connection = @bidi.ws
|
|
42
|
+
rescue StandardError
|
|
43
|
+
quit
|
|
44
|
+
raise
|
|
45
|
+
end
|
|
30
46
|
end
|
|
31
47
|
|
|
32
48
|
def get(url)
|
|
33
|
-
browsing_context.navigate(url)
|
|
49
|
+
browsing_context.navigate(context: window_handle, url: url, wait: readiness_state)
|
|
50
|
+
nil
|
|
34
51
|
end
|
|
35
52
|
|
|
36
53
|
def go_back
|
|
37
|
-
browsing_context.traverse_history(-1)
|
|
54
|
+
browsing_context.traverse_history(context: window_handle, delta: -1)
|
|
55
|
+
nil
|
|
38
56
|
end
|
|
39
57
|
|
|
40
58
|
def go_forward
|
|
41
|
-
browsing_context.traverse_history(1)
|
|
59
|
+
browsing_context.traverse_history(context: window_handle, delta: 1)
|
|
60
|
+
nil
|
|
42
61
|
end
|
|
43
62
|
|
|
44
63
|
def refresh
|
|
45
|
-
browsing_context.reload
|
|
64
|
+
browsing_context.reload(context: window_handle, wait: readiness_state)
|
|
65
|
+
nil
|
|
46
66
|
end
|
|
47
67
|
|
|
48
68
|
def quit
|
|
49
|
-
|
|
69
|
+
bidi&.close
|
|
70
|
+
rescue *QUIT_ERRORS
|
|
71
|
+
nil
|
|
50
72
|
ensure
|
|
51
|
-
|
|
73
|
+
super
|
|
52
74
|
end
|
|
53
75
|
|
|
54
76
|
def close
|
|
@@ -57,8 +79,20 @@ module Selenium
|
|
|
57
79
|
|
|
58
80
|
private
|
|
59
81
|
|
|
82
|
+
def validated_socket_url
|
|
83
|
+
url = @capabilities[:web_socket_url]
|
|
84
|
+
return url if url.is_a?(String) && url.start_with?('ws://', 'wss://')
|
|
85
|
+
|
|
86
|
+
raise Error::WebDriverError,
|
|
87
|
+
"BiDi was enabled, but the remote end did not return a valid webSocketUrl: #{url.inspect}."
|
|
88
|
+
end
|
|
89
|
+
|
|
60
90
|
def browsing_context
|
|
61
|
-
@browsing_context ||=
|
|
91
|
+
@browsing_context ||= BiDi::Protocol::BrowsingContext.new(connection)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def readiness_state
|
|
95
|
+
READINESS_STATE.fetch(capabilities[:page_load_strategy] || 'normal')
|
|
62
96
|
end
|
|
63
97
|
end # BiDiBridge
|
|
64
98
|
end # Remote
|
|
@@ -51,20 +51,13 @@ module Selenium
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
#
|
|
54
|
-
#
|
|
55
|
-
# @param [String, URI] url url for the remote server
|
|
56
|
-
# @param [Object] http_client an HTTP client instance that implements the same protocol as Http::Default
|
|
54
|
+
# @param [Object] http_client a configured HTTP client implementing the same protocol as Http::Default
|
|
57
55
|
# @api private
|
|
58
56
|
#
|
|
59
57
|
|
|
60
|
-
def initialize(
|
|
61
|
-
|
|
62
|
-
uri.path += '/' unless uri.path.end_with?('/')
|
|
63
|
-
|
|
64
|
-
@http = http_client || Http::Default.new
|
|
65
|
-
@http.server_url = uri
|
|
58
|
+
def initialize(http_client:)
|
|
59
|
+
@http = http_client
|
|
66
60
|
@file_detector = nil
|
|
67
|
-
|
|
68
61
|
@locator_converter = self.class.locator_converter
|
|
69
62
|
end
|
|
70
63
|
|
|
@@ -206,12 +199,20 @@ module Selenium
|
|
|
206
199
|
switch_to_frame nil
|
|
207
200
|
end
|
|
208
201
|
|
|
209
|
-
QUIT_ERRORS = [IOError].freeze
|
|
202
|
+
QUIT_ERRORS = [IOError, EOFError, WebSocket::Error].freeze
|
|
210
203
|
|
|
211
204
|
def quit
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
205
|
+
begin
|
|
206
|
+
execute :delete_session
|
|
207
|
+
rescue *QUIT_ERRORS => e
|
|
208
|
+
WebDriver.logger.debug "delete_session failed during quit: #{e.class}: #{e.message}", id: :quit
|
|
209
|
+
ensure
|
|
210
|
+
begin
|
|
211
|
+
http.close
|
|
212
|
+
rescue *QUIT_ERRORS => e
|
|
213
|
+
WebDriver.logger.debug "http.close failed during quit: #{e.class}: #{e.message}", id: :quit
|
|
214
|
+
end
|
|
215
|
+
end
|
|
215
216
|
nil
|
|
216
217
|
end
|
|
217
218
|
|
|
@@ -597,6 +598,11 @@ module Selenium
|
|
|
597
598
|
raise(WebDriver::Error::WebDriverError, msg)
|
|
598
599
|
end
|
|
599
600
|
|
|
601
|
+
def connection
|
|
602
|
+
msg = 'BiDi must be enabled by setting #web_socket_url to true in options class'
|
|
603
|
+
raise(WebDriver::Error::WebDriverError, msg)
|
|
604
|
+
end
|
|
605
|
+
|
|
600
606
|
def command_list
|
|
601
607
|
COMMANDS
|
|
602
608
|
end
|
|
@@ -29,13 +29,18 @@ module Selenium
|
|
|
29
29
|
include DriverExtensions::UploadsFiles
|
|
30
30
|
include DriverExtensions::HasSessionId
|
|
31
31
|
include DriverExtensions::HasFileDownloads
|
|
32
|
+
include DriverExtensions::HasSessionEvents
|
|
32
33
|
|
|
33
|
-
def initialize(capabilities: nil, options: nil, service: nil, url: nil,
|
|
34
|
-
|
|
34
|
+
def initialize(capabilities: nil, options: nil, service: nil, url: nil, http_client: nil, client_config: nil,
|
|
35
|
+
**)
|
|
36
|
+
assert_arguments(service, url, http_client, client_config)
|
|
35
37
|
|
|
36
|
-
url ||= "http://#{Platform.localhost}:4444/wd/hub"
|
|
37
38
|
caps = process_options(options, capabilities)
|
|
38
|
-
|
|
39
|
+
http_client ||= Remote::Http::Default.new(client_config: client_config)
|
|
40
|
+
server_url = url || client_config&.server_url || "http://#{Platform.localhost}:4444/wd/hub"
|
|
41
|
+
http_client.server_url = server_url
|
|
42
|
+
caps['se:remoteUrl'] = server_url.to_s.chomp('/')
|
|
43
|
+
super(caps: caps, http_client: http_client, **)
|
|
39
44
|
@bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
|
|
40
45
|
command_list = @bridge.command_list
|
|
41
46
|
@bridge.extend(WebDriver::Remote::Features)
|
|
@@ -44,6 +49,12 @@ module Selenium
|
|
|
44
49
|
|
|
45
50
|
private
|
|
46
51
|
|
|
52
|
+
def assert_arguments(service, url, http_client, client_config)
|
|
53
|
+
raise ArgumentError, "Can not set :service object on #{self.class}" if service
|
|
54
|
+
raise ArgumentError, 'Cannot use both :http_client and :client_config' if http_client && client_config
|
|
55
|
+
raise ArgumentError, 'Cannot use both :url and a ClientConfig#server_url' if url && client_config&.server_url
|
|
56
|
+
end
|
|
57
|
+
|
|
47
58
|
def devtools_url
|
|
48
59
|
capabilities['se:cdp']
|
|
49
60
|
end
|
|
@@ -25,7 +25,8 @@ module Selenium
|
|
|
25
25
|
upload_file: [:post, 'session/:session_id/se/file'],
|
|
26
26
|
get_downloadable_files: [:get, 'session/:session_id/se/files'],
|
|
27
27
|
download_file: [:post, 'session/:session_id/se/files'],
|
|
28
|
-
delete_downloadable_files: [:delete, 'session/:session_id/se/files']
|
|
28
|
+
delete_downloadable_files: [:delete, 'session/:session_id/se/files'],
|
|
29
|
+
fire_session_event: [:post, 'session/:session_id/se/event']
|
|
29
30
|
}.freeze
|
|
30
31
|
|
|
31
32
|
def add_commands(commands)
|
|
@@ -69,6 +70,30 @@ module Selenium
|
|
|
69
70
|
def delete_downloadable_files
|
|
70
71
|
execute :delete_downloadable_files
|
|
71
72
|
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Fires a custom session event to the remote server event bus.
|
|
76
|
+
# This allows test code to trigger server-side utilities that subscribe to
|
|
77
|
+
# the event bus.
|
|
78
|
+
#
|
|
79
|
+
# @param [String] event_type The type of event (e.g., "test:failed", "log:collect")
|
|
80
|
+
# @param [Hash] payload Optional data to include with the event
|
|
81
|
+
# @return [Hash] Response data including success status, event type, and timestamp
|
|
82
|
+
#
|
|
83
|
+
# @example Fire a simple event
|
|
84
|
+
# driver.fire_session_event("test:started")
|
|
85
|
+
#
|
|
86
|
+
# @example Fire an event with payload
|
|
87
|
+
# driver.fire_session_event("test:failed", {
|
|
88
|
+
# testName: "LoginTest",
|
|
89
|
+
# error: "Element not found"
|
|
90
|
+
# })
|
|
91
|
+
#
|
|
92
|
+
def fire_session_event(event_type, payload = nil)
|
|
93
|
+
params = {eventType: event_type}
|
|
94
|
+
params[:payload] = payload if payload
|
|
95
|
+
execute :fire_session_event, {}, params
|
|
96
|
+
end
|
|
72
97
|
end
|
|
73
98
|
end # Remote
|
|
74
99
|
end # WebDriver
|
|
@@ -28,17 +28,35 @@ module Selenium
|
|
|
28
28
|
'Accept' => CONTENT_TYPE,
|
|
29
29
|
'Content-Type' => "#{CONTENT_TYPE}; charset=UTF-8"
|
|
30
30
|
}.freeze
|
|
31
|
+
BINARY_ENCODINGS = [Encoding::BINARY, Encoding::ASCII_8BIT].freeze
|
|
31
32
|
|
|
32
33
|
class << self
|
|
33
|
-
attr_accessor :extra_headers
|
|
34
|
-
attr_writer :user_agent
|
|
35
|
-
|
|
36
34
|
def user_agent
|
|
37
|
-
|
|
35
|
+
ClientConfig.default_user_agent
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def user_agent=(value)
|
|
39
|
+
ClientConfig.default_user_agent = value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def extra_headers
|
|
43
|
+
ClientConfig.default_extra_headers
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def extra_headers=(value)
|
|
47
|
+
ClientConfig.default_extra_headers = value
|
|
38
48
|
end
|
|
39
49
|
end
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
attr_reader :client_config
|
|
52
|
+
|
|
53
|
+
def initialize(client_config: nil)
|
|
54
|
+
@client_config = client_config || ClientConfig.new
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def server_url=(url)
|
|
58
|
+
client_config.server_url = url
|
|
59
|
+
end
|
|
42
60
|
|
|
43
61
|
def quit_errors
|
|
44
62
|
[IOError]
|
|
@@ -55,6 +73,7 @@ module Selenium
|
|
|
55
73
|
headers['Cache-Control'] = 'no-cache' if verb == :get
|
|
56
74
|
|
|
57
75
|
if command_hash
|
|
76
|
+
command_hash = ensure_utf8_encoding(command_hash)
|
|
58
77
|
payload = JSON.generate(command_hash)
|
|
59
78
|
headers['Content-Length'] = payload.bytesize.to_s if %i[post put].include?(verb)
|
|
60
79
|
|
|
@@ -74,23 +93,49 @@ module Selenium
|
|
|
74
93
|
def common_headers
|
|
75
94
|
@common_headers ||= begin
|
|
76
95
|
headers = DEFAULT_HEADERS.dup
|
|
77
|
-
headers['User-Agent'] =
|
|
78
|
-
headers
|
|
79
|
-
|
|
80
|
-
headers
|
|
96
|
+
headers['User-Agent'] = client_config.user_agent
|
|
97
|
+
headers.merge(client_config.extra_headers || {})
|
|
81
98
|
end
|
|
82
99
|
end
|
|
83
100
|
|
|
84
101
|
def server_url
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
raise Error::WebDriverError, 'server_url not set'
|
|
102
|
+
client_config.server_url || raise(Error::WebDriverError, 'server_url not set')
|
|
88
103
|
end
|
|
89
104
|
|
|
90
105
|
def request(*)
|
|
91
106
|
raise NotImplementedError, 'subclass responsibility'
|
|
92
107
|
end
|
|
93
108
|
|
|
109
|
+
def ensure_utf8_encoding(obj)
|
|
110
|
+
case obj
|
|
111
|
+
when String
|
|
112
|
+
encode_string_to_utf8(obj)
|
|
113
|
+
when Array
|
|
114
|
+
obj.map { |item| ensure_utf8_encoding(item) }
|
|
115
|
+
when Hash
|
|
116
|
+
obj.each_with_object({}) do |(key, value), result|
|
|
117
|
+
result[ensure_utf8_encoding(key)] = ensure_utf8_encoding(value)
|
|
118
|
+
end
|
|
119
|
+
else
|
|
120
|
+
obj
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def encode_string_to_utf8(str)
|
|
125
|
+
return str if str.encoding == Encoding::UTF_8 && str.valid_encoding?
|
|
126
|
+
|
|
127
|
+
if BINARY_ENCODINGS.include?(str.encoding)
|
|
128
|
+
result = str.dup.force_encoding(Encoding::UTF_8)
|
|
129
|
+
return result if result.valid_encoding?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
str.encode(Encoding::UTF_8)
|
|
133
|
+
rescue EncodingError => e
|
|
134
|
+
raise Error::WebDriverError,
|
|
135
|
+
"Unable to encode string to UTF-8: #{e.message}. " \
|
|
136
|
+
"String encoding: #{str.encoding}, content: #{str.inspect}"
|
|
137
|
+
end
|
|
138
|
+
|
|
94
139
|
def create_response(code, body, content_type)
|
|
95
140
|
code = code.to_i
|
|
96
141
|
body = body.to_s.strip
|
|
@@ -26,20 +26,18 @@ module Selenium
|
|
|
26
26
|
#
|
|
27
27
|
# An alternative to the default Net::HTTP client.
|
|
28
28
|
#
|
|
29
|
-
#
|
|
30
|
-
# installed.
|
|
31
|
-
#
|
|
32
|
-
# @example Using Curb
|
|
33
|
-
# require 'selenium/webdriver/remote/http/curb'
|
|
34
|
-
# include Selenium
|
|
35
|
-
#
|
|
36
|
-
# driver = WebDriver.for :firefox, :http_client => WebDriver::Remote::Http::Curb.new
|
|
29
|
+
# @deprecated Subclass {Selenium::WebDriver::Remote::Http::Common} to implement a custom HTTP client.
|
|
37
30
|
#
|
|
38
31
|
|
|
39
32
|
class Curb < Common
|
|
40
33
|
attr_accessor :timeout
|
|
41
34
|
|
|
42
35
|
def initialize(timeout: nil)
|
|
36
|
+
WebDriver.logger.deprecate(
|
|
37
|
+
'Selenium::WebDriver::Remote::Http::Curb',
|
|
38
|
+
'a custom subclass of Selenium::WebDriver::Remote::Http::Common',
|
|
39
|
+
id: :curb
|
|
40
|
+
)
|
|
43
41
|
@timeout = timeout
|
|
44
42
|
super()
|
|
45
43
|
end
|
|
@@ -24,19 +24,41 @@ module Selenium
|
|
|
24
24
|
module Http
|
|
25
25
|
# @api private
|
|
26
26
|
class Default < Common
|
|
27
|
-
attr_writer :proxy
|
|
28
|
-
|
|
29
|
-
attr_accessor :open_timeout, :read_timeout
|
|
30
|
-
|
|
31
27
|
# Initializes object.
|
|
32
28
|
# Warning: Setting {#open_timeout} to non-nil values will cause a separate thread to spawn.
|
|
33
29
|
# Debuggers that freeze the process will not be able to evaluate any operations if that happens.
|
|
30
|
+
# @param [ClientConfig] client_config - Configuration used to build the HTTP client.
|
|
34
31
|
# @param [Numeric] open_timeout - Open timeout to apply to HTTP client.
|
|
35
32
|
# @param [Numeric] read_timeout - Read timeout (seconds) to apply to HTTP client.
|
|
36
|
-
def initialize(open_timeout: nil, read_timeout: nil)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
def initialize(client_config: nil, open_timeout: nil, read_timeout: nil)
|
|
34
|
+
if client_config && (open_timeout || read_timeout)
|
|
35
|
+
raise ArgumentError, 'Cannot use both :client_config and :open_timeout/:read_timeout'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
client_config ||= ClientConfig.new
|
|
39
|
+
client_config.open_timeout = open_timeout if open_timeout
|
|
40
|
+
client_config.read_timeout = read_timeout if read_timeout
|
|
41
|
+
super(client_config: client_config)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def open_timeout
|
|
45
|
+
client_config.open_timeout
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def read_timeout
|
|
49
|
+
client_config.read_timeout
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def open_timeout=(value)
|
|
53
|
+
client_config.open_timeout = value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def read_timeout=(value)
|
|
57
|
+
client_config.read_timeout = value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def proxy=(value)
|
|
61
|
+
client_config.proxy = value
|
|
40
62
|
end
|
|
41
63
|
|
|
42
64
|
def close
|
|
@@ -94,16 +116,20 @@ module Selenium
|
|
|
94
116
|
end
|
|
95
117
|
|
|
96
118
|
if response.is_a? Net::HTTPRedirection
|
|
97
|
-
|
|
98
|
-
raise Error::WebDriverError, 'too many redirects' if redirects >= MAX_REDIRECTS
|
|
99
|
-
|
|
100
|
-
request(:get, URI.parse(response['Location']), DEFAULT_HEADERS.dup, nil, redirects + 1)
|
|
119
|
+
follow_redirect(response, redirects)
|
|
101
120
|
else
|
|
102
121
|
WebDriver.logger.debug(" <<< #{response.instance_variable_get(:@header).inspect}", id: :header)
|
|
103
122
|
create_response response.code, response.body, response.content_type
|
|
104
123
|
end
|
|
105
124
|
end
|
|
106
125
|
|
|
126
|
+
def follow_redirect(response, redirects)
|
|
127
|
+
WebDriver.logger.debug("Redirect to #{response['Location']}; times: #{redirects}", id: :redirect)
|
|
128
|
+
raise Error::WebDriverError, 'too many redirects' if redirects >= client_config.max_redirects
|
|
129
|
+
|
|
130
|
+
request(:get, URI.parse(response['Location']), DEFAULT_HEADERS.dup, nil, redirects + 1)
|
|
131
|
+
end
|
|
132
|
+
|
|
107
133
|
def new_request_for(verb, url, headers, payload)
|
|
108
134
|
req = Net::HTTP.const_get(verb.to_s.capitalize).new(url.path, headers)
|
|
109
135
|
|
|
@@ -120,52 +146,43 @@ module Selenium
|
|
|
120
146
|
|
|
121
147
|
def new_http_client
|
|
122
148
|
if use_proxy?
|
|
123
|
-
url =
|
|
149
|
+
url = proxy.http
|
|
124
150
|
unless proxy.respond_to?(:http) && url
|
|
125
151
|
raise Error::WebDriverError,
|
|
126
|
-
"expected HTTP proxy, got #{
|
|
152
|
+
"expected HTTP proxy, got #{proxy.inspect}"
|
|
127
153
|
end
|
|
128
154
|
|
|
129
|
-
|
|
155
|
+
proxy_uri = URI.parse(url)
|
|
130
156
|
|
|
131
|
-
Net::HTTP.new(server_url.host, server_url.port,
|
|
157
|
+
Net::HTTP.new(server_url.host, server_url.port,
|
|
158
|
+
proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
|
|
132
159
|
else
|
|
133
160
|
Net::HTTP.new server_url.host, server_url.port
|
|
134
161
|
end
|
|
135
162
|
end
|
|
136
163
|
|
|
137
164
|
def proxy
|
|
138
|
-
|
|
139
|
-
proxy = ENV.fetch('http_proxy', nil) || ENV.fetch('HTTP_PROXY', nil)
|
|
140
|
-
no_proxy = ENV.fetch('no_proxy', nil) || ENV.fetch('NO_PROXY', nil)
|
|
141
|
-
|
|
142
|
-
if proxy
|
|
143
|
-
proxy = "http://#{proxy}" unless proxy.start_with?('http://')
|
|
144
|
-
Proxy.new(http: proxy, no_proxy: no_proxy)
|
|
145
|
-
end
|
|
146
|
-
end
|
|
165
|
+
client_config.proxy
|
|
147
166
|
end
|
|
148
167
|
|
|
149
168
|
def use_proxy?
|
|
150
169
|
return false if proxy.nil?
|
|
170
|
+
return true unless proxy.no_proxy
|
|
151
171
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
host == '*' ||
|
|
155
|
-
host == server_url.host || (
|
|
156
|
-
begin
|
|
157
|
-
IPAddr.new(host).include?(server_url.host)
|
|
158
|
-
rescue ArgumentError
|
|
159
|
-
false
|
|
160
|
-
end
|
|
161
|
-
)
|
|
162
|
-
end
|
|
172
|
+
!proxy_ignored?
|
|
173
|
+
end
|
|
163
174
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
175
|
+
def proxy_ignored?
|
|
176
|
+
proxy.no_proxy.split(',').map(&:strip).reject(&:empty?).any? do |host|
|
|
177
|
+
host == '*' || host == server_url.host || ip_match?(host)
|
|
167
178
|
end
|
|
168
179
|
end
|
|
180
|
+
|
|
181
|
+
def ip_match?(host)
|
|
182
|
+
IPAddr.new(host).include?(server_url.host)
|
|
183
|
+
rescue ArgumentError
|
|
184
|
+
false
|
|
185
|
+
end
|
|
169
186
|
end # Default
|
|
170
187
|
end # Http
|
|
171
188
|
end # Remote
|
|
@@ -31,9 +31,10 @@ module Selenium
|
|
|
31
31
|
|
|
32
32
|
include LocalDriver
|
|
33
33
|
|
|
34
|
-
def initialize(options: nil, service: nil, url: nil, **)
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
|
|
35
|
+
initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
|
|
36
|
+
super(caps: caps, http_client: client, **)
|
|
37
|
+
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def browser
|