Code Monkey home page Code Monkey logo

ferrum's Introduction

Ferrum - high-level API to control Chrome in Ruby

Ferrum logo

As simple as Puppeteer, though even simpler.

It is Ruby clean and high-level API to Chrome. Runs headless by default, but you can configure it to run in a headful mode. All you need is Ruby and Chrome or Chromium. Ferrum connects to the browser by CDP protocol and there's no Selenium/WebDriver/ChromeDriver dependency. The emphasis was made on a raw CDP protocol because Chrome allows you to do so many things that are barely supported by WebDriver because it should have consistent design with other browsers.

  • Cuprite is a pure Ruby driver for Capybara based on Ferrum. If you are going to crawl sites you better use Ferrum or Vessel because you crawl, not test.

  • Vessel high-level web crawling framework based on Ferrum and Mechanize.

The development is done in RubyMine provided by OSS license.

Index

Install

There's no official Chrome or Chromium package for Linux don't install it this way because it's either outdated or unofficial, both are bad. Download it from official source for Chrome or Chromium. Chrome binary should be in the PATH or BROWSER_PATH and you can pass it as an option to browser instance see :browser_path in Customization.

Add this to your Gemfile and run bundle install.

gem "ferrum"

Examples

Navigate to a website and save a screenshot:

browser = Ferrum::Browser.new
browser.go_to("https://google.com")
browser.screenshot(path: "google.png")
browser.quit

When you work with browser instance Ferrum creates and maintains a default page for you, in fact all the methods above are sent to the page instance that is created in the default_context of the browser instance. You can interact with a page created manually and this is preferred:

browser = Ferrum::Browser.new
page = browser.create_page
page.go_to("https://google.com")
input = page.at_xpath("//input[@name='q']")
input.focus.type("Ruby headless driver for Chrome", :Enter)
page.at_css("a > h3").text # => "rubycdp/ferrum: Ruby Chrome/Chromium driver - GitHub"
browser.quit

Evaluate some JavaScript and get full width/height:

browser = Ferrum::Browser.new
page = browser.create_page
page.go_to("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
width, height = page.evaluate <<~JS
  [document.documentElement.offsetWidth,
   document.documentElement.offsetHeight]
JS
# => [1024, 1931]
browser.quit

Do any mouse movements you like:

# Trace a 100x100 square
browser = Ferrum::Browser.new
page = browser.create_page
page.go_to("https://google.com")
page.mouse
  .move(x: 0, y: 0)
  .down
  .move(x: 0, y: 100)
  .move(x: 100, y: 100)
  .move(x: 100, y: 0)
  .move(x: 0, y: 0)
  .up

browser.quit

Docker

In docker as root you must pass the no-sandbox browser option:

Ferrum::Browser.new(browser_options: { 'no-sandbox': nil })

It has also been reported that the Chrome process repeatedly crashes when running inside a Docker container on an M1 Mac preventing Ferrum from working. Ferrum should work as expected when deployed to a Docker container on a non-M1 Mac.

Customization

You can customize options with the following code in your test setup:

Ferrum::Browser.new(options)
  • options Hash
    • :headless (String | Boolean) - Set browser as headless or not, true by default. You can set "new" to support new headless mode.
    • :xvfb (Boolean) - Run browser in a virtual framebuffer, false by default.
    • :flatten (Boolean) - Use one websocket connection to the browser and all the pages in flatten mode.
    • :window_size (Array) - The dimensions of the browser window in which to test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
    • :extensions (Array[String | Hash]) - An array of paths to files or JS source code to be preloaded into the browser e.g.: ["/path/to/script.js", { source: "window.secret = 'top'" }]
    • :logger (Object responding to puts) - When present, debug output is written to this object.
    • :slowmo (Integer | Float) - Set a delay in seconds to wait before sending command. Useful companion of headless option, so that you have time to see changes.
    • :timeout (Numeric) - The number of seconds we'll wait for a response when communicating with browser. Default is 5.
    • :js_errors (Boolean) - When true, JavaScript errors get re-raised in Ruby.
    • :pending_connection_errors (Boolean) - When main frame is still waiting for slow responses while timeout is reached PendingConnectionsError is raised. It's better to figure out why you have slow responses and fix or block them rather than turn this setting off. Default is true.
    • :browser_name (Symbol) - :chrome by default, only experimental support for :firefox for now.
    • :browser_path (String) - Path to Chrome binary, you can also set ENV variable as BROWSER_PATH=some/path/chrome bundle exec rspec.
    • :browser_options (Hash) - Additional command line options, see them all e.g. { "ignore-certificate-errors" => nil }
    • :ignore_default_browser_options (Boolean) - Ferrum has a number of default options it passes to the browser, if you set this to true then only options you put in :browser_options will be passed to the browser, except required ones of course.
    • :port (Integer) - Remote debugging port for headless Chrome.
    • :host (String) - Remote debugging address for headless Chrome.
    • :url (String) - URL for a running instance of Chrome. If this is set, a browser process will not be spawned.
    • :ws_url (String) - Websocket url for a running instance of Chrome. If this is set, a browser process will not be spawned. It's higher priority than :url, setting both doesn't make sense.
    • :process_timeout (Integer) - How long to wait for the Chrome process to respond on startup.
    • :ws_max_receive_size (Integer) - How big messages to accept from Chrome over the web socket, in bytes. Defaults to 64MB. Incoming messages larger than this will cause a Ferrum::DeadBrowserError.
    • :proxy (Hash) - Specify proxy settings, read more
    • :save_path (String) - Path to save attachments with Content-Disposition header.
    • :env (Hash) - Environment variables you'd like to pass through to the process

Navigation

go_to(url) : String

Navigate page to.

  • url String The url should include scheme unless you set base_url when configuring driver.
page.go_to("https://github.com/")

back

Navigate to the previous page in history.

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

forward

Navigate to the next page in history.

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

refresh

Reload current page.

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

stop

Stop all navigations and loading pending resources on the page

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

position = **options

Set the position for the browser window

  • options Hash
    • :left Integer
    • :top Integer
browser.position = { left: 10, top: 20 }

position : Array<Integer>

Get the position for the browser window

browser.position # => [10, 20]

window_bounds = **options

Set window bounds

  • options Hash
    • :left Integer
    • :top Integer
    • :width Integer
    • :height Integer
    • :window_state String
browser.window_bounds = { left: 10, top: 20, width: 1024, height: 768, window_state: "normal" }

window_bounds : Hash<String, Integer | String>

Get window bounds

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

window_id : Integer

Current window id

browser.window_id # => 1

Finders

at_css(selector, **options) : Node | nil

Find node by selector. Runs document.querySelector within the document or provided node.

  • selector String
  • options Hash
    • :within Node | nil
page.go_to("https://github.com/")
page.at_css("a[aria-label='Issues you created']") # => Node

css(selector, **options) : Array<Node> | []

Find nodes by selector. The method runs document.querySelectorAll within the document or provided node.

  • selector String
  • options Hash
    • :within Node | nil
page.go_to("https://github.com/")
page.css("a[aria-label='Issues you created']") # => [Node]

at_xpath(selector, **options) : Node | nil

Find node by xpath.

  • selector String
  • options Hash
    • :within Node | nil
page.go_to("https://github.com/")
page.at_xpath("//a[@aria-label='Issues you created']") # => Node

xpath(selector, **options) : Array<Node> | []

Find nodes by xpath.

  • selector String
  • options Hash
    • :within Node | nil
page.go_to("https://github.com/")
page.xpath("//a[@aria-label='Issues you created']") # => [Node]

current_url : String

Returns current top window location href.

page.go_to("https://google.com/")
page.current_url # => "https://www.google.com/"

current_title : String

Returns current top window title

page.go_to("https://google.com/")
page.current_title # => "Google"

body : String

Returns current page's html.

page.go_to("https://google.com/")
page.body # => '<html itemscope="" itemtype="http://schema.org/WebPage" lang="ru"><head>...

Screenshots

screenshot(**options) : String | Integer

Saves screenshot on a disk or returns it as base64.

  • options Hash
    • :path String to save a screenshot on the disk. :encoding will be set to :binary automatically
    • :encoding Symbol :base64 | :binary you can set it to return image as Base64
    • :format String "jpeg" | "png"
    • :quality Integer 0-100 works for jpeg only
    • :full Boolean whether you need full page screenshot or a viewport
    • :selector String css selector for given element, optional
    • :area Hash area for screenshot, optional
      • :x Integer
      • :y Integer
      • :width Integer
      • :height Integer
    • :scale Float zoom in/out
    • :background_color Ferrum::RGBA.new(0, 0, 0, 0.0) to have specific background color
page.go_to("https://google.com/")
# Save on the disk in PNG
page.screenshot(path: "google.png") # => 134660
# Save on the disk in JPG
page.screenshot(path: "google.jpg") # => 30902
# Save to Base64 the whole page not only viewport and reduce quality
page.screenshot(full: true, quality: 60, encoding: :base64) # "iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAYAAAC6uhUNAAAAAXNSR0IArs4c6Q...
# Save on the disk with the selected element in PNG
page.screenshot(path: "google.png", selector: 'textarea') # => 11340
# Save to Base64 with an area of the page in PNG
page.screenshot(path: "google.png", area: { x: 0, y: 0, width: 400, height: 300 }) # => 54239
# Save with specific background color
page.screenshot(background_color: Ferrum::RGBA.new(0, 0, 0, 0.0))

pdf(**options) : String | Boolean

Saves PDF on a disk or returns it as base64.

  • options Hash
    • :path String to save a pdf on the disk. :encoding will be set to :binary automatically

    • :encoding Symbol :base64 | :binary you can set it to return pdf as Base64

    • :landscape Boolean paper orientation. Defaults to false.

    • :scale Float zoom in/out

    • :format symbol standard paper sizes :letter, :legal, :tabloid, :ledger, :A0, :A1, :A2, :A3, :A4, :A5, :A6

    • :paper_width Float set paper width

    • :paper_height Float set paper height

    • See other native options you can pass

page.go_to("https://google.com/")
# Save to disk as a PDF
page.pdf(path: "google.pdf", paper_width: 1.0, paper_height: 1.0) # => true

mhtml(**options) : String | Integer

Saves MHTML on a disk or returns it as a string.

  • options Hash
    • :path String to save a file on the disk.
page.go_to("https://google.com/")
page.mhtml(path: "google.mhtml") # => 87742

Network

page.network

traffic Array<Network::Exchange>

Returns all information about network traffic as Network::Exchange instance which in general is a wrapper around request, response and error.

page.go_to("https://github.com/")
page.network.traffic # => [#<Ferrum::Network::Exchange, ...]

request : Network::Request

Page request of the main frame.

page.go_to("https://github.com/")
page.network.request # => #<Ferrum::Network::Request...

response : Network::Response

Page response of the main frame.

page.go_to("https://github.com/")
page.network.response # => #<Ferrum::Network::Response...

status : Integer

Contains the status code of the main page response (e.g., 200 for a success). This is just a shortcut for response.status.

page.go_to("https://github.com/")
page.network.status # => 200

wait_for_idle(**options) : Boolean

Waits for network idle, returns true in case of success and false if there are still connections.

  • options Hash
    • :connections Integer how many connections are allowed for network to be idling, 0 by default
    • :duration Float sleep for given amount of time and check again, 0.05 by default
    • :timeout Float during what time we try to check idle, browser.timeout by default
page.go_to("https://example.com/")
page.at_xpath("//a[text() = 'No UI changes button']").click
page.network.wait_for_idle # => true

wait_for_idle!(**options)

Waits for network idle or raises Ferrum::TimeoutError error. Accepts same arguments as wait_for_idle.

page.go_to("https://example.com/")
page.at_xpath("//a[text() = 'No UI changes button']").click
page.network.wait_for_idle! # might raise an error

clear(type)

Clear page's cache or collected traffic.

  • type Symbol it is either :traffic or :cache
traffic = page.network.traffic # => []
page.go_to("https://github.com/")
traffic.size # => 51
page.network.clear(:traffic)
traffic.size # => 0

intercept(**options)

Set request interception for given options. This method is only sets request interception, you should use on callback to catch requests and abort or continue them.

  • options Hash
    • :pattern String * by default
    • :resource_type Symbol one of the resource types
browser = Ferrum::Browser.new
page = browser.create_page
page.network.intercept
page.on(:request) do |request|
  if request.match?(/bla-bla/)
    request.abort
  elsif request.match?(/lorem/)
    request.respond(body: "Lorem ipsum")
  else
    request.continue
  end
end
page.go_to("https://google.com")

authorize(**options, &block)

If site or proxy uses authorization you can provide credentials using this method.

  • options Hash
    • :type Symbol :server | :proxy site or proxy authorization
    • :user String
    • :password String
  • &block accepts authenticated request, which you must subsequently allow or deny, if you don't care about unwanted requests just call request.continue.
page.network.authorize(user: "login", password: "pass") { |req| req.continue }
page.go_to("http://example.com/authenticated")
puts page.network.status # => 200
puts page.body # => Welcome, authenticated client

Since Chrome implements authorize using request interception you must continue or abort authorized requests. If you already have code that uses interception you can use authorize without block, but if not you are obliged to pass block, so this is version doesn't pass block and can work just fine:

browser = Ferrum::Browser.new
page = browser.create_page
page.network.intercept
page.on(:request) do |request|
  if request.resource_type == "Image"
    request.abort
  else
    request.continue
  end
end

page.network.authorize(user: "login", password: "pass", type: :proxy)

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

You used to call authorize method without block, but since it's implemented using request interception there could be a collision with another part of your code that also uses request interception, so that authorize allows the request while your code denies but it's too late. The block is mandatory now.

emulate_network_conditions(**options)

Activates emulation of network conditions.

  • options Hash
    • :offline Boolean emulate internet disconnection, false by default
    • :latency Integer minimum latency from request sent to response headers received (ms), 0 by default
    • :download_throughput Integer maximal aggregated download throughput (bytes/sec), -1 by default, disables download throttling
    • :upload_throughput Integer maximal aggregated upload throughput (bytes/sec), -1 by default, disables download throttling
    • :connection_type String connection type if known, one of: none, cellular2g, cellular3g, cellular4g, bluetooth, ethernet, wifi, wimax, other. nil by default
page.network.emulate_network_conditions(connection_type: "cellular2g")
page.go_to("https://github.com/")

offline_mode

Activates offline mode for a page.

page.network.offline_mode
page.go_to("https://github.com/") # => Ferrum::StatusError (Request to https://github.com/ failed(net::ERR_INTERNET_DISCONNECTED))

cache(disable: Boolean)

Toggles ignoring cache for each request. If true, cache will not be used.

page.network.cache(disable: true)

Downloads

page.downloads

files Array<Hash>

Returns all information about downloaded files as a Hash.

page.go_to("http://localhost/attachment.pdf")
page.downloads.files # => [{"frameId"=>"E3316DF1B5383D38F8ADF7485005FDE3", "guid"=>"11a68745-98ac-4d54-9b57-9f9016c268b3", "url"=>"http://localhost/attachment.pdf", "suggestedFilename"=>"attachment.pdf", "totalBytes"=>4911, "receivedBytes"=>4911, "state"=>"completed"}]

wait(timeout)

Waits until the download is finished.

page.go_to("http://localhost/attachment.pdf")
page.downloads.wait

or

page.go_to("http://localhost/page")
page.downloads.wait { page.at_css("#download").click }

set_behavior(**options)

Sets behavior in case of file to be downloaded.

  • options Hash
    • :save_path String absolute path of where to store the file
    • :behavior Symbol deny | allow | allowAndName | default, allow by default
page.go_to("https://example.com/")
page.downloads.set_behavior(save_path: "/tmp", behavior: :allow)

Proxy

You can set a proxy with a :proxy option:

Ferrum::Browser.new(proxy: { host: "x.x.x.x", port: "8800", user: "user", password: "pa$$" })

:bypass can specify semi-colon-separated list of hosts for which proxy shouldn't be used:

Ferrum::Browser.new(proxy: { host: "x.x.x.x", port: "8800", bypass: "*.google.com;*foo.com" })

In general passing a proxy option when instantiating a browser results in a browser running with proxy command line flags, so that it affects all pages and contexts. You can create a page in a new context which can use its own proxy settings:

browser = Ferrum::Browser.new

browser.create_page(proxy: { host: "x.x.x.x", port: 31337, user: "user", password: "password" }) do |page|
  page.go_to("https://api.ipify.org?format=json")
  page.body # => "x.x.x.x"
end

browser.create_page(proxy: { host: "y.y.y.y", port: 31337, user: "user", password: "password" }) do |page|
  page.go_to("https://api.ipify.org?format=json")
  page.body # => "y.y.y.y"
end

Mouse

page.mouse

scroll_to(x, y)

Scroll page to a given x, y

  • x Integer the pixel along the horizontal axis of the document that you want displayed in the upper left
  • y Integer the pixel along the vertical axis of the document that you want displayed in the upper left
page.go_to("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
page.mouse.scroll_to(0, 400)

click(**options) : Mouse

Click given coordinates, fires mouse move, down and up events.

  • options Hash
    • :x Integer
    • :y Integer
    • :delay Float defaults to 0. Delay between mouse down and mouse up events
    • :button Symbol :left | :right, defaults to :left
    • :count Integer defaults to 1
    • :modifiers Integer bitfield for key modifiers. Seekeyboard.modifiers

down(**options) : Mouse

Mouse down for given coordinates.

  • options Hash
    • :button Symbol :left | :right, defaults to :left
    • :count Integer defaults to 1
    • :modifiers Integer bitfield for key modifiers. Seekeyboard.modifiers

up(**options) : Mouse

Mouse up for given coordinates.

  • options Hash
    • :button Symbol :left | :right, defaults to :left
    • :count Integer defaults to 1
    • :modifiers Integer bitfield for key modifiers. Seekeyboard.modifiers

move(x:, y:, steps: 1) : Mouse

Mouse move to given x and y.

  • options Hash
    • :x Integer
    • :y Integer
    • :steps Integer defaults to 1. Sends intermediate mousemove events.

Keyboard

page.keyboard

down(key) : Keyboard

Dispatches a keydown event.

  • key String | Symbol Name of key such as "a", :enter, :backspace

up(key) : Keyboard

Dispatches a keyup event.

  • key String | Symbol Name of key such as "b", :enter, :backspace

type(*keys) : Keyboard

Sends a keydown, keypress/input, and keyup event for each character in the text.

  • text String | Array<String> | Array<Symbol> A text to type into a focused element, [:Shift, "s"], "tring"

modifiers(keys) : Integer

Returns bitfield for a given keys

  • keys Array<Symbol> :alt | :ctrl | :command | :shift

Cookies

page.cookies

all : Hash<String, Cookie>

Returns cookies hash

page.cookies.all # => {"NID"=>#<Ferrum::Cookies::Cookie:0x0000558624b37a40 @attributes={"name"=>"NID", "value"=>"...", "domain"=>".google.com", "path"=>"/", "expires"=>1583211046.575681, "size"=>178, "httpOnly"=>true, "secure"=>false, "session"=>false}>}

: Cookie

Returns cookie

  • value String
page.cookies["NID"] # => <Ferrum::Cookies::Cookie:0x0000558624b67a88 @attributes={"name"=>"NID", "value"=>"...", "domain"=>".google.com", "path"=>"/", "expires"=>1583211046.575681, "size"=>178, "httpOnly"=>true, "secure"=>false, "session"=>false}>

set(value) : Boolean

Sets a cookie

  • value Hash
    • :name String
    • :value String
    • :domain String
    • :expires Integer
    • :samesite String
    • :httponly Boolean
page.cookies.set(name: "stealth", value: "omg", domain: "google.com") # => true
  • value Cookie
nid_cookie = page.cookies["NID"] # => <Ferrum::Cookies::Cookie:0x0000558624b67a88>
page.cookies.set(nid_cookie) # => true

remove(**options) : Boolean

Removes given cookie

  • options Hash
    • :name String
    • :domain String
    • :url String
page.cookies.remove(name: "stealth", domain: "google.com") # => true

clear : Boolean

Removes all cookies for current page

page.cookies.clear # => true

Headers

page.headers

get : Hash

Get all headers

set(headers) : Boolean

Set given headers. Eventually clear all headers and set given ones.

  • headers Hash key-value pairs for example "User-Agent" => "Browser"

add(headers) : Boolean

Adds given headers to already set ones.

  • headers Hash key-value pairs for example "Referer" => "http://example.com"

clear : Boolean

Clear all headers.

JavaScript

evaluate(expression, *args)

Evaluate and return result for given JS expression

  • expression String should be valid JavaScript
  • args Object you can pass arguments, though it should be a valid Node or a simple value.
page.evaluate("[window.scrollX, window.scrollY]")

evaluate_async(expression, wait_time, *args)

Evaluate asynchronous expression and return result

  • expression String should be valid JavaScript
  • wait_time How long we should wait for Promise to resolve or reject
  • args Object you can pass arguments, though it should be a valid Node or a simple value.
page.evaluate_async(%(arguments[0]({foo: "bar"})), 5) # => { "foo" => "bar" }

execute(expression, *args)

Execute expression. Doesn't return the result

  • expression String should be valid JavaScript
  • args Object you can pass arguments, though it should be a valid Node or a simple value.
page.execute(%(1 + 1)) # => true

evaluate_on_new_document(expression)

Evaluate JavaScript to modify things before a page load

  • expression String should be valid JavaScript
browser.evaluate_on_new_document <<~JS
  Object.defineProperty(navigator, "languages", {
    get: function() { return ["tlh"]; }
  });
JS

add_script_tag(**options) : Boolean

  • options Hash
    • :url String
    • :path String
    • :content String
    • :type String - text/javascript by default
page.add_script_tag(url: "http://example.com/stylesheet.css") # => true

add_style_tag(**options) : Boolean

  • options Hash
    • :url String
    • :path String
    • :content String
page.add_style_tag(content: "h1 { font-size: 40px; }") # => true

bypass_csp(**options) : Boolean

  • options Hash
    • :enabled Boolean, true by default
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

Emulation

disable_javascript

Disables Javascripts from the loaded HTML source. You can still evaluate JavaScript with evaluate or execute. Returns nothing.

page.disable_javascript

set_viewport

Overrides device screen dimensions and emulates viewport.

  • options Hash
    • :width Integer, viewport width. 0 by default
    • :height Integer, viewport height. 0 by default
    • :scale_factor Float, device scale factor. 0 by default
    • :mobile Boolean, whether to emulate mobile device. false by default
page.set_viewport(width: 1000, height: 600, scale_factor: 3)

Frames

frames : Array[Frame] | []

Returns all the frames current page have.

page.go_to("https://www.w3schools.com/tags/tag_frame.asp")
page.frames # =>
# [
#   #<Ferrum::Frame @id="C6D104CE454A025FBCF22B98DE612B12" @parent_id=nil @name=nil @state=:stopped_loading @execution_id=1>,
#   #<Ferrum::Frame @id="C09C4E4404314AAEAE85928EAC109A93" @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=2>,
#   #<Ferrum::Frame @id="2E9C7F476ED09D87A42F2FEE3C6FBC3C" @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=3>,
#   ...
# ]

main_frame : Frame

Returns page's main frame, the top of the tree and the parent of all frames.

frame_by(**options) : Frame | nil

Find frame by given options.

  • options Hash
    • :id String - Unique frame's id that browser provides
    • :name String - Frame's name if there's one
page.frame_by(id: "C6D104CE454A025FBCF22B98DE612B12")

Frame

id : String

Frame's unique id.

parent_id : String | nil

Parent frame id if this one is nested in another one.

execution_id : Integer

Execution context id which is used by JS, each frame has it's own context in which JS evaluates.

name : String | nil

If frame was given a name it should be here.

state : Symbol | nil

One of the states frame's in:

  • :started_loading
  • :navigated
  • :stopped_loading

url : String

Returns current frame's location href.

page.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
frame = page.frames[1]
frame.url # => https://interactive-examples.mdn.mozilla.net/pages/tabbed/iframe.html

title

Returns current frame's title.

page.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
frame = page.frames[1]
frame.title # => HTML Demo: <iframe>

main? : Boolean

If current frame is the main frame of the page (top of the tree).

page.go_to("https://www.w3schools.com/tags/tag_frame.asp")
frame = page.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
frame.main? # => false

current_url : String

Returns current frame's top window location href.

page.go_to("https://www.w3schools.com/tags/tag_frame.asp")
frame = page.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
frame.current_url # => "https://www.w3schools.com/tags/tag_frame.asp"

current_title : String

Returns current frame's top window title.

page.go_to("https://www.w3schools.com/tags/tag_frame.asp")
frame = page.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
frame.current_title # => "HTML frame tag"

body : String

Returns current frame's html.

page.go_to("https://www.w3schools.com/tags/tag_frame.asp")
frame = page.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
frame.body # => "<html><head></head><body></body></html>"

doctype

Returns current frame's doctype.

page.go_to("https://www.w3schools.com/tags/tag_frame.asp")
page.main_frame.doctype # => "<!DOCTYPE html>"

content = html

Sets a content of a given frame.

  • html String
page.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
frame = page.frames[1]
frame.body # <html lang="en"><head><style>body {transition: opacity ease-in 0.2s; }...
frame.content = "<html><head></head><body><p>lol</p></body></html>"
frame.body # => <html><head></head><body><p>lol</p></body></html>

Dialogs

accept(text)

Accept dialog with given text or default prompt if applicable

  • text String

dismiss

Dismiss dialog

page.on(:dialog) do |dialog|
  if dialog.match?(/bla-bla/)
    dialog.accept
  else
    dialog.dismiss
  end
end
page.go_to("https://google.com")

Animation

You can slow down or speed up CSS animations.

playback_rate : Integer

Returns playback rate for CSS animations, defaults to 1.

playback_rate = value

Sets playback rate of CSS animations

  • value Integer
page.playback_rate = 2000
page.go_to("https://google.com")
page.playback_rate # => 2000

Node

node? : Boolean

frame_id

frame : Frame

Returns Frame object for current node, you can keep using Finders for that object:

frame =  page.at_xpath("//iframe").frame # => Frame
frame.at_css("//a[text() = 'Log in']") # => Node

focus

focusable?

moving? : Boolean

wait_for_stop_moving

blur

type

click

hover

select_file

at_xpath

at_css

xpath

css

text

inner_text

value

property

attribute

evaluate

selected : Array<Node>

select

scroll_into_view

in_viewport?(of: Node | nil) : Boolean

(chainable) Selects options by passed attribute.

page.at_xpath("//*[select]").select(["1"]) # => Node (select)
page.at_xpath("//*[select]").select(["text"], by: :text) # => Node (select)

Accept string, array or strings:

page.at_xpath("//*[select]").select("1")
page.at_xpath("//*[select]").select("1", "2")
page.at_xpath("//*[select]").select(["1", "2"])

Tracing

You can use tracing.record to create a trace file which can be opened in Chrome DevTools or timeline viewer.

page.tracing.record(path: "trace.json") do
  page.go_to("https://www.google.com")
end

tracing.record(**options) : String

Accepts block, records trace and by default returns trace data from Tracing.tracingComplete event as output. When path is specified returns true and stores trace data into file.

  • options Hash
    • :path String save data on the disk, nil by default
    • :encoding Symbol :base64 | :binary encode output as Base64 or plain text. :binary by default
    • :timeout Float wait until file streaming finishes in the specified time or raise error, defaults to nil
    • :screenshots Boolean capture screenshots in the trace, false by default
    • :trace_config Hash<String, Object> config for trace, for categories see getCategories, only one trace config can be active at a time per browser.

Clean Up

reset

Closes browser tabs opened by the Browser instance.

# connect to a long-running Chrome process
browser = Ferrum::Browser.new(url: 'http://localhost:9222')

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

# clean up, lest the tab stays there hanging forever
browser.reset

browser.quit

Thread safety

Ferrum is fully thread-safe. You can create one browser or a few as you wish and start playing around using threads. Example below shows how to create a few pages which share the same context. Context is similar to an incognito profile but you can have more than one, think of it like it's independent browser session:

browser = Ferrum::Browser.new
context = browser.contexts.create

t1 = Thread.new(context) do |c|
  page = c.create_page
  page.go_to("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
  page.screenshot(path: "t1.png")
end

t2 = Thread.new(context) do |c|
  page = c.create_page
  page.go_to("https://www.google.com/search?q=Ruby+static+typing")
  page.screenshot(path: "t2.png")
end

t1.join
t2.join

context.dispose
browser.quit

or you can create two independent contexts:

browser = Ferrum::Browser.new

t1 = Thread.new(browser) do |b|
  context = b.contexts.create
  page = context.create_page
  page.go_to("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
  page.screenshot(path: "t1.png")
  context.dispose
end

t2 = Thread.new(browser) do |b|
  context = b.contexts.create
  page = context.create_page
  page.go_to("https://www.google.com/search?q=Ruby+static+typing")
  page.screenshot(path: "t2.png")
  context.dispose
end

t1.join
t2.join

browser.quit

Development

After checking out the repo, run bundle install to install dependencies.

Then, run bundle exec rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub.

License

The gem is available as open source under the terms of the MIT License.

ferrum's People

Contributors

aaronjensen avatar alexanderadam avatar alexwayfer avatar betelgeuse avatar buonomo avatar denisadamchik avatar drnluz avatar erickgskovik avatar ericparshall avatar francisbeaudoin avatar gbaptista avatar gordonbondon avatar intrepidd avatar janbiedermann avatar julioolvr avatar mifrill avatar msnexploder avatar nakilon avatar nenoganchev avatar nickhammond avatar postmodern avatar rmarot avatar route avatar sebyx07 avatar sigmike avatar sloanesturz avatar stoivo avatar ttilberg avatar valve avatar zw963 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ferrum's Issues

Remove browser_options in DEFAULT_OPTIONS

Currently the DEFAULT_OPTIONS in lib/ferrum/browser/chrome.rb can't be removed by the options given to Ferrum::Browser.new. Is there any way to do that?

The problem I'm facing is that some website check the "origin" HTTP header on the server side, and with "--disable-web-security" Chrome doesn't send the origin header at all.

Workaround I'm using now is:

opt = Ferrum::Browser::Chrome.send(:remove_const, :DEFAULT_OPTIONS).dup
opt.delete('disable-web-security')
Ferrum::Browser::Chrome.const_set(:DEFAULT_OPTIONS, opt)

`Ferrum::DeadBrowserError` is raised after running with multiprocess

Hello.

My environments and sample code is here.

OS: macOS 0.13.6 (17G8030)
Ruby: ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-darwin17]
Gems:
  ferrum 0.8
  parallel 1.19.2
bundle exec ruby sample.rb
# sample.rb

require 'ferrum'
require 'parallel'
require 'pry'

browser = Ferrum::Browser.new
browser.goto('https://google.com')

Parallel.each(['1','2']) do |i|
  p i
end

# binding.pry
#browser.restart

browser.goto('https://www.amazon.com/')

browser.goto('https://www.amazon.com/') is raised Ferrum::DeadBrowserError. I checked a browser process after multi processing by browser.process.pid and ps command at binding.pry, then the process is zombie status.

So browser.restart to set will work just fine. Parallel with in_threads as multi threads mode also will work fine.

I'm not sure if the cause is ferrum, or parallel, or something else. I would like to know if you have any helpful information.

Thanks.

Chromium failing to start with "failed to dup supplied urandom fd: Bad file descriptor"

Hello,

I was seing this error randomly in my builds:

Browser did not produce websocket url within 5 seconds

The machine was not saturated, of course I tried increasing it (to 20 seconds) but the problem was still the same so it was clear it was not a timing issue.

I tried to enable debug logging but it was way too verbose so impossible to find the error in here. I ended up looking at the code adding just a puts to see the output where the ws URL is missing:

        unless ws_url
          @logger.puts(output) if @logger
          puts output
          raise ProcessTimeoutError.new(timeout)
        end

And so when the errors occurs I got this:

failed to dup supplied urandom fd: Bad file descriptor

This error comes from chromium code I guess, but I'm not sure why I have it here and not when I use chromium directly. It doesn't look like Ferrum is "supplying" a custom FD for this.

> chromium --version
Chromium 81.0.4044.113 snap

I tried starting chromium manually with the exact same parameters:

/snap/bin/chromium --headless --disable-gpu --hide-scrollbars --mute-audio --enable-automation --disable-web-security --disable-session-crashed-bubble --disable-breakpad --disable-sync --no-first-run --use-mock-keychain --keep-alive-for-test --disable-popup-blocking --disable-extensions --disable-hang-monitor --disable-features=site-per-process,TranslateUI --disable-translate --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-client-side-phishing-detection --disable-default-apps --disable-dev-shm-usage --disable-ipc-flooding-protection --disable-prompt-on-repost --disable-renderer-backgrounding --force-color-profile=srgb --metrics-recording-only --safebrowsing-disable-auto-update --password-store=basic --window-size=1024,768 --remote-debugging-port=0 --remote-debugging-address=127.0.0.1 --user-data-dir=/tmp/d20200508-7638-17rc0fb

Many times but never reproduced this error message.

I run my tests in parallel using paraspec, I could reproduce the problem even with a very low concurrency of 2 (on a 6 cores CPU) so the problem is not starting time or slowdown it seems, but maybe concurrency issue somewhere?

I managed to write a small Ferrum script to reproduce the issue using fork:

#!/usr/bin/env ruby

require 'ferrum'

10.times do
  fork do
    puts "[#{$$}] Start Ferrum in fork"
    browser = Ferrum::Browser.new
    browser.goto("https://updown.io")
    browser.quit
    puts "[#{$$}] Done."
  end
end
Process.waitall

Launching this script reproduce this error every time on my machine (sometimes I also see /snap/chromium/1117/bin/desktop-launch: line 302: /dev/null: Operation not permitted)

Any idea about where could this come from and how to fix it?

Full screenshots don't show page content

Hi, I'm finding Ferrum to be a great lib, thank you! I have a bug to report when taking screenshots.

The full: true option when passed to #screenshot does not show the full page, instead it only renders the visible area of the page according to the viewport and then the rest is a solid background colour. I think the tests don't catch this because they only check the image dimensions.

Here's an example:

b = Ferrum::Browser.new
b.goto "http://www.csszengarden.com/"
b.resize width: 800, height: 200
b.screenshot(path: "zg200.png", full: true)

b.resize width: 800, height: 600
b.screenshot(path: "zg600.png", full: true)

b.resize width: 800, height: b.evaluate("document.documentElement.offsetHeight")
b.screenshot(path: "zgfull.png", full: true)

Ferrum::Network::Exchange doesn't support #url

I have a test helper function that calls
page.driver.network_traffic.select { |request| request.url =~ /foo/ }

This code used to work but is broken with Cuprite 0.8 and the advent of Ferrum::Network::Exchange. Is this an intentional change of the API?

Thank you for your help!

browser.css(...) #=> undefined method `compact' for "(cyclic structure)":String

Chrome:
Version 81.0.4044.122 (Official Build) (64-bit)

require 'ferrum'
b = Ferrum::Browser.new(headless: false)
b.goto 'https://www.npr.org/sections/news/'
b.css('article')

NoMethodError: undefined method `compact' for "(cyclic structure)":String
from /Users/ttilberg/.asdf/installs/ruby/2.6.5/lib/ruby/gems/2.6.0/gems/ferrum-0.8/lib/ferrum/frame/runtime.rb:189:in `handle_response'

I'm getting this error with several (and all) different sites and css strings passing in. Perhaps there is a change in recent versions of Chrome/CDP?

MacOS Mojave 10.14.6
Chrome 81.0.4044.122
Ruby 2.6.5
Ferrum 0.8

Expose API to enable mobile emulation mode

From what I see in the library there's only one call to setDeviceMetricsOverride that sets mobile: false.

It would be great if there was an exposed API to enable mobile emulation and manage width and height of the viewport dynamically.

Spec error "There aren't targets available" on Windows 7

Hi, I'm using Ferrum 0.2.1 on Windows 7 Professional SP1. I tried running the first "save a screenshot" example from README.md, and I get the error "There aren't targets available (Ferrum::EmptyTargetsError)"

I get the same error running the specs:

C:\ferrum-master>bundle exec rake
C:/Ruby/Ruby255-x64/bin/ruby.exe -I'C:/Ruby/Ruby255-x64/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib';'C:/Ruby/Ruby255-x64/lib/ruby/g
ems/2.5.0/gems/rspec-support-3.8.2/lib' 'C:/Ruby/Ruby255-x64/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/exe/rspec' --pattern 'spec/**{,/*/*
*}/*_spec.rb'
Starting Puma
* Version 4.1.0 , codename: Fourth and One
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:60259

basic http authentication
  denies without credentials (FAILED - 1)

Failures:

  1) basic http authentication denies without credentials
     Got 0 failures and 2 other errors:

     1.1) Failure/Error: raise EmptyTargetsError if targets.empty?

          Ferrum::EmptyTargetsError:
            There aren't targets available
          # ./lib/ferrum/targets.rb:110:in `block in targets'
          # ./lib/ferrum.rb:95:in `with_attempts'
          # ./lib/ferrum/targets.rb:105:in `targets'
          # ./lib/ferrum/targets.rb:12:in `initialize'
          # ./lib/ferrum/browser.rb:109:in `new'
          # ./lib/ferrum/browser.rb:109:in `targets'
          # ./spec/browser/basic_auth_spec.rb:8:in `block (2 levels) in <module:Ferrum>'

     1.2) Failure/Error: raise EmptyTargetsError if targets.empty?

          Ferrum::EmptyTargetsError:
            There aren't targets available
          # ./lib/ferrum/targets.rb:110:in `block in targets'
          # ./lib/ferrum.rb:95:in `with_attempts'
          # ./lib/ferrum/targets.rb:105:in `targets'
          # ./lib/ferrum/targets.rb:12:in `initialize'
          # ./lib/ferrum/browser.rb:109:in `new'
          # ./lib/ferrum/browser.rb:109:in `targets'
          # ./lib/ferrum/browser.rb:94:in `reset'
          # ./spec/spec_helper.rb:58:in `block (2 levels) in <top (required)>'

Finished in 1.8 seconds (files took 2.23 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/browser/basic_auth_spec.rb:7 # basic http authentication denies without credentials

C:/Ruby/Ruby255-x64/bin/ruby.exe -I'C:/Ruby/Ruby255-x64/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/lib';'C:/Ruby/Ruby255-x64/lib/ruby/g
ems/2.5.0/gems/rspec-support-3.8.2/lib' 'C:/Ruby/Ruby255-x64/lib/ruby/gems/2.5.0/gems/rspec-core-3.8.2/exe/rspec' --pattern 'spec/**{,/*/*
*}/*_spec.rb' failed

C:\ferrum-master>ruby -v
ruby 2.5.5p157 (2019-03-15 revision 67260) [x64-mingw32]

Thanks for any assistance!

Leaking chromium.launcher processes?

I've noticed on my CI server (using cuprite) a lot of orphan chomium.launcher process with sleep 60 as child:
image

It looks like this process is started when specs are running:
image
But then it stays orphan after the spec finishes, this may have nothing to do with ferrum but I didn't notice this with chrome-driver previously so I though I would report this.

I'm using Ubuntu 19.10 with Google Chrome 81.0.4044.113
These processes do not die after 60 seconds, it seems the launcher process is calling sleep 60 in loop. I can also reproduce the issue on my desktop (Ubuntu 19.10 too)

I use this cuprite config:

Capybara.javascript_driver = :cuprite
Capybara.register_driver(:cuprite) do |app|
  Capybara::Cuprite::Driver.new(app, headless: true, js_errors: true, timeout: 10, process_timeout: 10)
end

Any idea what could cause that?

Allow Ferrum to use extensions?

Hey, currently It doesn't work to add extensions(Use them I mean)

I've tried to run it without headless enabled and they do really load.

But they load only in the initial window, the ferrum provided tabs don't have access to them :/

Pick option in select

I can't figure out a way to pick an option item in a select.

Example HTML:
<select id="cars" name="cars"> <option value="volvo">Volvo</option> <option value="saab">Saab</option> <option value="fiat">Fiat</option> <option value="audi">Audi</option> </select>

Clicking on the select opens the drop-down list. Using tap { browser.keyboard.type(:Down) } did not work. Option cannot be clicked on, because of being unable to calculate quads. I could be missing something obvious, and any help would be appreciated.

Full screenshot as blank at bottom

First, thank you for writing this gem. I wrote some samples and got some good results.
But when I use the option full as true. It causes the bottom as blank below:

Run:

require "ferrum"

browser = Ferrum::Browser.new
browser.goto("https://google.com")
search_input = browser.at_css("input[name='q']")
search_input.focus.type("Gym", :Enter)

browser.screenshot(path: "google.jpg", full: true)

Result:
google

More screenshot options

Looking at the source and docs, it seems like the only options for screeshot dimensions are either "full" or "viewport".

Is there a way to specify the width and height of the viewport?

Allow Ferrum to use default (non incognito) context

I'd like my browser state/session to be preserved between uses of my script.

Currently, Ferrum places all commands inside a BrowserContext, which acts like an Incognito session and doesn't load stored cookies.

I would like to use the browser's default session (which isn't inside a context), and take advantage of the --user-data-dir flag to be able to persist state across sessions. This would require modifying how Ferrum uses and creates contexts to allow for non-browserContext requests. I am happy to do this, and submit a PR, but would like some author feedback (if possible) about whether such an option is in line with the project's vision before I start working on code for this.

No Websocket url within 2 seconds

Fresh install on MacOS El Capitan. I tried the Google connection, and other sites, and this error pops up:

/Users/rich/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/ferrum-0.7/lib/ferrum/browser/process.rb:137:in parse_ws_url': Browser process did not produce websocket url within 2 seconds (RuntimeError)`

I'm not sure what to provide at this point. /usr/local/bin/chromedriver is installed.

Jruby error

below script works fine on MRI but fails sometime on Jruby

require 'ferrum'
require 'concurrent'


browser = Ferrum::Browser.new(process_timeout: 10, headless: false)
pool = Concurrent::FixedThreadPool.new(5)

context = browser.contexts.create

page = context.create_page
page.goto("https://www.google.com/search?q=Ruby+static+typing")
page.close

def extract_data(context)
    page = context.create_page
    page.goto("https://www.google.com/search?q=Ruby+static+typing")
    p page.at_xpath('/html/body/div[6]/div[3]/div[8]/div[1]/div[2]/div/div[2]/div[2]/div/div/div[1]/div/div[1]/a/h3').text
    page.close

end

100.times do 
    pool.post do
        begin
            extract_data(context)
        rescue Exception => e
            p e.message
        end
    end
end

pool.shutdown
pool.wait_for_termination

context.dispose
browser.quit

output

warning: thread "Ruby-0-Thread-3: /home/oshan/.asdf/installs/ruby/jruby-9.2.11.1/lib/ruby/gems/shared/gems/ferrum-0.8/lib/ferrum/browser/client.rb:19" terminated with exception (report_on_exception is true):
Concurrent::MultipleAssignmentError: Concurrent::MultipleAssignmentError
         set at /home/oshan/.asdf/installs/ruby/jruby-9.2.11.1/lib/ruby/gems/shared/gems/concurrent-ruby-1.1.6/lib/concurrent-ruby/concurrent/ivar.rb:114
  initialize at /home/oshan/.asdf/installs/ruby/jruby-9.2.11.1/lib/ruby/gems/shared/gems/ferrum-0.8/lib/ferrum/browser/client.rb:31

please run multiple attempts to see the errors.
I can see browser opening blank tabs. that cause to print Timed out waiting for response. It's possible that this happened because something took a very long time (for example a page load was slow). If so, setting the :timeout option to a higher value might help. and when I wasn't handling the exception rescue I got same warning as above. so I guess it relate to page close method?

undefined method `window_handles'

Hi,

Not sure if it's a Ferrum or a Cuprite problem but it appears when updating from Ferrum 0.4 to 0.5.

I have this error when calling windows.count in a cucumber step using Capybara.

Cuprite version : 0.7.1

Stacktrace :
undefined method `window_handles' for #<Capybara::Cuprite::Browser:0x00007f90ba8018d8>
Did you mean?  window_size (NoMethodError)
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cuprite-0.7.1/lib/capybara/cuprite/driver.rb:124:in `window_handles'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/capybara-3.29.0/lib/capybara/session.rb:448:in `windows'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/capybara-3.29.0/lib/capybara/dsl.rb:51:in `block (2 levels) in <module:DSL>'
/Users/benoit/www/minifeed.rails/features/step_definitions/common_steps.rb:2:in `block in <top (required)>'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/glue/invoke_in_world.rb:39:in `instance_exec'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/glue/invoke_in_world.rb:39:in `block in cucumber_instance_exec_in'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/glue/invoke_in_world.rb:54:in `cucumber_run_with_backtrace_filtering'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/glue/invoke_in_world.rb:27:in `cucumber_instance_exec_in'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/glue/step_definition.rb:110:in `invoke'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/step_match.rb:31:in `invoke'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/step_match.rb:24:in `block in activate'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/action.rb:24:in `execute'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/step.rb:32:in `execute'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/runner.rb:104:in `execute'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/runner.rb:51:in `execute'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/runner.rb:27:in `test_step'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/step.rb:17:in `describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:28:in `block (3 levels) in describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:27:in `each'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:27:in `block (2 levels) in describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/filters/prepare_world.rb:22:in `block in test_case'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/around_hook.rb:17:in `execute'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/runner.rb:104:in `execute'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/runner.rb:51:in `execute'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/runner.rb:34:in `around_hook'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/around_hook.rb:12:in `describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:120:in `block (2 levels) in compose_around_hooks'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:121:in `compose_around_hooks'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:26:in `block in describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/runner.rb:19:in `test_case'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:25:in `describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/filters/prepare_world.rb:11:in `test_case'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:25:in `describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/filter.rb:57:in `test_case'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/filters/retry.rb:18:in `test_case'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:25:in `describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/filters/quit.rb:12:in `test_case'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/case.rb:25:in `describe_to'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/filters/broadcast_test_run_started_event.rb:21:in `block in done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/filters/broadcast_test_run_started_event.rb:20:in `map'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/filters/broadcast_test_run_started_event.rb:20:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/filter.rb:62:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/filter.rb:62:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/filter.rb:62:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/filter.rb:62:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/filter.rb:62:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/filters/locations_filter.rb:20:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/filter.rb:62:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/test/filters/tag_filter.rb:18:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/compiler.rb:24:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core/gherkin/parser.rb:39:in `done'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core.rb:32:in `parse'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-core-3.2.1/lib/cucumber/core.rb:21:in `compile'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/runtime.rb:75:in `run!'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/lib/cucumber/cli/main.rb:34:in `execute!'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/cucumber-3.1.2/bin/cucumber:9:in `<main>'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in `block in load'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:257:in `load_dependency'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in `load'
/Users/benoit/www/minifeed.rails/bin/cucumber:8:in `<main>'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in `block in load'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:257:in `load_dependency'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:285:in `load'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Users/benoit/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'

browser.current_title doesn't work

README says browser should have a method called 'current_title'

Screen Shot 2020-04-02 at 10 54 52 AM

This says it doesn't work. I did sudo gem install ferrum to ensure updates. Got ferrum-0.7.

how to pass Basic Auth when opening a page

I Know with puppeteer you can simply pass basic auth by:

page.authenticate({'username':'YOUR_BASIC_AUTH_USERNAME', 'password': 'YOUR_BASIC_AUTH_PASSWORD'});

is there a way to do it with Ferrum ?

NoMethodError: undefined method `compact' for "(cyclic structure)":String

When using together with Cuprite and executing Rspec command like:
page.evaluate_script("jQuery('body').addClass('not-reloaded');")
it's erroring with:

NoMethodError: undefined method `compact' for "(cyclic structure)":String
from (pry):5:in `rescue in handle_response'
Caused by NoMethodError: undefined method `compact' for "(cyclic structure)":String
from ~/.rvm/gems/ruby-2.5.3/bundler/gems/ferrum-5b6cb3f617b0/lib/ferrum/page/runtime.rb:127:in `handle_response'

Correspoding reduce_props(object_id, []) at https://github.com/route/ferrum/blob/master/lib/ferrum/page/runtime.rb#L122 is returning "(cyclic structure)" String, so it's clear why compact call is failing.

Not sure what's best approach to handle this case.

Install Chromium binary as Puppeteer it does

https://github.com/puppeteer/puppeteer/blob/4ee4649/README.md#installation

Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, or to download a different browser, see Environment variables.

I think it makes things easier.

Also, I don't want to download trunk version and I didn't find stable one at the link from this project README, https://www.chromium.org/getting-involved/download-chromium

NoMethodError (undefined method `compact' for "(cyclic structure)":String in Browser#css

Problem

Calling Browser#css with seemingly any parameter fails with the following error:

NoMethodError (undefined method `compact' for "(cyclic structure)":String)

Steps to reproduce

browser = Ferrum::Browser.new
browser.goto("https://www.google.com")
# this works:
browser.at_css('input') # => Ferrum::Node
# this doesn't:
browser.css('input') # => NoMethodError (undefined method `compact' for "(cyclic structure)":String)
# but this does:
browser.xpath('//input') # => [Ferrum::Node]

Expected behavior

I was expecting the method to return an Array of Ferrum::Node objects containing all the nodes matched by the selector, similar to how Browser#xpath behaves.

Is there any way I could use a proxy?

Hi, thanks for the great tools, seems all good, and I am looking around if I could pass in a proxy setting in the middle of my local machine to target website. Is there any options for me to do that now?

Brave instead of Chrome?

Is it possible to use Brave browser instead of Chrome?

It's supposedly build off of Chromium, if that helps?

Sorry I don't know much so I'm not even sure if this is a valid question to ask.

Thanks!

infinate scroll pagination

p browser.xpath("//div[@class='foobar']/div").length
browser.execute(%(window.scrollTo(0, document.body.scrollHeight)))
sleep 3 # for ajax load
p browser.xpath("//div[@class='foobar']/div").length

this is the code I'm trying after a scroll down div count has to increase, but its same.
when I set headless: false I can see pagination is working and new divs are appearing.

browser.screenshot throwing a Ferrum::DeadBrowserError on Raspberry pi

I encounter some issue with Ferrum on the raspberry pi 3 (raspbian).
Things are overall working fine it seems, but when I try to get a screenshot a Browser is dead (Ferrum::DeadBrowserError) is thrown.

My code:

browser = Ferrum::Browser.new(logger: STDERR, timeout: 20, process_timeout: 20)
browser.goto("https://github.com")
browser.screenshot(path:"github.png")
browser.quit

Chromedriver version: ChromeDriver 74.0.3729.157 (7b16107ab85c5364cdcd0b2dea2539a1f2dc327a-refs/branch-heads/3729@{#998})

Chromium version: Chromium 74.0.3729.157 Built on Raspbian , running on Raspbian 10

Logs:



▶ 0.8312325989973033 {"method":"Target.setDiscoverTargets","params":{"discover":true},"id":1}
    ◀ 0.8561899380001705 {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"3451b391-c1bc-4c79-954a-47d2b1dd5aa9","type":"browser","title":"","url":"","attached":true}}}
    ◀ 0.8572876209946116 {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"576D116D51492A2AE7E9F8B02C3C7AFA","type":"page","title":"","url":"about:blank","attached":false,"browserContextId":"83AD84A5DA67ABEA43ED2DCBA0CF24B1"}}}
    ◀ 0.8581510280055227 {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"e1ab0774-f216-4a41-99fe-79b449627d85","type":"browser","title":"","url":"","attached":false}}}


▶ 0.8603847810009029 {"method":"Target.createBrowserContext","params":{},"id":2}
    ◀ 0.8596250749978935 {"id":1,"result":{}}


▶ 0.8640326320019085 {"method":"Target.createTarget","params":{"browserContextId":"86496573612177A95997CE86C8B75E09","url":"about:blank"},"id":3}
    ◀ 0.8627023909939453 {"id":2,"result":{"browserContextId":"86496573612177A95997CE86C8B75E09"}}
    ◀ 0.8695459459995618 {"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"9FE06F59BCD0841F26F0B53F1762670F","type":"page","title":"","url":"","attached":false,"browserContextId":"86496573612177A95997CE86C8B75E09"}}}
    ◀ 0.8787524249928538 {"id":3,"result":{"targetId":"9FE06F59BCD0841F26F0B53F1762670F"}}


▶ 0.8835369180014823 {"method":"Page.enable","params":{},"id":1001}
    ◀ 0.8894098250020761 {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"9FE06F59BCD0841F26F0B53F1762670F","type":"page","title":"","url":"about:blank","attached":true,"browserContextId":"86496573612177A95997CE86C8B75E09"}}}
    ◀ 0.9641696489998139 {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"576D116D51492A2AE7E9F8B02C3C7AFA","type":"page","title":"about:blank","url":"about:blank","attached":false,"browserContextId":"83AD84A5DA67ABEA43ED2DCBA0CF24B1"}}}
    ◀ 0.9858697190065868 {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"9FE06F59BCD0841F26F0B53F1762670F","type":"page","title":"about:blank","url":"about:blank","attached":true,"browserContextId":"86496573612177A95997CE86C8B75E09"}}}
    ◀ 0.9902511819964275 {"id":1001,"result":{}}


▶ 0.9915030359989032 {"method":"DOM.enable","params":{},"id":1002}
    ◀ 0.9979021009930875 {"id":1002,"result":{}}


▶ 0.9987974870018661 {"method":"CSS.enable","params":{},"id":1003}
    ◀ 1.0031615540065104 {"id":1003,"result":{}}


▶ 1.0041059520008275 {"method":"Runtime.enable","params":{},"id":1004}
    ◀ 1.013563062006142 {"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"origin":"://","name":"","auxData":{"isDefault":true,"type":"default","frameId":"9FE06F59BCD0841F26F0B53F1762670F"}}}}
    ◀ 1.0143244350038003 {"id":1004,"result":{}}


▶ 1.0159129639941966 {"method":"Log.enable","params":{},"id":1005}
    ◀ 1.0179426499962574 {"id":1005,"result":{}}


▶ 1.0187779309926555 {"method":"Network.enable","params":{},"id":1006}
    ◀ 1.0205057870043674 {"id":1006,"result":{}}


▶ 1.0214511230005883 {"method":"Browser.getWindowForTarget","params":{"targetId":"9FE06F59BCD0841F26F0B53F1762670F"},"id":4}
    ◀ 1.0228925650008023 {"id":4,"result":{"windowId":2,"bounds":{"left":0,"top":0,"width":1024,"height":768,"windowState":"normal"}}}


▶ 1.024038218005444 {"method":"Browser.setWindowBounds","params":{"windowId":2,"bounds":{"windowState":"normal"}},"id":5}
    ◀ 1.025311531004263 {"id":5,"result":{}}


▶ 1.026535311000771 {"method":"Browser.setWindowBounds","params":{"windowId":2,"bounds":{"width":1024,"height":768,"windowState":"normal"}},"id":6}
    ◀ 1.0276017440046417 {"id":6,"result":{}}


▶ 1.0288821919966722 {"method":"Emulation.setDeviceMetricsOverride","params":{"width":1024,"height":768,"deviceScaleFactor":1,"mobile":false},"id":1007}
    ◀ 1.0306274960021256 {"method":"CSS.mediaQueryResultChanged","params":{}}
    ◀ 1.031602520000888 {"id":1007,"result":{}}


▶ 1.0325803049927345 {"method":"Page.getNavigationHistory","params":{},"id":1008}
    ◀ 1.0333433969935868 {"id":1008,"result":{"currentIndex":0,"entries":[{"id":2,"url":"about:blank","userTypedURL":"about:blank","title":"","transitionType":"typed"}]}}


▶ 1.0348001520032994 {"method":"Page.navigate","params":{"url":"https://github.com"},"id":1009}
    ◀ 1.0379407510044985 {"method":"Network.requestWillBeSent","params":{"requestId":"D374D9C283AECDB0069C187BE57287DF","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.com/","method":"GET","headers":{"Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"no-referrer-when-downgrade"},"timestamp":87098.883114,"wallTime":1569897098.774687,"initiator":{"type":"other"},"type":"Document","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 1.9378308190061944 {"method":"Network.responseReceived","params":{"requestId":"D374D9C283AECDB0069C187BE57287DF","loaderId":"D374D9C283AECDB0069C187BE57287DF","timestamp":87099.781194,"type":"Document","response":{"url":"https://github.com/","status":200,"statusText":"OK","headers":{"Date":"Tue, 01 Oct 2019 02:31:39 GMT","Content-Type":"text/html; charset=utf-8","Transfer-Encoding":"chunked","Server":"GitHub.com","Status":"200 OK","Vary":"X-PJAX\nAccept-Encoding","ETag":"W/\"49b9a4c05e52dd898d5c71f5a115d091\"","Cache-Control":"max-age=0, private, must-revalidate","Set-Cookie":"has_recent_activity=1; path=/; expires=Tue, 01 Oct 2019 03:31:39 -0000\n_octo=GH1.1.20502231.1569897099; domain=.github.com; path=/; expires=Fri, 01 Oct 2021 02:31:39 -0000\nlogged_in=no; domain=.github.com; path=/; expires=Sat, 01 Oct 2039 02:31:39 -0000; secure; HttpOnly\n_gh_sess=dWhMODdXemVHMkU3bGhCU3VuR2dyRnBNdUd2V3hTaGErTkRhWnhjUHVTV0ZwRUw3Nkt3ZFB4SCt1MUY2WUwwZVB5b1hNWi8za29jM1VSam4rSWxsYzVlbVJLVWdidVRqMEt2N1YxU3Z2M3V3Zkdua0JoWG5hZEFOOVA5OGNIWmwxVFdwd1JhenQ2cFUvaG4ralc4RHp3PT0tLWd4WVM0ZGFhWmQ4Z1VXTzU2T2NNU2c9PQ%3D%3D--a70968cb6995f43fb6bca31a803eb2f48036403e; path=/; secure; HttpOnly","X-Request-Id":"018ae935-c848-410f-979c-30162c1f72b7","Strict-Transport-Security":"max-age=31536000; includeSubdomains; preload","X-Frame-Options":"deny","X-Content-Type-Options":"nosniff","X-XSS-Protection":"1; mode=block","Referrer-Policy":"origin-when-cross-origin, strict-origin-when-cross-origin","Expect-CT":"max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"","Content-Security-Policy":"default-src 'none'; base-uri 'self'; block-all-mixed-content; connect-src 'self' uploads.github.com www.githubstatus.com collector.githubapp.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com wss://live.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.githubusercontent.com customer-stories-feed.github.com; manifest-src 'self'; media-src 'none'; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com","Content-Encoding":"gzip","X-GitHub-Request-Id":"A41E:2D4C:5497E5:7DC656:5D92BA8A"},"headersText":"HTTP/1.1 200 OK\r\nDate: Tue, 01 Oct 2019 02:31:39 GMT\r\nContent-Type: text/html; charset=utf-8\r\nTransfer-Encoding: chunked\r\nServer: GitHub.com\r\nStatus: 200 OK\r\nVary: X-PJAX\r\nETag: W/\"49b9a4c05e52dd898d5c71f5a115d091\"\r\nCache-Control: max-age=0, private, must-revalidate\r\nSet-Cookie: has_recent_activity=1; path=/; expires=Tue, 01 Oct 2019 03:31:39 -0000\r\nSet-Cookie: _octo=GH1.1.20502231.1569897099; domain=.github.com; path=/; expires=Fri, 01 Oct 2021 02:31:39 -0000\r\nSet-Cookie: logged_in=no; domain=.github.com; path=/; expires=Sat, 01 Oct 2039 02:31:39 -0000; secure; HttpOnly\r\nSet-Cookie: _gh_sess=dWhMODdXemVHMkU3bGhCU3VuR2dyRnBNdUd2V3hTaGErTkRhWnhjUHVTV0ZwRUw3Nkt3ZFB4SCt1MUY2WUwwZVB5b1hNWi8za29jM1VSam4rSWxsYzVlbVJLVWdidVRqMEt2N1YxU3Z2M3V3Zkdua0JoWG5hZEFOOVA5OGNIWmwxVFdwd1JhenQ2cFUvaG4ralc4RHp3PT0tLWd4WVM0ZGFhWmQ4Z1VXTzU2T2NNU2c9PQ%3D%3D--a70968cb6995f43fb6bca31a803eb2f48036403e; path=/; secure; HttpOnly\r\nX-Request-Id: 018ae935-c848-410f-979c-30162c1f72b7\r\nStrict-Transport-Security: max-age=31536000; includeSubdomains; preload\r\nX-Frame-Options: deny\r\nX-Content-Type-Options: nosniff\r\nX-XSS-Protection: 1; mode=block\r\nReferrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin\r\nExpect-CT: max-age=2592000, report-uri=\"https://api.github.com/_private/browser/errors\"\r\nContent-Security-Policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; connect-src 'self' uploads.github.com www.githubstatus.com collector.githubapp.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com wss://live.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.githubusercontent.com customer-stories-feed.github.com; manifest-src 'self'; media-src 'none'; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com\r\nContent-Encoding: gzip\r\nVary: Accept-Encoding\r\nX-GitHub-Request-Id: A41E:2D4C:5497E5:7DC656:5D92BA8A\r\n\r\n","mimeType":"text/html","requestHeaders":{"Host":"github.com","Connection":"keep-alive","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3","Accept-Encoding":"gzip, deflate, br"},"requestHeadersText":"GET / HTTP/1.1\r\nHost: github.com\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nAccept-Encoding: gzip, deflate, br\r\n","connectionReused":false,"connectionId":13,"remoteIPAddress":"13.114.40.48","remotePort":443,"fromDiskCache":false,"fromServiceWorker":false,"encodedDataLength":2284,"timing":{"requestTime":87098.884314,"proxyStart":-1,"proxyEnd":-1,"dnsStart":1.383,"dnsEnd":87.815,"connectStart":87.815,"connectEnd":540.267,"sslStart":155.815,"sslEnd":540.041,"workerStart":-1,"workerReady":-1,"sendStart":540.565,"sendEnd":540.859,"pushStart":0,"pushEnd":0,"receiveHeadersEnd":891.523},"protocol":"http/1.1","securityState":"secure","securityDetails":{"protocol":"TLS 1.3","keyExchange":"","keyExchangeGroup":"X25519","cipher":"CHACHA20_POLY1305","certificateId":0,"subjectName":"github.com","sanList":["github.com","www.github.com"],"issuer":"DigiCert SHA2 Extended Validation Server CA","validFrom":1525737600,"validTo":1591185600,"signedCertificateTimestampList":[],"certificateTransparencyCompliance":"unknown"}},"frameId":"9FE06F59BCD0841F26F0B53F1762670F"}}
    ◀ 1.940412705997005 {"id":1009,"result":{"frameId":"9FE06F59BCD0841F26F0B53F1762670F","loaderId":"D374D9C283AECDB0069C187BE57287DF"}}
    ◀ 1.9548210850043688 {"method":"Page.frameStartedLoading","params":{"frameId":"9FE06F59BCD0841F26F0B53F1762670F"}}
    ◀ 1.9630006630031858 {"method":"Target.targetInfoChanged","params":{"targetInfo":{"targetId":"9FE06F59BCD0841F26F0B53F1762670F","type":"page","title":"https://github.com","url":"https://github.com/","attached":true,"browserContextId":"86496573612177A95997CE86C8B75E09"}}}
    ◀ 1.967038107002736 {"method":"Network.dataReceived","params":{"requestId":"D374D9C283AECDB0069C187BE57287DF","timestamp":87099.794201,"dataLength":36184,"encodedDataLength":0}}
    ◀ 1.9678763050033012 {"method":"Runtime.executionContextDestroyed","params":{"executionContextId":1}}
    ◀ 1.9690215940063354 {"method":"Runtime.executionContextsCleared","params":{}}
    ◀ 1.9706022580066929 {"method":"Page.frameNavigated","params":{"frame":{"id":"9FE06F59BCD0841F26F0B53F1762670F","loaderId":"D374D9C283AECDB0069C187BE57287DF","url":"https://github.com/","securityOrigin":"https://github.com","mimeType":"text/html"}}}
    ◀ 1.9718270799930906 {"method":"Runtime.executionContextCreated","params":{"context":{"id":2,"origin":"https://github.com","name":"","auxData":{"isDefault":true,"type":"default","frameId":"9FE06F59BCD0841F26F0B53F1762670F"}}}}
    ◀ 1.9730189330002759 {"method":"DOM.documentUpdated","params":{}}
    ◀ 2.0057064639986493 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.3","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/assets/frameworks-849637ecbd4bd65815cc113d80fee2d4.css","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36","Referer":"https://github.com/","Origin":"https://github.com"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.826108,"wallTime":1569897099.71768,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":20},"type":"Stylesheet","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.008440385994618 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.4","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/assets/site-d20aa35917cf810095fab7458ea2a8e4.css","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36","Referer":"https://github.com/","Origin":"https://github.com"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.826904,"wallTime":1569897099.71848,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":21},"type":"Stylesheet","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.010343453992391 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.5","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/assets/github-ae81e416c83f4ddf38bf4c8c149384b3.css","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36","Referer":"https://github.com/","Origin":"https://github.com"},"mixedContentType":"none","initialPriority":"VeryHigh","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.827925,"wallTime":1569897099.7195,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":22},"type":"Stylesheet","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0118947430019034 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.7","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/search-key-slash.svg","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.829196,"wallTime":1569897099.72077,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":307},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0139962020039093 {"method":"Network.dataReceived","params":{"requestId":"D374D9C283AECDB0069C187BE57287DF","timestamp":87099.857246,"dataLength":65536,"encodedDataLength":0}}
    ◀ 2.0397416329942644 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.13","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/airbnb-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.868609,"wallTime":1569897099.76018,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":849},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.041765486006625 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.14","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/sap-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.869468,"wallTime":1569897099.76104,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":850},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0437285030056955 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.15","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/ibm-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.87061,"wallTime":1569897099.76218,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":851},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0458571500057587 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.16","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/google-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.871762,"wallTime":1569897099.76333,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":852},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.048426067995024 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.17","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/paypal-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.873033,"wallTime":1569897099.76461,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":853},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.050860660005128 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.18","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/bloomberg-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.873971,"wallTime":1569897099.76554,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":854},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0718247220065678 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.19","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/spotify-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.874782,"wallTime":1569897099.76635,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":855},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.073466689995257 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.20","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/swift-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.875848,"wallTime":1569897099.76742,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":856},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0751272000052268 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.21","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/facebook-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.876732,"wallTime":1569897099.76831,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":857},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.076691458001733 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.22","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/node-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.87753,"wallTime":1569897099.7691,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":858},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0783243110054173 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.23","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/nasa-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.878373,"wallTime":1569897099.76995,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":859},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.079959195005358 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.24","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/logos/walmart-logo.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.879174,"wallTime":1569897099.77075,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":860},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.081534754994209 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.25","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/home-illo-team.svg","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.879987,"wallTime":1569897099.77156,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":889},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.083197297004517 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.26","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/home-illo-team-code.svg","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.880858,"wallTime":1569897099.77243,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":894},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0850211439974373 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.27","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/home-illo-team-chaos.svg","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.881627,"wallTime":1569897099.7732,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":906},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.086779834004119 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.28","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/home-illo-team-tools.svg","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.882413,"wallTime":1569897099.77399,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":918},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.088596180998138 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.29","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/home-illo-business.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.884041,"wallTime":1569897099.77561,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1084},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.0902107519941637 {"method":"Network.dataReceived","params":{"requestId":"D374D9C283AECDB0069C187BE57287DF","timestamp":87099.897246,"dataLength":33037,"encodedDataLength":0}}
    ◀ 2.1026737700012745 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.47","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/integrators/slackhq.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.932177,"wallTime":1569897099.82375,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1133},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.1052716999984113 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.48","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/integrators/zenhubio.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.934165,"wallTime":1569897099.82574,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1134},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.1075669129932066 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.49","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/integrators/travis-ci.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.936193,"wallTime":1569897099.82777,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1135},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.1092225790052908 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.50","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/integrators/atom.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.93797,"wallTime":1569897099.82954,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1136},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.111207159992773 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.51","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/integrators/circleci.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.938894,"wallTime":1569897099.83047,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1137},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.1131719480035827 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.52","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/integrators/google.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.939673,"wallTime":1569897099.83125,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1138},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.1152705419954145 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.53","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/images/modules/site/integrators/codeclimate.png","method":"GET","headers":{"Referer":"https://github.com/","User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.940651,"wallTime":1569897099.83222,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1139},"type":"Image","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.1171746519976296 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.54","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/assets/compat-bootstrap-94789d4c.js","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36","Referer":"https://github.com/","Origin":"https://github.com"},"mixedContentType":"none","initialPriority":"Medium","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.943472,"wallTime":1569897099.83504,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1495},"type":"Script","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.1191648569947574 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.55","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/assets/frameworks-ace387cc.js","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36","Referer":"https://github.com/","Origin":"https://github.com"},"mixedContentType":"none","initialPriority":"Medium","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.944634,"wallTime":1569897099.83621,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1496},"type":"Script","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.121061726997141 {"method":"Network.requestWillBeSent","params":{"requestId":"1000025199.56","loaderId":"D374D9C283AECDB0069C187BE57287DF","documentURL":"https://github.com/","request":{"url":"https://github.githubassets.com/assets/github-bootstrap-17708fdc.js","method":"GET","headers":{"User-Agent":"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 HeadlessChrome/74.0.3729.157 Safari/537.36","Referer":"https://github.com/","Origin":"https://github.com"},"mixedContentType":"none","initialPriority":"Low","referrerPolicy":"strict-origin-when-cross-origin"},"timestamp":87099.945806,"wallTime":1569897099.83738,"initiator":{"type":"parser","url":"https://github.com/","lineNumber":1498},"type":"Script","frameId":"9FE06F59BCD0841F26F0B53F1762670F","hasUserGesture":false}}
    ◀ 2.122604942996986 {"method":"Network.loadingFinished","params":{"requestId":"D374D9C283AECDB0069C187BE57287DF","timestamp":87099.895208,"encodedDataLength":42529,"shouldReportCorbBlocking":false}}
    ◀ 2.3038363759987988 {"method":"Network.responseReceived","params":{"requestId":"1000025199.3","loaderId":"D374D9C283AECDB0069C187BE57287DF","timestamp":87100.147468,"type":"Stylesheet","response":{"url":"https://github.githubassets.com/assets/frameworks-849637ecbd4bd65815cc113d80fee2d4.css","status":200,"statusText":"","headers":{"x-fastly-request-id":"2469cbdd9f3e2f677b4b5e3ff8d601d99bbe0316","date":"Tue, 01 Oct 2019 02:31:39 GMT","content-encoding":"gzip","vary":"Origin, Access-Control-Request-Headers, Access-Control-Request-Method, Accept-Encoding","age":"3833","x-cache":"HIT, HIT","status":"200","content-length":"24647","x-served-by":"cache-iad2138-IAD, cache-itm18827-ITM","access-control-allow-origin":"*","last-modified":"Mon, 23 Sep 2019 17:52:31 GMT","server":"AmazonS3","x-timer":"S1569897100.975827,VS0,VE0","etag":"\"fdc991ec3a542f3627092917c3baa28d\"","access-control-max-age":"3600","access-control-allow-methods":"GET","content-type":"text/css","via":"1.1 varnish, 1.1 varnish","accept-ranges":"bytes","x-cache-hits":"4, 180"},"mimeType":"text/css","connectionReused":false,"connectionId":73,"remoteIPAddress":"185.199.108.154","remotePort":443,"fromDiskCache":false,"fromServiceWorker":false,"encodedDataLength":395,"timing":{"requestTime":87099.827162,"proxyStart":-1,"proxyEnd":-1,"dnsStart":3.322,"dnsEnd":79.7,"connectStart":79.7,"connectEnd":251.44,"sslStart":145.753,"sslEnd":251.419,"workerStart":-1,"workerReady":-1,"sendStart":260.941,"sendEnd":262.827,"pushStart":0,"pushEnd":0,"receiveHeadersEnd":318.359},"protocol":"h2","securityState":"secure","securityDetails":{"protocol":"TLS 1.2","keyExchange":"ECDHE_RSA","keyExchangeGroup":"X25519","cipher":"AES_128_GCM","certificateId":0,"subjectName":"*.githubassets.com","sanList":["*.githubassets.com","githubassets.com"],"issuer":"DigiCert SHA2 High Assurance Server CA","validFrom":1540771200,"validTo":1604318400,"signedCertificateTimestampList":[],"certificateTransparencyCompliance":"unknown"}},"frameId":"9FE06F59BCD0841F26F0B53F1762670F"}}
    ◀ 2.3079078839946305 {"method":"Network.responseReceived","params":{"requestId":"1000025199.4","loaderId":"D374D9C283AECDB0069C187BE57287DF","timestamp":87100.151769,"type":"Stylesheet","response":{"url":"https://github.githubassets.com/assets/site-d20aa35917cf810095fab7458ea2a8e4.css","status":200,"statusText":"","headers":{"x-fastly-request-id":"cd466b037f4bd7c028c988123ae94632fa891846","date":"Tue, 01 Oct 2019 02:31:39 GMT","content-encoding":"gzip","vary":"Origin, Access-Control-Request-Headers, Access-Control-Request-Method, Accept-Encoding","age":"3162","x-cache":"HIT, HIT","status":"200","content-length":"14030","x-served-by":"cache-iad2147-IAD, cache-itm18827-ITM","access-control-allow-origin":"*","last-modified":"Thu, 19 Sep 2019 08:34:46 GMT","server":"AmazonS3","x-timer":"S1569897100.975837,VS0,VE0","etag":"\"4d55638d0fc43883b9646f209770d33b\"","access-control-max-age":"3600","access-control-allow-methods":"GET","content-type":"text/css","via":"1.1 varnish, 1.1 varnish","accept-ranges":"bytes","x-cache-hits":"1, 174"},"mimeType":"text/css","connectionReused":true,"connectionId":73,"remoteIPAddress":"185.199.108.154","remotePort":443,"fromDiskCache":false,"fromServiceWorker":false,"encodedDataLength":181,"timing":{"requestTime":87099.82938,"proxyStart":-1,"proxyEnd":-1,"dnsStart":-1,"dnsEnd":-1,"connectStart":-1,"connectEnd":-1,"sslStart":-1,"sslEnd":-1,"workerStart":-1,"workerReady":-1,"sendStart":258.87,"sendEnd":260.616,"pushStart":0,"pushEnd":0,"receiveHeadersEnd":319.966},"protocol":"h2","securityState":"secure","securityDetails":{"protocol":"TLS 1.2","keyExchange":"ECDHE_RSA","keyExchangeGroup":"X25519","cipher":"AES_128_GCM","certificateId":0,"subjectName":"*.githubassets.com","sanList":["*.githubassets.com","githubassets.com"],"issuer":"DigiCert SHA2 High Assurance Server CA","validFrom":1540771200,"validTo":1604318400,"signedCertificateTimestampList":[],"certificateTransparencyCompliance":"unknown"}},"frameId":"9FE06F59BCD0841F26F0B53F1762670F"}}
    ◀ 2.31049372900452 {"method":"Network.dataReceived","params":{"requestId":"1000025199.3","timestamp":87100.155399,"dataLength":20215,"encodedDataLength":4942}}
    ◀ 2.342035659996327 {"method":"Network.dataReceived","params":{"requestId":"1000025199.3","timestamp":87100.187109,"dataLength":28584,"encodedDataLength":5518}}
    ◀ 2.349421937993611 {"method":"Network.dataReceived","params":{"requestId":"1000025199.3","timestamp":87100.194383,"dataLength":35839,"encodedDataLength":5348}}
    ◀ 2.3563673710013973 {"method":"Network.dataReceived","params":{"requestId":"1000025199.3","timestamp":87100.201162,"dataLength":56530,"encodedDataLength":8277}}
    ◀ 2.3586233649984933 {"method":"Network.dataReceived","params":{"requestId":"1000025199.3","timestamp":87100.202809,"dataLength":2926,"encodedDataLength":580}}
    ◀ 2.403287028995692 {"method":"Network.loadingFinished","params":{"requestId":"1000025199.3","timestamp":87100.203826,"encodedDataLength":25060,"shouldReportCorbBlocking":false}}
    ◀ 2.4061445999977877 {"method":"Network.dataReceived","params":{"requestId":"1000025199.4","timestamp":87100.250606,"dataLength":42516,"encodedDataLength":7527}}


▶ 41.97464164199482 {"method":"Page.captureScreenshot","params":{"format":"png"},"id":1010}
Traceback (most recent call last):
        4: from tamanager.rb:7:in `<main>'
        3: from /home/pi/.rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0/forwardable.rb:230:in `screenshot'
        2: from /home/pi/.rvm/gems/ruby-2.6.3/gems/ferrum-0.5/lib/ferrum/page/screenshot.rb:9:in `screenshot'
        1: from /home/pi/.rvm/gems/ruby-2.6.3/gems/ferrum-0.5/lib/ferrum/page.rb:110:in `command'
/home/pi/.rvm/gems/ruby-2.6.3/gems/ferrum-0.5/lib/ferrum/browser/client.rb:41:in `command': Browser is dead (Ferrum::DeadBrowserError)

The Page.captureScreenshot seems to take a really long time to process. I tried to increase the timeouts to 100 but it's simply waiting longer and fail.
Any idea what could be the problem? Save to pdf feature leads to the same error.

Thanks

"_cuprite is undefined" error, `execution_id` of `main_frame` can be overwritten.

execution_id of main_frame can be overwritten during Runtime.executionContextCreated event for chrome-extension for example.
I temporary fixed this with
frame.execution_id = context_id unless frame.instance_variable_get(:@execution_id)
I faced this issue in Opera browser which creates execution context forVideo handler extension for every frame.

Ferrum needs to listen DOM.documentUpdated in order to use NodeId values properly

https://chromedevtools.github.io/devtools-protocol/tot/DOM/#event-documentUpdated

Problem

Since ferrum tracks node ids in frontend but does not listen to DOM.documentUpdated it's possible that the following sequence of events happens:

  1. Ferrum::Node has node_id in memory
  2. DOM.documentUpdated happens
  3. calling methods on the node returns information for a new node that has matching id
    1. this would happen because they seem to start again from zero so they are not unique during single session

Fix

Make all Node objects listen to DOM.documentUpdated after which the objects should become stale so that all new commands issued with the node raise an exception.

Is there a way to fetch the Javascript console?

I've tried to setup a logger: option when initializing the Ferrum::Browser but my console.log("xyz"); don't make it there.

Is there a way to fetch the Javascript console?

Thanks for the excellent gem.

Use scrollHeight/Width instead of offset for full?

The full option for screenshots is currently using the offsetHeight and offsetWidth:

https://github.com/route/ferrum/blob/0e96c1cf87ac768df521a6f321ced22f13899b7e/lib/ferrum/page/screenshot.rb#L49-L54

However, on my device™ (in Chrome Version 80.0.3987.116) this returns the size of the viewport. Thus when I run browser.screenshot(full: true, "google.png") I get the same result as without the full: true option.

Using the scrollHeight/scrollWidth instead returns the actual height/width of the rendered site.

PS: thank you for this gem, it has been a joy to use :)

Warnings

There are a two warnings when running rake test for app build with ferrum:

/Library/Ruby/Gems/2.6.0/gems/ferrum-0.8/lib/ferrum/context.rb:45: warning: assigned but unused variable - target_id
/Library/Ruby/Gems/2.6.0/gems/ferrum-0.8/lib/ferrum/browser/process.rb:96: warning: instance variable @user_data_dir not initialized

Probably nothing dangerous but a bit unclean.

Xvfb support

Hey, I've got ferrum working with xvfb. Are you interested to have ferrum automatically handle Xvfb opening and choosing a display?

Like when you pass headless: :xfb

I could PR on this

Dealing with JS alerts

Hello Ferrum Developers,

I'm in the midst of programming the macros for a website that includes JavaScript alerts. Thanks to your software it's been a joy so far engaging with this automation work. I do however face a problem with alerts as of now. It seems that there's no native way of dealing with alerts? I would like to either automatically approve them or have them disabled from the start of chromium. I haven't been able to find a proper solution yet, but I would like to hear whether this could be done with Ferrum.

Thank you!

Ferrum::DeadBrowserError Errors raised on Heroku

I mentioned this in #96 , but after successfully having Ferrum running within my application on Heroku, I have recently seen all of my Ferrum workers start to error out and throw the error Ferrum::DeadBrowserError

I'm using the Heroku Chrome Buildpack with the Chrome version of 84.0.4147.105 and the Rails version of 5.1.7.

When I run this locally everything works as expected, and I don't believe there's been any changes to my code (since the error is generated before we even really hit my code)

Steps to reproduce:

Open up a Rails Console
heroku run rails console

Create a browser instance
client = Ferrum::Browser.new

Go to a website
client.goto("http://yahoo.com")

get the error Ferrum::DeadBrowserError

Set value for non-focusable input

Is there a way how to fill hidden input field? Eg. a TEXTAREA with style="display: none;".

When I do this:

page.at_css('#hidden-field').focus.type('foobar')

I get Ferrum::BrowserError: Element is not focusable

Currenly this is working for me, but some native way of setting hidden values would be nice.

page.execute("document.getElementById('hidden-field').value = 'foobar'")

Is there a better way?

Any plans to allow passing in HTML instead of a URL?

Hi folks, this is amazing and we hope to be able to collapse our current Grover/Puppeteer/Rails stack to just Ferrum/Rails in the future for screenshot generation of some views.

However, we rely on being able to pass in our views as rendered HTML strings rather than as a URL to fetch. (I think Grover uses Puppeteer's setContent() for this.)

Are there plans for Ferrum to support this in the future?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.