ferrum 0.12 → 0.14

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +28 -22
  3. data/lib/ferrum/browser/client.rb +6 -5
  4. data/lib/ferrum/browser/command.rb +9 -6
  5. data/lib/ferrum/browser/options/base.rb +1 -4
  6. data/lib/ferrum/browser/options/chrome.rb +22 -10
  7. data/lib/ferrum/browser/options/firefox.rb +3 -6
  8. data/lib/ferrum/browser/options.rb +84 -0
  9. data/lib/ferrum/browser/process.rb +6 -7
  10. data/lib/ferrum/browser/version_info.rb +71 -0
  11. data/lib/ferrum/browser/web_socket.rb +1 -1
  12. data/lib/ferrum/browser/xvfb.rb +1 -1
  13. data/lib/ferrum/browser.rb +184 -64
  14. data/lib/ferrum/context.rb +3 -2
  15. data/lib/ferrum/contexts.rb +2 -2
  16. data/lib/ferrum/cookies/cookie.rb +183 -0
  17. data/lib/ferrum/cookies.rb +122 -49
  18. data/lib/ferrum/dialog.rb +30 -0
  19. data/lib/ferrum/frame/dom.rb +177 -0
  20. data/lib/ferrum/frame/runtime.rb +41 -61
  21. data/lib/ferrum/frame.rb +91 -3
  22. data/lib/ferrum/headers.rb +28 -0
  23. data/lib/ferrum/keyboard.rb +45 -2
  24. data/lib/ferrum/mouse.rb +84 -0
  25. data/lib/ferrum/network/exchange.rb +104 -5
  26. data/lib/ferrum/network/intercepted_request.rb +3 -12
  27. data/lib/ferrum/network/request.rb +58 -19
  28. data/lib/ferrum/network/request_params.rb +57 -0
  29. data/lib/ferrum/network/response.rb +106 -4
  30. data/lib/ferrum/network.rb +193 -8
  31. data/lib/ferrum/node.rb +21 -1
  32. data/lib/ferrum/page/animation.rb +16 -0
  33. data/lib/ferrum/page/frames.rb +66 -11
  34. data/lib/ferrum/page/screenshot.rb +97 -0
  35. data/lib/ferrum/page/tracing.rb +26 -0
  36. data/lib/ferrum/page.rb +158 -45
  37. data/lib/ferrum/proxy.rb +91 -2
  38. data/lib/ferrum/target.rb +6 -4
  39. data/lib/ferrum/version.rb +1 -1
  40. metadata +7 -101
@@ -5,12 +5,54 @@ require "ferrum/frame"
5
5
  module Ferrum
6
6
  class Page
7
7
  module Frames
8
+ # The page's main frame, the top of the tree and the parent of all frames.
9
+ #
10
+ # @return [Frame]
8
11
  attr_reader :main_frame
9
12
 
13
+ #
14
+ # Returns all the frames current page have.
15
+ #
16
+ # @return [Array<Frame>]
17
+ #
18
+ # @example
19
+ # browser.go_to("https://www.w3schools.com/tags/tag_frame.asp")
20
+ # browser.frames # =>
21
+ # # [
22
+ # # #<Ferrum::Frame
23
+ # # @id="C6D104CE454A025FBCF22B98DE612B12"
24
+ # # @parent_id=nil @name=nil @state=:stopped_loading @execution_id=1>,
25
+ # # #<Ferrum::Frame
26
+ # # @id="C09C4E4404314AAEAE85928EAC109A93"
27
+ # # @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=2>,
28
+ # # #<Ferrum::Frame
29
+ # # @id="2E9C7F476ED09D87A42F2FEE3C6FBC3C"
30
+ # # @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=3>,
31
+ # # ...
32
+ # # ]
33
+ #
10
34
  def frames
11
35
  @frames.values
12
36
  end
13
37
 
38
+ #
39
+ # Find frame by given options.
40
+ #
41
+ # @param [String] id
42
+ # Unique frame's id that browser provides.
43
+ #
44
+ # @param [String] name
45
+ # Frame's name if there's one.
46
+ #
47
+ # @param [String] execution_id
48
+ # Frame's context execution id.
49
+ #
50
+ # @return [Frame, nil]
51
+ # The matching frame.
52
+ #
53
+ # @example
54
+ # browser.frame_by(id: "C6D104CE454A025FBCF22B98DE612B12")
55
+ #
14
56
  def frame_by(id: nil, name: nil, execution_id: nil)
15
57
  if id
16
58
  @frames[id]
@@ -25,6 +67,7 @@ module Ferrum
25
67
 
26
68
  def frames_subscribe
27
69
  subscribe_frame_attached
70
+ subscribe_frame_detached
28
71
  subscribe_frame_started_loading
29
72
  subscribe_frame_navigated
30
73
  subscribe_frame_stopped_loading
@@ -43,14 +86,26 @@ module Ferrum
43
86
  def subscribe_frame_attached
44
87
  on("Page.frameAttached") do |params|
45
88
  parent_frame_id, frame_id = params.values_at("parentFrameId", "frameId")
46
- @frames[frame_id] = Frame.new(frame_id, self, parent_frame_id)
89
+ @frames.put_if_absent(frame_id, Frame.new(frame_id, self, parent_frame_id))
90
+ end
91
+ end
92
+
93
+ def subscribe_frame_detached
94
+ on("Page.frameDetached") do |params|
95
+ frame = @frames[params["frameId"]]
96
+
97
+ if frame&.main?
98
+ frame.execution_id = nil
99
+ else
100
+ @frames.delete(params["frameId"])
101
+ end
47
102
  end
48
103
  end
49
104
 
50
105
  def subscribe_frame_started_loading
51
106
  on("Page.frameStartedLoading") do |params|
52
107
  frame = @frames[params["frameId"]]
53
- frame.state = :started_loading
108
+ frame.state = :started_loading if frame
54
109
  @event.reset
55
110
  end
56
111
  end
@@ -59,8 +114,11 @@ module Ferrum
59
114
  on("Page.frameNavigated") do |params|
60
115
  frame_id, name = params["frame"]&.values_at("id", "name")
61
116
  frame = @frames[frame_id]
62
- frame.state = :navigated
63
- frame.name = name unless name.to_s.empty?
117
+
118
+ if frame
119
+ frame.state = :navigated
120
+ frame.name = name
121
+ end
64
122
  end
65
123
  end
66
124
 
@@ -107,14 +165,12 @@ module Ferrum
107
165
  root_frame = command("Page.getFrameTree").dig("frameTree", "frame", "id")
108
166
  if frame_id == root_frame
109
167
  @main_frame.id = frame_id
110
- @frames[frame_id] = @main_frame
168
+ @frames.put_if_absent(frame_id, @main_frame)
111
169
  end
112
170
  end
113
171
 
114
- frame = @frames[frame_id] || Frame.new(frame_id, self)
172
+ frame = @frames.fetch_or_store(frame_id, Frame.new(frame_id, self))
115
173
  frame.execution_id = context_id
116
-
117
- @frames[frame_id] ||= frame
118
174
  end
119
175
  end
120
176
 
@@ -128,13 +184,12 @@ module Ferrum
128
184
 
129
185
  def subscribe_execution_contexts_cleared
130
186
  on("Runtime.executionContextsCleared") do
131
- @frames.delete_if { |_, f| !f.main? }
132
- @main_frame.execution_id = nil
187
+ @frames.each_value { |f| f.execution_id = nil }
133
188
  end
134
189
  end
135
190
 
136
191
  def idling?
137
- @frames.all? { |_, f| f.state == :stopped_loading }
192
+ @frames.values.all? { |f| f.state == :stopped_loading }
138
193
  end
139
194
  end
140
195
  end
@@ -26,6 +26,51 @@ module Ferrum
26
26
  A6: { width: 4.13, height: 5.83 }
27
27
  }.freeze
28
28
 
29
+ #
30
+ # Saves screenshot on a disk or returns it as base64.
31
+ #
32
+ # @param [Hash{Symbol => Object}] opts
33
+ #
34
+ # @option opts [String] :path
35
+ # The path to save a screenshot on the disk. `:encoding` will be set to
36
+ # `:binary` automatically.
37
+ #
38
+ # @option opts [:base64, :binary] :encoding
39
+ # The encoding the image should be returned in.
40
+ #
41
+ # @option opts ["jpeg", "png"] :format
42
+ # The format the image should be returned in.
43
+ #
44
+ # @option opts [Integer] :quality
45
+ # The image quality. **Note:** 0-100 works for jpeg only.
46
+ #
47
+ # @option opts [Boolean] :full
48
+ # Whether you need full page screenshot or a viewport.
49
+ #
50
+ # @option opts [String] :selector
51
+ # CSS selector for the given element.
52
+ #
53
+ # @option opts [Float] :scale
54
+ # Zoom in/out.
55
+ #
56
+ # @option opts [Ferrum::RGBA] :background_color
57
+ # Sets the background color.
58
+ #
59
+ # @example
60
+ # browser.go_to("https://google.com/")
61
+ #
62
+ # @example Save on the disk in PNG:
63
+ # browser.screenshot(path: "google.png") # => 134660
64
+ #
65
+ # @example Save on the disk in JPG:
66
+ # browser.screenshot(path: "google.jpg") # => 30902
67
+ #
68
+ # @example Save to Base64 the whole page not only viewport and reduce quality:
69
+ # browser.screenshot(full: true, quality: 60) # "iVBORw0KGgoAAAANS...
70
+ #
71
+ # @example Save with specific background color:
72
+ # browser.screenshot(background_color: Ferrum::RGBA.new(0, 0, 0, 0.0))
73
+ #
29
74
  def screenshot(**opts)
30
75
  path, encoding = common_options(**opts)
31
76
  options = screenshot_options(path, **opts)
@@ -36,6 +81,42 @@ module Ferrum
36
81
  save_file(path, bin)
37
82
  end
38
83
 
84
+ #
85
+ # Saves PDF on a disk or returns it as Base64.
86
+ #
87
+ # @param [Hash{Symbol => Object}] opts
88
+ #
89
+ # @option opts [String] :path
90
+ # The path to save a screenshot on the disk. `:encoding` will be set to
91
+ # `:binary` automatically.
92
+ #
93
+ # @option opts [:base64, :binary] :encoding
94
+ # The encoding the image should be returned in.
95
+ #
96
+ # @option opts [Boolean] :landscape (false)
97
+ # Page orientation.
98
+ #
99
+ # @option opts [Float] :scale
100
+ # Zoom in/out.
101
+ #
102
+ # @option opts [:letter, :legal, :tabloid, :ledger, :A0, :A1, :A2, :A3, :A4, :A5, :A6] :format
103
+ # The standard paper size.
104
+ #
105
+ # @option opts [Float] :paper_width
106
+ # Sets the paper's width.
107
+ #
108
+ # @option opts [Float] :paper_height
109
+ # Sets the paper's height.
110
+ #
111
+ # @note
112
+ # See other [native options](https://chromedevtools.github.io/devtools-protocol/tot/Page#method-printToPDF) you
113
+ # can pass.
114
+ #
115
+ # @example
116
+ # browser.go_to("https://google.com/")
117
+ # # Save to disk as a PDF
118
+ # browser.pdf(path: "google.pdf", paper_width: 1.0, paper_height: 1.0) # => true
119
+ #
39
120
  def pdf(**opts)
40
121
  path, encoding = common_options(**opts)
41
122
  options = pdf_options(**opts).merge(transferMode: "ReturnAsStream")
@@ -43,6 +124,16 @@ module Ferrum
43
124
  stream_to(path: path, encoding: encoding, handle: handle)
44
125
  end
45
126
 
127
+ #
128
+ # Saves MHTML on a disk or returns it as a string.
129
+ #
130
+ # @param [String, nil] path
131
+ # The path to save a file on the disk.
132
+ #
133
+ # @example
134
+ # browser.go_to("https://google.com/")
135
+ # browser.mhtml(path: "google.mhtml") # => 87742
136
+ #
46
137
  def mhtml(path: nil)
47
138
  data = command("Page.captureSnapshot", format: :mhtml).fetch("data")
48
139
  return data if path.nil?
@@ -56,6 +147,12 @@ module Ferrum
56
147
  JS
57
148
  end
58
149
 
150
+ def device_pixel_ratio
151
+ evaluate <<~JS
152
+ window.devicePixelRatio
153
+ JS
154
+ end
155
+
59
156
  def document_size
60
157
  evaluate <<~JS
61
158
  [document.documentElement.scrollWidth,
@@ -19,6 +19,32 @@ module Ferrum
19
19
  @subscribed_tracing_complete = false
20
20
  end
21
21
 
22
+ #
23
+ # Accepts block, records trace and by default returns trace data from `Tracing.tracingComplete` event as output.
24
+ #
25
+ # @param [String, nil] path
26
+ # Save data on the disk.
27
+ #
28
+ # @param [:binary, :base64] encoding
29
+ # Encode output as Base64 or plain text.
30
+ #
31
+ # @param [Float, nil] timeout
32
+ # Wait until file streaming finishes in the specified time or raise
33
+ # error.
34
+ #
35
+ # @param [Boolean] screenshots
36
+ # capture screenshots in the trace.
37
+ #
38
+ # @param [Hash{String => Object}] trace_config
39
+ # config for [trace](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#type-TraceConfig),
40
+ # for categories see [getCategories](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#method-getCategories),
41
+ # only one trace config can be active at a time per browser.
42
+ #
43
+ # @return [String, true]
44
+ # The trace data from the `Tracing.tracingComplete` event.
45
+ # When `path` is specified returns `true` and stores trace data into
46
+ # file.
47
+ #
22
48
  def record(path: nil, encoding: :binary, timeout: nil, trace_config: nil, screenshots: false)
23
49
  @path = path
24
50
  @encoding = encoding
data/lib/ferrum/page.rb CHANGED
@@ -35,7 +35,7 @@ module Ferrum
35
35
  extend Forwardable
36
36
  delegate %i[at_css at_xpath css xpath
37
37
  current_url current_title url title body doctype content=
38
- execution_id evaluate evaluate_on evaluate_async execute evaluate_func
38
+ execution_id execution_id! evaluate evaluate_on evaluate_async execute evaluate_func
39
39
  add_script_tag add_style_tag] => :main_frame
40
40
 
41
41
  include Animation
@@ -43,23 +43,47 @@ module Ferrum
43
43
  include Frames
44
44
  include Stream
45
45
 
46
- attr_accessor :referrer
47
- attr_reader :target_id, :browser,
48
- :headers, :cookies, :network,
49
- :mouse, :keyboard, :event,
50
- :tracing
46
+ attr_accessor :referrer, :timeout
47
+ attr_reader :target_id, :browser, :event, :tracing
51
48
 
52
- def initialize(target_id, browser)
53
- @frames = {}
49
+ # Mouse object.
50
+ #
51
+ # @return [Mouse]
52
+ attr_reader :mouse
53
+
54
+ # Keyboard object.
55
+ #
56
+ # @return [Keyboard]
57
+ attr_reader :keyboard
58
+
59
+ # Network object.
60
+ #
61
+ # @return [Network]
62
+ attr_reader :network
63
+
64
+ # Headers object.
65
+ #
66
+ # @return [Headers]
67
+ attr_reader :headers
68
+
69
+ # Cookie store.
70
+ #
71
+ # @return [Cookies]
72
+ attr_reader :cookies
73
+
74
+ def initialize(target_id, browser, proxy: nil)
75
+ @frames = Concurrent::Map.new
54
76
  @main_frame = Frame.new(nil, self)
55
77
  @browser = browser
56
78
  @target_id = target_id
79
+ @timeout = @browser.timeout
57
80
  @event = Event.new.tap(&:set)
81
+ self.proxy = proxy
58
82
 
59
- host = @browser.process.host
60
- port = @browser.process.port
61
- ws_url = "ws://#{host}:#{port}/devtools/page/#{@target_id}"
62
- @client = Browser::Client.new(browser, ws_url, id_starts_with: 1000)
83
+ @client = Browser::Client.new(ws_url, self,
84
+ logger: @browser.options.logger,
85
+ ws_max_receive_size: @browser.options.ws_max_receive_size,
86
+ id_starts_with: 1000)
63
87
 
64
88
  @mouse = Mouse.new(self)
65
89
  @keyboard = Keyboard.new(self)
@@ -72,30 +96,31 @@ module Ferrum
72
96
  prepare_page
73
97
  end
74
98
 
75
- def timeout
76
- @browser.timeout
77
- end
78
-
79
99
  def context
80
100
  @browser.contexts.find_by(target_id: target_id)
81
101
  end
82
102
 
103
+ #
104
+ # Navigates the page to a URL.
105
+ #
106
+ # @param [String, nil] url
107
+ # The URL to navigate to. The url should include scheme unless you set
108
+ # `{Browser#base_url = url}` when configuring driver.
109
+ #
110
+ # @example
111
+ # browser.go_to("https://github.com/")
112
+ #
83
113
  def go_to(url = nil)
84
114
  options = { url: combine_url!(url) }
85
115
  options.merge!(referrer: referrer) if referrer
86
116
  response = command("Page.navigate", wait: GOTO_WAIT, **options)
87
- # https://cs.chromium.org/chromium/src/net/base/net_error_list.h
88
- if %w[net::ERR_NAME_NOT_RESOLVED
89
- net::ERR_NAME_RESOLUTION_FAILED
90
- net::ERR_INTERNET_DISCONNECTED
91
- net::ERR_CONNECTION_TIMED_OUT].include?(response["errorText"])
92
- raise StatusError, options[:url]
93
- end
117
+ error_text = response["errorText"]
118
+ raise StatusError.new(options[:url], "Request to #{options[:url]} failed (#{error_text})") if error_text
94
119
 
95
120
  response["frameId"]
96
121
  rescue TimeoutError
97
- if @browser.pending_connection_errors
98
- pendings = network.traffic.select(&:pending?).map { |e| e.request.url }
122
+ if @browser.options.pending_connection_errors
123
+ pendings = network.traffic.select(&:pending?).map(&:url).compact
99
124
  raise PendingConnectionsError.new(options[:url], pendings) unless pendings.empty?
100
125
  end
101
126
  end
@@ -120,34 +145,87 @@ module Ferrum
120
145
  command("Emulation.setDeviceMetricsOverride", slowmoable: true,
121
146
  width: width,
122
147
  height: height,
123
- deviceScaleFactor: 1,
124
- mobile: false,
125
- fitWindow: false)
148
+ deviceScaleFactor: 0,
149
+ mobile: false)
126
150
  end
127
151
 
152
+ #
153
+ # The current position of the browser window.
154
+ #
155
+ # @return [(Integer, Integer)]
156
+ # The left, top coordinates of the browser window.
157
+ #
158
+ # @example
159
+ # browser.position # => [10, 20]
160
+ #
128
161
  def position
129
162
  @browser.command("Browser.getWindowBounds", windowId: window_id).fetch("bounds").values_at("left", "top")
130
163
  end
131
164
 
165
+ #
166
+ # Sets the position of the browser window.
167
+ #
168
+ # @param [Hash{Symbol => Object}] options
169
+ #
170
+ # @option options [Integer] :left
171
+ # The number of pixels from the left-hand side of the screen.
172
+ #
173
+ # @option options [Integer] :top
174
+ # The number of pixels from the top of the screen.
175
+ #
176
+ # @example
177
+ # browser.position = { left: 10, top: 20 }
178
+ #
132
179
  def position=(options)
133
180
  @browser.command("Browser.setWindowBounds",
134
181
  windowId: window_id,
135
182
  bounds: { left: options[:left], top: options[:top] })
136
183
  end
137
184
 
185
+ #
186
+ # Reloads the current page.
187
+ #
188
+ # @example
189
+ # browser.go_to("https://github.com/")
190
+ # browser.refresh
191
+ #
138
192
  def refresh
139
193
  command("Page.reload", wait: timeout, slowmoable: true)
140
194
  end
141
195
  alias reload refresh
142
196
 
197
+ #
198
+ # Stop all navigations and loading pending resources on the page.
199
+ #
200
+ # @example
201
+ # browser.go_to("https://github.com/")
202
+ # browser.stop
203
+ #
143
204
  def stop
144
205
  command("Page.stopLoading", slowmoable: true)
145
206
  end
146
207
 
208
+ #
209
+ # Navigates to the previous URL in the browser's history.
210
+ #
211
+ # @example
212
+ # browser.go_to("https://github.com/")
213
+ # browser.at_xpath("//a").click
214
+ # browser.back
215
+ #
147
216
  def back
148
217
  history_navigate(delta: -1)
149
218
  end
150
219
 
220
+ #
221
+ # Navigates to the next URL in the browser's history.
222
+ #
223
+ # @example
224
+ # browser.go_to("https://github.com/")
225
+ # browser.at_xpath("//a").click
226
+ # browser.back
227
+ # browser.forward
228
+ #
151
229
  def forward
152
230
  history_navigate(delta: 1)
153
231
  end
@@ -158,6 +236,20 @@ module Ferrum
158
236
  @event.set
159
237
  end
160
238
 
239
+ #
240
+ # Enables/disables CSP bypass.
241
+ #
242
+ # @param [Boolean] enabled
243
+ #
244
+ # @return [Boolean]
245
+ #
246
+ # @example
247
+ # browser.bypass_csp # => true
248
+ # browser.go_to("https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md")
249
+ # browser.refresh
250
+ # browser.add_script_tag(content: "window.__injected = 42")
251
+ # browser.evaluate("window.__injected") # => 42
252
+ #
161
253
  def bypass_csp(enabled: true)
162
254
  command("Page.setBypassCSP", enabled: enabled)
163
255
  enabled
@@ -173,16 +265,16 @@ module Ferrum
173
265
 
174
266
  def command(method, wait: 0, slowmoable: false, **params)
175
267
  iteration = @event.reset if wait.positive?
176
- sleep(@browser.slowmo) if slowmoable && @browser.slowmo.positive?
268
+ sleep(@browser.options.slowmo) if slowmoable && @browser.options.slowmo.positive?
177
269
  result = @client.command(method, params)
178
270
 
179
271
  if wait.positive?
180
- @event.wait(wait)
181
272
  # Wait a bit after command and check if iteration has
182
273
  # changed which means there was some network event for
183
274
  # the main frame and it started to load new content.
275
+ @event.wait(wait)
184
276
  if iteration != @event.iteration
185
- set = @event.wait(@browser.timeout)
277
+ set = @event.wait(timeout)
186
278
  raise TimeoutError unless set
187
279
  end
188
280
  end
@@ -218,19 +310,31 @@ module Ferrum
218
310
  @client.subscribed?(event)
219
311
  end
220
312
 
313
+ def use_proxy?
314
+ @proxy_host && @proxy_port
315
+ end
316
+
317
+ def use_authorized_proxy?
318
+ use_proxy? && @proxy_user && @proxy_password
319
+ end
320
+
321
+ def document_node_id
322
+ command("DOM.getDocument", depth: 0).dig("root", "nodeId")
323
+ end
324
+
221
325
  private
222
326
 
223
327
  def subscribe
224
328
  frames_subscribe
225
329
  network.subscribe
226
330
 
227
- if @browser.logger
331
+ if @browser.options.logger
228
332
  on("Runtime.consoleAPICalled") do |params|
229
- params["args"].each { |r| @browser.logger.puts(r["value"]) }
333
+ params["args"].each { |r| @browser.options.logger.puts(r["value"]) }
230
334
  end
231
335
  end
232
336
 
233
- if @browser.js_errors
337
+ if @browser.options.js_errors
234
338
  on("Runtime.exceptionThrown") do |params|
235
339
  # FIXME: https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
236
340
  Thread.main.raise JavaScriptError.new(
@@ -243,7 +347,7 @@ module Ferrum
243
347
  on(:dialog) do |dialog, _index, total|
244
348
  if total == 1
245
349
  warn "Dialog was shown but you didn't provide `on(:dialog)` callback, accepting it by default. " \
246
- "Please take a look at https://github.com/rubycdp/ferrum#dialog"
350
+ "Please take a look at https://github.com/rubycdp/ferrum#dialogs"
247
351
  dialog.accept
248
352
  end
249
353
  end
@@ -257,21 +361,22 @@ module Ferrum
257
361
  command("Log.enable")
258
362
  command("Network.enable")
259
363
 
260
- if @browser.proxy_options && @browser.proxy_options[:user] && @browser.proxy_options[:password]
261
- auth_options = @browser.proxy_options.slice(:user, :password)
262
- network.authorize(type: :proxy, **auth_options) do |request, _index, _total|
364
+ if use_authorized_proxy?
365
+ network.authorize(user: @proxy_user,
366
+ password: @proxy_password,
367
+ type: :proxy) do |request, _index, _total|
263
368
  request.continue
264
369
  end
265
370
  end
266
371
 
267
- if @browser.options[:save_path]
268
- unless Pathname.new(@browser.options[:save_path]).absolute?
372
+ if @browser.options.save_path
373
+ unless Pathname.new(@browser.options.save_path).absolute?
269
374
  raise Error, "supply absolute path for `:save_path` option"
270
375
  end
271
376
 
272
377
  @browser.command("Browser.setDownloadBehavior",
273
378
  browserContextId: context.id,
274
- downloadPath: browser.options[:save_path],
379
+ downloadPath: @browser.options.save_path,
275
380
  behavior: "allow", eventsEnabled: true)
276
381
  end
277
382
 
@@ -285,7 +390,8 @@ module Ferrum
285
390
  resize(width: width, height: height)
286
391
 
287
392
  response = command("Page.getNavigationHistory")
288
- return unless response.dig("entries", 0, "transitionType") != "typed"
393
+ transition_type = response.dig("entries", 0, "transitionType")
394
+ return if transition_type == "auto_toplevel"
289
395
 
290
396
  # If we create page by clicking links, submitting forms and so on it
291
397
  # opens a new window for which `frameStoppedLoading` event never
@@ -303,7 +409,7 @@ module Ferrum
303
409
  # We also evaluate script just in case because
304
410
  # `Page.addScriptToEvaluateOnNewDocument` doesn't work in popups.
305
411
  command("Runtime.evaluate", expression: extension,
306
- contextId: execution_id,
412
+ executionContextId: execution_id!,
307
413
  returnByValue: true)
308
414
  end
309
415
  end
@@ -333,8 +439,15 @@ module Ferrum
333
439
  (nil_or_relative ? @browser.base_url.join(url.to_s) : url).to_s
334
440
  end
335
441
 
336
- def document_node_id
337
- command("DOM.getDocument", depth: 0).dig("root", "nodeId")
442
+ def ws_url
443
+ "ws://#{@browser.process.host}:#{@browser.process.port}/devtools/page/#{@target_id}"
444
+ end
445
+
446
+ def proxy=(options)
447
+ @proxy_host = options&.[](:host) || @browser.options.proxy&.[](:host)
448
+ @proxy_port = options&.[](:port) || @browser.options.proxy&.[](:port)
449
+ @proxy_user = options&.[](:user) || @browser.options.proxy&.[](:user)
450
+ @proxy_password = options&.[](:password) || @browser.options.proxy&.[](:password)
338
451
  end
339
452
  end
340
453
  end