selenium-webdriver 4.36.0 → 4.43.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 +49 -0
- 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 +30 -5
- 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 +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/session.rb +4 -0
- data/lib/selenium/webdriver/bidi.rb +0 -1
- 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/platform.rb +1 -3
- 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/response.rb +3 -3
- 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
- data/selenium-webdriver.gemspec +0 -2
- metadata +3 -42
- data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +0 -35
- data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +0 -35
- data/lib/selenium/webdriver/bidi/log/filter_by.rb +0 -40
- 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
|
@@ -27,7 +27,17 @@ module Selenium
|
|
|
27
27
|
caps = process_options(options, service)
|
|
28
28
|
url = service_url(service)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
begin
|
|
31
|
+
yield(caps, url) if block_given?
|
|
32
|
+
rescue Selenium::WebDriver::Error::WebDriverError
|
|
33
|
+
@service_manager&.stop
|
|
34
|
+
raise
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def service_url(service)
|
|
39
|
+
@service_manager = service.launch
|
|
40
|
+
@service_manager.uri
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
def process_options(options, service)
|
|
@@ -55,9 +55,32 @@ module Selenium
|
|
|
55
55
|
@ignored = Array(ignored)
|
|
56
56
|
@allowed = Array(allowed)
|
|
57
57
|
@first_warning = false
|
|
58
|
+
@level_forced = false
|
|
59
|
+
@output_forced = false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
# Forces debug level and prevents it from being overridden.
|
|
64
|
+
#
|
|
65
|
+
def debug!
|
|
66
|
+
@level_forced = true
|
|
67
|
+
@logger.level = :debug
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
# Forces output to stderr and prevents it from being overridden.
|
|
72
|
+
#
|
|
73
|
+
def stderr!
|
|
74
|
+
@output_forced = true
|
|
75
|
+
@logger.reopen($stderr)
|
|
58
76
|
end
|
|
59
77
|
|
|
60
78
|
def level=(level)
|
|
79
|
+
if @level_forced
|
|
80
|
+
warn('Logger level is forced; ignoring override', id: :logger)
|
|
81
|
+
return
|
|
82
|
+
end
|
|
83
|
+
|
|
61
84
|
if level == :info && @logger.level == :info
|
|
62
85
|
info(':info is now the default log level, to see additional logging, set log level to :debug')
|
|
63
86
|
end
|
|
@@ -71,6 +94,11 @@ module Selenium
|
|
|
71
94
|
# @param [String] io
|
|
72
95
|
#
|
|
73
96
|
def output=(io)
|
|
97
|
+
if @output_forced
|
|
98
|
+
warn('Logger output is forced; ignoring override', id: :logger)
|
|
99
|
+
return
|
|
100
|
+
end
|
|
101
|
+
|
|
74
102
|
@logger.reopen(io)
|
|
75
103
|
end
|
|
76
104
|
|
|
@@ -71,6 +71,8 @@ module Selenium
|
|
|
71
71
|
def initialize(**opts)
|
|
72
72
|
self.class.set_capabilities
|
|
73
73
|
|
|
74
|
+
opts[:web_socket_url] = opts.delete(:bidi) if opts.key?(:bidi)
|
|
75
|
+
|
|
74
76
|
@options = opts
|
|
75
77
|
@options[:browser_name] = self.class::BROWSER
|
|
76
78
|
end
|
|
@@ -91,6 +93,14 @@ module Selenium
|
|
|
91
93
|
@options[name] = value
|
|
92
94
|
end
|
|
93
95
|
|
|
96
|
+
def enable_bidi!
|
|
97
|
+
@options[:web_socket_url] = true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def bidi?
|
|
101
|
+
!!@options[:web_socket_url]
|
|
102
|
+
end
|
|
103
|
+
|
|
94
104
|
def ==(other)
|
|
95
105
|
return false unless other.is_a? self.class
|
|
96
106
|
|
|
@@ -104,6 +104,12 @@ module Selenium
|
|
|
104
104
|
def env_path
|
|
105
105
|
ENV.fetch(self.class::DRIVER_PATH_ENV_KEY, nil)
|
|
106
106
|
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def warn_driver_log_override
|
|
111
|
+
WebDriver.logger.warn('SE_DEBUG is set; overriding user-specified driver logging settings', id: :se_debug)
|
|
112
|
+
end
|
|
107
113
|
end # Service
|
|
108
114
|
end # WebDriver
|
|
109
115
|
end # Selenium
|
|
@@ -80,7 +80,13 @@ module Selenium
|
|
|
80
80
|
def build_process(*command)
|
|
81
81
|
WebDriver.logger.debug("Executing Process #{command}", id: :driver_service)
|
|
82
82
|
@process = ChildProcess.build(*command)
|
|
83
|
-
|
|
83
|
+
if ENV.key?('SE_DEBUG')
|
|
84
|
+
if @io && @io != WebDriver.logger.io
|
|
85
|
+
WebDriver.logger.warn('SE_DEBUG is set; overriding user-specified driver log output to use stderr',
|
|
86
|
+
id: :se_debug)
|
|
87
|
+
end
|
|
88
|
+
@io = WebDriver.logger.io
|
|
89
|
+
end
|
|
84
90
|
@process.io = @io if @io
|
|
85
91
|
|
|
86
92
|
@process
|
|
@@ -127,10 +133,36 @@ module Selenium
|
|
|
127
133
|
end
|
|
128
134
|
|
|
129
135
|
def connect_until_stable
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
deadline = current_time + START_TIMEOUT
|
|
137
|
+
|
|
138
|
+
loop do
|
|
139
|
+
error = check_connection_error
|
|
140
|
+
return unless error
|
|
141
|
+
|
|
142
|
+
raise Error::WebDriverError, "#{cannot_connect_error_text}: #{error}" if current_time > deadline
|
|
143
|
+
|
|
144
|
+
sleep 0.1
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def check_connection_error
|
|
149
|
+
response = Net::HTTP.start(@host, @port, open_timeout: 0.5, read_timeout: 1) do |http|
|
|
150
|
+
http.get('/status', {'Connection' => 'close'})
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
return "status returned #{response.code}\n#{response.body}" unless response.is_a?(Net::HTTPSuccess)
|
|
154
|
+
|
|
155
|
+
status = JSON.parse(response.body)
|
|
156
|
+
ready = status['ready'] || status.dig('value', 'ready')
|
|
157
|
+
"driver not ready: #{response.body}" unless ready
|
|
158
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT,
|
|
159
|
+
Errno::EADDRNOTAVAIL, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout,
|
|
160
|
+
EOFError, SocketError, Net::HTTPBadResponse, JSON::ParserError => e
|
|
161
|
+
"#{e.class}: #{e.message}"
|
|
162
|
+
end
|
|
132
163
|
|
|
133
|
-
|
|
164
|
+
def current_time
|
|
165
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
134
166
|
end
|
|
135
167
|
|
|
136
168
|
def cannot_connect_error_text
|
|
@@ -93,7 +93,7 @@ module Selenium
|
|
|
93
93
|
true
|
|
94
94
|
rescue *NOT_CONNECTED_ERRORS
|
|
95
95
|
sock&.close
|
|
96
|
-
WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}", id: :
|
|
96
|
+
WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}", id: :socket_poller)
|
|
97
97
|
false
|
|
98
98
|
end
|
|
99
99
|
end
|
|
@@ -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'
|
|
@@ -34,9 +34,9 @@ module Selenium
|
|
|
34
34
|
id: id,
|
|
35
35
|
code: params['responseStatusCode'],
|
|
36
36
|
body: (Base64.strict_decode64(encoded_body) if encoded_body),
|
|
37
|
-
headers: params.fetch('responseHeaders', []).
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
headers: params.fetch('responseHeaders', []).to_h do |header|
|
|
38
|
+
[header['name'], header['value']]
|
|
39
|
+
end
|
|
40
40
|
)
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -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
|