capybara-chrome 0.1.22

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8541435afe87267dc82259cc09f0c1ca7484588a
4
+ data.tar.gz: b80fe620c9549091dab72dfe7e62a505dbbbe83b
5
+ SHA512:
6
+ metadata.gz: c90f45a45e4f3f96ce46c48956b73afd65c01bb39b4b3830c9905fb3b101f4ccbe31f5806b581b74cc6d533ad9495b8f655a9af1270fa45e867a1c200dd118c1
7
+ data.tar.gz: c678cd3da799220fb700e873562a8586ec850b86460049c92cbb65b28e2921fc2a5e0cc511cfb647614e8406f4f90a3f50b6c2c0b4094e4ac1abce1e25f189bf
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.2
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in capybara-chrome.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Sandro Turriate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,110 @@
1
+ # Capybara::Chrome
2
+
3
+ Use [Capybara](https://github.com/teamcapybara/capybara) to drive Chrome in headless mode via the [debugging protocol](https://chromedevtools.github.io/devtools-protocol/).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'capybara-chrome'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install capybara-chrome
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ Capybara.javascript_driver = :chrome
25
+ Capybara::Chrome.configuration.chrome_port = 9222 # optional
26
+ ```
27
+
28
+ The standard port for the debugging protocol is `9222`. Visit `localhost:9222` in a Chrome tab to watch the tests execute. Note, the port will be random by default.
29
+
30
+ ### Using thin
31
+
32
+ I like using [thin](https://github.com/macournoyer/thin) instead of WEBrick as
33
+ my Capybara server. It's a little faster, gives me greater control of logging,
34
+ shows errors, and allows me to disable signal handlers.
35
+ Below are my settings:
36
+
37
+ ```ruby
38
+ Capybara.register_server :thin do |app, port, host|
39
+ require "rack/handler/thin"
40
+ Thin::Logging.silent = false
41
+ # Thin::Logging.debug = true # uncomment to see request and response codes
42
+ # Thin::Logging.trace = true # uncomment to see full requests/responses
43
+ Rack::Handler::Thin.run(app, Host: host, Port: port, signals: false)
44
+ end
45
+ Capybara.server = :thin
46
+ ```
47
+
48
+ ### Debugging
49
+
50
+ Use `byebug` instead of `binding.pry` when debugging a test. The Pry debugger tends to hang when `visit` is called.
51
+
52
+ ### Using the repl
53
+
54
+ You can use the capybara-chrome browser without providing a rack app. This can be helpful in debugging.
55
+
56
+ ```
57
+ [2] pry(main)> driver = Capybara::Chrome::Driver.new(nil, port:9222); driver.start; browser = driver.browser
58
+ [3] pry(main)> browser.visit "http://google.com"
59
+ => true
60
+ [4] pry(main)> browser.current_url
61
+ => "https://www.google.com/?gws_rd=ssl"
62
+ [5] pry(main)>
63
+
64
+ ```
65
+
66
+ Further, you can run a local netcat server and point the capybara-chrome browser to it to see the entire request that's being sent.
67
+
68
+ Terminal one contains the browser:
69
+
70
+ ```
71
+ [1] pry(main)> driver = Capybara::Chrome::Driver.new(nil, port:9222); driver.start; browser = driver.browser
72
+ [2] pry(main)> browser.header "x-foo", "bar"
73
+ [3] pry(main)> browser.visit "http://localhost:8000"
74
+ ```
75
+
76
+ Terminal two prints the request
77
+
78
+ ```
79
+ $ while true; do { echo -e "HTTP/1.1 200 OK \r\n"; echo "hi"; } | nc -l 8000; done
80
+ GET / HTTP/1.1
81
+ Host: localhost:8000
82
+ Connection: keep-alive
83
+ Upgrade-Insecure-Requests: 1
84
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/68.0.3440.106 Safari/537.36
85
+ x-foo: bar
86
+ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
87
+ Accept-Encoding: gzip, deflate
88
+ ```
89
+
90
+ ### Videos
91
+
92
+ A video showing capybara-chrome running in a browser tab
93
+
94
+ [![Preview of visualizing capybara-chrome](http://img.youtube.com/vi/SLmkx5z-lAA/0.jpg)](http://www.youtube.com/watch?v=SLmkx5z-lAA)
95
+
96
+ A video demonstrating debugging an rspec test with `byebug`
97
+
98
+ [![Preview of debugging capybara-chrome](http://img.youtube.com/vi/McEQG9YEAdE/0.jpg)](http://www.youtube.com/watch?v=McEQG9YEAdE)
99
+
100
+ A video showing capybara-chrome running against a netcat backend
101
+
102
+ [![Preview of debugging capybara-chrome with netcat](http://img.youtube.com/vi/B1__LeLyXBo/0.jpg)](http://www.youtube.com/watch?v=B1__LeLyXBo)
103
+
104
+ ## Contributing
105
+
106
+ Bug reports and pull requests are welcome on GitHub at https://github.com/carezone/capybara-chrome.
107
+
108
+ ## License
109
+
110
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ task :tag do
9
+ puts `git tag #{Capybara::Chrome::VERSION} && git tag | sort -V | tail`
10
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "capybara/chrome"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "capybara/chrome/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "capybara-chrome"
8
+ spec.version = Capybara::Chrome::VERSION
9
+ spec.authors = ["Sandro Turriate"]
10
+ spec.email = ["sandro.turriate@gmail.com"]
11
+
12
+ spec.summary = %q{Chrome driver for capybara using remote debugging protocol.}
13
+ spec.description = %q{Chrome driver for capybara using remote debugging protocol.}
14
+ spec.homepage = "https://github.com/carezone/capybara-chrome"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_runtime_dependency("capybara")
25
+ spec.add_runtime_dependency("json")
26
+ spec.add_runtime_dependency("websocket-driver")
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.16"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ end
@@ -0,0 +1 @@
1
+ require "capybara/chrome"
@@ -0,0 +1,56 @@
1
+ require "capybara/chrome/version"
2
+ require "capybara"
3
+ require "websocket/driver"
4
+
5
+ module Capybara
6
+ module Chrome
7
+ require "capybara/chrome/errors"
8
+ autoload :Configuration, "capybara/chrome/configuration"
9
+
10
+ autoload :Driver, "capybara/chrome/driver"
11
+ autoload :Browser, "capybara/chrome/browser"
12
+ autoload :Node, "capybara/chrome/node"
13
+ autoload :Service, "capybara/chrome/service"
14
+
15
+ autoload :RDPClient, "capybara/chrome/rdp_client"
16
+ autoload :RDPWebSocketClient, "capybara/chrome/rdp_web_socket_client"
17
+ autoload :RDPSocket, "capybara/chrome/rdp_socket"
18
+
19
+ autoload :Debug, "capybara/chrome/debug"
20
+
21
+ def self.configure(reset: false)
22
+ @configuration = nil if reset
23
+ yield configuration
24
+ end
25
+
26
+ def self.configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ def self.wants_to_quit
31
+ @wants_to_quit
32
+ end
33
+
34
+ def self.trap_interrupt
35
+ previous_interrupt = trap("INT") do
36
+ @wants_to_quit = true
37
+ if previous_interrupt.respond_to?(:call)
38
+ previous_interrupt.call
39
+ else
40
+ exit 1
41
+ end
42
+ end
43
+ end
44
+
45
+ Capybara.register_driver :chrome do |app|
46
+ driver = Capybara::Chrome::Driver.new(app, port: configuration.chrome_port)
47
+ if driver.browser.chrome_running?
48
+ driver = Capybara::Chrome::Driver.new(app)
49
+ end
50
+ driver.start
51
+ Capybara::Chrome.trap_interrupt if Capybara::Chrome.configuration.trap_interrupt?
52
+ driver
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,393 @@
1
+ module Capybara::Chrome
2
+
3
+ class Browser
4
+ require 'rbconfig'
5
+
6
+ RECOGNIZED_SCHEME = /^https?/
7
+
8
+ include Debug
9
+ include Service
10
+
11
+ attr_reader :remote, :driver, :console_messages, :error_messages
12
+ attr_accessor :chrome_port
13
+ def initialize(driver, host: "127.0.0.1", port: nil)
14
+ @driver = driver
15
+ @chrome_pid = nil
16
+ @chrome_host = host
17
+ @chrome_port = port || find_available_port(host)
18
+ @remote = nil
19
+ @responses = {}
20
+ @last_response = nil
21
+ @frame_mutex = Mutex.new
22
+ @network_mutex = Mutex.new
23
+ @console_messages = []
24
+ @error_messages = []
25
+ @js_dialog_handlers = Hash.new {|h,key| h[key] = []}
26
+ @unrecognized_scheme_requests = []
27
+ @loader_ids = []
28
+ @loaded_loaders = {}
29
+ end
30
+
31
+ def start
32
+ start_chrome
33
+ start_remote
34
+ end
35
+
36
+ def evaluate_script(script, *args)
37
+ val = execute_script(script, *args)
38
+ val["result"]["value"]
39
+ end
40
+
41
+ def execute_script(script, *args)
42
+ default_options = {expression: script, includeCommandLineAPI: true, awaitPromise: true}
43
+ opts = args[0].respond_to?(:merge) ? args[0] : {}
44
+ opts = default_options.merge(opts)
45
+ val = remote.send_cmd "Runtime.evaluate", opts
46
+ debug script, val
47
+ if details = val["exceptionDetails"]
48
+ if details["exception"]["className"] == "NodeNotFoundError"
49
+ raise Capybara::ElementNotFound
50
+ else
51
+ raise JSException.new(details["exception"].inspect)
52
+ end
53
+ end
54
+ val
55
+ end
56
+
57
+ def execute_script!(script, options={})
58
+ remote.send_cmd!("Runtime.evaluate", {expression: script, includeCommandLineAPI: true}.merge(options))
59
+ end
60
+
61
+ def evaluate_async_script(script, *args)
62
+ raise "i dunno"
63
+ end
64
+
65
+ def wait_for_load
66
+ remote.send_cmd "DOM.getDocument"
67
+ loop do
68
+ val = evaluate_script %(window.ChromeRemotePageLoaded), awaitPromise: false
69
+ break val if val
70
+ end
71
+ end
72
+
73
+ def visit(path, attributes={})
74
+ uri = URI.parse(path)
75
+ if uri.scheme.nil?
76
+ uri.host = Capybara.current_session.server.host unless uri.host.present?
77
+ uri.port = Capybara.current_session.server.port unless uri.port.present?
78
+ end
79
+ debug ["visit #{uri}"]
80
+ @last_navigate = remote.send_cmd "Page.navigate", url: uri.to_s, transitionType: "typed"
81
+ wait_for_load
82
+ end
83
+
84
+ def with_retry(n:10, timeout: 0.05, &block)
85
+ skip_retry = [Errno::EPIPE, EOFError, ResponseTimeoutError]
86
+ begin
87
+ block.call
88
+ rescue => e
89
+ if n == 0 || skip_retry.detect {|klass| e.instance_of?(klass)}
90
+ raise e
91
+ else
92
+ puts "RETRYING #{e}"
93
+ sleep timeout
94
+ with_retry(n: n-1, timeout: timeout, &block)
95
+ end
96
+ end
97
+ end
98
+
99
+ def track_network_events
100
+ return if @track_network_events
101
+ remote.on("Network.requestWillBeSent") do |req|
102
+ if req["type"] == "Document"
103
+ if !RECOGNIZED_SCHEME.match req["request"]["url"]
104
+ puts "ADDING SCHEME"
105
+ @unrecognized_scheme_requests << req["request"]["url"]
106
+ else
107
+ @last_response = nil
108
+ end
109
+ end
110
+ end
111
+ remote.on("Network.responseReceived") do |params|
112
+ debug params["response"]["url"], params["requestId"], params["loaderId"], params["type"]
113
+ if params["type"] == "Document"
114
+ @responses[params["requestId"]] = params["response"]
115
+ @last_response = params["response"]
116
+ end
117
+ end
118
+ remote.on("Network.loadingFailed") do |params|
119
+ debug ["loadingFailed", params]
120
+ end
121
+ @track_network_events = true
122
+ end
123
+
124
+ def last_response
125
+ @last_response
126
+ end
127
+
128
+ def last_response_or_err
129
+ loop do
130
+ break last_response if last_response
131
+ remote.read_and_process(0.01)
132
+ end
133
+ rescue Timeout::Error
134
+ raise Capybara::ExpectationNotMet
135
+ end
136
+
137
+ def status_code
138
+ last_response_or_err["status"]
139
+ end
140
+
141
+ def current_url
142
+ document_root["documentURL"]
143
+ end
144
+
145
+ def unrecognized_scheme_requests
146
+ remote.read_and_process(1)
147
+ @unrecognized_scheme_requests
148
+ end
149
+
150
+ def has_body?(resp)
151
+ debug
152
+ if resp["root"] && resp["root"]["children"]
153
+ resp["root"]["children"].detect do |child|
154
+ next unless child.has_key?("children")
155
+ child["children"].detect do |grandchild|
156
+ grandchild["localName"] == "body"
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def get_document
163
+ val = remote.send_cmd "DOM.getDocument"
164
+ end
165
+
166
+ def document_root
167
+ @document_root = get_document["root"]
168
+ end
169
+
170
+ def root_node
171
+ @root_node = find_css("html")[0]
172
+ end
173
+
174
+ def unset_root_node
175
+ @root_node = nil
176
+ end
177
+
178
+ def html
179
+ val = root_node.html
180
+ debug "root", val.size
181
+ val
182
+ end
183
+
184
+ def find_css(query)
185
+ debug query
186
+ nodes = query_selector_all(query)
187
+ nodes
188
+ end
189
+
190
+ def query_selector_all(query, index=nil)
191
+ wait_for_load
192
+ query = query.dup
193
+ query.gsub!('"', '\"')
194
+ result = if index
195
+ evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findCssWithin(#{index}, "#{query}") )
196
+ else
197
+ evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findCss("#{query}") )
198
+ end
199
+ get_node_results result
200
+ end
201
+
202
+ # object_id represents a script that returned of an array of nodes
203
+ def request_nodes(object_id)
204
+ nodes = []
205
+ results = remote.send_cmd("Runtime.getProperties", objectId: object_id, ownProperties: true)
206
+ raise Capybara::ExpectationNotMet if results.nil?
207
+ results["result"].each do |prop|
208
+ if prop["value"]["subtype"] == "node"
209
+ lookup = remote.send_cmd("DOM.requestNode", objectId: prop["value"]["objectId"])
210
+ raise Capybara::ExpectationNotMet if lookup.nil?
211
+ id = lookup["nodeId"]
212
+ if id == 0
213
+ raise Capybara::ExpectationNotMet
214
+ else
215
+ nodes << Node.new(driver, self, id)
216
+ end
217
+ end
218
+ end
219
+ nodes
220
+ end
221
+
222
+ def get_node_results(result)
223
+ vals = result.split(",")
224
+ nodes = []
225
+ if vals.any?
226
+ nodes = result.split(",").map do |id|
227
+ Node.new driver, self, id.to_i
228
+ end
229
+ end
230
+ nodes
231
+ end
232
+
233
+ def find_xpath(query, index=nil)
234
+ wait_for_load
235
+ query = query.dup
236
+ query.gsub!('"', '\"')
237
+ result = if index
238
+ evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findXPathWithin(#{index}, "#{query}") )
239
+ else
240
+ evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.findXPath("#{query}") )
241
+ end
242
+ get_node_results result
243
+ end
244
+
245
+ def title
246
+ nodes = find_xpath("/html/head/title")
247
+ if nodes && nodes.first
248
+ nodes[0].text
249
+ else
250
+ ""
251
+ end
252
+ end
253
+
254
+ def start_remote
255
+ # @remote = ChromeRemoteClient.new(::ChromeRemote.send(:get_ws_url, {host: "localhost", port: @chrome_port}))
256
+ @remote = RDPClient.new chrome_host: @chrome_host, chrome_port: @chrome_port, browser: self
257
+ remote.start
258
+ after_remote_start
259
+ end
260
+
261
+ def after_remote_start
262
+ track_network_events
263
+ enable_console_log
264
+ # enable_lifecycle_events
265
+ enable_js_dialog
266
+ enable_script_debug
267
+ enable_network_interception
268
+ set_viewport(width: 1680, height: 1050)
269
+ end
270
+
271
+ def set_viewport(width:, height:, device_scale_factor: 1, mobile: false)
272
+ remote.send_cmd!("Emulation.setDeviceMetricsOverride", width: width, height: height, deviceScaleFactor: device_scale_factor, mobile: mobile)
273
+ end
274
+
275
+ def enable_network_interception
276
+ remote.send_cmd! "Network.setRequestInterception", patterns: [{urlPattern: "*"}]
277
+ remote.on("Network.requestIntercepted") do |params|
278
+ if Capybara::Chrome.configuration.block_url?(params["request"]["url"]) || (Capybara::Chrome.configuration.skip_image_loading? && params["resourceType"] == "Image")
279
+ # p ["blocking", params["request"]["url"]]
280
+ remote.send_cmd "Network.continueInterceptedRequest", interceptionId: params["interceptionId"], errorReason: "ConnectionRefused"
281
+ else
282
+ # p ["allowing", params["request"]["url"]]
283
+ remote.send_cmd "Network.continueInterceptedRequest", interceptionId: params["interceptionId"]
284
+ end
285
+ end
286
+ end
287
+
288
+ def enable_script_debug
289
+ remote.send_cmd "Debugger.enable"
290
+ remote.on("Debugger.scriptFailedToParse") do |params|
291
+ puts "\n\n!!! ERROR: SCRIPT FAILED TO PARSE !!!\n\n"
292
+ p params
293
+ end
294
+ end
295
+
296
+ def enable_js_dialog
297
+ remote.on("Page.javascriptDialogOpening") do |params|
298
+ debug ["Dialog Opening", params]
299
+ handler = @js_dialog_handlers[params["type"]].last
300
+ if handler
301
+ debug ["have handler", handler]
302
+ args = {accept: handler[:accept]}
303
+ args.merge!(promptText: handler[:prompt_text]) if params[:type] == "prompt"
304
+ remote.send_cmd("Page.handleJavaScriptDialog", args)
305
+ @js_dialog_handlers[params["type"]].delete(params["type"].size - 1)
306
+ else
307
+ puts "WARNING: Accepting unhandled modal. Use #accept_modal or #dismiss_modal to handle this modal properly."
308
+ remote.send_cmd("Page.handleJavaScriptDialog", accept: true)
309
+ end
310
+ end
311
+ end
312
+
313
+ def accept_modal(type, text_or_options=nil, options={}, &block)
314
+ @js_dialog_handlers[type.to_s] << {accept: true}
315
+ block.call if block
316
+ end
317
+
318
+ def dismiss_modal(type, text_or_options=nil, options={}, &block)
319
+ @js_dialog_handlers[type.to_s] << {accept: false}
320
+ block.call if block
321
+ debug [type, text_or_options, options]
322
+ end
323
+
324
+ def enable_console_log
325
+ remote.send_cmd! "Console.enable"
326
+ remote.on "Console.messageAdded" do |params|
327
+ str = "#{params["message"]["source"]}:#{params["message"]["line"]} #{params["message"]["text"]}"
328
+ if params["message"]["level"] == "error"
329
+ @error_messages << str
330
+ else
331
+ @console_messages << str
332
+ end
333
+ end
334
+ end
335
+
336
+ def enable_lifecycle_events
337
+ remote.send_cmd! "Page.setLifecycleEventsEnabled", enabled: true
338
+ remote.on("Page.lifecycleEvent") do |params|
339
+ if params["name"] == "init"
340
+ @loader_ids.push(params["loaderId"])
341
+ elsif params["name"] == "load"
342
+ @loaded_loaders[params["loaderId"]] = true
343
+ elsif params["name"] == "networkIdle"
344
+ end
345
+ end
346
+ end
347
+
348
+ def loader_loaded?(loader_id)
349
+ @loaded_loaders[loader_id]
350
+ end
351
+
352
+ def save_screenshot(path, options={})
353
+ options[:width] ||= 1000
354
+ options[:height] ||= 10
355
+ render path, options[:width], options[:height]
356
+ end
357
+
358
+ def render(path, width=nil, height=nil)
359
+ response = remote.send_cmd "Page.getLayoutMetrics"
360
+ width = response["contentSize"]["width"]
361
+ height = response["contentSize"]["height"]
362
+ response = remote.send_cmd "Page.captureScreenshot", clip: {width: width, height: height, x: 0, y: 0, scale: 1}
363
+ File.open path, "wb" do |f|
364
+ f.write Base64.decode64(response["data"])
365
+ end
366
+ end
367
+
368
+ def header(key, value)
369
+ if key.downcase == "user-agent"
370
+ remote.send_cmd!("Network.setUserAgentOverride", userAgent: value)
371
+ else
372
+ remote.send_cmd!("Network.setExtraHTTPHeaders", headers: {key => value})
373
+ end
374
+ end
375
+
376
+ def reset
377
+ unset_root_node
378
+ @responses.clear
379
+ @last_response = nil
380
+ @console_messages.clear
381
+ @error_messages.clear
382
+ @js_dialog_handlers.clear
383
+ @unrecognized_scheme_requests.clear
384
+ remote.reset
385
+ remote.send_cmd! "Network.clearBrowserCookies"
386
+ remote.send_cmd! "Runtime.discardConsoleEntries"
387
+ remote.send_cmd! "Network.setExtraHTTPHeaders", headers: {}
388
+ remote.send_cmd! "Network.setUserAgentOverride", userAgent: ""
389
+ visit "about:blank"
390
+ end
391
+ end
392
+
393
+ end