Class: Capybara::Chrome::Browser
- Inherits:
-
Object
- Object
- Capybara::Chrome::Browser
show all
- Includes:
- Debug, Service
- Defined in:
- lib/capybara/chrome/browser.rb
Constant Summary
collapse
- RECOGNIZED_SCHEME =
/^https?/
Constants included
from Service
Service::CHROME_ARGS
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#accept_modal(type, text_or_options = nil, options = {}, &block) ⇒ Object
-
#after_remote_start ⇒ Object
-
#current_url ⇒ Object
-
#dismiss_modal(type, text_or_options = nil, options = {}, &block) ⇒ Object
-
#document_root ⇒ Object
-
#enable_console_log ⇒ Object
-
#enable_js_dialog ⇒ Object
-
#enable_lifecycle_events ⇒ Object
-
#enable_network_interception ⇒ Object
-
#enable_script_debug ⇒ Object
-
#evaluate_async_script(script, *args) ⇒ Object
-
#evaluate_script(script, *args) ⇒ Object
-
#execute_script(script, *args) ⇒ Object
-
#execute_script!(script, options = {}) ⇒ Object
-
#find_css(query) ⇒ Object
-
#find_xpath(query, index = nil) ⇒ Object
-
#get_document ⇒ Object
-
#get_node_results(result) ⇒ Object
-
#has_body?(resp) ⇒ Boolean
-
#header(key, value) ⇒ Object
-
#html ⇒ Object
-
#initialize(driver, host: "127.0.0.1", port: nil) ⇒ Browser
constructor
A new instance of Browser.
-
#last_response ⇒ Object
-
#last_response_or_err ⇒ Object
-
#loader_loaded?(loader_id) ⇒ Boolean
-
#query_selector_all(query, index = nil) ⇒ Object
-
#render(path, width = nil, height = nil) ⇒ Object
-
#request_nodes(object_id) ⇒ Object
object_id represents a script that returned of an array of nodes.
-
#reset ⇒ Object
-
#root_node ⇒ Object
-
#save_screenshot(path, options = {}) ⇒ Object
-
#set_viewport(width:, height:, device_scale_factor: 1, mobile: false) ⇒ Object
-
#start ⇒ Object
-
#start_remote ⇒ Object
-
#status_code ⇒ Object
-
#title ⇒ Object
-
#track_network_events ⇒ Object
-
#unrecognized_scheme_requests ⇒ Object
-
#unset_root_node ⇒ Object
-
#visit(path, attributes = {}) ⇒ Object
-
#wait_for_load ⇒ Object
-
#with_retry(n: 10, timeout: 0.05, &block) ⇒ Object
Methods included from Service
#chrome_args, #chrome_path, #chrome_pid, #chrome_running?, find_available_port, #os, #restart_chrome, #start_chrome, #stop_chrome, #wait_for_chrome
Methods included from Debug
#debug, #info
Constructor Details
#initialize(driver, host: "127.0.0.1", port: nil) ⇒ Browser
Returns a new instance of Browser.
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# File 'lib/capybara/chrome/browser.rb', line 13
def initialize(driver, host: "127.0.0.1", port: nil)
@driver = driver
@chrome_pid = nil
@chrome_host = host
@chrome_port = port || find_available_port(host)
@remote = nil
@responses = {}
@last_response = nil
@frame_mutex = Mutex.new
@network_mutex = Mutex.new
@console_messages = []
@error_messages = []
@js_dialog_handlers = Hash.new {|h,key| h[key] = []}
@unrecognized_scheme_requests = []
@loader_ids = []
@loaded_loaders = {}
end
|
Instance Attribute Details
#chrome_port ⇒ Object
Returns the value of attribute chrome_port.
12
13
14
|
# File 'lib/capybara/chrome/browser.rb', line 12
def chrome_port
@chrome_port
end
|
#console_messages ⇒ Object
Returns the value of attribute console_messages.
11
12
13
|
# File 'lib/capybara/chrome/browser.rb', line 11
def console_messages
@console_messages
end
|
#driver ⇒ Object
Returns the value of attribute driver.
11
12
13
|
# File 'lib/capybara/chrome/browser.rb', line 11
def driver
@driver
end
|
#error_messages ⇒ Object
Returns the value of attribute error_messages.
11
12
13
|
# File 'lib/capybara/chrome/browser.rb', line 11
def error_messages
@error_messages
end
|
#remote ⇒ Object
Returns the value of attribute remote.
11
12
13
|
# File 'lib/capybara/chrome/browser.rb', line 11
def remote
@remote
end
|
Instance Method Details
#accept_modal(type, text_or_options = nil, options = {}, &block) ⇒ Object
314
315
316
317
|
# File 'lib/capybara/chrome/browser.rb', line 314
def accept_modal(type, text_or_options=nil, options={}, &block)
@js_dialog_handlers[type.to_s] << {accept: true}
block.call if block
end
|
#after_remote_start ⇒ Object
261
262
263
264
265
266
267
268
269
|
# File 'lib/capybara/chrome/browser.rb', line 261
def after_remote_start
track_network_events
enable_console_log
enable_js_dialog
enable_script_debug
enable_network_interception
set_viewport(width: 1680, height: 1050)
end
|
#current_url ⇒ Object
141
142
143
|
# File 'lib/capybara/chrome/browser.rb', line 141
def current_url
document_root["documentURL"]
end
|
#dismiss_modal(type, text_or_options = nil, options = {}, &block) ⇒ Object
319
320
321
322
323
|
# File 'lib/capybara/chrome/browser.rb', line 319
def dismiss_modal(type, text_or_options=nil, options={}, &block)
@js_dialog_handlers[type.to_s] << {accept: false}
block.call if block
debug [type, text_or_options, options]
end
|
#document_root ⇒ Object
166
167
168
|
# File 'lib/capybara/chrome/browser.rb', line 166
def document_root
@document_root = get_document["root"]
end
|
#enable_console_log ⇒ Object
325
326
327
328
329
330
331
332
333
334
335
|
# File 'lib/capybara/chrome/browser.rb', line 325
def enable_console_log
remote.send_cmd! "Console.enable"
remote.on "Console.messageAdded" do |params|
str = "#{params["message"]["source"]}:#{params["message"]["line"]} #{params["message"]["text"]}"
if params["message"]["level"] == "error"
@error_messages << str
else
@console_messages << str
end
end
end
|
#enable_js_dialog ⇒ Object
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
|
# File 'lib/capybara/chrome/browser.rb', line 296
def enable_js_dialog
remote.on("Page.javascriptDialogOpening") do |params|
debug ["Dialog Opening", params]
handler = @js_dialog_handlers[params["type"]].last
if handler
debug ["have handler", handler]
args = {accept: handler[:accept]}
args.merge!(promptText: handler[:prompt_text]) if params[:type] == "prompt"
remote.send_cmd("Page.handleJavaScriptDialog", args)
@js_dialog_handlers[params["type"]].delete(params["type"].size - 1)
else
puts "WARNING: Accepting unhandled modal. Use #accept_modal or #dismiss_modal to handle this modal properly."
puts "Details: #{params.inspect}"
remote.send_cmd("Page.handleJavaScriptDialog", accept: true)
end
end
end
|
#enable_lifecycle_events ⇒ Object
337
338
339
340
341
342
343
344
345
346
347
|
# File 'lib/capybara/chrome/browser.rb', line 337
def enable_lifecycle_events
remote.send_cmd! "Page.setLifecycleEventsEnabled", enabled: true
remote.on("Page.lifecycleEvent") do |params|
if params["name"] == "init"
@loader_ids.push(params["loaderId"])
elsif params["name"] == "load"
@loaded_loaders[params["loaderId"]] = true
elsif params["name"] == "networkIdle"
end
end
end
|
#enable_network_interception ⇒ Object
275
276
277
278
279
280
281
282
283
284
285
286
|
# File 'lib/capybara/chrome/browser.rb', line 275
def enable_network_interception
remote.send_cmd! "Network.setRequestInterception", patterns: [{urlPattern: "*"}]
remote.on("Network.requestIntercepted") do |params|
if Capybara::Chrome.configuration.block_url?(params["request"]["url"]) || (Capybara::Chrome.configuration.skip_image_loading? && params["resourceType"] == "Image")
remote.send_cmd "Network.continueInterceptedRequest", interceptionId: params["interceptionId"], errorReason: "ConnectionRefused"
else
remote.send_cmd "Network.continueInterceptedRequest", interceptionId: params["interceptionId"]
end
end
end
|
#enable_script_debug ⇒ Object
288
289
290
291
292
293
294
|
# File 'lib/capybara/chrome/browser.rb', line 288
def enable_script_debug
remote.send_cmd "Debugger.enable"
remote.on("Debugger.scriptFailedToParse") do |params|
puts "\n\n!!! ERROR: SCRIPT FAILED TO PARSE !!!\n\n"
p params
end
end
|
#evaluate_async_script(script, *args) ⇒ Object
61
62
63
|
# File 'lib/capybara/chrome/browser.rb', line 61
def evaluate_async_script(script, *args)
raise "i dunno"
end
|
#evaluate_script(script, *args) ⇒ Object
36
37
38
39
|
# File 'lib/capybara/chrome/browser.rb', line 36
def evaluate_script(script, *args)
val = execute_script(script, *args)
val["result"]["value"]
end
|
#execute_script(script, *args) ⇒ Object
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
# File 'lib/capybara/chrome/browser.rb', line 41
def execute_script(script, *args)
default_options = {expression: script, includeCommandLineAPI: true, awaitPromise: true}
opts = args[0].respond_to?(:merge) ? args[0] : {}
opts = default_options.merge(opts)
val = remote.send_cmd "Runtime.evaluate", opts
debug script, val
if details = val["exceptionDetails"]
if details["exception"]["className"] == "NodeNotFoundError"
raise Capybara::ElementNotFound
else
raise JSException.new(details["exception"].inspect)
end
end
val
end
|
#execute_script!(script, options = {}) ⇒ Object
57
58
59
|
# File 'lib/capybara/chrome/browser.rb', line 57
def execute_script!(script, options={})
remote.send_cmd!("Runtime.evaluate", {expression: script, includeCommandLineAPI: true}.merge(options))
end
|
#find_css(query) ⇒ Object
184
185
186
187
188
|
# File 'lib/capybara/chrome/browser.rb', line 184
def find_css(query)
debug query
nodes = query_selector_all(query)
nodes
end
|
#find_xpath(query, index = nil) ⇒ Object
233
234
235
236
237
238
239
240
241
242
243
|
# File 'lib/capybara/chrome/browser.rb', line 233
def find_xpath(query, index=nil)
wait_for_load
query = query.dup
query.gsub!('"', '\"')
result = if index
evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findXPathWithin(#{index}, "#{query}") )
else
evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findXPath("#{query}") )
end
get_node_results result
end
|
#get_document ⇒ Object
162
163
164
|
# File 'lib/capybara/chrome/browser.rb', line 162
def get_document
val = remote.send_cmd "DOM.getDocument"
end
|
#get_node_results(result) ⇒ Object
222
223
224
225
226
227
228
229
230
231
|
# File 'lib/capybara/chrome/browser.rb', line 222
def get_node_results(result)
vals = result.split(",")
nodes = []
if vals.any?
nodes = result.split(",").map do |id|
Node.new driver, self, id.to_i
end
end
nodes
end
|
#has_body?(resp) ⇒ Boolean
150
151
152
153
154
155
156
157
158
159
160
|
# File 'lib/capybara/chrome/browser.rb', line 150
def has_body?(resp)
debug
if resp["root"] && resp["root"]["children"]
resp["root"]["children"].detect do |child|
next unless child.has_key?("children")
child["children"].detect do |grandchild|
grandchild["localName"] == "body"
end
end
end
end
|
369
370
371
372
373
374
375
|
# File 'lib/capybara/chrome/browser.rb', line 369
def (key, value)
if key.downcase == "user-agent"
remote.send_cmd!("Network.setUserAgentOverride", userAgent: value)
else
remote.send_cmd!("Network.setExtraHTTPHeaders", headers: {key => value})
end
end
|
#html ⇒ Object
178
179
180
181
182
|
# File 'lib/capybara/chrome/browser.rb', line 178
def html
val = root_node.html
debug "root", val.size
val
end
|
#last_response ⇒ Object
124
125
126
|
# File 'lib/capybara/chrome/browser.rb', line 124
def last_response
@last_response
end
|
#last_response_or_err ⇒ Object
128
129
130
131
132
133
134
135
|
# File 'lib/capybara/chrome/browser.rb', line 128
def last_response_or_err
loop do
break last_response if last_response
remote.read_and_process(0.01)
end
rescue Timeout::Error
raise Capybara::ExpectationNotMet
end
|
#loader_loaded?(loader_id) ⇒ Boolean
349
350
351
|
# File 'lib/capybara/chrome/browser.rb', line 349
def loader_loaded?(loader_id)
@loaded_loaders[loader_id]
end
|
#query_selector_all(query, index = nil) ⇒ Object
190
191
192
193
194
195
196
197
198
199
200
|
# File 'lib/capybara/chrome/browser.rb', line 190
def query_selector_all(query, index=nil)
wait_for_load
query = query.dup
query.gsub!('"', '\"')
result = if index
evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findCssWithin(#{index}, "#{query}") )
else
evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findCss("#{query}") )
end
get_node_results result
end
|
#render(path, width = nil, height = nil) ⇒ Object
359
360
361
362
363
364
365
366
367
|
# File 'lib/capybara/chrome/browser.rb', line 359
def render(path, width=nil, height=nil)
response = remote.send_cmd "Page.getLayoutMetrics"
width = response["contentSize"]["width"]
height = response["contentSize"]["height"]
response = remote.send_cmd "Page.captureScreenshot", clip: {width: width, height: height, x: 0, y: 0, scale: 1}
File.open path, "wb" do |f|
f.write Base64.decode64(response["data"])
end
end
|
#request_nodes(object_id) ⇒ Object
object_id represents a script that returned of an array of nodes
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
|
# File 'lib/capybara/chrome/browser.rb', line 203
def request_nodes(object_id)
nodes = []
results = remote.send_cmd("Runtime.getProperties", objectId: object_id, ownProperties: true)
raise Capybara::ExpectationNotMet if results.nil?
results["result"].each do |prop|
if prop["value"]["subtype"] == "node"
lookup = remote.send_cmd("DOM.requestNode", objectId: prop["value"]["objectId"])
raise Capybara::ExpectationNotMet if lookup.nil?
id = lookup["nodeId"]
if id == 0
raise Capybara::ExpectationNotMet
else
nodes << Node.new(driver, self, id)
end
end
end
nodes
end
|
#reset ⇒ Object
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
|
# File 'lib/capybara/chrome/browser.rb', line 377
def reset
unset_root_node
@responses.clear
@last_response = nil
@console_messages.clear
@error_messages.clear
@js_dialog_handlers.clear
@unrecognized_scheme_requests.clear
remote.reset
remote.send_cmd! "Network.clearBrowserCookies"
remote.send_cmd! "Runtime.discardConsoleEntries"
remote.send_cmd! "Network.setExtraHTTPHeaders", headers: {}
remote.send_cmd! "Network.setUserAgentOverride", userAgent: ""
visit "about:blank"
end
|
#root_node ⇒ Object
170
171
172
|
# File 'lib/capybara/chrome/browser.rb', line 170
def root_node
@root_node = find_css("html")[0]
end
|
#save_screenshot(path, options = {}) ⇒ Object
353
354
355
356
357
|
# File 'lib/capybara/chrome/browser.rb', line 353
def save_screenshot(path, options={})
options[:width] ||= 1000
options[:height] ||= 10
render path, options[:width], options[:height]
end
|
#set_viewport(width:, height:, device_scale_factor: 1, mobile: false) ⇒ Object
271
272
273
|
# File 'lib/capybara/chrome/browser.rb', line 271
def set_viewport(width:, height:, device_scale_factor: 1, mobile: false)
remote.send_cmd!("Emulation.setDeviceMetricsOverride", width: width, height: height, deviceScaleFactor: device_scale_factor, mobile: mobile)
end
|
#start ⇒ Object
31
32
33
34
|
# File 'lib/capybara/chrome/browser.rb', line 31
def start
start_chrome
start_remote
end
|
#start_remote ⇒ Object
254
255
256
257
258
259
|
# File 'lib/capybara/chrome/browser.rb', line 254
def start_remote
@remote = RDPClient.new chrome_host: @chrome_host, chrome_port: @chrome_port, browser: self
remote.start
after_remote_start
end
|
#status_code ⇒ Object
137
138
139
|
# File 'lib/capybara/chrome/browser.rb', line 137
def status_code
last_response_or_err["status"]
end
|
#title ⇒ Object
245
246
247
248
249
250
251
252
|
# File 'lib/capybara/chrome/browser.rb', line 245
def title
nodes = find_xpath("/html/head/title")
if nodes && nodes.first
nodes[0].text
else
""
end
end
|
#track_network_events ⇒ Object
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
# File 'lib/capybara/chrome/browser.rb', line 99
def track_network_events
return if @track_network_events
remote.on("Network.requestWillBeSent") do |req|
if req["type"] == "Document"
if !RECOGNIZED_SCHEME.match req["request"]["url"]
puts "ADDING SCHEME"
@unrecognized_scheme_requests << req["request"]["url"]
else
@last_response = nil
end
end
end
remote.on("Network.responseReceived") do |params|
debug params["response"]["url"], params["requestId"], params["loaderId"], params["type"]
if params["type"] == "Document"
@responses[params["requestId"]] = params["response"]
@last_response = params["response"]
end
end
remote.on("Network.loadingFailed") do |params|
debug ["loadingFailed", params]
end
@track_network_events = true
end
|
#unrecognized_scheme_requests ⇒ Object
145
146
147
148
|
# File 'lib/capybara/chrome/browser.rb', line 145
def unrecognized_scheme_requests
remote.read_and_process(1)
@unrecognized_scheme_requests
end
|
#unset_root_node ⇒ Object
174
175
176
|
# File 'lib/capybara/chrome/browser.rb', line 174
def unset_root_node
@root_node = nil
end
|
#visit(path, attributes = {}) ⇒ Object
73
74
75
76
77
78
79
80
81
82
|
# File 'lib/capybara/chrome/browser.rb', line 73
def visit(path, attributes={})
uri = URI.parse(path)
if uri.scheme.nil?
uri.host = Capybara.current_session.server.host unless uri.host.present?
uri.port = Capybara.current_session.server.port unless uri.port.present?
end
debug ["visit #{uri}"]
@last_navigate = remote.send_cmd "Page.navigate", url: uri.to_s, transitionType: "typed"
wait_for_load
end
|
#wait_for_load ⇒ Object
65
66
67
68
69
70
71
|
# File 'lib/capybara/chrome/browser.rb', line 65
def wait_for_load
remote.send_cmd "DOM.getDocument"
loop do
val = evaluate_script %(window.ChromeRemotePageLoaded), awaitPromise: false
break val if val
end
end
|
#with_retry(n: 10, timeout: 0.05, &block) ⇒ Object
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/capybara/chrome/browser.rb', line 84
def with_retry(n:10, timeout: 0.05, &block)
skip_retry = [Errno::EPIPE, EOFError, ResponseTimeoutError]
begin
block.call
rescue => e
if n == 0 || skip_retry.detect {|klass| e.instance_of?(klass)}
raise e
else
puts "RETRYING #{e}"
sleep timeout
with_retry(n: n-1, timeout: timeout, &block)
end
end
end
|