ferrum 0.12 → 0.13
Sign up to get free protection for your applications and to get access to all the features.
- 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
|