ferrum 0.17.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/ferrum/accessibility/ax_node.rb +108 -0
  4. data/lib/ferrum/accessibility.rb +106 -0
  5. data/lib/ferrum/browser/binary.rb +42 -2
  6. data/lib/ferrum/browser/command.rb +20 -2
  7. data/lib/ferrum/browser/options/base.rb +66 -0
  8. data/lib/ferrum/browser/options/chrome.rb +35 -2
  9. data/lib/ferrum/browser/options.rb +41 -2
  10. data/lib/ferrum/browser/process/killer.rb +188 -0
  11. data/lib/ferrum/browser/process.rb +128 -72
  12. data/lib/ferrum/browser/xvfb.rb +24 -0
  13. data/lib/ferrum/browser.rb +47 -17
  14. data/lib/ferrum/client/subscriber.rb +65 -2
  15. data/lib/ferrum/client/web_socket.rb +126 -13
  16. data/lib/ferrum/client.rb +225 -12
  17. data/lib/ferrum/context.rb +113 -7
  18. data/lib/ferrum/contexts.rb +188 -19
  19. data/lib/ferrum/cookies/cookie.rb +6 -0
  20. data/lib/ferrum/cookies.rb +5 -0
  21. data/lib/ferrum/dialog.rb +18 -2
  22. data/lib/ferrum/downloads.rb +54 -1
  23. data/lib/ferrum/errors.rb +103 -4
  24. data/lib/ferrum/frame/dom.rb +65 -0
  25. data/lib/ferrum/frame/runtime.rb +57 -15
  26. data/lib/ferrum/frame.rb +58 -1
  27. data/lib/ferrum/headers.rb +6 -0
  28. data/lib/ferrum/interceptable.rb +83 -0
  29. data/lib/ferrum/keyboard.rb +25 -0
  30. data/lib/ferrum/mouse.rb +6 -0
  31. data/lib/ferrum/network/auth_request.rb +81 -2
  32. data/lib/ferrum/network/error.rb +15 -0
  33. data/lib/ferrum/network/exchange.rb +10 -0
  34. data/lib/ferrum/network/intercepted_request.rb +94 -2
  35. data/lib/ferrum/network/request.rb +1 -1
  36. data/lib/ferrum/network/response.rb +2 -0
  37. data/lib/ferrum/network.rb +121 -9
  38. data/lib/ferrum/node.rb +305 -15
  39. data/lib/ferrum/page/animation.rb +5 -0
  40. data/lib/ferrum/page/frames.rb +69 -7
  41. data/lib/ferrum/page/screencast.rb +5 -0
  42. data/lib/ferrum/page/screenshot.rb +55 -23
  43. data/lib/ferrum/page/stream.rb +71 -0
  44. data/lib/ferrum/page/tracing.rb +6 -0
  45. data/lib/ferrum/page.rb +148 -45
  46. data/lib/ferrum/proxy.rb +52 -2
  47. data/lib/ferrum/rgba.rb +10 -0
  48. data/lib/ferrum/target.rb +130 -3
  49. data/lib/ferrum/utils/attempt.rb +20 -0
  50. data/lib/ferrum/utils/elapsed_time.rb +38 -0
  51. data/lib/ferrum/utils/event.rb +14 -0
  52. data/lib/ferrum/utils/platform.rb +21 -0
  53. data/lib/ferrum/utils/thread.rb +15 -1
  54. data/lib/ferrum/version.rb +1 -1
  55. data/lib/ferrum/worker.rb +125 -0
  56. data/lib/ferrum.rb +7 -0
  57. metadata +6 -16
  58. data/lib/ferrum/browser/options/firefox.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbed705a172379d21bd97f46bdd7ed29e444b08d79e6191ecdaf1e3cdc254793
4
- data.tar.gz: d5143b49451e8c7a31ae394ac226a8c2fb8feda6c7b30aee35e7349e3c5c818f
3
+ metadata.gz: 4a719b34dd1893e7c0ba2b71e121e1a2b7cc7950b96a1e462ac6a3a9f36c04e0
4
+ data.tar.gz: 4c96a549ff3aa00d76420b43c2f7b4870cacb712d390efe7394ad2a8245f6825
5
5
  SHA512:
6
- metadata.gz: 3c217d761ea4bde8cbbdd231229a0d7e209e97b15de7756e76f4354fefb08c5f7eb5774842a239d2312feba262685166712e915d2ea1dcc50d5572809fdafe76
7
- data.tar.gz: 640b40256d2b9db5a1a62093d865ea51ad5b92ce2ac3dc1ba5c332665148e54f7fd10da5675eceb7e18ba54840454fc21e31849b4ced8770fee60ea592f582cb
6
+ metadata.gz: 1a4e9981f2e6334b2862a073eb645b893bbebd42399f0e4b3090d98ea3d69ab0d3104bebe34f86e66b3ec2ea01b827efa28b75c2be55482de6f89a8b082b2113
7
+ data.tar.gz: acab89439a3655e51c3173bc30199bbccb6511567803fa16f066b26fbd6e5934f75492b6ed6383c8c59a27e397e23c781226abd439c551bc6be63c001868bc3a
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## [Documentation](https://docs.rubycdp.com/docs/ferrum/introduction)
4
4
 
5
5
  <img align="right"
6
- width="320" height="241"
6
+ width="160" height="160"
7
7
  alt="Ferrum logo"
8
8
  src="https://raw.githubusercontent.com/rubycdp/ferrum/main/logo.svg?sanitize=true">
9
9
 
@@ -15,9 +15,11 @@ protocol because Chrome allows you to do so many things that are barely
15
15
  supported by WebDriver because it should have consistent design with other
16
16
  browsers.
17
17
 
18
- * [Cuprite](https://github.com/rubycdp/cuprite) is a pure Ruby driver for[Capybara](https://github.com/teamcapybara/capybara) based on Ferrum.
18
+ * [Cuprite](https://github.com/rubycdp/cuprite) is a pure Ruby driver for [Capybara](https://github.com/teamcapybara/capybara) based on Ferrum.
19
19
  * [Vessel](https://github.com/rubycdp/vessel) high-level web crawling framework based on Ferrum and Mechanize.
20
20
 
21
+ <br clear="right">
22
+
21
23
  ## Install
22
24
 
23
25
  There's no official Chrome or Chromium package for Linux don't install it this
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ferrum
4
+ class Accessibility
5
+ #
6
+ # Represents an [AXNode](https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/#type-AXNode)
7
+ # from the CDP Accessibility domain.
8
+ #
9
+ class AXNode
10
+ #
11
+ # @param [Hash{String => Object}] params
12
+ # The parsed CDP AXNode attributes.
13
+ #
14
+ def initialize(params)
15
+ @params = deep_freeze(params)
16
+ end
17
+
18
+ # The AX node's role, e.g. `"button"`.
19
+ #
20
+ # @return [String, nil]
21
+ def role
22
+ @params.dig("role", "value")
23
+ end
24
+
25
+ # The AX node's accessible name.
26
+ #
27
+ # @return [String, nil]
28
+ def name
29
+ @params.dig("name", "value")
30
+ end
31
+
32
+ # The AX node's accessible description.
33
+ #
34
+ # @return [String, nil]
35
+ def description
36
+ @params.dig("description", "value")
37
+ end
38
+
39
+ # The AX node's current value.
40
+ #
41
+ # @return [String, Numeric, Boolean, nil] raw CDP AXValue.value; type varies by control
42
+ def value
43
+ @params.dig("value", "value")
44
+ end
45
+
46
+ # ARIA/computed properties flattened to `name => value`.
47
+ #
48
+ # @return [Hash{String => Object}]
49
+ def properties
50
+ @properties ||= Array(@params["properties"]).to_h do |property|
51
+ [property["name"], property.dig("value", "value")]
52
+ end.freeze
53
+ end
54
+
55
+ # Whether the node is ignored by the accessibility tree.
56
+ #
57
+ # @return [Boolean]
58
+ def ignored?
59
+ @params["ignored"] == true
60
+ end
61
+
62
+ # The reasons the node is ignored, if any.
63
+ #
64
+ # @return [Array, nil]
65
+ def ignored_reasons
66
+ @params["ignoredReasons"]
67
+ end
68
+
69
+ # The AX node's id.
70
+ #
71
+ # @return [String, nil]
72
+ def node_id
73
+ @params["nodeId"]
74
+ end
75
+
76
+ # The id of the DOM node this AX node is backed by.
77
+ #
78
+ # @return [Integer, nil]
79
+ def backend_dom_node_id
80
+ @params["backendDOMNodeId"]
81
+ end
82
+
83
+ # The ids of this AX node's children.
84
+ #
85
+ # @return [Array, nil]
86
+ def child_ids
87
+ @params["childIds"]
88
+ end
89
+
90
+ # The raw CDP AXNode hash.
91
+ #
92
+ # @return [Hash]
93
+ def to_h
94
+ @params
95
+ end
96
+
97
+ private
98
+
99
+ def deep_freeze(object)
100
+ case object
101
+ when Hash then object.each { |key, value| deep_freeze(key.freeze) && deep_freeze(value) }
102
+ when Array then object.each { |value| deep_freeze(value) }
103
+ end
104
+ object.freeze
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ferrum/accessibility/ax_node"
4
+
5
+ module Ferrum
6
+ #
7
+ # Wraps the CDP [Accessibility](https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/)
8
+ # domain. The query commands work without `enable`; `enable`/`disable` are
9
+ # provided for completeness (live AX events).
10
+ #
11
+ # @note The node-scoped methods (`node_for`, `partial_tree`, `query` with a
12
+ # `node:`) issue the command against the node's owning page session. They
13
+ # support same-process (same-target) iframes; nodes living in an
14
+ # out-of-process iframe (OOPIF, separate CDP target) are not resolvable and
15
+ # will error or return an empty result.
16
+ #
17
+ class Accessibility
18
+ def initialize(page)
19
+ @page = page
20
+ end
21
+
22
+ #
23
+ # The single non-ignored AXNode for a DOM node, or `nil`.
24
+ #
25
+ # @param [Ferrum::Node] node
26
+ # @return [AXNode, nil]
27
+ #
28
+ def node_for(node)
29
+ partial_tree(node: node).find { |ax_node| !ax_node.ignored? }
30
+ end
31
+
32
+ #
33
+ # The partial AX tree for a DOM node.
34
+ #
35
+ # @param [Ferrum::Node] node
36
+ # @param [Boolean] fetch_relatives
37
+ # @return [Array<AXNode>]
38
+ #
39
+ def partial_tree(node:, fetch_relatives: false)
40
+ nodes = node.page.command("Accessibility.getPartialAXTree",
41
+ nodeId: node.node_id,
42
+ fetchRelatives: fetch_relatives)["nodes"]
43
+ build(nodes)
44
+ end
45
+
46
+ #
47
+ # The full AX tree for the page.
48
+ #
49
+ # @param [Integer, nil] depth
50
+ # @param [String, nil] frame_id
51
+ # @return [Array<AXNode>]
52
+ #
53
+ def snapshot(depth: nil, frame_id: nil)
54
+ params = { depth: depth, frameId: frame_id }.compact
55
+ build(@page.command("Accessibility.getFullAXTree", **params)["nodes"])
56
+ end
57
+
58
+ #
59
+ # Query the AX tree by accessible name and/or role.
60
+ #
61
+ # @param [String, nil] name
62
+ # @param [String, nil] role
63
+ # @param [Ferrum::Node, nil] node Scope the query to this node's subtree.
64
+ # @return [Array<AXNode>]
65
+ #
66
+ def query(name: nil, role: nil, node: nil)
67
+ page = node ? node.page : @page
68
+ params = { accessibleName: name, role: role }.compact
69
+ params[:nodeId] = node ? node.node_id : page.document_node_id
70
+ build(page.command("Accessibility.queryAXTree", **params)["nodes"])
71
+ end
72
+
73
+ #
74
+ # The root AXNode of the (optionally framed) document.
75
+ #
76
+ # @param [String, nil] frame_id
77
+ # @return [AXNode, nil]
78
+ #
79
+ def root(frame_id: nil)
80
+ params = { depth: 1, frameId: frame_id }.compact
81
+ build(@page.command("Accessibility.getFullAXTree", **params)["nodes"]).first
82
+ end
83
+
84
+ # Enables the Accessibility domain, activating live AX tree change events.
85
+ #
86
+ # @return [self]
87
+ def enable
88
+ @page.command("Accessibility.enable")
89
+ self
90
+ end
91
+
92
+ # Disables the Accessibility domain.
93
+ #
94
+ # @return [self]
95
+ def disable
96
+ @page.command("Accessibility.disable")
97
+ self
98
+ end
99
+
100
+ private
101
+
102
+ def build(nodes)
103
+ Array(nodes).map { |node| AXNode.new(node) }
104
+ end
105
+ end
106
+ end
@@ -2,23 +2,64 @@
2
2
 
3
3
  module Ferrum
4
4
  class Browser
5
+ #
6
+ # Locates an executable on the system `PATH`, mirroring what a shell's
7
+ # `which`/`where` would find. Used to resolve the browser (and Xvfb)
8
+ # binary when no explicit path is configured.
9
+ #
5
10
  module Binary
6
11
  module_function
7
12
 
13
+ #
14
+ # Finds the first executable path for the given command(s) on `PATH`.
15
+ #
16
+ # @param [String, Array<String>] commands
17
+ # Command name(s) to look up.
18
+ #
19
+ # @return [String, nil]
20
+ # Absolute path to the executable, or `nil` if none is found.
21
+ #
8
22
  def find(commands)
9
23
  enum(commands).first
10
24
  end
11
25
 
26
+ #
27
+ # Finds all executable paths for the given command(s) on `PATH`.
28
+ #
29
+ # @param [String, Array<String>] commands
30
+ # Command name(s) to look up.
31
+ #
32
+ # @return [Array<String>]
33
+ # Absolute paths to matching executables.
34
+ #
12
35
  def all(commands)
13
36
  enum(commands).force
14
37
  end
15
38
 
39
+ #
40
+ # Lazily enumerates executable paths for the given command(s) on `PATH`.
41
+ #
42
+ # @param [String, Array<String>] commands
43
+ # Command name(s) to look up.
44
+ #
45
+ # @return [Enumerator::Lazy]
46
+ # Lazy enumerator yielding matching executable paths.
47
+ #
16
48
  def enum(commands)
17
49
  paths, exts = prepare_paths
18
50
  cmds = Array(commands).product(paths, exts)
19
51
  lazy_find(cmds)
20
52
  end
21
53
 
54
+ #
55
+ # Directories on `PATH` and the executable extensions to try against them.
56
+ #
57
+ # @return [Array(Array<String>, Array<String>)]
58
+ # The `PATH` directories, and the extensions from `PATHEXT` (plus `""`).
59
+ #
60
+ # @raise [EmptyPathError]
61
+ # If `PATH` is empty.
62
+ #
22
63
  def prepare_paths
23
64
  exts = (ENV.key?("PATHEXT") ? ENV.fetch("PATHEXT").split(";") : []) << ""
24
65
  paths = ENV["PATH"].split(File::PATH_SEPARATOR)
@@ -27,7 +68,7 @@ module Ferrum
27
68
  [paths, exts]
28
69
  end
29
70
 
30
- # rubocop:disable Style/CollectionCompact
71
+ # rubocop:disable-next Style/CollectionCompact
31
72
  def lazy_find(cmds)
32
73
  cmds.lazy.map do |cmd, path, ext|
33
74
  absolute_path = File.absolute_path(cmd)
@@ -40,7 +81,6 @@ module Ferrum
40
81
  cmd
41
82
  end.reject(&:nil?) # .compact isn't defined on Enumerator::Lazy
42
83
  end
43
- # rubocop:enable Style/CollectionCompact
44
84
  end
45
85
  end
46
86
  end
@@ -2,6 +2,12 @@
2
2
 
3
3
  module Ferrum
4
4
  class Browser
5
+ #
6
+ # Builds the OS-level command used to spawn the browser process: resolves
7
+ # the executable path and merges the browser-specific required/default
8
+ # flags (see {Options::Base}) with any user-supplied ones into a single
9
+ # argument list.
10
+ #
5
11
  class Command
6
12
  NOT_FOUND = "Could not find an executable for the browser. Try to make " \
7
13
  "it available on the PATH or set environment variable for " \
@@ -11,8 +17,6 @@ module Ferrum
11
17
  # https://github.com/cyrus-and/chrome-remote-interface#implementations
12
18
  def self.build(options, user_data_dir)
13
19
  defaults = case options.browser_name
14
- when :firefox
15
- Options::Firefox.options
16
20
  when :chrome, :opera, :edge, nil
17
21
  Options::Chrome.options
18
22
  else
@@ -35,14 +39,28 @@ module Ferrum
35
39
  merge_options
36
40
  end
37
41
 
42
+ # Whether the browser should be launched under Xvfb.
43
+ #
44
+ # @return [Boolean]
38
45
  def xvfb?
39
46
  !!options.xvfb
40
47
  end
41
48
 
49
+ #
50
+ # Command line arguments for spawning the browser process.
51
+ #
52
+ # @return [Array<String>]
53
+ # The browser path followed by its CLI flags.
54
+ #
42
55
  def to_a
43
56
  [path] + @flags.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
44
57
  end
45
58
 
59
+ #
60
+ # String representation of the command used to spawn the browser process.
61
+ #
62
+ # @return [String]
63
+ #
46
64
  def to_s
47
65
  to_a.join(" \\ \n ")
48
66
  end
@@ -6,14 +6,29 @@ require "open3"
6
6
  module Ferrum
7
7
  class Browser
8
8
  class Options
9
+ #
10
+ # Abstract base for browser-specific default option builders. Subclasses
11
+ # (e.g. {Options::Chrome}) declare their own `DEFAULT_OPTIONS` and
12
+ # `PLATFORM_PATH` constants and implement {#merge_required}/{#merge_default}
13
+ # to produce the final CLI flags used by {Command}.
14
+ #
9
15
  class Base
10
16
  include Singleton
11
17
 
18
+ #
19
+ # Singleton instance holding the browser's default options.
20
+ #
21
+ # @return [Base]
22
+ #
12
23
  def self.options
13
24
  instance
14
25
  end
15
26
 
27
+ # The installed browser's version string, obtained by running its
28
+ # binary with `--version`.
29
+ #
16
30
  # @return [String, nil]
31
+ # The version output, or `nil` if the binary can't be found/run.
17
32
  def self.version
18
33
  out, = Open3.capture2(instance.detect_path, "--version")
19
34
  out.strip
@@ -21,22 +36,73 @@ module Ferrum
21
36
  nil
22
37
  end
23
38
 
39
+ #
40
+ # Default CLI flags for the browser.
41
+ #
42
+ # @return [Hash{String => Object}]
43
+ #
24
44
  def to_h
25
45
  self.class::DEFAULT_OPTIONS
26
46
  end
27
47
 
48
+ #
49
+ # Default CLI flags excluding the given keys.
50
+ #
51
+ # @param [Array<String>] keys
52
+ # Flag names to exclude.
53
+ #
54
+ # @return [Hash{String => Object}]
55
+ #
28
56
  def except(*keys)
29
57
  to_h.except(*keys)
30
58
  end
31
59
 
60
+ #
61
+ # Locates the browser executable on the system.
62
+ #
63
+ # @return [String, nil]
64
+ # Absolute path to the browser binary, or `nil` if not found.
65
+ #
32
66
  def detect_path
33
67
  Binary.find(self.class::PLATFORM_PATH[Utils::Platform.platform_name])
34
68
  end
35
69
 
70
+ #
71
+ # Merges the CLI flags that are always required to drive the browser
72
+ # over CDP (e.g. remote debugging port, user data dir) into `flags`.
73
+ # Abstract; overridden by {Options::Chrome}.
74
+ #
75
+ # @param [Hash{String => Object}] flags
76
+ # The flags accumulated so far.
77
+ # @param [Options] options
78
+ # The user-supplied browser options.
79
+ # @param [String] user_data_dir
80
+ # Path to the browser's user data directory.
81
+ #
82
+ # @return [Hash{String => Object}]
83
+ #
84
+ # @raise [NotImplementedError]
85
+ # Always, unless overridden by a subclass.
86
+ #
36
87
  def merge_required(flags, options, user_data_dir)
37
88
  raise NotImplementedError
38
89
  end
39
90
 
91
+ #
92
+ # Merges this browser's default CLI flags (see {#to_h}) into `flags`,
93
+ # unless `options.ignore_default_browser_options` is set. Abstract;
94
+ # overridden by {Options::Chrome}.
95
+ #
96
+ # @param [Hash{String => Object}] flags
97
+ # The flags accumulated so far.
98
+ # @param [Options] options
99
+ # The user-supplied browser options.
100
+ #
101
+ # @return [Hash{String => Object}]
102
+ #
103
+ # @raise [NotImplementedError]
104
+ # Always, unless overridden by a subclass.
105
+ #
40
106
  def merge_default(flags, options)
41
107
  raise NotImplementedError
42
108
  end
@@ -3,6 +3,12 @@
3
3
  module Ferrum
4
4
  class Browser
5
5
  class Options
6
+ #
7
+ # Chrome/Chromium-specific default flags and binary locations. Provides
8
+ # the CLI flags required to drive Chrome over CDP as well as the
9
+ # hardened defaults (disabling background networking, extensions,
10
+ # infobars, etc.) applied unless the user opts out.
11
+ #
6
12
  class Chrome < Base
7
13
  DEFAULT_OPTIONS = {
8
14
  "allow-pre-commit-input" => nil,
@@ -80,9 +86,23 @@ module Ferrum
80
86
  linux: LINUX_BIN_PATH
81
87
  }.freeze
82
88
 
89
+ #
90
+ # Merges CLI flags required for Chrome to work with CDP.
91
+ #
92
+ # @param [Hash] flags
93
+ # Flags to merge required ones into.
94
+ #
95
+ # @param [Ferrum::Browser::Options] options
96
+ # Browser options.
97
+ #
98
+ # @param [String] user_data_dir
99
+ # Path to the browser's user data directory.
100
+ #
101
+ # @return [Hash]
102
+ # Merged flags.
103
+ #
83
104
  def merge_required(flags, options, user_data_dir)
84
105
  flags = flags.merge("remote-debugging-port" => options.port,
85
- "remote-debugging-address" => options.host,
86
106
  "window-size" => options.window_size&.join(","),
87
107
  "user-data-dir" => user_data_dir)
88
108
 
@@ -94,8 +114,21 @@ module Ferrum
94
114
  flags
95
115
  end
96
116
 
117
+ #
118
+ # Merges Chrome's default flags with the given ones, unless the browser
119
+ # is configured to ignore default browser options.
120
+ #
121
+ # @param [Hash] flags
122
+ # Flags that take precedence over the defaults.
123
+ #
124
+ # @param [Ferrum::Browser::Options] options
125
+ # Browser options.
126
+ #
127
+ # @return [Hash]
128
+ # Merged flags.
129
+ #
97
130
  def merge_default(flags, options)
98
- defaults = options.headless == false ? except("headless", "disable-gpu") : DEFAULT_OPTIONS
131
+ defaults = options.headless == false ? except("headless", "disable-gpu") : DEFAULT_OPTIONS.dup
99
132
  defaults.delete("no-startup-window") if options.incognito == false
100
133
 
101
134
  if options.dockerize || ENV["FERRUM_CHROME_DOCKERIZE"] == "true"
@@ -2,12 +2,19 @@
2
2
 
3
3
  module Ferrum
4
4
  class Browser
5
+ #
6
+ # Resolves and normalizes the options hash passed to {Browser.new},
7
+ # applying defaults for connection settings (host, port, timeouts),
8
+ # window size, and other launch behavior. Used throughout the browser to
9
+ # look up user-configured settings.
10
+ #
5
11
  class Options
6
12
  BROWSER_PORT = "0"
7
13
  BROWSER_HOST = "127.0.0.1"
8
14
  WINDOW_SIZE = [1024, 768].freeze
9
15
  BASE_URL_SCHEMA = %w[http https].freeze
10
16
  DEFAULT_TIMEOUT = ENV.fetch("FERRUM_DEFAULT_TIMEOUT", 5).to_i
17
+ DEFAULT_PROTOCOL_TIMEOUT = ENV.fetch("FERRUM_PROTOCOL_TIMEOUT", DEFAULT_TIMEOUT).to_i
11
18
  PROCESS_TIMEOUT = ENV.fetch("FERRUM_PROCESS_TIMEOUT", 10).to_i
12
19
  DEBUG_MODE = !ENV.fetch("FERRUM_DEBUG", nil).nil?
13
20
 
@@ -16,7 +23,7 @@ module Ferrum
16
23
  :url, :ws_url, :env, :process_timeout, :browser_name, :browser_path,
17
24
  :save_path, :proxy, :port, :host, :headless, :incognito, :dockerize, :browser_options,
18
25
  :ignore_default_browser_options, :xvfb, :flatten
19
- attr_accessor :timeout, :default_user_agent
26
+ attr_accessor :timeout, :protocol_timeout, :default_user_agent
20
27
 
21
28
  def initialize(options = nil)
22
29
  @options = Hash(options&.dup)
@@ -24,13 +31,14 @@ module Ferrum
24
31
  @port = @options.fetch(:port, BROWSER_PORT)
25
32
  @host = @options.fetch(:host, BROWSER_HOST)
26
33
  @timeout = @options.fetch(:timeout, DEFAULT_TIMEOUT)
34
+ @protocol_timeout = @options.fetch(:protocol_timeout, DEFAULT_PROTOCOL_TIMEOUT)
27
35
  @window_size = @options.fetch(:window_size, WINDOW_SIZE)
28
36
  @js_errors = @options.fetch(:js_errors, false)
29
37
  @headless = @options.fetch(:headless, true)
30
38
  @incognito = @options.fetch(:incognito, true)
31
39
  @dockerize = @options.fetch(:dockerize, false)
32
40
  @flatten = @options.fetch(:flatten, true)
33
- @pending_connection_errors = @options.fetch(:pending_connection_errors, true)
41
+ @pending_connection_errors = @options.fetch(:pending_connection_errors, false)
34
42
  @process_timeout = @options.fetch(:process_timeout, PROCESS_TIMEOUT)
35
43
  @slowmo = @options[:slowmo].to_f
36
44
 
@@ -52,16 +60,42 @@ module Ferrum
52
60
  @browser_options = @options.fetch(:browser_options, {}).freeze
53
61
  end
54
62
 
63
+ #
64
+ # Sets the base URL relative navigations are resolved against.
65
+ #
66
+ # @param [String] value
67
+ # An absolute URL including scheme, e.g. `"https://example.com"`.
68
+ #
69
+ # @return [Addressable::URI]
70
+ #
55
71
  def base_url=(value)
56
72
  @base_url = parse_base_url(value)
57
73
  end
58
74
 
75
+ #
76
+ # JS source to preload into the browser, read from `:extensions` option.
77
+ #
78
+ # @return [Array<String>]
79
+ # JS source code for each configured extension.
80
+ #
59
81
  def extensions
60
82
  @extensions ||= Array(@options[:extensions]).map do |extension|
61
83
  (extension.is_a?(Hash) && extension[:source]) || File.read(extension)
62
84
  end
63
85
  end
64
86
 
87
+ #
88
+ # Validates the `:proxy` option, if given.
89
+ #
90
+ # @param [Hash, nil] options
91
+ # The `:proxy` option as passed to {#initialize}.
92
+ #
93
+ # @return [Hash, nil]
94
+ # The same `options`, unchanged.
95
+ #
96
+ # @raise [ArgumentError]
97
+ # If `options` is not a `Hash`, or is a `Hash` without `:host` or `:port`.
98
+ #
65
99
  def validate_proxy(options)
66
100
  return unless options
67
101
 
@@ -74,6 +108,11 @@ module Ferrum
74
108
  options
75
109
  end
76
110
 
111
+ #
112
+ # Raw options hash used to initialize the browser.
113
+ #
114
+ # @return [Hash{Symbol => Object}]
115
+ #
77
116
  def to_h
78
117
  @options
79
118
  end