Class: Ferrum::Page

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Interceptable, Animation, Frames, Screencast, Screenshot, Stream
Defined in:
lib/ferrum/page.rb,
lib/ferrum/page/frames.rb,
lib/ferrum/page/stream.rb,
lib/ferrum/page/tracing.rb,
lib/ferrum/page/animation.rb,
lib/ferrum/page/screencast.rb,
lib/ferrum/page/screenshot.rb

Overview

Represents a single browser tab (a CDP target of type page). Owns the tab's Mouse, Keyboard, Network, Cookies, Headers, Downloads and Accessibility helpers, as well as its frame tree (see the included Frames module), and is the object that navigation, DOM search and JavaScript evaluation methods are ultimately delegated to from Browser.

Defined Under Namespace

Modules: Animation, Frames, Screencast, Screenshot, Stream Classes: Tracing

Constant Summary collapse

GOTO_WAIT =
ENV.fetch("FERRUM_GOTO_WAIT", 0.1).to_f

Constants included from Stream

Stream::STREAM_CHUNK

Constants included from Screenshot

Screenshot::AREA_WARNING, Screenshot::DEFAULT_PDF_OPTIONS, Screenshot::DEFAULT_RENDER_TIMEOUT, Screenshot::DEFAULT_SCREENSHOT_FORMAT, Screenshot::FULL_WARNING, Screenshot::PAPER_FORMATS, Screenshot::SUPPORTED_SCREENSHOT_FORMAT

Constants included from Screencast

Screencast::START_SCREENCAST_KEY_CONV

Instance Attribute Summary collapse

Attributes included from Frames

#main_frame

Instance Method Summary collapse

Methods included from Interceptable

#subscribed?

Methods included from Stream

#stream, #stream_to, #stream_to_file, #stream_to_memory

Methods included from Frames

#frame_by, #frames

Methods included from Screenshot

#device_pixel_ratio, #document_size, #mhtml, #pdf, #screenshot, #viewport_size

Methods included from Screencast

#start_screencast, #stop_screencast

Methods included from Animation

#playback_rate, #playback_rate=

Constructor Details

#initialize(client, context_id:, target_id:, proxy: nil) ⇒ Page

Returns a new instance of Page.



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/ferrum/page.rb', line 90

def initialize(client, context_id:, target_id:, proxy: nil)
  @client = client
  @context_id = context_id
  @target_id = target_id
  @options = client.options

  @frames = Concurrent::Map.new
  @main_frame = Frame.new(nil, self)
  @event = Utils::Event.new.tap(&:set)
  self.proxy = proxy

  @mouse = Mouse.new(self)
  @keyboard = Keyboard.new(self)
  @headers = Headers.new(self)
  @cookies = Cookies.new(self)
  @network = Network.new(self)
  @accessibility = Accessibility.new(self)
  @tracing = Tracing.new(self)
  @downloads = Downloads.new(self)

  subscribe
  prepare_page
end

Instance Attribute Details

#accessibilityAccessibility (readonly)

Accessibility object.

Returns:



73
74
75
# File 'lib/ferrum/page.rb', line 73

def accessibility
  @accessibility
end

#clientClient (readonly)

Client connection.

Returns:



53
54
55
# File 'lib/ferrum/page.rb', line 53

def client
  @client
end

#context_idObject (readonly)

Returns the value of attribute context_id.



48
49
50
# File 'lib/ferrum/page.rb', line 48

def context_id
  @context_id
end

#cookiesCookies (readonly)

Cookie store.

Returns:



83
84
85
# File 'lib/ferrum/page.rb', line 83

def cookies
  @cookies
end

#downloadsDownloads (readonly)

Downloads object.

Returns:



88
89
90
# File 'lib/ferrum/page.rb', line 88

def downloads
  @downloads
end

#eventObject (readonly)

Returns the value of attribute event.



48
49
50
# File 'lib/ferrum/page.rb', line 48

def event
  @event
end

#headersHeaders (readonly)

Headers object.

Returns:



78
79
80
# File 'lib/ferrum/page.rb', line 78

def headers
  @headers
end

#keyboardKeyboard (readonly)

Keyboard object.

Returns:



63
64
65
# File 'lib/ferrum/page.rb', line 63

def keyboard
  @keyboard
end

#mouseMouse (readonly)

Mouse object.

Returns:



58
59
60
# File 'lib/ferrum/page.rb', line 58

def mouse
  @mouse
end

#networkNetwork (readonly)

Network object.

Returns:



68
69
70
# File 'lib/ferrum/page.rb', line 68

def network
  @network
end

#referrerObject

Returns the value of attribute referrer.



47
48
49
# File 'lib/ferrum/page.rb', line 47

def referrer
  @referrer
end

#target_idObject (readonly)

Returns the value of attribute target_id.



48
49
50
# File 'lib/ferrum/page.rb', line 48

def target_id
  @target_id
end

#tracingObject (readonly)

Returns the value of attribute tracing.



48
49
50
# File 'lib/ferrum/page.rb', line 48

def tracing
  @tracing
end

Instance Method Details

#activateBoolean

Activates (focuses) the target for the given page. When you have multiple tabs you work with, and you need to switch a given one.

Examples:

page.activate # => true

Returns:

  • (Boolean)


409
410
411
412
# File 'lib/ferrum/page.rb', line 409

def activate
  command("Target.activateTarget", targetId: target_id)
  true
end

#backObject

Navigates to the previous URL in the history.

Examples:

page.go_to("https://github.com/")
page.at_xpath("//a").click
page.back


349
350
351
# File 'lib/ferrum/page.rb', line 349

def back
  history_navigate(delta: -1)
end

#bypass_csp(enabled: true) ⇒ Boolean

Enables/disables CSP bypass.

Examples:

page.bypass_csp # => true
page.go_to("https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md")
page.refresh
page.add_script_tag(content: "window.__injected = 42")
page.evaluate("window.__injected") # => 42

Parameters:

  • enabled (Boolean) (defaults to: true)

Returns:

  • (Boolean)


395
396
397
398
# File 'lib/ferrum/page.rb', line 395

def bypass_csp(enabled: true)
  command("Page.setBypassCSP", enabled: enabled)
  enabled
end

#closeBoolean

Closes the page's target and its underlying client connection.

Examples:

page.close # => true

Returns:

  • (Boolean)


151
152
153
154
155
156
157
# File 'lib/ferrum/page.rb', line 151

def close
  @headers.clear
  client.command("Target.closeTarget", async: true, targetId: @target_id)
  close_connection

  true
end

#close_connectionObject

Closes the underlying client connection only, without closing the target itself. Useful when you want to detach from a page without ending the browser tab it represents.



164
165
166
# File 'lib/ferrum/page.rb', line 164

def close_connection
  client&.close
end

#command(method, wait: 0, slowmoable: false, timeout: nil, **params) ⇒ Hash{String => Object}

Sends a CDP command to the browser and optionally waits for network activity on the main frame to settle before returning.

Examples:

page.command("Page.navigate", url: "https://github.com/")

Parameters:

  • method (String)

    The CDP method name, e.g. "Page.navigate".

  • wait (Numeric) (defaults to: 0)

    How many seconds to wait for a network event on the main frame after the command is sent. 0 disables waiting.

  • slowmoable (Boolean) (defaults to: false)

    Whether to sleep for Browser::Options#slowmo seconds before sending the command.

  • timeout (Numeric, nil) (defaults to: nil)

    Overrides the timeout this command's response is bound by. Defaults to the page's timeout. Callers with their own budget (e.g. #pdf/ #screenshot) pass it explicitly.

Returns:

  • (Hash{String => Object})


439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/ferrum/page.rb', line 439

def command(method, wait: 0, slowmoable: false, timeout: nil, **params)
  iteration = @event.reset if wait.positive?
  sleep(@options.slowmo) if slowmoable && @options.slowmo.positive?
  result = client.command(method, timeout: timeout || self.timeout, **params)

  if wait.positive?
    # Wait a bit after command and check if iteration has
    #  changed, which means there was some network event for
    # the main frame, and it started to load new content.
    @event.wait(wait)
    if iteration != @event.iteration
      set = @event.wait(self.timeout)
      raise TimeoutError unless set
    end
  end
  result
end

#disable_javascriptObject

Disables JavaScript execution from the HTML source for the page.

This doesn't prevent users evaluate JavaScript with Ferrum.



229
230
231
# File 'lib/ferrum/page.rb', line 229

def disable_javascript
  command("Emulation.setScriptExecutionDisabled", value: true)
end

#document_node_id(async: false) ⇒ Integer, Boolean

Returns the node id of the document's root element.

Parameters:

  • async (Boolean) (defaults to: false)

    Whether to send the command without waiting for a response.

Returns:

  • (Integer, Boolean)

    The root node id, or true when sent asynchronously.



510
511
512
513
514
# File 'lib/ferrum/page.rb', line 510

def document_node_id(async: false)
  return client.command("DOM.getDocument", async: true, depth: 0) if async

  command("DOM.getDocument", depth: 0).dig("root", "nodeId")
end

#forwardObject

Navigates to the next URL in the history.

Examples:

page.go_to("https://github.com/")
page.at_xpath("//a").click
page.back
page.forward


362
363
364
# File 'lib/ferrum/page.rb', line 362

def forward
  history_navigate(delta: 1)
end

#go_to(url = nil) ⇒ Object Also known as: goto, go

Navigates the page to a URL.

Examples:

page.go_to("https://github.com/")

Parameters:

  • url (String, nil) (defaults to: nil)

    The URL to navigate to. The url should include scheme unless you set {Browser#base_url = url} when configuring.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/ferrum/page.rb', line 124

def go_to(url = nil)
  options = { url: combine_url!(url) }
  options.merge!(referrer: referrer) if referrer
  response = command("Page.navigate", wait: GOTO_WAIT, **options)
  error_text = response["errorText"] # https://cs.chromium.org/chromium/src/net/base/net_error_list.h
  if error_text && error_text != "net::ERR_ABORTED" # Request aborted due to user action or download
    raise StatusError.new(options[:url], "Request to #{options[:url]} failed (#{error_text})")
  end

  response["frameId"]
rescue TimeoutError
  if @options.pending_connection_errors
    pendings = network.traffic.select(&:pending?).map(&:url).compact
    raise PendingConnectionsError.new(options[:url], Array(pendings))
  end
end

#off(name, id) ⇒ void

This method returns an undefined value.

Unsubscribes a listener previously registered via #on.

Parameters:

  • name (Symbol, String)
  • id (Integer)

    The subscription id returned by #on.



481
482
483
484
485
# File 'lib/ferrum/page.rb', line 481

def off(name, id)
  return super unless name == :dialog

  client.off("Page.javascriptDialogOpening", id)
end

#on(name, &block) ⇒ Integer

Subscribes to a CDP event, or to :dialog, :request, :auth (the latter two handled by Interceptable).

Parameters:

  • name (Symbol, String)

Returns:

  • (Integer)

    The subscription id, used to unsubscribe via #off.



464
465
466
467
468
469
470
471
# File 'lib/ferrum/page.rb', line 464

def on(name, &block)
  return super unless name == :dialog

  client.on("Page.javascriptDialogOpening") do |params, index, total|
    dialog = Dialog.new(self, params)
    block.call(dialog, index, total)
  end
end

#position(Integer, Integer)

The current position of the window.

Examples:

page.position # => [10, 20]

Returns:

  • ((Integer, Integer))

    The left, top coordinates of the window.



242
243
244
# File 'lib/ferrum/page.rb', line 242

def position
  window_bounds.values_at("left", "top")
end

#position=(options) ⇒ Object

Sets the position of the window.

Examples:

page.position = { left: 10, top: 20 }

Parameters:

  • options (Hash{Symbol => Object})

Options Hash (options):

  • :left (Integer)

    The number of pixels from the left-hand side of the screen.

  • :top (Integer)

    The number of pixels from the top of the screen.



260
261
262
# File 'lib/ferrum/page.rb', line 260

def position=(options)
  self.window_bounds = { left: options[:left], top: options[:top] }
end

#refreshObject Also known as: reload

Reloads the current page.

Examples:

page.go_to("https://github.com/")
page.refresh


325
326
327
# File 'lib/ferrum/page.rb', line 325

def refresh
  command("Page.reload", wait: timeout, slowmoable: true)
end

#resize(width: nil, height: nil, fullscreen: false) ⇒ Hash{String => Object}

Resizes the window and emulates the viewport accordingly, optionally switching to fullscreen.

Examples:

page.resize(width: 1024, height: 768)
page.resize(fullscreen: true)

Parameters:

  • width (Integer, nil) (defaults to: nil)

    width value in pixels.

  • height (Integer, nil) (defaults to: nil)

    height value in pixels.

  • fullscreen (Boolean) (defaults to: false)

    whether to put the window into fullscreen mode. When true, width and height are read from Ferrum::Page::Screenshot#document_size instead of the given arguments.

Returns:

  • (Hash{String => Object})


212
213
214
215
216
217
218
219
220
221
222
# File 'lib/ferrum/page.rb', line 212

def resize(width: nil, height: nil, fullscreen: false)
  if fullscreen
    width, height = document_size
    self.window_bounds = { window_state: "fullscreen" }
  else
    self.window_bounds = { window_state: "normal" }
    self.window_bounds = { width: width, height: height }
  end

  set_viewport(width: width, height: height)
end

#set_viewport(width:, height:, scale_factor: 0, mobile: false) ⇒ Object

Overrides device screen dimensions and emulates viewport according to parameters

Read more here.

Parameters:

  • width (Integer)

    width value in pixels. 0 disables the override

  • height (Integer)

    height value in pixels. 0 disables the override

  • scale_factor (Float) (defaults to: 0)

    device scale factor value. 0 disables the override

  • mobile (Boolean) (defaults to: false)

    whether to emulate mobile device



181
182
183
184
185
186
187
188
189
190
# File 'lib/ferrum/page.rb', line 181

def set_viewport(width:, height:, scale_factor: 0, mobile: false)
  command(
    "Emulation.setDeviceMetricsOverride",
    slowmoable: true,
    width: width,
    height: height,
    deviceScaleFactor: scale_factor,
    mobile: mobile
  )
end

#stopObject

Stop all navigations and loading pending resources on the page.

Examples:

page.go_to("https://github.com/")
page.stop


337
338
339
# File 'lib/ferrum/page.rb', line 337

def stop
  command("Page.stopLoading", slowmoable: true)
end

#use_authorized_proxy?Boolean

Whether the page is configured to use a proxy that requires authentication.

Returns:

  • (Boolean)


497
498
499
# File 'lib/ferrum/page.rb', line 497

def use_authorized_proxy?
  use_proxy? && @proxy_user && @proxy_password
end

#use_proxy?Boolean

Whether the page is configured to use a proxy.

Returns:

  • (Boolean)


490
491
492
# File 'lib/ferrum/page.rb', line 490

def use_proxy?
  @proxy_host && @proxy_port
end

#wait_for_reload(timeout = 1) ⇒ Object

Blocks until the page reloads or the timeout is reached.

Examples:

page.wait_for_reload

Parameters:

  • timeout (Numeric) (defaults to: 1)

    Maximum time in seconds to wait for a reload event.



375
376
377
378
379
# File 'lib/ferrum/page.rb', line 375

def wait_for_reload(timeout = 1)
  @event.reset if @event.set?
  @event.wait(timeout)
  @event.set
end

#window_boundsHash{String => (Integer, String)}

Current window bounds.

Examples:

page.window_bounds # => { "left": 0, "top": 1286, "width": 10, "height": 10, "windowState": "normal" }

Returns:

  • (Hash{String => (Integer, String)})


302
303
304
# File 'lib/ferrum/page.rb', line 302

def window_bounds
  client.command("Browser.getWindowBounds", windowId: window_id).fetch("bounds")
end

#window_bounds=(bounds) ⇒ Object

Sets the position of the window.

Examples:

page.window_bounds = { left: 10, top: 20, width: 1024, height: 768, window_state: "normal" }

Parameters:

  • bounds (Hash{Symbol => Object})
  • options (Hash)

    a customizable set of options



286
287
288
289
290
291
292
# File 'lib/ferrum/page.rb', line 286

def window_bounds=(bounds)
  options = bounds.dup
  window_state = options.delete(:window_state)
  bounds = { windowState: window_state, **options }.compact

  client.command("Browser.setWindowBounds", windowId: window_id, bounds: bounds)
end

#window_idInteger

Current window id.

Examples:

page.window_id # => 1

Returns:

  • (Integer)


314
315
316
# File 'lib/ferrum/page.rb', line 314

def window_id
  client.command("Browser.getWindowForTarget", targetId: target_id)["windowId"]
end