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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/ferrum/accessibility/ax_node.rb +108 -0
- data/lib/ferrum/accessibility.rb +106 -0
- data/lib/ferrum/browser/binary.rb +41 -0
- data/lib/ferrum/browser/command.rb +20 -0
- data/lib/ferrum/browser/options/base.rb +67 -0
- data/lib/ferrum/browser/options/chrome.rb +36 -1
- data/lib/ferrum/browser/options/firefox.rb +32 -0
- data/lib/ferrum/browser/options.rb +41 -2
- data/lib/ferrum/browser/process.rb +51 -0
- data/lib/ferrum/browser/xvfb.rb +24 -0
- data/lib/ferrum/browser.rb +32 -10
- data/lib/ferrum/client/subscriber.rb +58 -0
- data/lib/ferrum/client/web_socket.rb +62 -11
- data/lib/ferrum/client.rb +199 -8
- data/lib/ferrum/context.rb +97 -4
- data/lib/ferrum/contexts.rb +119 -9
- data/lib/ferrum/cookies/cookie.rb +6 -0
- data/lib/ferrum/cookies.rb +5 -0
- data/lib/ferrum/dialog.rb +18 -2
- data/lib/ferrum/downloads.rb +52 -0
- data/lib/ferrum/errors.rb +58 -3
- data/lib/ferrum/frame/dom.rb +17 -0
- data/lib/ferrum/frame/runtime.rb +48 -7
- data/lib/ferrum/frame.rb +58 -1
- data/lib/ferrum/headers.rb +6 -0
- data/lib/ferrum/interceptable.rb +62 -0
- data/lib/ferrum/keyboard.rb +25 -0
- data/lib/ferrum/mouse.rb +6 -0
- data/lib/ferrum/network/auth_request.rb +81 -2
- data/lib/ferrum/network/error.rb +15 -0
- data/lib/ferrum/network/exchange.rb +10 -0
- data/lib/ferrum/network/intercepted_request.rb +94 -2
- data/lib/ferrum/network/request.rb +1 -1
- data/lib/ferrum/network/response.rb +2 -0
- data/lib/ferrum/network.rb +121 -9
- data/lib/ferrum/node.rb +305 -15
- data/lib/ferrum/page/animation.rb +5 -0
- data/lib/ferrum/page/frames.rb +69 -7
- data/lib/ferrum/page/screencast.rb +5 -0
- data/lib/ferrum/page/screenshot.rb +52 -20
- data/lib/ferrum/page/stream.rb +56 -0
- data/lib/ferrum/page/tracing.rb +6 -0
- data/lib/ferrum/page.rb +139 -42
- data/lib/ferrum/proxy.rb +52 -2
- data/lib/ferrum/rgba.rb +10 -0
- data/lib/ferrum/target.rb +124 -1
- data/lib/ferrum/utils/attempt.rb +20 -0
- data/lib/ferrum/utils/elapsed_time.rb +38 -0
- data/lib/ferrum/utils/event.rb +14 -0
- data/lib/ferrum/utils/platform.rb +21 -0
- data/lib/ferrum/utils/thread.rb +12 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum/worker.rb +125 -0
- data/lib/ferrum.rb +7 -0
- metadata +6 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7bb51607dd8307f471fa1fcf5214a392485caf1620588798c60dadd6fb41382
|
|
4
|
+
data.tar.gz: d4ce789910ecec970a5a33ca3843ca79b148934b75397ec19df58dd81cca49af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
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
|