ferrum 0.11 → 0.13
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/LICENSE +1 -1
- data/README.md +174 -30
- data/lib/ferrum/browser/binary.rb +46 -0
- data/lib/ferrum/browser/client.rb +17 -16
- data/lib/ferrum/browser/command.rb +10 -12
- data/lib/ferrum/browser/options/base.rb +2 -11
- data/lib/ferrum/browser/options/chrome.rb +29 -18
- data/lib/ferrum/browser/options/firefox.rb +13 -9
- data/lib/ferrum/browser/options.rb +84 -0
- data/lib/ferrum/browser/process.rb +45 -40
- data/lib/ferrum/browser/subscriber.rb +1 -3
- data/lib/ferrum/browser/version_info.rb +71 -0
- data/lib/ferrum/browser/web_socket.rb +9 -12
- data/lib/ferrum/browser/xvfb.rb +4 -8
- data/lib/ferrum/browser.rb +193 -47
- data/lib/ferrum/context.rb +9 -4
- data/lib/ferrum/contexts.rb +12 -10
- data/lib/ferrum/cookies/cookie.rb +126 -0
- data/lib/ferrum/cookies.rb +93 -55
- data/lib/ferrum/dialog.rb +30 -0
- data/lib/ferrum/errors.rb +115 -0
- data/lib/ferrum/frame/dom.rb +177 -0
- data/lib/ferrum/frame/runtime.rb +58 -75
- data/lib/ferrum/frame.rb +118 -23
- data/lib/ferrum/headers.rb +30 -2
- data/lib/ferrum/keyboard.rb +56 -13
- data/lib/ferrum/mouse.rb +92 -7
- data/lib/ferrum/network/auth_request.rb +7 -2
- data/lib/ferrum/network/exchange.rb +97 -12
- data/lib/ferrum/network/intercepted_request.rb +10 -8
- data/lib/ferrum/network/request.rb +69 -0
- data/lib/ferrum/network/response.rb +85 -3
- data/lib/ferrum/network.rb +285 -36
- data/lib/ferrum/node.rb +69 -23
- data/lib/ferrum/page/animation.rb +16 -1
- data/lib/ferrum/page/frames.rb +111 -30
- data/lib/ferrum/page/screenshot.rb +142 -65
- data/lib/ferrum/page/stream.rb +38 -0
- data/lib/ferrum/page/tracing.rb +97 -0
- data/lib/ferrum/page.rb +224 -60
- data/lib/ferrum/proxy.rb +147 -0
- data/lib/ferrum/{rbga.rb → rgba.rb} +4 -2
- data/lib/ferrum/target.rb +7 -4
- data/lib/ferrum/utils/attempt.rb +20 -0
- data/lib/ferrum/utils/elapsed_time.rb +27 -0
- data/lib/ferrum/utils/platform.rb +28 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum.rb +4 -146
- metadata +63 -51
data/lib/ferrum/browser.rb
CHANGED
@@ -3,89 +3,208 @@
|
|
3
3
|
require "base64"
|
4
4
|
require "forwardable"
|
5
5
|
require "ferrum/page"
|
6
|
+
require "ferrum/proxy"
|
6
7
|
require "ferrum/contexts"
|
7
8
|
require "ferrum/browser/xvfb"
|
9
|
+
require "ferrum/browser/options"
|
8
10
|
require "ferrum/browser/process"
|
9
11
|
require "ferrum/browser/client"
|
12
|
+
require "ferrum/browser/binary"
|
13
|
+
require "ferrum/browser/version_info"
|
10
14
|
|
11
15
|
module Ferrum
|
12
16
|
class Browser
|
13
|
-
DEFAULT_TIMEOUT = ENV.fetch("FERRUM_DEFAULT_TIMEOUT", 5).to_i
|
14
|
-
WINDOW_SIZE = [1024, 768].freeze
|
15
|
-
BASE_URL_SCHEMA = %w[http https].freeze
|
16
|
-
|
17
17
|
extend Forwardable
|
18
18
|
delegate %i[default_context] => :contexts
|
19
|
-
delegate %i[targets create_target
|
20
|
-
delegate %i[go_to back forward refresh reload stop wait_for_reload
|
19
|
+
delegate %i[targets create_target page pages windows] => :default_context
|
20
|
+
delegate %i[go_to goto go back forward refresh reload stop wait_for_reload
|
21
21
|
at_css at_xpath css xpath current_url current_title url title
|
22
|
-
body doctype
|
22
|
+
body doctype content=
|
23
23
|
headers cookies network
|
24
24
|
mouse keyboard
|
25
25
|
screenshot pdf mhtml viewport_size
|
26
26
|
frames frame_by main_frame
|
27
27
|
evaluate evaluate_on evaluate_async execute evaluate_func
|
28
28
|
add_script_tag add_style_tag bypass_csp
|
29
|
-
on
|
29
|
+
on position position=
|
30
30
|
playback_rate playback_rate=] => :page
|
31
31
|
delegate %i[default_user_agent] => :process
|
32
32
|
|
33
|
-
attr_reader :client, :process, :contexts, :
|
34
|
-
|
35
|
-
attr_writer :timeout
|
33
|
+
attr_reader :client, :process, :contexts, :options, :window_size, :base_url
|
34
|
+
attr_accessor :timeout
|
36
35
|
|
36
|
+
#
|
37
|
+
# Initializes the browser.
|
38
|
+
#
|
39
|
+
# @param [Hash{Symbol => Object}, nil] options
|
40
|
+
# Additional browser options.
|
41
|
+
#
|
42
|
+
# @option options [Boolean] :headless (true)
|
43
|
+
# Set browser as headless or not.
|
44
|
+
#
|
45
|
+
# @option options [Boolean] :xvfb (false)
|
46
|
+
# Run browser in a virtual framebuffer.
|
47
|
+
#
|
48
|
+
# @option options [(Integer, Integer)] :window_size ([1024, 768])
|
49
|
+
# The dimensions of the browser window in which to test, expressed as a
|
50
|
+
# 2-element array, e.g. `[1024, 768]`.
|
51
|
+
#
|
52
|
+
# @option options [Array<String, Hash>] :extensions
|
53
|
+
# An array of paths to files or JS source code to be preloaded into the
|
54
|
+
# browser e.g.: `["/path/to/script.js", { source: "window.secret = 'top'" }]`
|
55
|
+
#
|
56
|
+
# @option options [#puts] :logger
|
57
|
+
# When present, debug output is written to this object.
|
58
|
+
#
|
59
|
+
# @option options [Integer, Float] :slowmo
|
60
|
+
# Set a delay in seconds to wait before sending command.
|
61
|
+
# Useful companion of headless option, so that you have time to see
|
62
|
+
# changes.
|
63
|
+
#
|
64
|
+
# @option options [Numeric] :timeout (5)
|
65
|
+
# The number of seconds we'll wait for a response when communicating with
|
66
|
+
# browser.
|
67
|
+
#
|
68
|
+
# @option options [Boolean] :js_errors
|
69
|
+
# When true, JavaScript errors get re-raised in Ruby.
|
70
|
+
#
|
71
|
+
# @option options [Boolean] :pending_connection_errors (true)
|
72
|
+
# When main frame is still waiting for slow responses while timeout is
|
73
|
+
# reached {PendingConnectionsError} is raised. It's better to figure out
|
74
|
+
# why you have slow responses and fix or block them rather than turn this
|
75
|
+
# setting off.
|
76
|
+
#
|
77
|
+
# @option options [:chrome, :firefox] :browser_name (:chrome)
|
78
|
+
# Sets the browser's name. **Note:** only experimental support for
|
79
|
+
# `:firefox` for now.
|
80
|
+
#
|
81
|
+
# @option options [String] :browser_path
|
82
|
+
# Path to Chrome binary, you can also set ENV variable as
|
83
|
+
# `BROWSER_PATH=some/path/chrome bundle exec rspec`.
|
84
|
+
#
|
85
|
+
# @option options [Hash] :browser_options
|
86
|
+
# Additional command line options, [see them all](https://peter.sh/experiments/chromium-command-line-switches/)
|
87
|
+
# e.g. `{ "ignore-certificate-errors" => nil }`
|
88
|
+
#
|
89
|
+
# @option options [Boolean] :ignore_default_browser_options
|
90
|
+
# Ferrum has a number of default options it passes to the browser,
|
91
|
+
# if you set this to `true` then only options you put in
|
92
|
+
# `:browser_options` will be passed to the browser, except required ones
|
93
|
+
# of course.
|
94
|
+
#
|
95
|
+
# @option options [Integer] :port
|
96
|
+
# Remote debugging port for headless Chrome.
|
97
|
+
#
|
98
|
+
# @option options [String] :host
|
99
|
+
# Remote debugging address for headless Chrome.
|
100
|
+
#
|
101
|
+
# @option options [String] :url
|
102
|
+
# URL for a running instance of Chrome. If this is set, a browser process
|
103
|
+
# will not be spawned.
|
104
|
+
#
|
105
|
+
# @option options [Integer] :process_timeout
|
106
|
+
# How long to wait for the Chrome process to respond on startup.
|
107
|
+
#
|
108
|
+
# @option options [Integer] :ws_max_receive_size
|
109
|
+
# How big messages to accept from Chrome over the web socket, in bytes.
|
110
|
+
# Defaults to 64MB. Incoming messages larger this will cause a
|
111
|
+
# {Ferrum::DeadBrowserError}.
|
112
|
+
#
|
113
|
+
# @option options [Hash] :proxy
|
114
|
+
# Specify proxy settings, [read more](https://github.com/rubycdp/ferrum#proxy).
|
115
|
+
#
|
116
|
+
# @option options [String] :save_path
|
117
|
+
# Path to save attachments with [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)
|
118
|
+
# header.
|
119
|
+
#
|
120
|
+
# @option options [Hash] :env
|
121
|
+
# Environment variables you'd like to pass through to the process.
|
122
|
+
#
|
37
123
|
def initialize(options = nil)
|
38
|
-
options
|
124
|
+
@options = Options.new(options)
|
125
|
+
@client = @process = @contexts = nil
|
39
126
|
|
40
|
-
@
|
41
|
-
@window_size = options.
|
42
|
-
@
|
127
|
+
@timeout = @options.timeout
|
128
|
+
@window_size = @options.window_size
|
129
|
+
@base_url = @options.base_url if @options.base_url
|
43
130
|
|
44
|
-
|
45
|
-
|
46
|
-
@options.values_at(:logger, :timeout, :ws_max_receive_size)
|
47
|
-
@js_errors = @options.fetch(:js_errors, false)
|
48
|
-
@pending_connection_errors = @options.fetch(:pending_connection_errors, true)
|
49
|
-
@slowmo = @options[:slowmo].to_f
|
131
|
+
start
|
132
|
+
end
|
50
133
|
|
51
|
-
|
52
|
-
|
53
|
-
|
134
|
+
#
|
135
|
+
# Sets the base URL.
|
136
|
+
#
|
137
|
+
# @param [String] value
|
138
|
+
# The new base URL value.
|
139
|
+
#
|
140
|
+
# @raise [ArgumentError] when path is not absolute or doesn't include schema
|
141
|
+
#
|
142
|
+
# @return [Addressable::URI]
|
143
|
+
# The parsed base URI value.
|
144
|
+
#
|
145
|
+
def base_url=(value)
|
146
|
+
@base_url = options.parse_base_url(value)
|
147
|
+
end
|
54
148
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
149
|
+
#
|
150
|
+
# Creates a new page.
|
151
|
+
#
|
152
|
+
# @param [Boolean] new_context
|
153
|
+
# Whether to create a page in a new context or not.
|
154
|
+
#
|
155
|
+
# @param [Hash] proxy
|
156
|
+
# Whether to use proxy for a page. The page will be created in a new context if so.
|
157
|
+
#
|
158
|
+
# @return [Ferrum::Page]
|
159
|
+
# Created page.
|
160
|
+
#
|
161
|
+
def create_page(new_context: false, proxy: nil)
|
162
|
+
page = if new_context || proxy
|
163
|
+
params = {}
|
60
164
|
|
61
|
-
|
165
|
+
if proxy
|
166
|
+
options.parse_proxy(proxy)
|
167
|
+
params.merge!(proxyServer: "#{proxy[:host]}:#{proxy[:port]}")
|
168
|
+
params.merge!(proxyBypassList: proxy[:bypass]) if proxy[:bypass]
|
169
|
+
end
|
62
170
|
|
63
|
-
|
64
|
-
|
171
|
+
context = contexts.create(**params)
|
172
|
+
context.create_page(proxy: proxy)
|
173
|
+
else
|
174
|
+
default_context.create_page
|
175
|
+
end
|
65
176
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
177
|
+
block_given? ? yield(page) : page
|
178
|
+
ensure
|
179
|
+
if block_given?
|
180
|
+
page.close
|
181
|
+
context.dispose if new_context
|
70
182
|
end
|
71
|
-
|
72
|
-
@base_url = parsed
|
73
183
|
end
|
74
184
|
|
75
185
|
def extensions
|
76
|
-
@extensions ||= Array(
|
186
|
+
@extensions ||= Array(options.extensions).map do |ext|
|
77
187
|
(ext.is_a?(Hash) && ext[:source]) || File.read(ext)
|
78
188
|
end
|
79
189
|
end
|
80
190
|
|
191
|
+
#
|
192
|
+
# Evaluate JavaScript to modify things before a page load.
|
193
|
+
#
|
194
|
+
# @param [String] expression
|
195
|
+
# The JavaScript to add to each new document.
|
196
|
+
#
|
197
|
+
# @example
|
198
|
+
# browser.evaluate_on_new_document <<~JS
|
199
|
+
# Object.defineProperty(navigator, "languages", {
|
200
|
+
# get: function() { return ["tlh"]; }
|
201
|
+
# });
|
202
|
+
# JS
|
203
|
+
#
|
81
204
|
def evaluate_on_new_document(expression)
|
82
205
|
extensions << expression
|
83
206
|
end
|
84
207
|
|
85
|
-
def timeout
|
86
|
-
@timeout || DEFAULT_TIMEOUT
|
87
|
-
end
|
88
|
-
|
89
208
|
def command(*args)
|
90
209
|
@client.command(*args)
|
91
210
|
rescue DeadBrowserError
|
@@ -93,8 +212,22 @@ module Ferrum
|
|
93
212
|
raise
|
94
213
|
end
|
95
214
|
|
215
|
+
#
|
216
|
+
# Closes browser tabs opened by the `Browser` instance.
|
217
|
+
#
|
218
|
+
# @example
|
219
|
+
# # connect to a long-running Chrome process
|
220
|
+
# browser = Ferrum::Browser.new(url: 'http://localhost:9222')
|
221
|
+
#
|
222
|
+
# browser.go_to("https://github.com/")
|
223
|
+
#
|
224
|
+
# # clean up, lest the tab stays there hanging forever
|
225
|
+
# browser.reset
|
226
|
+
#
|
227
|
+
# browser.quit
|
228
|
+
#
|
96
229
|
def reset
|
97
|
-
@window_size =
|
230
|
+
@window_size = options.window_size
|
98
231
|
contexts.reset
|
99
232
|
end
|
100
233
|
|
@@ -118,12 +251,25 @@ module Ferrum
|
|
118
251
|
command("Browser.crash")
|
119
252
|
end
|
120
253
|
|
254
|
+
#
|
255
|
+
# Gets the version information from the browser.
|
256
|
+
#
|
257
|
+
# @return [VersionInfo]
|
258
|
+
#
|
259
|
+
# @since 0.13
|
260
|
+
#
|
261
|
+
def version
|
262
|
+
VersionInfo.new(command("Browser.getVersion"))
|
263
|
+
end
|
264
|
+
|
121
265
|
private
|
122
266
|
|
123
267
|
def start
|
124
|
-
|
125
|
-
@process = Process.start(
|
126
|
-
@client = Client.new(
|
268
|
+
Utils::ElapsedTime.start
|
269
|
+
@process = Process.start(options)
|
270
|
+
@client = Client.new(@process.ws_url, self,
|
271
|
+
logger: options.logger,
|
272
|
+
ws_max_receive_size: options.ws_max_receive_size)
|
127
273
|
@contexts = Contexts.new(self)
|
128
274
|
end
|
129
275
|
end
|
data/lib/ferrum/context.rb
CHANGED
@@ -9,7 +9,9 @@ module Ferrum
|
|
9
9
|
attr_reader :id, :targets
|
10
10
|
|
11
11
|
def initialize(browser, contexts, id)
|
12
|
-
@
|
12
|
+
@id = id
|
13
|
+
@browser = browser
|
14
|
+
@contexts = contexts
|
13
15
|
@targets = Concurrent::Map.new
|
14
16
|
@pendings = Concurrent::MVar.new
|
15
17
|
end
|
@@ -32,13 +34,15 @@ module Ferrum
|
|
32
34
|
# usually is the last one.
|
33
35
|
def windows(pos = nil, size = 1)
|
34
36
|
raise ArgumentError if pos && !POSITION.include?(pos)
|
37
|
+
|
35
38
|
windows = @targets.values.select(&:window?)
|
36
39
|
windows = windows.send(pos, size) if pos
|
37
40
|
windows.map(&:page)
|
38
41
|
end
|
39
42
|
|
40
|
-
def create_page
|
41
|
-
create_target
|
43
|
+
def create_page(**options)
|
44
|
+
target = create_target
|
45
|
+
target.page = target.build_page(**options)
|
42
46
|
end
|
43
47
|
|
44
48
|
def create_target
|
@@ -47,6 +51,7 @@ module Ferrum
|
|
47
51
|
url: "about:blank")
|
48
52
|
target = @pendings.take(@browser.timeout)
|
49
53
|
raise NoSuchTargetError unless target.is_a?(Target)
|
54
|
+
|
50
55
|
@targets.put_if_absent(target.id, target)
|
51
56
|
target
|
52
57
|
end
|
@@ -72,7 +77,7 @@ module Ferrum
|
|
72
77
|
@contexts.dispose(@id)
|
73
78
|
end
|
74
79
|
|
75
|
-
def
|
80
|
+
def target?(target_id)
|
76
81
|
!!@targets[target_id]
|
77
82
|
end
|
78
83
|
|
data/lib/ferrum/contexts.rb
CHANGED
@@ -19,12 +19,12 @@ module Ferrum
|
|
19
19
|
|
20
20
|
def find_by(target_id:)
|
21
21
|
context = nil
|
22
|
-
@contexts.each_value { |c| context = c if c.
|
22
|
+
@contexts.each_value { |c| context = c if c.target?(target_id) }
|
23
23
|
context
|
24
24
|
end
|
25
25
|
|
26
|
-
def create
|
27
|
-
response = @browser.command("Target.createBrowserContext")
|
26
|
+
def create(**options)
|
27
|
+
response = @browser.command("Target.createBrowserContext", **options)
|
28
28
|
context_id = response["browserContextId"]
|
29
29
|
context = Context.new(@browser, self, context_id)
|
30
30
|
@contexts[context_id] = context
|
@@ -41,7 +41,11 @@ module Ferrum
|
|
41
41
|
|
42
42
|
def reset
|
43
43
|
@default_context = nil
|
44
|
-
@contexts.
|
44
|
+
@contexts.each_key { |id| dispose(id) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def size
|
48
|
+
@contexts.size
|
45
49
|
end
|
46
50
|
|
47
51
|
private
|
@@ -64,15 +68,13 @@ module Ferrum
|
|
64
68
|
end
|
65
69
|
|
66
70
|
@browser.client.on("Target.targetDestroyed") do |params|
|
67
|
-
|
68
|
-
|
69
|
-
end
|
71
|
+
context = find_by(target_id: params["targetId"])
|
72
|
+
context&.delete_target(params["targetId"])
|
70
73
|
end
|
71
74
|
|
72
75
|
@browser.client.on("Target.targetCrashed") do |params|
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
+
context = find_by(target_id: params["targetId"])
|
77
|
+
context&.delete_target(params["targetId"])
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Cookies
|
5
|
+
#
|
6
|
+
# Represents a [cookie value](https://chromedevtools.github.io/devtools-protocol/1-3/Network/#type-Cookie).
|
7
|
+
#
|
8
|
+
class Cookie
|
9
|
+
# The parsed JSON attributes.
|
10
|
+
#
|
11
|
+
# @return [Hash{String => [String, Boolean, nil]}]
|
12
|
+
attr_reader :attributes
|
13
|
+
|
14
|
+
#
|
15
|
+
# Initializes the cookie.
|
16
|
+
#
|
17
|
+
# @param [Hash{String => String}] attributes
|
18
|
+
# The parsed JSON attributes.
|
19
|
+
#
|
20
|
+
def initialize(attributes)
|
21
|
+
@attributes = attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# The cookie's name.
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
def name
|
30
|
+
attributes["name"]
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# The cookie's value.
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
#
|
38
|
+
def value
|
39
|
+
attributes["value"]
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# The cookie's domain.
|
44
|
+
#
|
45
|
+
# @return [String]
|
46
|
+
#
|
47
|
+
def domain
|
48
|
+
attributes["domain"]
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# The cookie's path.
|
53
|
+
#
|
54
|
+
# @return [String]
|
55
|
+
#
|
56
|
+
def path
|
57
|
+
attributes["path"]
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# The `sameSite` configuration.
|
62
|
+
#
|
63
|
+
# @return ["Strict", "Lax", "None", nil]
|
64
|
+
#
|
65
|
+
def samesite
|
66
|
+
attributes["sameSite"]
|
67
|
+
end
|
68
|
+
alias same_site samesite
|
69
|
+
|
70
|
+
#
|
71
|
+
# The cookie's size.
|
72
|
+
#
|
73
|
+
# @return [Integer]
|
74
|
+
#
|
75
|
+
def size
|
76
|
+
attributes["size"]
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Specifies whether the cookie is secure or not.
|
81
|
+
#
|
82
|
+
# @return [Boolean]
|
83
|
+
#
|
84
|
+
def secure?
|
85
|
+
attributes["secure"]
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Specifies whether the cookie is HTTP-only or not.
|
90
|
+
#
|
91
|
+
# @return [Boolean]
|
92
|
+
#
|
93
|
+
def httponly?
|
94
|
+
attributes["httpOnly"]
|
95
|
+
end
|
96
|
+
alias http_only? httponly?
|
97
|
+
|
98
|
+
#
|
99
|
+
# Specifies whether the cookie is a session cookie or not.
|
100
|
+
#
|
101
|
+
# @return [Boolean]
|
102
|
+
#
|
103
|
+
def session?
|
104
|
+
attributes["session"]
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Specifies when the cookie will expire.
|
109
|
+
#
|
110
|
+
# @return [Time, nil]
|
111
|
+
#
|
112
|
+
def expires
|
113
|
+
Time.at(attributes["expires"]) if attributes["expires"].positive?
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Compares different cookie objects.
|
118
|
+
#
|
119
|
+
# @return [Boolean]
|
120
|
+
#
|
121
|
+
def ==(other)
|
122
|
+
other.class == self.class && other.attributes == attributes
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/ferrum/cookies.rb
CHANGED
@@ -1,84 +1,114 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "ferrum/cookies/cookie"
|
4
|
+
|
3
5
|
module Ferrum
|
4
6
|
class Cookies
|
5
|
-
class Cookie
|
6
|
-
def initialize(attributes)
|
7
|
-
@attributes = attributes
|
8
|
-
end
|
9
|
-
|
10
|
-
def name
|
11
|
-
@attributes["name"]
|
12
|
-
end
|
13
|
-
|
14
|
-
def value
|
15
|
-
@attributes["value"]
|
16
|
-
end
|
17
|
-
|
18
|
-
def domain
|
19
|
-
@attributes["domain"]
|
20
|
-
end
|
21
|
-
|
22
|
-
def path
|
23
|
-
@attributes["path"]
|
24
|
-
end
|
25
|
-
|
26
|
-
def samesite
|
27
|
-
@attributes["sameSite"]
|
28
|
-
end
|
29
|
-
|
30
|
-
def size
|
31
|
-
@attributes["size"]
|
32
|
-
end
|
33
|
-
|
34
|
-
def secure?
|
35
|
-
@attributes["secure"]
|
36
|
-
end
|
37
|
-
|
38
|
-
def httponly?
|
39
|
-
@attributes["httpOnly"]
|
40
|
-
end
|
41
|
-
|
42
|
-
def session?
|
43
|
-
@attributes["session"]
|
44
|
-
end
|
45
|
-
|
46
|
-
def expires
|
47
|
-
if @attributes["expires"] > 0
|
48
|
-
Time.at(@attributes["expires"])
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
7
|
def initialize(page)
|
54
8
|
@page = page
|
55
9
|
end
|
56
10
|
|
11
|
+
#
|
12
|
+
# Returns cookies hash.
|
13
|
+
#
|
14
|
+
# @return [Hash{String => Cookie}]
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# browser.cookies.all # => {
|
18
|
+
# # "NID" => #<Ferrum::Cookies::Cookie:0x0000558624b37a40 @attributes={
|
19
|
+
# # "name"=>"NID", "value"=>"...", "domain"=>".google.com", "path"=>"/",
|
20
|
+
# # "expires"=>1583211046.575681, "size"=>178, "httpOnly"=>true, "secure"=>false, "session"=>false
|
21
|
+
# # }>
|
22
|
+
# # }
|
23
|
+
#
|
57
24
|
def all
|
58
25
|
cookies = @page.command("Network.getAllCookies")["cookies"]
|
59
|
-
cookies.
|
26
|
+
cookies.to_h { |c| [c["name"], Cookie.new(c)] }
|
60
27
|
end
|
61
28
|
|
29
|
+
#
|
30
|
+
# Returns cookie.
|
31
|
+
#
|
32
|
+
# @param [String] name
|
33
|
+
# The cookie name to fetch.
|
34
|
+
#
|
35
|
+
# @return [Cookie, nil]
|
36
|
+
# The cookie with the matching name.
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# browser.cookies["NID"] # =>
|
40
|
+
# # <Ferrum::Cookies::Cookie:0x0000558624b67a88 @attributes={
|
41
|
+
# # "name"=>"NID", "value"=>"...", "domain"=>".google.com",
|
42
|
+
# # "path"=>"/", "expires"=>1583211046.575681, "size"=>178,
|
43
|
+
# # "httpOnly"=>true, "secure"=>false, "session"=>false
|
44
|
+
# # }>
|
45
|
+
#
|
62
46
|
def [](name)
|
63
47
|
all[name]
|
64
48
|
end
|
65
49
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
50
|
+
#
|
51
|
+
# Sets a cookie.
|
52
|
+
#
|
53
|
+
# @param [Hash{Symbol => Object}, Cookie] options
|
54
|
+
#
|
55
|
+
# @option options [String] :name
|
56
|
+
#
|
57
|
+
# @option options [String] :value
|
58
|
+
#
|
59
|
+
# @option options [String] :domain
|
60
|
+
#
|
61
|
+
# @option options [String] :path
|
62
|
+
#
|
63
|
+
# @option options [Integer] :expires
|
64
|
+
#
|
65
|
+
# @option options [Integer] :size
|
66
|
+
#
|
67
|
+
# @option options [Boolean] :httponly
|
68
|
+
#
|
69
|
+
# @option options [Boolean] :secure
|
70
|
+
#
|
71
|
+
# @option options [String] :samesite
|
72
|
+
#
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# browser.cookies.set(name: "stealth", value: "omg", domain: "google.com") # => true
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# nid_cookie = browser.cookies["NID"] # => <Ferrum::Cookies::Cookie:0x0000558624b67a88>
|
79
|
+
# browser.cookies.set(nid_cookie) # => true
|
80
|
+
#
|
81
|
+
def set(options)
|
82
|
+
cookie = (
|
83
|
+
options.is_a?(Cookie) ? options.attributes : options
|
84
|
+
).dup.transform_keys(&:to_sym)
|
85
|
+
|
70
86
|
cookie[:domain] ||= default_domain
|
71
87
|
|
72
88
|
cookie[:httpOnly] = cookie.delete(:httponly) if cookie.key?(:httponly)
|
73
89
|
cookie[:sameSite] = cookie.delete(:samesite) if cookie.key?(:samesite)
|
74
90
|
|
75
91
|
expires = cookie.delete(:expires).to_i
|
76
|
-
cookie[:expires] = expires if expires
|
92
|
+
cookie[:expires] = expires if expires.positive?
|
77
93
|
|
78
94
|
@page.command("Network.setCookie", **cookie)["success"]
|
79
95
|
end
|
80
96
|
|
81
|
-
#
|
97
|
+
#
|
98
|
+
# Removes given cookie.
|
99
|
+
#
|
100
|
+
# @param [String] name
|
101
|
+
#
|
102
|
+
# @param [Hash{Symbol => Object}] options
|
103
|
+
# Additional keyword arguments.
|
104
|
+
#
|
105
|
+
# @option options [String] :domain
|
106
|
+
#
|
107
|
+
# @option options [String] :url
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
# browser.cookies.remove(name: "stealth", domain: "google.com") # => true
|
111
|
+
#
|
82
112
|
def remove(name:, **options)
|
83
113
|
raise "Specify :domain or :url option" if !options[:domain] && !options[:url] && !default_domain
|
84
114
|
|
@@ -90,6 +120,14 @@ module Ferrum
|
|
90
120
|
true
|
91
121
|
end
|
92
122
|
|
123
|
+
#
|
124
|
+
# Removes all cookies for current page.
|
125
|
+
#
|
126
|
+
# @return [true]
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# browser.cookies.clear # => true
|
130
|
+
#
|
93
131
|
def clear
|
94
132
|
@page.command("Network.clearBrowserCookies")
|
95
133
|
true
|