ferrum 0.11 → 0.12
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 +178 -29
- data/lib/ferrum/browser/binary.rb +46 -0
- data/lib/ferrum/browser/client.rb +13 -12
- data/lib/ferrum/browser/command.rb +7 -8
- data/lib/ferrum/browser/options/base.rb +1 -7
- data/lib/ferrum/browser/options/chrome.rb +17 -11
- data/lib/ferrum/browser/options/firefox.rb +11 -4
- data/lib/ferrum/browser/process.rb +41 -35
- data/lib/ferrum/browser/subscriber.rb +1 -3
- data/lib/ferrum/browser/web_socket.rb +9 -12
- data/lib/ferrum/browser/xvfb.rb +4 -8
- data/lib/ferrum/browser.rb +44 -12
- data/lib/ferrum/context.rb +6 -2
- data/lib/ferrum/contexts.rb +10 -8
- data/lib/ferrum/cookies.rb +10 -9
- data/lib/ferrum/errors.rb +115 -0
- data/lib/ferrum/frame/runtime.rb +20 -17
- data/lib/ferrum/frame.rb +32 -24
- data/lib/ferrum/headers.rb +2 -2
- data/lib/ferrum/keyboard.rb +11 -11
- data/lib/ferrum/mouse.rb +8 -7
- data/lib/ferrum/network/auth_request.rb +7 -2
- data/lib/ferrum/network/exchange.rb +14 -10
- data/lib/ferrum/network/intercepted_request.rb +10 -8
- data/lib/ferrum/network/request.rb +5 -0
- data/lib/ferrum/network/response.rb +4 -4
- data/lib/ferrum/network.rb +124 -35
- data/lib/ferrum/node.rb +69 -23
- data/lib/ferrum/page/animation.rb +0 -1
- data/lib/ferrum/page/frames.rb +46 -20
- data/lib/ferrum/page/screenshot.rb +51 -65
- data/lib/ferrum/page/stream.rb +38 -0
- data/lib/ferrum/page/tracing.rb +71 -0
- data/lib/ferrum/page.rb +81 -36
- data/lib/ferrum/proxy.rb +58 -0
- data/lib/ferrum/{rbga.rb → rgba.rb} +4 -2
- data/lib/ferrum/target.rb +1 -0
- 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 +60 -51
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "cliver"
|
4
3
|
require "net/http"
|
5
4
|
require "json"
|
6
5
|
require "addressable"
|
@@ -22,7 +21,6 @@ module Ferrum
|
|
22
21
|
:default_user_agent, :browser_version, :protocol_version,
|
23
22
|
:v8_version, :webkit_version, :xvfb
|
24
23
|
|
25
|
-
|
26
24
|
extend Forwardable
|
27
25
|
delegate path: :command
|
28
26
|
|
@@ -32,41 +30,50 @@ module Ferrum
|
|
32
30
|
|
33
31
|
def self.process_killer(pid)
|
34
32
|
proc do
|
35
|
-
|
36
|
-
|
33
|
+
if Utils::Platform.windows?
|
34
|
+
# Process.kill is unreliable on Windows
|
35
|
+
::Process.kill("KILL", pid) unless system("taskkill /f /t /pid #{pid} >NUL 2>NUL")
|
36
|
+
else
|
37
|
+
::Process.kill("USR1", pid)
|
38
|
+
start = Utils::ElapsedTime.monotonic_time
|
39
|
+
while ::Process.wait(pid, ::Process::WNOHANG).nil?
|
40
|
+
sleep(WAIT_KILLED)
|
41
|
+
next unless Utils::ElapsedTime.timeout?(start, KILL_TIMEOUT)
|
42
|
+
|
37
43
|
::Process.kill("KILL", pid)
|
38
|
-
|
39
|
-
|
40
|
-
start = Ferrum.monotonic_time
|
41
|
-
while ::Process.wait(pid, ::Process::WNOHANG).nil?
|
42
|
-
sleep(WAIT_KILLED)
|
43
|
-
next unless Ferrum.timeout?(start, KILL_TIMEOUT)
|
44
|
-
::Process.kill("KILL", pid)
|
45
|
-
::Process.wait(pid)
|
46
|
-
break
|
47
|
-
end
|
44
|
+
::Process.wait(pid)
|
45
|
+
break
|
48
46
|
end
|
49
|
-
rescue Errno::ESRCH, Errno::ECHILD
|
50
47
|
end
|
48
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
49
|
+
# nop
|
51
50
|
end
|
52
51
|
end
|
53
52
|
|
54
53
|
def self.directory_remover(path)
|
55
|
-
proc {
|
54
|
+
proc {
|
55
|
+
begin
|
56
|
+
FileUtils.remove_entry(path)
|
57
|
+
rescue StandardError
|
58
|
+
Errno::ENOENT
|
59
|
+
end
|
60
|
+
}
|
56
61
|
end
|
57
62
|
|
58
63
|
def initialize(options)
|
64
|
+
@pid = @xvfb = @user_data_dir = nil
|
65
|
+
|
59
66
|
if options[:url]
|
60
67
|
url = URI.join(options[:url].to_s, "/json/version")
|
61
68
|
response = JSON.parse(::Net::HTTP.get(url))
|
62
|
-
|
69
|
+
self.ws_url = response["webSocketDebuggerUrl"]
|
63
70
|
parse_browser_versions
|
64
71
|
return
|
65
72
|
end
|
66
73
|
|
67
|
-
@pid = @xvfb = @user_data_dir = nil
|
68
74
|
@logger = options[:logger]
|
69
75
|
@process_timeout = options.fetch(:process_timeout, PROCESS_TIMEOUT)
|
76
|
+
@env = Hash(options[:env])
|
70
77
|
|
71
78
|
tmpdir = Dir.mktmpdir("ferrum_user_data_dir_")
|
72
79
|
ObjectSpace.define_finalizer(self, self.class.directory_remover(tmpdir))
|
@@ -81,7 +88,7 @@ module Ferrum
|
|
81
88
|
begin
|
82
89
|
read_io, write_io = IO.pipe
|
83
90
|
process_options = { in: File::NULL }
|
84
|
-
process_options[:pgroup] = true unless
|
91
|
+
process_options[:pgroup] = true unless Utils::Platform.windows?
|
85
92
|
process_options[:out] = process_options[:err] = write_io
|
86
93
|
|
87
94
|
if @command.xvfb?
|
@@ -89,7 +96,8 @@ module Ferrum
|
|
89
96
|
ObjectSpace.define_finalizer(self, self.class.process_killer(@xvfb.pid))
|
90
97
|
end
|
91
98
|
|
92
|
-
|
99
|
+
env = Hash(@xvfb&.to_env).merge(@env)
|
100
|
+
@pid = ::Process.spawn(env, *@command.to_a, process_options)
|
93
101
|
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
|
94
102
|
|
95
103
|
parse_ws_url(read_io, @process_timeout)
|
@@ -128,29 +136,29 @@ module Ferrum
|
|
128
136
|
|
129
137
|
def parse_ws_url(read_io, timeout)
|
130
138
|
output = ""
|
131
|
-
start =
|
139
|
+
start = Utils::ElapsedTime.monotonic_time
|
132
140
|
max_time = start + timeout
|
133
|
-
regexp =
|
134
|
-
while (now =
|
141
|
+
regexp = %r{DevTools listening on (ws://.*)}
|
142
|
+
while (now = Utils::ElapsedTime.monotonic_time) < max_time
|
135
143
|
begin
|
136
144
|
output += read_io.read_nonblock(512)
|
137
145
|
rescue IO::WaitReadable
|
138
|
-
|
146
|
+
read_io.wait_readable(max_time - now)
|
139
147
|
else
|
140
148
|
if output.match(regexp)
|
141
|
-
|
149
|
+
self.ws_url = output.match(regexp)[1].strip
|
142
150
|
break
|
143
151
|
end
|
144
152
|
end
|
145
153
|
end
|
146
154
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
155
|
+
return if ws_url
|
156
|
+
|
157
|
+
@logger&.puts(output)
|
158
|
+
raise ProcessTimeoutError.new(timeout, output)
|
151
159
|
end
|
152
160
|
|
153
|
-
def
|
161
|
+
def ws_url=(url)
|
154
162
|
@ws_url = Addressable::URI.parse(url)
|
155
163
|
@host = @ws_url.host
|
156
164
|
@port = @ws_url.port
|
@@ -171,11 +179,9 @@ module Ferrum
|
|
171
179
|
|
172
180
|
def close_io(*ios)
|
173
181
|
ios.each do |io|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
raise unless RUBY_ENGINE == "jruby"
|
178
|
-
end
|
182
|
+
io.close unless io.closed?
|
183
|
+
rescue IOError
|
184
|
+
raise unless RUBY_ENGINE == "jruby"
|
179
185
|
end
|
180
186
|
end
|
181
187
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent-ruby"
|
4
|
-
|
5
3
|
module Ferrum
|
6
4
|
class Browser
|
7
5
|
class Subscriber
|
@@ -29,7 +27,7 @@ module Ferrum
|
|
29
27
|
method, params = message.values_at("method", "params")
|
30
28
|
total = @on[method].size
|
31
29
|
@on[method].each_with_index do |block, index|
|
32
|
-
#
|
30
|
+
# In case of multiple callbacks we provide current index and total
|
33
31
|
block.call(params, index, total)
|
34
32
|
end
|
35
33
|
end
|
@@ -21,9 +21,7 @@ module Ferrum
|
|
21
21
|
@driver = ::WebSocket::Driver.client(self, max_length: max_receive_size)
|
22
22
|
@messages = Queue.new
|
23
23
|
|
24
|
-
if SKIP_LOGGING_SCREENSHOTS
|
25
|
-
@screenshot_commands = Concurrent::Hash.new
|
26
|
-
end
|
24
|
+
@screenshot_commands = Concurrent::Hash.new if SKIP_LOGGING_SCREENSHOTS
|
27
25
|
|
28
26
|
@driver.on(:open, &method(:on_open))
|
29
27
|
@driver.on(:message, &method(:on_message))
|
@@ -31,12 +29,13 @@ module Ferrum
|
|
31
29
|
|
32
30
|
@thread = Thread.new do
|
33
31
|
Thread.current.abort_on_exception = true
|
34
|
-
if Thread.current.respond_to?(:report_on_exception=)
|
35
|
-
Thread.current.report_on_exception = true
|
36
|
-
end
|
32
|
+
Thread.current.report_on_exception = true if Thread.current.respond_to?(:report_on_exception=)
|
37
33
|
|
38
34
|
begin
|
39
|
-
|
35
|
+
loop do
|
36
|
+
data = @sock.readpartial(512)
|
37
|
+
break unless data
|
38
|
+
|
40
39
|
@driver.parse(data)
|
41
40
|
end
|
42
41
|
rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
|
@@ -62,7 +61,7 @@ module Ferrum
|
|
62
61
|
output.sub!(/{"data":"(.*)"}/, %("Set FERRUM_LOGGING_SCREENSHOTS=true to see screenshots in Base64"))
|
63
62
|
end
|
64
63
|
|
65
|
-
@logger&.puts(" ◀ #{
|
64
|
+
@logger&.puts(" ◀ #{Utils::ElapsedTime.elapsed_time} #{output}\n")
|
66
65
|
end
|
67
66
|
|
68
67
|
def on_close(_event)
|
@@ -71,13 +70,11 @@ module Ferrum
|
|
71
70
|
end
|
72
71
|
|
73
72
|
def send_message(data)
|
74
|
-
if SKIP_LOGGING_SCREENSHOTS
|
75
|
-
@screenshot_commands[data[:id]] = true
|
76
|
-
end
|
73
|
+
@screenshot_commands[data[:id]] = true if SKIP_LOGGING_SCREENSHOTS
|
77
74
|
|
78
75
|
json = data.to_json
|
79
76
|
@driver.text(json)
|
80
|
-
@logger&.puts("\n\n▶ #{
|
77
|
+
@logger&.puts("\n\n▶ #{Utils::ElapsedTime.elapsed_time} #{json}")
|
81
78
|
end
|
82
79
|
|
83
80
|
def write(data)
|
data/lib/ferrum/browser/xvfb.rb
CHANGED
@@ -4,23 +4,19 @@ module Ferrum
|
|
4
4
|
class Browser
|
5
5
|
class Xvfb
|
6
6
|
NOT_FOUND = "Could not find an executable for the Xvfb. Try to install " \
|
7
|
-
"it with your package manager"
|
7
|
+
"it with your package manager"
|
8
8
|
|
9
9
|
def self.start(*args)
|
10
10
|
new(*args).tap(&:start)
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.xvfb_path
|
14
|
-
Cliver.detect("Xvfb")
|
15
|
-
end
|
16
|
-
|
17
13
|
attr_reader :screen_size, :display_id, :pid
|
18
14
|
|
19
15
|
def initialize(options)
|
20
|
-
@path =
|
21
|
-
raise
|
16
|
+
@path = Binary.find("Xvfb")
|
17
|
+
raise BinaryNotFoundError, NOT_FOUND unless @path
|
22
18
|
|
23
|
-
@screen_size = options.fetch(:window_size, [1024, 768]).join(
|
19
|
+
@screen_size = "#{options.fetch(:window_size, [1024, 768]).join('x')}x24"
|
24
20
|
@display_id = (Time.now.to_f * 1000).to_i % 100_000_000
|
25
21
|
end
|
26
22
|
|
data/lib/ferrum/browser.rb
CHANGED
@@ -3,10 +3,12 @@
|
|
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"
|
8
9
|
require "ferrum/browser/process"
|
9
10
|
require "ferrum/browser/client"
|
11
|
+
require "ferrum/browser/binary"
|
10
12
|
|
11
13
|
module Ferrum
|
12
14
|
class Browser
|
@@ -16,22 +18,23 @@ module Ferrum
|
|
16
18
|
|
17
19
|
extend Forwardable
|
18
20
|
delegate %i[default_context] => :contexts
|
19
|
-
delegate %i[targets create_target
|
20
|
-
delegate %i[go_to back forward refresh reload stop wait_for_reload
|
21
|
+
delegate %i[targets create_target page pages windows] => :default_context
|
22
|
+
delegate %i[go_to goto go back forward refresh reload stop wait_for_reload
|
21
23
|
at_css at_xpath css xpath current_url current_title url title
|
22
|
-
body doctype
|
24
|
+
body doctype content=
|
23
25
|
headers cookies network
|
24
26
|
mouse keyboard
|
25
27
|
screenshot pdf mhtml viewport_size
|
26
28
|
frames frame_by main_frame
|
27
29
|
evaluate evaluate_on evaluate_async execute evaluate_func
|
28
30
|
add_script_tag add_style_tag bypass_csp
|
29
|
-
on
|
31
|
+
on position position=
|
30
32
|
playback_rate playback_rate=] => :page
|
31
33
|
delegate %i[default_user_agent] => :process
|
32
34
|
|
33
35
|
attr_reader :client, :process, :contexts, :logger, :js_errors, :pending_connection_errors,
|
34
|
-
:slowmo, :base_url, :options, :window_size, :ws_max_receive_size
|
36
|
+
:slowmo, :base_url, :options, :window_size, :ws_max_receive_size, :proxy_options,
|
37
|
+
:proxy_server
|
35
38
|
attr_writer :timeout
|
36
39
|
|
37
40
|
def initialize(options = nil)
|
@@ -45,16 +48,29 @@ module Ferrum
|
|
45
48
|
@logger, @timeout, @ws_max_receive_size =
|
46
49
|
@options.values_at(:logger, :timeout, :ws_max_receive_size)
|
47
50
|
@js_errors = @options.fetch(:js_errors, false)
|
51
|
+
|
52
|
+
if @options[:proxy]
|
53
|
+
@proxy_options = @options[:proxy]
|
54
|
+
|
55
|
+
if @proxy_options[:server]
|
56
|
+
@proxy_server = Proxy.start(**@proxy_options.slice(:host, :port, :user, :password))
|
57
|
+
@proxy_options.merge!(host: @proxy_server.host, port: @proxy_server.port)
|
58
|
+
end
|
59
|
+
|
60
|
+
@options[:browser_options] ||= {}
|
61
|
+
address = "#{@proxy_options[:host]}:#{@proxy_options[:port]}"
|
62
|
+
@options[:browser_options].merge!("proxy-server" => address)
|
63
|
+
@options[:browser_options].merge!("proxy-bypass-list" => @proxy_options[:bypass]) if @proxy_options[:bypass]
|
64
|
+
end
|
65
|
+
|
48
66
|
@pending_connection_errors = @options.fetch(:pending_connection_errors, true)
|
49
67
|
@slowmo = @options[:slowmo].to_f
|
50
68
|
|
51
|
-
if @options.key?(:base_url)
|
52
|
-
self.base_url = @options[:base_url]
|
53
|
-
end
|
69
|
+
self.base_url = @options[:base_url] if @options.key?(:base_url)
|
54
70
|
|
55
|
-
if ENV
|
56
|
-
|
57
|
-
@logger =
|
71
|
+
if ENV.fetch("FERRUM_DEBUG", nil) && !@logger
|
72
|
+
$stdout.sync = true
|
73
|
+
@logger = $stdout
|
58
74
|
@options[:logger] = @logger
|
59
75
|
end
|
60
76
|
|
@@ -72,6 +88,22 @@ module Ferrum
|
|
72
88
|
@base_url = parsed
|
73
89
|
end
|
74
90
|
|
91
|
+
def create_page(new_context: false)
|
92
|
+
page = if new_context
|
93
|
+
context = contexts.create
|
94
|
+
context.create_page
|
95
|
+
else
|
96
|
+
default_context.create_page
|
97
|
+
end
|
98
|
+
|
99
|
+
block_given? ? yield(page) : page
|
100
|
+
ensure
|
101
|
+
if block_given?
|
102
|
+
page.close
|
103
|
+
context.dispose if new_context
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
75
107
|
def extensions
|
76
108
|
@extensions ||= Array(@options[:extensions]).map do |ext|
|
77
109
|
(ext.is_a?(Hash) && ext[:source]) || File.read(ext)
|
@@ -121,7 +153,7 @@ module Ferrum
|
|
121
153
|
private
|
122
154
|
|
123
155
|
def start
|
124
|
-
|
156
|
+
Utils::ElapsedTime.start
|
125
157
|
@process = Process.start(@options)
|
126
158
|
@client = Client.new(self, @process.ws_url)
|
127
159
|
@contexts = Contexts.new(self)
|
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,6 +34,7 @@ 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)
|
@@ -47,6 +50,7 @@ module Ferrum
|
|
47
50
|
url: "about:blank")
|
48
51
|
target = @pendings.take(@browser.timeout)
|
49
52
|
raise NoSuchTargetError unless target.is_a?(Target)
|
53
|
+
|
50
54
|
@targets.put_if_absent(target.id, target)
|
51
55
|
target
|
52
56
|
end
|
@@ -72,7 +76,7 @@ module Ferrum
|
|
72
76
|
@contexts.dispose(@id)
|
73
77
|
end
|
74
78
|
|
75
|
-
def
|
79
|
+
def target?(target_id)
|
76
80
|
!!@targets[target_id]
|
77
81
|
end
|
78
82
|
|
data/lib/ferrum/contexts.rb
CHANGED
@@ -19,7 +19,7 @@ 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
|
|
@@ -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
|
|
data/lib/ferrum/cookies.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Ferrum
|
4
4
|
class Cookies
|
5
5
|
class Cookie
|
6
|
+
attr_reader :attributes
|
7
|
+
|
6
8
|
def initialize(attributes)
|
7
9
|
@attributes = attributes
|
8
10
|
end
|
@@ -44,9 +46,7 @@ module Ferrum
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def expires
|
47
|
-
if @attributes["expires"]
|
48
|
-
Time.at(@attributes["expires"])
|
49
|
-
end
|
49
|
+
Time.at(@attributes["expires"]) if @attributes["expires"].positive?
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -56,24 +56,25 @@ module Ferrum
|
|
56
56
|
|
57
57
|
def all
|
58
58
|
cookies = @page.command("Network.getAllCookies")["cookies"]
|
59
|
-
cookies.
|
59
|
+
cookies.to_h { |c| [c["name"], Cookie.new(c)] }
|
60
60
|
end
|
61
61
|
|
62
62
|
def [](name)
|
63
63
|
all[name]
|
64
64
|
end
|
65
65
|
|
66
|
-
def set(
|
67
|
-
cookie =
|
68
|
-
|
69
|
-
|
66
|
+
def set(options)
|
67
|
+
cookie = (
|
68
|
+
options.is_a?(Cookie) ? options.attributes : options
|
69
|
+
).dup.transform_keys(&:to_sym)
|
70
|
+
|
70
71
|
cookie[:domain] ||= default_domain
|
71
72
|
|
72
73
|
cookie[:httpOnly] = cookie.delete(:httponly) if cookie.key?(:httponly)
|
73
74
|
cookie[:sameSite] = cookie.delete(:samesite) if cookie.key?(:samesite)
|
74
75
|
|
75
76
|
expires = cookie.delete(:expires).to_i
|
76
|
-
cookie[:expires] = expires if expires
|
77
|
+
cookie[:expires] = expires if expires.positive?
|
77
78
|
|
78
79
|
@page.command("Network.setCookie", **cookie)["success"]
|
79
80
|
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Error < StandardError; end
|
5
|
+
class NoSuchPageError < Error; end
|
6
|
+
class NoSuchTargetError < Error; end
|
7
|
+
class NotImplementedError < Error; end
|
8
|
+
class BinaryNotFoundError < Error; end
|
9
|
+
class EmptyPathError < Error; end
|
10
|
+
|
11
|
+
class StatusError < Error
|
12
|
+
def initialize(url, message = nil)
|
13
|
+
super(message || "Request to #{url} failed to reach server, check DNS and server status")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class PendingConnectionsError < StatusError
|
18
|
+
attr_reader :pendings
|
19
|
+
|
20
|
+
def initialize(url, pendings = [])
|
21
|
+
@pendings = pendings
|
22
|
+
|
23
|
+
message = "Request to #{url} reached server, but there are still pending connections: #{pendings.join(', ')}"
|
24
|
+
|
25
|
+
super(url, message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class TimeoutError < Error
|
30
|
+
def message
|
31
|
+
"Timed out waiting for response. It's possible that this happened " \
|
32
|
+
"because something took a very long time (for example a page load " \
|
33
|
+
"was slow). If so, setting the :timeout option to a higher value might " \
|
34
|
+
"help."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ScriptTimeoutError < Error
|
39
|
+
def message
|
40
|
+
"Timed out waiting for evaluated script to return a value"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ProcessTimeoutError < Error
|
45
|
+
attr_reader :output
|
46
|
+
|
47
|
+
def initialize(timeout, output)
|
48
|
+
@output = output
|
49
|
+
super("Browser did not produce websocket url within #{timeout} seconds, try to increase `:process_timeout`. See https://github.com/rubycdp/ferrum#customization")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class DeadBrowserError < Error
|
54
|
+
def initialize(message = "Browser is dead or given window is closed")
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class NodeMovingError < Error
|
60
|
+
def initialize(node, prev, current)
|
61
|
+
@node = node
|
62
|
+
@prev = prev
|
63
|
+
@current = current
|
64
|
+
super(message)
|
65
|
+
end
|
66
|
+
|
67
|
+
def message
|
68
|
+
"#{@node.inspect} that you're trying to click is moving, hence " \
|
69
|
+
"we cannot. Previously it was at #{@prev.inspect} but now at " \
|
70
|
+
"#{@current.inspect}."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class CoordinatesNotFoundError < Error
|
75
|
+
def initialize(message = "Could not compute content quads")
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class BrowserError < Error
|
81
|
+
attr_reader :response
|
82
|
+
|
83
|
+
def initialize(response)
|
84
|
+
@response = response
|
85
|
+
super(response["message"])
|
86
|
+
end
|
87
|
+
|
88
|
+
def code
|
89
|
+
response["code"]
|
90
|
+
end
|
91
|
+
|
92
|
+
def data
|
93
|
+
response["data"]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class NodeNotFoundError < BrowserError; end
|
98
|
+
|
99
|
+
class NoExecutionContextError < BrowserError
|
100
|
+
def initialize(response = nil)
|
101
|
+
response ||= { "message" => "There's no context available" }
|
102
|
+
super(response)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class JavaScriptError < BrowserError
|
107
|
+
attr_reader :class_name, :message, :stack_trace
|
108
|
+
|
109
|
+
def initialize(response, stack_trace = nil)
|
110
|
+
@class_name, @message = response.values_at("className", "description")
|
111
|
+
@stack_trace = stack_trace
|
112
|
+
super(response.merge("message" => @message))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|