ferrum 0.17.2 → 0.18.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  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 +41 -0
  6. data/lib/ferrum/browser/command.rb +20 -0
  7. data/lib/ferrum/browser/options/base.rb +67 -0
  8. data/lib/ferrum/browser/options/chrome.rb +36 -1
  9. data/lib/ferrum/browser/options/firefox.rb +32 -0
  10. data/lib/ferrum/browser/options.rb +41 -2
  11. data/lib/ferrum/browser/process.rb +51 -0
  12. data/lib/ferrum/browser/xvfb.rb +24 -0
  13. data/lib/ferrum/browser.rb +32 -10
  14. data/lib/ferrum/client/subscriber.rb +58 -0
  15. data/lib/ferrum/client/web_socket.rb +62 -11
  16. data/lib/ferrum/client.rb +199 -8
  17. data/lib/ferrum/context.rb +97 -4
  18. data/lib/ferrum/contexts.rb +119 -9
  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 +52 -0
  23. data/lib/ferrum/errors.rb +58 -3
  24. data/lib/ferrum/frame/dom.rb +17 -0
  25. data/lib/ferrum/frame/runtime.rb +48 -7
  26. data/lib/ferrum/frame.rb +58 -1
  27. data/lib/ferrum/headers.rb +6 -0
  28. data/lib/ferrum/interceptable.rb +62 -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 +52 -20
  43. data/lib/ferrum/page/stream.rb +56 -0
  44. data/lib/ferrum/page/tracing.rb +6 -0
  45. data/lib/ferrum/page.rb +139 -42
  46. data/lib/ferrum/proxy.rb +52 -2
  47. data/lib/ferrum/rgba.rb +10 -0
  48. data/lib/ferrum/target.rb +124 -1
  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 +12 -0
  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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbed705a172379d21bd97f46bdd7ed29e444b08d79e6191ecdaf1e3cdc254793
4
- data.tar.gz: d5143b49451e8c7a31ae394ac226a8c2fb8feda6c7b30aee35e7349e3c5c818f
3
+ metadata.gz: c7bb51607dd8307f471fa1fcf5214a392485caf1620588798c60dadd6fb41382
4
+ data.tar.gz: d4ce789910ecec970a5a33ca3843ca79b148934b75397ec19df58dd81cca49af
5
5
  SHA512:
6
- metadata.gz: 3c217d761ea4bde8cbbdd231229a0d7e209e97b15de7756e76f4354fefb08c5f7eb5774842a239d2312feba262685166712e915d2ea1dcc50d5572809fdafe76
7
- data.tar.gz: 640b40256d2b9db5a1a62093d865ea51ad5b92ce2ac3dc1ba5c332665148e54f7fd10da5675eceb7e18ba54840454fc21e31849b4ced8770fee60ea592f582cb
6
+ metadata.gz: b77f1fb177afcd34a22b4c82f1cb31469cf319edfaf6d67758091cd87d10a2227fdeaa51875979d5cc6e9e357bc0df0ab3ab182920525abcf5f3d81133e34c86
7
+ data.tar.gz: 40d23db769aba58e34709d187ec680b10ce630826d6174ddf829fa15d2e7095d99eea0fc7729c75640505a8290ad6caf221c8837d50df2dd4c0671b015cffd75
data/README.md CHANGED
@@ -15,7 +15,7 @@ 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
21
  ## Install
@@ -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)
@@ -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 " \
@@ -35,14 +41,28 @@ module Ferrum
35
41
  merge_options
36
42
  end
37
43
 
44
+ # Whether the browser should be launched under Xvfb.
45
+ #
46
+ # @return [Boolean]
38
47
  def xvfb?
39
48
  !!options.xvfb
40
49
  end
41
50
 
51
+ #
52
+ # Command line arguments for spawning the browser process.
53
+ #
54
+ # @return [Array<String>]
55
+ # The browser path followed by its CLI flags.
56
+ #
42
57
  def to_a
43
58
  [path] + @flags.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
44
59
  end
45
60
 
61
+ #
62
+ # String representation of the command used to spawn the browser process.
63
+ #
64
+ # @return [String]
65
+ #
46
66
  def to_s
47
67
  to_a.join(" \\ \n ")
48
68
  end
@@ -6,14 +6,30 @@ 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}, {Options::Firefox}) declare their own
12
+ # `DEFAULT_OPTIONS` and `PLATFORM_PATH` constants and implement
13
+ # {#merge_required}/{#merge_default} to produce the final CLI flags used
14
+ # by {Command}.
15
+ #
9
16
  class Base
10
17
  include Singleton
11
18
 
19
+ #
20
+ # Singleton instance holding the browser's default options.
21
+ #
22
+ # @return [Base]
23
+ #
12
24
  def self.options
13
25
  instance
14
26
  end
15
27
 
28
+ # The installed browser's version string, obtained by running its
29
+ # binary with `--version`.
30
+ #
16
31
  # @return [String, nil]
32
+ # The version output, or `nil` if the binary can't be found/run.
17
33
  def self.version
18
34
  out, = Open3.capture2(instance.detect_path, "--version")
19
35
  out.strip
@@ -21,22 +37,73 @@ module Ferrum
21
37
  nil
22
38
  end
23
39
 
40
+ #
41
+ # Default CLI flags for the browser.
42
+ #
43
+ # @return [Hash{String => Object}]
44
+ #
24
45
  def to_h
25
46
  self.class::DEFAULT_OPTIONS
26
47
  end
27
48
 
49
+ #
50
+ # Default CLI flags excluding the given keys.
51
+ #
52
+ # @param [Array<String>] keys
53
+ # Flag names to exclude.
54
+ #
55
+ # @return [Hash{String => Object}]
56
+ #
28
57
  def except(*keys)
29
58
  to_h.except(*keys)
30
59
  end
31
60
 
61
+ #
62
+ # Locates the browser executable on the system.
63
+ #
64
+ # @return [String, nil]
65
+ # Absolute path to the browser binary, or `nil` if not found.
66
+ #
32
67
  def detect_path
33
68
  Binary.find(self.class::PLATFORM_PATH[Utils::Platform.platform_name])
34
69
  end
35
70
 
71
+ #
72
+ # Merges the CLI flags that are always required to drive the browser
73
+ # over CDP (e.g. remote debugging port, user data dir) into `flags`.
74
+ # Abstract; overridden by {Options::Chrome}/{Options::Firefox}.
75
+ #
76
+ # @param [Hash{String => Object}] flags
77
+ # The flags accumulated so far.
78
+ # @param [Options] options
79
+ # The user-supplied browser options.
80
+ # @param [String] user_data_dir
81
+ # Path to the browser's user data directory.
82
+ #
83
+ # @return [Hash{String => Object}]
84
+ #
85
+ # @raise [NotImplementedError]
86
+ # Always, unless overridden by a subclass.
87
+ #
36
88
  def merge_required(flags, options, user_data_dir)
37
89
  raise NotImplementedError
38
90
  end
39
91
 
92
+ #
93
+ # Merges this browser's default CLI flags (see {#to_h}) into `flags`,
94
+ # unless `options.ignore_default_browser_options` is set. Abstract;
95
+ # overridden by {Options::Chrome}/{Options::Firefox}.
96
+ #
97
+ # @param [Hash{String => Object}] flags
98
+ # The flags accumulated so far.
99
+ # @param [Options] options
100
+ # The user-supplied browser options.
101
+ #
102
+ # @return [Hash{String => Object}]
103
+ #
104
+ # @raise [NotImplementedError]
105
+ # Always, unless overridden by a subclass.
106
+ #
40
107
  def merge_default(flags, options)
41
108
  raise NotImplementedError
42
109
  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,
@@ -55,6 +61,7 @@ module Ferrum
55
61
  "metrics-recording-only" => nil,
56
62
  "mute-audio" => nil,
57
63
  "no-crash-upload" => nil,
64
+ "no-crashpad" => nil,
58
65
  "no-default-browser-check" => nil,
59
66
  "no-first-run" => nil,
60
67
  "no-startup-window" => nil,
@@ -80,6 +87,21 @@ module Ferrum
80
87
  linux: LINUX_BIN_PATH
81
88
  }.freeze
82
89
 
90
+ #
91
+ # Merges CLI flags required for Chrome to work with CDP.
92
+ #
93
+ # @param [Hash] flags
94
+ # Flags to merge required ones into.
95
+ #
96
+ # @param [Ferrum::Browser::Options] options
97
+ # Browser options.
98
+ #
99
+ # @param [String] user_data_dir
100
+ # Path to the browser's user data directory.
101
+ #
102
+ # @return [Hash]
103
+ # Merged flags.
104
+ #
83
105
  def merge_required(flags, options, user_data_dir)
84
106
  flags = flags.merge("remote-debugging-port" => options.port,
85
107
  "remote-debugging-address" => options.host,
@@ -94,8 +116,21 @@ module Ferrum
94
116
  flags
95
117
  end
96
118
 
119
+ #
120
+ # Merges Chrome's default flags with the given ones, unless the browser
121
+ # is configured to ignore default browser options.
122
+ #
123
+ # @param [Hash] flags
124
+ # Flags that take precedence over the defaults.
125
+ #
126
+ # @param [Ferrum::Browser::Options] options
127
+ # Browser options.
128
+ #
129
+ # @return [Hash]
130
+ # Merged flags.
131
+ #
97
132
  def merge_default(flags, options)
98
- defaults = options.headless == false ? except("headless", "disable-gpu") : DEFAULT_OPTIONS
133
+ defaults = options.headless == false ? except("headless", "disable-gpu") : DEFAULT_OPTIONS.dup
99
134
  defaults.delete("no-startup-window") if options.incognito == false
100
135
 
101
136
  if options.dockerize || ENV["FERRUM_CHROME_DOCKERIZE"] == "true"
@@ -3,6 +3,11 @@
3
3
  module Ferrum
4
4
  class Browser
5
5
  class Options
6
+ #
7
+ # Firefox-specific default flags and binary locations. Provides the CLI
8
+ # flags required to drive Firefox over CDP (remote debugger address and
9
+ # profile directory) along with its (minimal) set of default flags.
10
+ #
6
11
  class Firefox < Base
7
12
  DEFAULT_OPTIONS = {
8
13
  "headless" => nil
@@ -22,10 +27,37 @@ module Ferrum
22
27
  linux: LINUX_BIN_PATH
23
28
  }.freeze
24
29
 
30
+ #
31
+ # Merges CLI flags required for Firefox to work with CDP.
32
+ #
33
+ # @param [Hash] flags
34
+ # Flags to merge required ones into.
35
+ #
36
+ # @param [Ferrum::Browser::Options] options
37
+ # Browser options.
38
+ #
39
+ # @param [String] user_data_dir
40
+ # Path to the browser's profile directory.
41
+ #
42
+ # @return [Hash]
43
+ # Merged flags.
44
+ #
25
45
  def merge_required(flags, options, user_data_dir)
26
46
  flags.merge("remote-debugger" => "#{options.host}:#{options.port}", "profile" => user_data_dir)
27
47
  end
28
48
 
49
+ #
50
+ # Merges Firefox's default flags with the given ones.
51
+ #
52
+ # @param [Hash] flags
53
+ # Flags that take precedence over the defaults.
54
+ #
55
+ # @param [Ferrum::Browser::Options] options
56
+ # Browser options.
57
+ #
58
+ # @return [Hash]
59
+ # Merged flags.
60
+ #
29
61
  def merge_default(flags, options)
30
62
  defaults = except("headless") unless options.headless
31
63
 
@@ -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