selenium-webdriver 4.38.0 → 4.41.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 +28 -1
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/NOTICE +1 -1
- 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 +29 -4
- data/lib/selenium/webdriver/atoms/findElements.js +62 -49
- data/lib/selenium/webdriver/atoms/getAttribute.js +17 -6
- data/lib/selenium/webdriver/atoms/isDisplayed.js +34 -23
- data/lib/selenium/webdriver/bidi/browser.rb +7 -0
- data/lib/selenium/webdriver/bidi/browsing_context.rb +2 -1
- data/lib/selenium/webdriver/bidi/log_handler.rb +5 -0
- data/lib/selenium/webdriver/bidi/network/cookies.rb +11 -4
- 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 +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_response.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/url_pattern.rb +4 -0
- data/lib/selenium/webdriver/bidi/network.rb +6 -0
- data/lib/selenium/webdriver/bidi/session.rb +4 -0
- data/lib/selenium/webdriver/chrome/driver.rb +3 -2
- data/lib/selenium/webdriver/chrome/service.rb +10 -0
- data/lib/selenium/webdriver/common/child_process.rb +2 -1
- data/lib/selenium/webdriver/common/driver.rb +0 -5
- data/lib/selenium/webdriver/common/driver_extensions/has_session_events.rb +48 -0
- data/lib/selenium/webdriver/common/error.rb +7 -0
- data/lib/selenium/webdriver/common/local_driver.rb +11 -1
- data/lib/selenium/webdriver/common/logger.rb +28 -0
- data/lib/selenium/webdriver/common/options.rb +10 -0
- data/lib/selenium/webdriver/common/service.rb +6 -0
- 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/wait.rb +4 -1
- data/lib/selenium/webdriver/common/websocket_connection.rb +73 -37
- data/lib/selenium/webdriver/common.rb +1 -0
- data/lib/selenium/webdriver/devtools.rb +1 -1
- data/lib/selenium/webdriver/edge/driver.rb +3 -2
- data/lib/selenium/webdriver/edge/service.rb +11 -0
- data/lib/selenium/webdriver/firefox/driver.rb +3 -2
- data/lib/selenium/webdriver/firefox/service.rb +21 -2
- data/lib/selenium/webdriver/ie/driver.rb +3 -2
- data/lib/selenium/webdriver/ie/service.rb +10 -0
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +4 -2
- data/lib/selenium/webdriver/remote/bridge.rb +12 -4
- data/lib/selenium/webdriver/remote/driver.rb +1 -0
- data/lib/selenium/webdriver/remote/features.rb +26 -1
- data/lib/selenium/webdriver/remote/http/common.rb +32 -0
- data/lib/selenium/webdriver/safari/driver.rb +3 -2
- 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/version.rb +1 -1
- data/lib/selenium/webdriver.rb +7 -2
- metadata +3 -2
|
@@ -37,7 +37,8 @@ module Selenium
|
|
|
37
37
|
@timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT)
|
|
38
38
|
@interval = opts.fetch(:interval, DEFAULT_INTERVAL)
|
|
39
39
|
@message = opts[:message]
|
|
40
|
-
@
|
|
40
|
+
@message_provider = opts[:message_provider]
|
|
41
|
+
@ignored = Array(opts[:ignore] || Error::NoSuchElementError)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
#
|
|
@@ -64,6 +65,8 @@ module Selenium
|
|
|
64
65
|
|
|
65
66
|
msg = if @message
|
|
66
67
|
@message.dup
|
|
68
|
+
elsif @message_provider
|
|
69
|
+
@message_provider.call
|
|
67
70
|
else
|
|
68
71
|
"timed out after #{@timeout} seconds"
|
|
69
72
|
end
|
|
@@ -24,7 +24,10 @@ module Selenium
|
|
|
24
24
|
class WebSocketConnection
|
|
25
25
|
CONNECTION_ERRORS = [
|
|
26
26
|
Errno::ECONNRESET, # connection is aborted (browser process was killed)
|
|
27
|
-
Errno::EPIPE # broken pipe (browser process was killed)
|
|
27
|
+
Errno::EPIPE, # broken pipe (browser process was killed)
|
|
28
|
+
Errno::EBADF, # file descriptor already closed (double-close or GC)
|
|
29
|
+
IOError, # Ruby socket read/write after close
|
|
30
|
+
EOFError # socket reached EOF after remote closed cleanly
|
|
28
31
|
].freeze
|
|
29
32
|
|
|
30
33
|
RESPONSE_WAIT_TIMEOUT = 30
|
|
@@ -35,6 +38,11 @@ module Selenium
|
|
|
35
38
|
def initialize(url:)
|
|
36
39
|
@callback_threads = ThreadGroup.new
|
|
37
40
|
|
|
41
|
+
@callbacks_mtx = Mutex.new
|
|
42
|
+
@messages_mtx = Mutex.new
|
|
43
|
+
@closing_mtx = Mutex.new
|
|
44
|
+
|
|
45
|
+
@closing = false
|
|
38
46
|
@session_id = nil
|
|
39
47
|
@url = url
|
|
40
48
|
|
|
@@ -43,9 +51,26 @@ module Selenium
|
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
def close
|
|
46
|
-
@
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
@closing_mtx.synchronize do
|
|
55
|
+
return if @closing
|
|
56
|
+
|
|
57
|
+
@closing = true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
socket.close
|
|
62
|
+
rescue *CONNECTION_ERRORS => e
|
|
63
|
+
WebDriver.logger.debug "WebSocket listener closed: #{e.class}: #{e.message}", id: :ws
|
|
64
|
+
# already closed
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Let threads unwind instead of calling exit
|
|
68
|
+
@socket_thread&.join(0.5)
|
|
69
|
+
@callback_threads.list.each do |thread|
|
|
70
|
+
thread.join(0.5)
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
WebDriver.logger.debug "Failed to join thread during close: #{e.class}: #{e.message}", id: :ws
|
|
73
|
+
end
|
|
49
74
|
end
|
|
50
75
|
|
|
51
76
|
def callbacks
|
|
@@ -53,62 +78,73 @@ module Selenium
|
|
|
53
78
|
end
|
|
54
79
|
|
|
55
80
|
def add_callback(event, &block)
|
|
56
|
-
|
|
57
|
-
|
|
81
|
+
@callbacks_mtx.synchronize do
|
|
82
|
+
callbacks[event] << block
|
|
83
|
+
block.object_id
|
|
84
|
+
end
|
|
58
85
|
end
|
|
59
86
|
|
|
60
87
|
def remove_callback(event, id)
|
|
61
|
-
|
|
88
|
+
@callbacks_mtx.synchronize do
|
|
89
|
+
return if @closing
|
|
90
|
+
|
|
91
|
+
callbacks_for_event = callbacks[event]
|
|
92
|
+
return if callbacks_for_event.reject! { |cb| cb.object_id == id }
|
|
62
93
|
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
ids = callbacks_for_event.map(&:object_id)
|
|
95
|
+
raise Error::WebDriverError, "Callback with ID #{id} does not exist for event #{event}: #{ids}"
|
|
96
|
+
end
|
|
65
97
|
end
|
|
66
98
|
|
|
67
99
|
def send_cmd(**payload)
|
|
68
100
|
id = next_id
|
|
69
101
|
data = payload.merge(id: id)
|
|
70
|
-
WebDriver.logger.debug "WebSocket -> #{data}"[...MAX_LOG_MESSAGE_SIZE], id: :
|
|
102
|
+
WebDriver.logger.debug "WebSocket -> #{data}"[...MAX_LOG_MESSAGE_SIZE], id: :ws
|
|
71
103
|
data = JSON.generate(data)
|
|
72
104
|
out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
|
|
73
|
-
socket.write(out_frame.to_s)
|
|
74
105
|
|
|
75
|
-
|
|
106
|
+
begin
|
|
107
|
+
socket.write(out_frame.to_s)
|
|
108
|
+
rescue *CONNECTION_ERRORS => e
|
|
109
|
+
raise e, "WebSocket is closed (#{e.class}: #{e.message})"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
wait.until { @messages_mtx.synchronize { messages.delete(id) } }
|
|
76
113
|
end
|
|
77
114
|
|
|
78
115
|
private
|
|
79
116
|
|
|
80
|
-
# We should be thread-safe to use the hash without synchronization
|
|
81
|
-
# because its keys are WebSocket message identifiers and they should be
|
|
82
|
-
# unique within a devtools session.
|
|
83
117
|
def messages
|
|
84
118
|
@messages ||= {}
|
|
85
119
|
end
|
|
86
120
|
|
|
87
121
|
def process_handshake
|
|
88
122
|
socket.print(ws.to_s)
|
|
89
|
-
ws << socket.readpartial(1024)
|
|
123
|
+
ws << socket.readpartial(1024) until ws.finished?
|
|
90
124
|
end
|
|
91
125
|
|
|
92
126
|
def attach_socket_listener
|
|
93
127
|
Thread.new do
|
|
94
|
-
Thread.current.abort_on_exception = true
|
|
95
128
|
Thread.current.report_on_exception = false
|
|
96
129
|
|
|
97
|
-
|
|
130
|
+
loop do
|
|
131
|
+
break if @closing
|
|
132
|
+
|
|
98
133
|
incoming_frame << socket.readpartial(1024)
|
|
99
134
|
|
|
100
135
|
while (frame = incoming_frame.next)
|
|
136
|
+
break if @closing
|
|
137
|
+
|
|
101
138
|
message = process_frame(frame)
|
|
102
139
|
next unless message['method']
|
|
103
140
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@callback_threads.add(callback_thread(params, &callback))
|
|
141
|
+
@messages_mtx.synchronize { callbacks[message['method']].dup }.each do |callback|
|
|
142
|
+
@callback_threads.add(callback_thread(message['params'], &callback))
|
|
107
143
|
end
|
|
108
144
|
end
|
|
109
145
|
end
|
|
110
|
-
rescue *CONNECTION_ERRORS
|
|
111
|
-
|
|
146
|
+
rescue *CONNECTION_ERRORS, WebSocket::Error => e
|
|
147
|
+
WebDriver.logger.debug "WebSocket listener closed: #{e.class}: #{e.message}", id: :ws
|
|
112
148
|
end
|
|
113
149
|
end
|
|
114
150
|
|
|
@@ -122,27 +158,27 @@ module Selenium
|
|
|
122
158
|
# Firefox will periodically fail on unparsable empty frame
|
|
123
159
|
return {} if message.empty?
|
|
124
160
|
|
|
125
|
-
|
|
126
|
-
messages[
|
|
127
|
-
WebDriver.logger.debug "WebSocket <- #{message}"[...MAX_LOG_MESSAGE_SIZE], id: :bidi
|
|
161
|
+
msg = JSON.parse(message)
|
|
162
|
+
@messages_mtx.synchronize { messages[msg['id']] = msg if msg.key?('id') }
|
|
128
163
|
|
|
129
|
-
|
|
164
|
+
WebDriver.logger.debug "WebSocket <- #{msg}"[...MAX_LOG_MESSAGE_SIZE], id: :ws
|
|
165
|
+
msg
|
|
130
166
|
end
|
|
131
167
|
|
|
132
168
|
def callback_thread(params)
|
|
133
169
|
Thread.new do
|
|
134
|
-
Thread.current.abort_on_exception =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
# For example, if network interception event raises error,
|
|
138
|
-
# the browser will keep waiting for the request to be proceeded
|
|
139
|
-
# before returning back to the original thread. In this case,
|
|
140
|
-
# we should at least print the error.
|
|
141
|
-
Thread.current.report_on_exception = true
|
|
170
|
+
Thread.current.abort_on_exception = false
|
|
171
|
+
Thread.current.report_on_exception = false
|
|
172
|
+
next if @closing
|
|
142
173
|
|
|
143
174
|
yield params
|
|
144
|
-
rescue Error::WebDriverError, *CONNECTION_ERRORS
|
|
145
|
-
|
|
175
|
+
rescue Error::WebDriverError, *CONNECTION_ERRORS => e
|
|
176
|
+
WebDriver.logger.debug "Callback aborted: #{e.class}: #{e.message}", id: :ws
|
|
177
|
+
rescue StandardError => e
|
|
178
|
+
next if @closing
|
|
179
|
+
|
|
180
|
+
bt = Array(e.backtrace).first(5).join("\n")
|
|
181
|
+
WebDriver.logger.error "Callback error: #{e.class}: #{e.message}\n#{bt}", id: :ws
|
|
146
182
|
end
|
|
147
183
|
end
|
|
148
184
|
|
|
@@ -78,6 +78,7 @@ require 'selenium/webdriver/common/driver_extensions/has_addons'
|
|
|
78
78
|
require 'selenium/webdriver/common/driver_extensions/has_bidi'
|
|
79
79
|
require 'selenium/webdriver/common/driver_extensions/has_devtools'
|
|
80
80
|
require 'selenium/webdriver/common/driver_extensions/has_file_downloads'
|
|
81
|
+
require 'selenium/webdriver/common/driver_extensions/has_session_events'
|
|
81
82
|
require 'selenium/webdriver/common/driver_extensions/has_authentication'
|
|
82
83
|
require 'selenium/webdriver/common/driver_extensions/has_logs'
|
|
83
84
|
require 'selenium/webdriver/common/driver_extensions/has_log_events'
|
|
@@ -84,7 +84,7 @@ module Selenium
|
|
|
84
84
|
def start_session(target_type:)
|
|
85
85
|
targets = target.get_targets.dig('result', 'targetInfos')
|
|
86
86
|
found_target = targets.find { |target| target['type'] == target_type }
|
|
87
|
-
raise Error::
|
|
87
|
+
raise Error::NoSuchTargetError, "Target type '#{target_type}' not found" unless found_target
|
|
88
88
|
|
|
89
89
|
session = target.attach_to_target(target_id: found_target['targetId'], flatten: true)
|
|
90
90
|
@session_id = session.dig('result', 'sessionId')
|
|
@@ -31,8 +31,9 @@ module Selenium
|
|
|
31
31
|
include LocalDriver
|
|
32
32
|
|
|
33
33
|
def initialize(options: nil, service: nil, url: nil, **)
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
initialize_local_driver(options, service, url) do |caps, driver_url|
|
|
35
|
+
super(caps: caps, url: driver_url, **)
|
|
36
|
+
end
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def browser
|
|
@@ -25,6 +25,17 @@ module Selenium
|
|
|
25
25
|
EXECUTABLE = 'msedgedriver'
|
|
26
26
|
SHUTDOWN_SUPPORTED = true
|
|
27
27
|
DRIVER_PATH_ENV_KEY = 'SE_EDGEDRIVER'
|
|
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 << '--verbose'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
|
|
28
39
|
def log
|
|
29
40
|
return @log unless @log.is_a? String
|
|
30
41
|
|
|
@@ -37,8 +37,9 @@ module Selenium
|
|
|
37
37
|
include LocalDriver
|
|
38
38
|
|
|
39
39
|
def initialize(options: nil, service: nil, url: nil, **)
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
initialize_local_driver(options, service, url) do |caps, driver_url|
|
|
41
|
+
super(caps: caps, url: driver_url, **)
|
|
42
|
+
end
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def browser
|
|
@@ -26,14 +26,33 @@ 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
|
+
if ENV.key?('SE_DEBUG')
|
|
37
|
+
remove_log_args(args)
|
|
38
|
+
args << '-v'
|
|
39
|
+
end
|
|
40
|
+
|
|
35
41
|
super
|
|
36
42
|
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def remove_log_args(args)
|
|
47
|
+
if (index = args.index('--log'))
|
|
48
|
+
args.delete_at(index) # delete '--log'
|
|
49
|
+
args.delete_at(index) if args[index] && !args[index].start_with?('-') # delete value if present
|
|
50
|
+
warn_driver_log_override
|
|
51
|
+
elsif (index = args.index { |arg| arg.start_with?('--log=') })
|
|
52
|
+
args.delete_at(index)
|
|
53
|
+
warn_driver_log_override
|
|
54
|
+
end
|
|
55
|
+
end
|
|
37
56
|
end # Service
|
|
38
57
|
end # Firefox
|
|
39
58
|
end # WebDriver
|
|
@@ -32,8 +32,9 @@ module Selenium
|
|
|
32
32
|
include LocalDriver
|
|
33
33
|
|
|
34
34
|
def initialize(options: nil, service: nil, url: nil, **)
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
initialize_local_driver(options, service, url) do |caps, driver_url|
|
|
36
|
+
super(caps: caps, url: driver_url, **)
|
|
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
|
|
@@ -206,12 +206,20 @@ module Selenium
|
|
|
206
206
|
switch_to_frame nil
|
|
207
207
|
end
|
|
208
208
|
|
|
209
|
-
QUIT_ERRORS = [IOError].freeze
|
|
209
|
+
QUIT_ERRORS = [IOError, EOFError, WebSocket::Error].freeze
|
|
210
210
|
|
|
211
211
|
def quit
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
begin
|
|
213
|
+
execute :delete_session
|
|
214
|
+
rescue *QUIT_ERRORS => e
|
|
215
|
+
WebDriver.logger.debug "delete_session failed during quit: #{e.class}: #{e.message}", id: :quit
|
|
216
|
+
ensure
|
|
217
|
+
begin
|
|
218
|
+
http.close
|
|
219
|
+
rescue *QUIT_ERRORS => e
|
|
220
|
+
WebDriver.logger.debug "http.close failed during quit: #{e.class}: #{e.message}", id: :quit
|
|
221
|
+
end
|
|
222
|
+
end
|
|
215
223
|
nil
|
|
216
224
|
end
|
|
217
225
|
|
|
@@ -29,6 +29,7 @@ module Selenium
|
|
|
29
29
|
include DriverExtensions::UploadsFiles
|
|
30
30
|
include DriverExtensions::HasSessionId
|
|
31
31
|
include DriverExtensions::HasFileDownloads
|
|
32
|
+
include DriverExtensions::HasSessionEvents
|
|
32
33
|
|
|
33
34
|
def initialize(capabilities: nil, options: nil, service: nil, url: nil, **)
|
|
34
35
|
raise ArgumentError, "Can not set :service object on #{self.class}" if service
|
|
@@ -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,6 +28,7 @@ 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
34
|
attr_accessor :extra_headers
|
|
@@ -55,6 +56,7 @@ module Selenium
|
|
|
55
56
|
headers['Cache-Control'] = 'no-cache' if verb == :get
|
|
56
57
|
|
|
57
58
|
if command_hash
|
|
59
|
+
command_hash = ensure_utf8_encoding(command_hash)
|
|
58
60
|
payload = JSON.generate(command_hash)
|
|
59
61
|
headers['Content-Length'] = payload.bytesize.to_s if %i[post put].include?(verb)
|
|
60
62
|
|
|
@@ -91,6 +93,36 @@ module Selenium
|
|
|
91
93
|
raise NotImplementedError, 'subclass responsibility'
|
|
92
94
|
end
|
|
93
95
|
|
|
96
|
+
def ensure_utf8_encoding(obj)
|
|
97
|
+
case obj
|
|
98
|
+
when String
|
|
99
|
+
encode_string_to_utf8(obj)
|
|
100
|
+
when Array
|
|
101
|
+
obj.map { |item| ensure_utf8_encoding(item) }
|
|
102
|
+
when Hash
|
|
103
|
+
obj.each_with_object({}) do |(key, value), result|
|
|
104
|
+
result[ensure_utf8_encoding(key)] = ensure_utf8_encoding(value)
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
obj
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def encode_string_to_utf8(str)
|
|
112
|
+
return str if str.encoding == Encoding::UTF_8 && str.valid_encoding?
|
|
113
|
+
|
|
114
|
+
if BINARY_ENCODINGS.include?(str.encoding)
|
|
115
|
+
result = str.dup.force_encoding(Encoding::UTF_8)
|
|
116
|
+
return result if result.valid_encoding?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
str.encode(Encoding::UTF_8)
|
|
120
|
+
rescue EncodingError => e
|
|
121
|
+
raise Error::WebDriverError,
|
|
122
|
+
"Unable to encode string to UTF-8: #{e.message}. " \
|
|
123
|
+
"String encoding: #{str.encoding}, content: #{str.inspect}"
|
|
124
|
+
end
|
|
125
|
+
|
|
94
126
|
def create_response(code, body, content_type)
|
|
95
127
|
code = code.to_i
|
|
96
128
|
body = body.to_s.strip
|
|
@@ -32,8 +32,9 @@ module Selenium
|
|
|
32
32
|
include LocalDriver
|
|
33
33
|
|
|
34
34
|
def initialize(options: nil, service: nil, url: nil, **)
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
initialize_local_driver(options, service, url) do |caps, driver_url|
|
|
36
|
+
super(caps: caps, url: driver_url, **)
|
|
37
|
+
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def browser
|
|
@@ -25,9 +25,13 @@ module Selenium
|
|
|
25
25
|
@callback = callback
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def method_missing(meth, *)
|
|
28
|
+
def method_missing(meth, *)
|
|
29
29
|
@callback.call(meth, *)
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
def respond_to_missing?(_meth, _include_private = false)
|
|
33
|
+
true
|
|
34
|
+
end
|
|
31
35
|
end # BlockEventListener
|
|
32
36
|
end # Support
|
|
33
37
|
end # WebDriver
|
|
@@ -72,26 +72,26 @@ module Selenium
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
def self.from_hsl(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if
|
|
82
|
-
r =
|
|
75
|
+
def self.from_hsl(hue, sat, light, alpha)
|
|
76
|
+
hue = Float(hue) / 360
|
|
77
|
+
sat = Float(sat) / 100
|
|
78
|
+
light = Float(light) / 100
|
|
79
|
+
alpha = Float(alpha || 1)
|
|
80
|
+
|
|
81
|
+
if sat.zero?
|
|
82
|
+
r = light
|
|
83
83
|
g = r
|
|
84
84
|
b = r
|
|
85
85
|
else
|
|
86
|
-
luminocity2 =
|
|
87
|
-
luminocity1 = (
|
|
86
|
+
luminocity2 = light < 0.5 ? light * (sat + 1) : light + sat - (light * sat)
|
|
87
|
+
luminocity1 = (light * 2) - luminocity2
|
|
88
88
|
|
|
89
|
-
r = hue_to_rgb(luminocity1, luminocity2,
|
|
90
|
-
g = hue_to_rgb(luminocity1, luminocity2,
|
|
91
|
-
b = hue_to_rgb(luminocity1, luminocity2,
|
|
89
|
+
r = hue_to_rgb(luminocity1, luminocity2, hue + (1.0 / 3.0))
|
|
90
|
+
g = hue_to_rgb(luminocity1, luminocity2, hue)
|
|
91
|
+
b = hue_to_rgb(luminocity1, luminocity2, hue - (1.0 / 3.0))
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
new (r * 255).round, (g * 255).round, (b * 255).round,
|
|
94
|
+
new (r * 255).round, (g * 255).round, (b * 255).round, alpha
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def self.hue_to_rgb(lum1, lum2, hue)
|
|
@@ -120,9 +120,13 @@ module Selenium
|
|
|
120
120
|
returned
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
def method_missing(meth, ...)
|
|
123
|
+
def method_missing(meth, ...)
|
|
124
124
|
@delegate.__send__(meth, ...)
|
|
125
125
|
end
|
|
126
|
+
|
|
127
|
+
def respond_to_missing?(meth, include_private = false)
|
|
128
|
+
@delegate.respond_to?(meth, include_private) || super
|
|
129
|
+
end
|
|
126
130
|
end # EventFiringBridge
|
|
127
131
|
end # Support
|
|
128
132
|
end # WebDriver
|
data/lib/selenium/webdriver.rb
CHANGED
|
@@ -95,8 +95,13 @@ module Selenium
|
|
|
95
95
|
#
|
|
96
96
|
|
|
97
97
|
def self.logger(**)
|
|
98
|
-
level = $DEBUG || ENV.key?('DEBUG') ? :debug : :info
|
|
99
|
-
@logger ||= WebDriver::Logger.new('Selenium', default_level: level, **)
|
|
98
|
+
level = $DEBUG || ENV.key?('DEBUG') || ENV.key?('SE_DEBUG') ? :debug : :info
|
|
99
|
+
@logger ||= WebDriver::Logger.new('Selenium', default_level: level, **).tap do |logger|
|
|
100
|
+
if ENV.key?('SE_DEBUG')
|
|
101
|
+
logger.debug!
|
|
102
|
+
logger.stderr!
|
|
103
|
+
end
|
|
104
|
+
end
|
|
100
105
|
end
|
|
101
106
|
end # WebDriver
|
|
102
107
|
end # Selenium
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: selenium-webdriver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.41.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Rodionov
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date:
|
|
13
|
+
date: 2026-02-20 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: base64
|
|
@@ -333,6 +333,7 @@ files:
|
|
|
333
333
|
- lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb
|
|
334
334
|
- lib/selenium/webdriver/common/driver_extensions/has_permissions.rb
|
|
335
335
|
- lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb
|
|
336
|
+
- lib/selenium/webdriver/common/driver_extensions/has_session_events.rb
|
|
336
337
|
- lib/selenium/webdriver/common/driver_extensions/has_session_id.rb
|
|
337
338
|
- lib/selenium/webdriver/common/driver_extensions/prints_page.rb
|
|
338
339
|
- lib/selenium/webdriver/common/driver_extensions/uploads_files.rb
|