ferrum 0.11 → 0.13
Sign up to get free protection for your applications and to get access to all the features.
- 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/dialog.rb
CHANGED
@@ -10,6 +10,22 @@ module Ferrum
|
|
10
10
|
@default_prompt = params["defaultPrompt"]
|
11
11
|
end
|
12
12
|
|
13
|
+
#
|
14
|
+
# Accept dialog with given text or default prompt if applicable
|
15
|
+
#
|
16
|
+
# @param [String, nil] prompt_text
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# browser = Ferrum::Browser.new
|
20
|
+
# browser.on(:dialog) do |dialog|
|
21
|
+
# if dialog.match?(/bla-bla/)
|
22
|
+
# dialog.accept
|
23
|
+
# else
|
24
|
+
# dialog.dismiss
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# browser.go_to("https://google.com")
|
28
|
+
#
|
13
29
|
def accept(prompt_text = nil)
|
14
30
|
options = { accept: true }
|
15
31
|
response = prompt_text || default_prompt
|
@@ -17,6 +33,20 @@ module Ferrum
|
|
17
33
|
@page.command("Page.handleJavaScriptDialog", slowmoable: true, **options)
|
18
34
|
end
|
19
35
|
|
36
|
+
#
|
37
|
+
# Dismiss dialog.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# browser = Ferrum::Browser.new
|
41
|
+
# browser.on(:dialog) do |dialog|
|
42
|
+
# if dialog.match?(/bla-bla/)
|
43
|
+
# dialog.accept
|
44
|
+
# else
|
45
|
+
# dialog.dismiss
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# browser.go_to("https://google.com")
|
49
|
+
#
|
20
50
|
def dismiss
|
21
51
|
@page.command("Page.handleJavaScriptDialog", slowmoable: true, accept: false)
|
22
52
|
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
|
data/lib/ferrum/frame/dom.rb
CHANGED
@@ -20,10 +20,59 @@
|
|
20
20
|
module Ferrum
|
21
21
|
class Frame
|
22
22
|
module DOM
|
23
|
+
SCRIPT_SRC_TAG = <<~JS
|
24
|
+
const script = document.createElement("script");
|
25
|
+
script.src = arguments[0];
|
26
|
+
script.type = arguments[1];
|
27
|
+
script.onload = arguments[2];
|
28
|
+
document.head.appendChild(script);
|
29
|
+
JS
|
30
|
+
SCRIPT_TEXT_TAG = <<~JS
|
31
|
+
const script = document.createElement("script");
|
32
|
+
script.text = arguments[0];
|
33
|
+
script.type = arguments[1];
|
34
|
+
document.head.appendChild(script);
|
35
|
+
arguments[2]();
|
36
|
+
JS
|
37
|
+
STYLE_TAG = <<~JS
|
38
|
+
const style = document.createElement("style");
|
39
|
+
style.type = "text/css";
|
40
|
+
style.appendChild(document.createTextNode(arguments[0]));
|
41
|
+
document.head.appendChild(style);
|
42
|
+
arguments[1]();
|
43
|
+
JS
|
44
|
+
LINK_TAG = <<~JS
|
45
|
+
const link = document.createElement("link");
|
46
|
+
link.rel = "stylesheet";
|
47
|
+
link.href = arguments[0];
|
48
|
+
link.onload = arguments[1];
|
49
|
+
document.head.appendChild(link);
|
50
|
+
JS
|
51
|
+
|
52
|
+
#
|
53
|
+
# Returns current top window `location href`.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
# The window's current URL.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# browser.go_to("https://google.com/")
|
60
|
+
# browser.current_url # => "https://www.google.com/"
|
61
|
+
#
|
23
62
|
def current_url
|
24
63
|
evaluate("window.top.location.href")
|
25
64
|
end
|
26
65
|
|
66
|
+
#
|
67
|
+
# Returns current top window title.
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
# The window's current title.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# browser.go_to("https://google.com/")
|
74
|
+
# browser.current_title # => "Google"
|
75
|
+
#
|
27
76
|
def current_title
|
28
77
|
evaluate("window.top.document.title")
|
29
78
|
end
|
@@ -32,10 +81,36 @@ module Ferrum
|
|
32
81
|
evaluate("document.doctype && new XMLSerializer().serializeToString(document.doctype)")
|
33
82
|
end
|
34
83
|
|
84
|
+
#
|
85
|
+
# Returns current page's html.
|
86
|
+
#
|
87
|
+
# @return [String]
|
88
|
+
# The HTML source of the current page.
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# browser.go_to("https://google.com/")
|
92
|
+
# browser.body # => '<html itemscope="" itemtype="http://schema.org/WebPage" lang="ru"><head>...
|
93
|
+
#
|
35
94
|
def body
|
36
95
|
evaluate("document.documentElement.outerHTML")
|
37
96
|
end
|
38
97
|
|
98
|
+
#
|
99
|
+
# Finds nodes by using a XPath selector.
|
100
|
+
#
|
101
|
+
# @param [String] selector
|
102
|
+
# The XPath selector.
|
103
|
+
#
|
104
|
+
# @param [Node, nil] within
|
105
|
+
# The parent node to search within.
|
106
|
+
#
|
107
|
+
# @return [Array<Node>]
|
108
|
+
# The matching nodes.
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# browser.go_to("https://github.com/")
|
112
|
+
# browser.xpath("//a[@aria-label='Issues you created']") # => [Node]
|
113
|
+
#
|
39
114
|
def xpath(selector, within: nil)
|
40
115
|
expr = <<~JS
|
41
116
|
function(selector, within) {
|
@@ -54,6 +129,22 @@ module Ferrum
|
|
54
129
|
evaluate_func(expr, selector, within)
|
55
130
|
end
|
56
131
|
|
132
|
+
#
|
133
|
+
# Finds a node by using a XPath selector.
|
134
|
+
#
|
135
|
+
# @param [String] selector
|
136
|
+
# The XPath selector.
|
137
|
+
#
|
138
|
+
# @param [Node, nil] within
|
139
|
+
# The parent node to search within.
|
140
|
+
#
|
141
|
+
# @return [Node, nil]
|
142
|
+
# The matching node.
|
143
|
+
#
|
144
|
+
# @example
|
145
|
+
# browser.go_to("https://github.com/")
|
146
|
+
# browser.at_xpath("//a[@aria-label='Issues you created']") # => Node
|
147
|
+
#
|
57
148
|
def at_xpath(selector, within: nil)
|
58
149
|
expr = <<~JS
|
59
150
|
function(selector, within) {
|
@@ -65,6 +156,22 @@ module Ferrum
|
|
65
156
|
evaluate_func(expr, selector, within)
|
66
157
|
end
|
67
158
|
|
159
|
+
#
|
160
|
+
# Finds nodes by using a CSS path selector.
|
161
|
+
#
|
162
|
+
# @param [String] selector
|
163
|
+
# The CSS path selector.
|
164
|
+
#
|
165
|
+
# @param [Node, nil] within
|
166
|
+
# The parent node to search within.
|
167
|
+
#
|
168
|
+
# @return [Array<Node>]
|
169
|
+
# The matching nodes.
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
# browser.go_to("https://github.com/")
|
173
|
+
# browser.css("a[aria-label='Issues you created']") # => [Node]
|
174
|
+
#
|
68
175
|
def css(selector, within: nil)
|
69
176
|
expr = <<~JS
|
70
177
|
function(selector, within) {
|
@@ -76,6 +183,22 @@ module Ferrum
|
|
76
183
|
evaluate_func(expr, selector, within)
|
77
184
|
end
|
78
185
|
|
186
|
+
#
|
187
|
+
# Finds a node by using a CSS path selector.
|
188
|
+
#
|
189
|
+
# @param [String] selector
|
190
|
+
# The CSS path selector.
|
191
|
+
#
|
192
|
+
# @param [Node, nil] within
|
193
|
+
# The parent node to search within.
|
194
|
+
#
|
195
|
+
# @return [Node, nil]
|
196
|
+
# The matching node.
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# browser.go_to("https://github.com/")
|
200
|
+
# browser.at_css("a[aria-label='Issues you created']") # => Node
|
201
|
+
#
|
79
202
|
def at_css(selector, within: nil)
|
80
203
|
expr = <<~JS
|
81
204
|
function(selector, within) {
|
@@ -86,6 +209,60 @@ module Ferrum
|
|
86
209
|
|
87
210
|
evaluate_func(expr, selector, within)
|
88
211
|
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Adds a `<script>` tag to the document.
|
215
|
+
#
|
216
|
+
# @param [String, nil] url
|
217
|
+
#
|
218
|
+
# @param [String, nil] path
|
219
|
+
#
|
220
|
+
# @param [String, nil] content
|
221
|
+
#
|
222
|
+
# @param [String] type
|
223
|
+
#
|
224
|
+
# @example
|
225
|
+
# browser.add_script_tag(url: "http://example.com/stylesheet.css") # => true
|
226
|
+
#
|
227
|
+
def add_script_tag(url: nil, path: nil, content: nil, type: "text/javascript")
|
228
|
+
expr, *args = if url
|
229
|
+
[SCRIPT_SRC_TAG, url, type]
|
230
|
+
elsif path || content
|
231
|
+
if path
|
232
|
+
content = File.read(path)
|
233
|
+
content += "\n//# sourceURL=#{path}"
|
234
|
+
end
|
235
|
+
[SCRIPT_TEXT_TAG, content, type]
|
236
|
+
end
|
237
|
+
|
238
|
+
evaluate_async(expr, @page.timeout, *args)
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# Adds a `<style>` tag to the document.
|
243
|
+
#
|
244
|
+
# @param [String, nil] url
|
245
|
+
#
|
246
|
+
# @param [String, nil] path
|
247
|
+
#
|
248
|
+
# @param [String, nil] content
|
249
|
+
#
|
250
|
+
# @example
|
251
|
+
# browser.add_style_tag(content: "h1 { font-size: 40px; }") # => true
|
252
|
+
#
|
253
|
+
def add_style_tag(url: nil, path: nil, content: nil)
|
254
|
+
expr, *args = if url
|
255
|
+
[LINK_TAG, url]
|
256
|
+
elsif path || content
|
257
|
+
if path
|
258
|
+
content = File.read(path)
|
259
|
+
content += "\n//# sourceURL=#{path}"
|
260
|
+
end
|
261
|
+
[STYLE_TAG, content]
|
262
|
+
end
|
263
|
+
|
264
|
+
evaluate_async(expr, @page.timeout, *args)
|
265
|
+
end
|
89
266
|
end
|
90
267
|
end
|
91
268
|
end
|
data/lib/ferrum/frame/runtime.rb
CHANGED
@@ -16,40 +16,38 @@ module Ferrum
|
|
16
16
|
INTERMITTENT_ATTEMPTS = ENV.fetch("FERRUM_INTERMITTENT_ATTEMPTS", 6).to_i
|
17
17
|
INTERMITTENT_SLEEP = ENV.fetch("FERRUM_INTERMITTENT_SLEEP", 0.1).to_f
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
arguments[2]();
|
32
|
-
JS
|
33
|
-
STYLE_TAG = <<~JS
|
34
|
-
const style = document.createElement("style");
|
35
|
-
style.type = "text/css";
|
36
|
-
style.appendChild(document.createTextNode(arguments[0]));
|
37
|
-
document.head.appendChild(style);
|
38
|
-
arguments[1]();
|
39
|
-
JS
|
40
|
-
LINK_TAG = <<~JS
|
41
|
-
const link = document.createElement("link");
|
42
|
-
link.rel = "stylesheet";
|
43
|
-
link.href = arguments[0];
|
44
|
-
link.onload = arguments[1];
|
45
|
-
document.head.appendChild(link);
|
46
|
-
JS
|
47
|
-
|
19
|
+
#
|
20
|
+
# Evaluate and return result for given JS expression.
|
21
|
+
#
|
22
|
+
# @param [String] expression
|
23
|
+
# The JavaScript to evaluate.
|
24
|
+
#
|
25
|
+
# @param [Array] args
|
26
|
+
# Additional arguments to pass to the JavaScript code.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# browser.evaluate("[window.scrollX, window.scrollY]")
|
30
|
+
#
|
48
31
|
def evaluate(expression, *args)
|
49
|
-
expression = "function() { return %s }"
|
32
|
+
expression = format("function() { return %s }", expression)
|
50
33
|
call(expression: expression, arguments: args)
|
51
34
|
end
|
52
35
|
|
36
|
+
#
|
37
|
+
# Evaluate asynchronous expression and return result.
|
38
|
+
#
|
39
|
+
# @param [String] expression
|
40
|
+
# The JavaScript to evaluate.
|
41
|
+
#
|
42
|
+
# @param [Integer] wait
|
43
|
+
# How long we should wait for Promise to resolve or reject.
|
44
|
+
#
|
45
|
+
# @param [Array] args
|
46
|
+
# Additional arguments to pass to the JavaScript code.
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# browser.evaluate_async(%(arguments[0]({foo: "bar"})), 5) # => { "foo" => "bar" }
|
50
|
+
#
|
53
51
|
def evaluate_async(expression, wait, *args)
|
54
52
|
template = <<~JS
|
55
53
|
function() {
|
@@ -66,12 +64,24 @@ module Ferrum
|
|
66
64
|
}
|
67
65
|
JS
|
68
66
|
|
69
|
-
expression = template
|
67
|
+
expression = format(template, wait * 1000, expression)
|
70
68
|
call(expression: expression, arguments: args, awaitPromise: true)
|
71
69
|
end
|
72
70
|
|
71
|
+
#
|
72
|
+
# Execute expression. Doesn't return the result.
|
73
|
+
#
|
74
|
+
# @param [String] expression
|
75
|
+
# The JavaScript to evaluate.
|
76
|
+
#
|
77
|
+
# @param [Array] args
|
78
|
+
# Additional arguments to pass to the JavaScript code.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# browser.execute(%(1 + 1)) # => true
|
82
|
+
#
|
73
83
|
def execute(expression, *args)
|
74
|
-
expression = "function() { %s }"
|
84
|
+
expression = format("function() { %s }", expression)
|
75
85
|
call(expression: expression, arguments: args, handle: false, returnByValue: true)
|
76
86
|
true
|
77
87
|
end
|
@@ -82,46 +92,17 @@ module Ferrum
|
|
82
92
|
|
83
93
|
def evaluate_on(node:, expression:, by_value: true, wait: 0)
|
84
94
|
options = { handle: true }
|
85
|
-
expression = "function() { return %s }"
|
95
|
+
expression = format("function() { return %s }", expression)
|
86
96
|
options = { handle: false, returnByValue: true } if by_value
|
87
97
|
call(expression: expression, on: node, wait: wait, **options)
|
88
98
|
end
|
89
99
|
|
90
|
-
def add_script_tag(url: nil, path: nil, content: nil, type: "text/javascript")
|
91
|
-
expr, *args = if url
|
92
|
-
[SCRIPT_SRC_TAG, url, type]
|
93
|
-
elsif path || content
|
94
|
-
if path
|
95
|
-
content = File.read(path)
|
96
|
-
content += "\n//# sourceURL=#{path}"
|
97
|
-
end
|
98
|
-
[SCRIPT_TEXT_TAG, content, type]
|
99
|
-
end
|
100
|
-
|
101
|
-
evaluate_async(expr, @page.timeout, *args)
|
102
|
-
end
|
103
|
-
|
104
|
-
def add_style_tag(url: nil, path: nil, content: nil)
|
105
|
-
expr, *args = if url
|
106
|
-
[LINK_TAG, url]
|
107
|
-
elsif path || content
|
108
|
-
if path
|
109
|
-
content = File.read(path)
|
110
|
-
content += "\n//# sourceURL=#{path}"
|
111
|
-
end
|
112
|
-
[STYLE_TAG, content]
|
113
|
-
end
|
114
|
-
|
115
|
-
evaluate_async(expr, @page.timeout, *args)
|
116
|
-
end
|
117
|
-
|
118
100
|
private
|
119
101
|
|
120
102
|
def call(expression:, arguments: [], on: nil, wait: 0, handle: true, **options)
|
121
103
|
errors = [NodeNotFoundError, NoExecutionContextError]
|
122
|
-
attempts, sleep = INTERMITTENT_ATTEMPTS, INTERMITTENT_SLEEP
|
123
104
|
|
124
|
-
|
105
|
+
Utils::Attempt.with_retry(errors: errors, max: INTERMITTENT_ATTEMPTS, wait: INTERMITTENT_SLEEP) do
|
125
106
|
params = options.dup
|
126
107
|
|
127
108
|
if on
|
@@ -131,7 +112,7 @@ module Ferrum
|
|
131
112
|
end
|
132
113
|
|
133
114
|
if params[:executionContextId].nil? && params[:objectId].nil?
|
134
|
-
params = params.merge(executionContextId: execution_id)
|
115
|
+
params = params.merge(executionContextId: execution_id!)
|
135
116
|
end
|
136
117
|
|
137
118
|
response = @page.command("Runtime.callFunctionOn",
|
@@ -141,7 +122,7 @@ module Ferrum
|
|
141
122
|
handle_error(response)
|
142
123
|
response = response["result"]
|
143
124
|
|
144
|
-
handle ? handle_response(response) : response
|
125
|
+
handle ? handle_response(response) : response["value"]
|
145
126
|
end
|
146
127
|
end
|
147
128
|
|
@@ -154,7 +135,7 @@ module Ferrum
|
|
154
135
|
when /\AError: timed out promise/
|
155
136
|
raise ScriptTimeoutError
|
156
137
|
else
|
157
|
-
raise JavaScriptError.new(result)
|
138
|
+
raise JavaScriptError.new(result, response.dig("exceptionDetails", "stackTrace"))
|
158
139
|
end
|
159
140
|
end
|
160
141
|
|
@@ -171,16 +152,17 @@ module Ferrum
|
|
171
152
|
|
172
153
|
case response["subtype"]
|
173
154
|
when "node"
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
155
|
+
# We cannot store object_id in the node because page can be reloaded
|
156
|
+
# and node destroyed so we need to retrieve it each time for given id.
|
157
|
+
# Though we can try to subscribe to `DOM.childNodeRemoved` and
|
158
|
+
# `DOM.childNodeInserted` in the future.
|
159
|
+
node_id = @page.command("DOM.requestNode", objectId: object_id)["nodeId"]
|
160
|
+
description = @page.command("DOM.describeNode", nodeId: node_id)["node"]
|
161
|
+
Node.new(self, @page.target_id, node_id, description)
|
181
162
|
when "array"
|
182
163
|
reduce_props(object_id, []) do |memo, key, value|
|
183
|
-
next(memo) unless
|
164
|
+
next(memo) unless Integer(key, exception: false)
|
165
|
+
|
184
166
|
value = value["objectId"] ? handle_response(value) : value["value"]
|
185
167
|
memo.insert(key.to_i, value)
|
186
168
|
end.compact
|
@@ -212,11 +194,12 @@ module Ferrum
|
|
212
194
|
|
213
195
|
def reduce_props(object_id, to)
|
214
196
|
if cyclic?(object_id).dig("result", "value")
|
215
|
-
|
197
|
+
to.is_a?(Array) ? [cyclic_object] : cyclic_object
|
216
198
|
else
|
217
199
|
props = @page.command("Runtime.getProperties", ownProperties: true, objectId: object_id)
|
218
200
|
props["result"].reduce(to) do |memo, prop|
|
219
201
|
next(memo) unless prop["enumerable"]
|
202
|
+
|
220
203
|
yield(memo, prop["name"], prop["value"])
|
221
204
|
end
|
222
205
|
end
|