ferrum 0.12 → 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/README.md +15 -20
- data/lib/ferrum/browser/client.rb +4 -4
- data/lib/ferrum/browser/command.rb +5 -6
- data/lib/ferrum/browser/options/base.rb +1 -4
- data/lib/ferrum/browser/options/chrome.rb +14 -9
- data/lib/ferrum/browser/options/firefox.rb +3 -6
- data/lib/ferrum/browser/options.rb +84 -0
- data/lib/ferrum/browser/process.rb +5 -6
- data/lib/ferrum/browser/version_info.rb +71 -0
- data/lib/ferrum/browser/xvfb.rb +1 -1
- data/lib/ferrum/browser.rb +176 -62
- data/lib/ferrum/context.rb +3 -2
- data/lib/ferrum/contexts.rb +2 -2
- data/lib/ferrum/cookies/cookie.rb +126 -0
- data/lib/ferrum/cookies.rb +86 -49
- data/lib/ferrum/dialog.rb +30 -0
- data/lib/ferrum/frame/dom.rb +177 -0
- data/lib/ferrum/frame/runtime.rb +41 -61
- data/lib/ferrum/frame.rb +90 -3
- data/lib/ferrum/headers.rb +28 -0
- data/lib/ferrum/keyboard.rb +45 -2
- data/lib/ferrum/mouse.rb +84 -0
- data/lib/ferrum/network/exchange.rb +86 -5
- data/lib/ferrum/network/request.rb +64 -0
- data/lib/ferrum/network/response.rb +83 -1
- data/lib/ferrum/network.rb +160 -0
- data/lib/ferrum/page/animation.rb +16 -0
- data/lib/ferrum/page/frames.rb +66 -11
- data/lib/ferrum/page/screenshot.rb +91 -0
- data/lib/ferrum/page/tracing.rb +26 -0
- data/lib/ferrum/page.rb +151 -32
- data/lib/ferrum/proxy.rb +91 -2
- data/lib/ferrum/target.rb +6 -4
- data/lib/ferrum/version.rb +1 -1
- metadata +5 -2
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
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() {
|
@@ -70,6 +68,18 @@ module Ferrum
|
|
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
84
|
expression = format("function() { %s }", expression)
|
75
85
|
call(expression: expression, arguments: args, handle: false, returnByValue: true)
|
@@ -87,42 +97,12 @@ module Ferrum
|
|
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
|
-
sleep = INTERMITTENT_SLEEP
|
123
|
-
attempts = INTERMITTENT_ATTEMPTS
|
124
104
|
|
125
|
-
Utils::Attempt.with_retry(errors: errors, max:
|
105
|
+
Utils::Attempt.with_retry(errors: errors, max: INTERMITTENT_ATTEMPTS, wait: INTERMITTENT_SLEEP) do
|
126
106
|
params = options.dup
|
127
107
|
|
128
108
|
if on
|
@@ -132,7 +112,7 @@ module Ferrum
|
|
132
112
|
end
|
133
113
|
|
134
114
|
if params[:executionContextId].nil? && params[:objectId].nil?
|
135
|
-
params = params.merge(executionContextId: execution_id)
|
115
|
+
params = params.merge(executionContextId: execution_id!)
|
136
116
|
end
|
137
117
|
|
138
118
|
response = @page.command("Runtime.callFunctionOn",
|
data/lib/ferrum/frame.rb
CHANGED
@@ -14,8 +14,30 @@ module Ferrum
|
|
14
14
|
stopped_loading
|
15
15
|
].freeze
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
# The Frame's unique id.
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
attr_accessor :id
|
21
|
+
|
22
|
+
# If frame was given a name it should be here.
|
23
|
+
#
|
24
|
+
# @return [String, nil]
|
25
|
+
attr_accessor :name
|
26
|
+
|
27
|
+
# The page the frame belongs to.
|
28
|
+
#
|
29
|
+
# @return [Page]
|
30
|
+
attr_reader :page
|
31
|
+
|
32
|
+
# Parent frame id if this one is nested in another one.
|
33
|
+
#
|
34
|
+
# @return [String, nil]
|
35
|
+
attr_reader :parent_id
|
36
|
+
|
37
|
+
# One of the states frame's in.
|
38
|
+
#
|
39
|
+
# @return [:started_loading, :navigated, :stopped_loading, nil]
|
40
|
+
attr_reader :state
|
19
41
|
|
20
42
|
def initialize(id, page, parent_id = nil)
|
21
43
|
@id = id
|
@@ -30,18 +52,60 @@ module Ferrum
|
|
30
52
|
@state = value
|
31
53
|
end
|
32
54
|
|
55
|
+
#
|
56
|
+
# Returns current frame's `location.href`.
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# browser.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
|
62
|
+
# frame = browser.frames[1]
|
63
|
+
# frame.url # => https://interactive-examples.mdn.mozilla.net/pages/tabbed/iframe.html
|
64
|
+
#
|
33
65
|
def url
|
34
66
|
evaluate("document.location.href")
|
35
67
|
end
|
36
68
|
|
69
|
+
#
|
70
|
+
# Returns current frame's title.
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# browser.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
|
76
|
+
# frame = browser.frames[1]
|
77
|
+
# frame.title # => HTML Demo: <iframe>
|
78
|
+
#
|
37
79
|
def title
|
38
80
|
evaluate("document.title")
|
39
81
|
end
|
40
82
|
|
83
|
+
#
|
84
|
+
# If current frame is the main frame of the page (top of the tree).
|
85
|
+
#
|
86
|
+
# @return [Boolean]
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# browser.go_to("https://www.w3schools.com/tags/tag_frame.asp")
|
90
|
+
# frame = browser.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
|
91
|
+
# frame.main? # => false
|
92
|
+
#
|
41
93
|
def main?
|
42
94
|
@parent_id.nil?
|
43
95
|
end
|
44
96
|
|
97
|
+
#
|
98
|
+
# Sets a content of a given frame.
|
99
|
+
#
|
100
|
+
# @param [String] html
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# browser.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
|
104
|
+
# frame = browser.frames[1]
|
105
|
+
# frame.body # <html lang="en"><head><style>body {transition: opacity ease-in 0.2s; }...
|
106
|
+
# frame.content = "<html><head></head><body><p>lol</p></body></html>"
|
107
|
+
# frame.body # => <html><head></head><body><p>lol</p></body></html>
|
108
|
+
#
|
45
109
|
def content=(html)
|
46
110
|
evaluate_async(%(
|
47
111
|
document.open();
|
@@ -52,13 +116,36 @@ module Ferrum
|
|
52
116
|
end
|
53
117
|
alias set_content content=
|
54
118
|
|
55
|
-
|
119
|
+
#
|
120
|
+
# Execution context id which is used by JS, each frame has it's own
|
121
|
+
# context in which JS evaluates. Locks for a page timeout and raises
|
122
|
+
# an error if an execution id hasn't been set yet, if id is set
|
123
|
+
# returns immediately.
|
124
|
+
#
|
125
|
+
# @return [Integer]
|
126
|
+
#
|
127
|
+
# @raise [NoExecutionContextError]
|
128
|
+
#
|
129
|
+
def execution_id!
|
56
130
|
value = @execution_id.borrow(@page.timeout, &:itself)
|
57
131
|
raise NoExecutionContextError if value.instance_of?(Object)
|
58
132
|
|
59
133
|
value
|
60
134
|
end
|
61
135
|
|
136
|
+
#
|
137
|
+
# Execution context id which is used by JS, each frame has it's own
|
138
|
+
# context in which JS evaluates.
|
139
|
+
#
|
140
|
+
# @return [Integer, nil]
|
141
|
+
#
|
142
|
+
def execution_id
|
143
|
+
value = @execution_id.value
|
144
|
+
return if value.instance_of?(Object)
|
145
|
+
|
146
|
+
value
|
147
|
+
end
|
148
|
+
|
62
149
|
def execution_id=(value)
|
63
150
|
if value.nil?
|
64
151
|
@execution_id.try_take!
|
data/lib/ferrum/headers.rb
CHANGED
@@ -7,20 +7,48 @@ module Ferrum
|
|
7
7
|
@headers = {}
|
8
8
|
end
|
9
9
|
|
10
|
+
#
|
11
|
+
# Get all headers.
|
12
|
+
#
|
13
|
+
# @return [Hash{String => String}]
|
14
|
+
#
|
10
15
|
def get
|
11
16
|
@headers
|
12
17
|
end
|
13
18
|
|
19
|
+
#
|
20
|
+
# Set given headers. Eventually clear all headers and set given ones.
|
21
|
+
#
|
22
|
+
# @param [Hash{String => String}] headers
|
23
|
+
# key-value pairs for example `"User-Agent" => "Browser"`.
|
24
|
+
#
|
25
|
+
# @return [true]
|
26
|
+
#
|
14
27
|
def set(headers)
|
15
28
|
clear
|
16
29
|
add(headers)
|
17
30
|
end
|
18
31
|
|
32
|
+
#
|
33
|
+
# Clear all headers.
|
34
|
+
#
|
35
|
+
# @return [true]
|
36
|
+
#
|
19
37
|
def clear
|
20
38
|
@headers = {}
|
21
39
|
true
|
22
40
|
end
|
23
41
|
|
42
|
+
#
|
43
|
+
# Adds given headers to already set ones.
|
44
|
+
#
|
45
|
+
# @param [Hash{String => String}] headers
|
46
|
+
# key-value pairs for example `"Referer" => "http://example.com"`.
|
47
|
+
#
|
48
|
+
# @param [Boolean] permanent
|
49
|
+
#
|
50
|
+
# @return [true]
|
51
|
+
#
|
24
52
|
def add(headers, permanent: true)
|
25
53
|
if headers["Referer"]
|
26
54
|
@page.referrer = headers["Referer"]
|
data/lib/ferrum/keyboard.rb
CHANGED
@@ -30,19 +30,44 @@ module Ferrum
|
|
30
30
|
@page = page
|
31
31
|
end
|
32
32
|
|
33
|
+
#
|
34
|
+
# Dispatches a `keydown` event.
|
35
|
+
#
|
36
|
+
# @param [String, Symbol] key
|
37
|
+
# Name of the key, such as `"a"`, `:enter`, or `:backspace`.
|
38
|
+
#
|
39
|
+
# @return [self]
|
40
|
+
#
|
33
41
|
def down(key)
|
34
|
-
key = normalize_keys(Array(key))
|
42
|
+
key = normalize_keys(Array(key)).first
|
35
43
|
type = key[:text] ? "keyDown" : "rawKeyDown"
|
36
44
|
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: type, **key)
|
37
45
|
self
|
38
46
|
end
|
39
47
|
|
48
|
+
#
|
49
|
+
# Dispatches a `keyup` event.
|
50
|
+
#
|
51
|
+
# @param [String, Symbol] key
|
52
|
+
# Name of the key, such as `"a"`, `:enter`, or `:backspace`.
|
53
|
+
#
|
54
|
+
# @return [self]
|
55
|
+
#
|
40
56
|
def up(key)
|
41
|
-
key = normalize_keys(Array(key))
|
57
|
+
key = normalize_keys(Array(key)).first
|
42
58
|
@page.command("Input.dispatchKeyEvent", slowmoable: true, type: "keyUp", **key)
|
43
59
|
self
|
44
60
|
end
|
45
61
|
|
62
|
+
#
|
63
|
+
# Sends a keydown, keypress/input, and keyup event for each character in
|
64
|
+
# the text.
|
65
|
+
#
|
66
|
+
# @param [Array<String, Symbol, (Symbol, String)>] keys
|
67
|
+
# The text to type into a focused element, `[:Shift, "s"], "tring"`.
|
68
|
+
#
|
69
|
+
# @return [self]
|
70
|
+
#
|
46
71
|
def type(*keys)
|
47
72
|
keys = normalize_keys(Array(keys))
|
48
73
|
|
@@ -55,15 +80,27 @@ module Ferrum
|
|
55
80
|
self
|
56
81
|
end
|
57
82
|
|
83
|
+
#
|
84
|
+
# Returns bitfield for a given keys.
|
85
|
+
#
|
86
|
+
# @param [Array<:alt, :ctrl, :command, :shift>] keys
|
87
|
+
#
|
88
|
+
# @return [Integer]
|
89
|
+
#
|
58
90
|
def modifiers(keys)
|
59
91
|
keys.map { |k| MODIFIERS[k.to_s] }.compact.reduce(0, :|)
|
60
92
|
end
|
61
93
|
|
62
94
|
private
|
63
95
|
|
96
|
+
# TODO: Refactor it, and try to simplify complexity
|
97
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
98
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
64
99
|
def normalize_keys(keys, pressed_keys = [], memo = [])
|
65
100
|
case keys
|
66
101
|
when Array
|
102
|
+
raise ArgumentError, "empty keys passed" if keys.empty?
|
103
|
+
|
67
104
|
pressed_keys.push([])
|
68
105
|
memo += combine_strings(keys).map do |key|
|
69
106
|
normalize_keys(key, pressed_keys, memo)
|
@@ -82,6 +119,8 @@ module Ferrum
|
|
82
119
|
to_options(key)
|
83
120
|
end
|
84
121
|
when String
|
122
|
+
raise ArgumentError, "empty keys passed" if keys.empty?
|
123
|
+
|
85
124
|
pressed = pressed_keys.flatten
|
86
125
|
keys.each_char.map do |char|
|
87
126
|
key = KEYS[char] || {}
|
@@ -102,8 +141,12 @@ module Ferrum
|
|
102
141
|
modifiers + [to_options(key)]
|
103
142
|
end.flatten
|
104
143
|
end
|
144
|
+
else
|
145
|
+
raise ArgumentError, "unexpected argument"
|
105
146
|
end
|
106
147
|
end
|
148
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
149
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
107
150
|
|
108
151
|
def combine_strings(keys)
|
109
152
|
keys
|