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/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
|