ferrum 0.12 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
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