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/frame.rb
CHANGED
@@ -5,37 +5,108 @@ require "ferrum/frame/runtime"
|
|
5
5
|
|
6
6
|
module Ferrum
|
7
7
|
class Frame
|
8
|
-
include DOM
|
8
|
+
include DOM
|
9
|
+
include Runtime
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
STATE_VALUES = %i[
|
12
|
+
started_loading
|
13
|
+
navigated
|
14
|
+
stopped_loading
|
15
|
+
].freeze
|
16
|
+
|
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
|
12
41
|
|
13
42
|
def initialize(id, page, parent_id = nil)
|
14
|
-
@
|
15
|
-
@
|
43
|
+
@id = id
|
44
|
+
@page = page
|
45
|
+
@parent_id = parent_id
|
46
|
+
@execution_id = Concurrent::MVar.new
|
16
47
|
end
|
17
48
|
|
18
|
-
# Can be one of:
|
19
|
-
# * started_loading
|
20
|
-
# * navigated
|
21
|
-
# * stopped_loading
|
22
49
|
def state=(value)
|
50
|
+
raise ArgumentError unless STATE_VALUES.include?(value)
|
51
|
+
|
23
52
|
@state = value
|
24
53
|
end
|
25
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
|
+
#
|
26
65
|
def url
|
27
66
|
evaluate("document.location.href")
|
28
67
|
end
|
29
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
|
+
#
|
30
79
|
def title
|
31
80
|
evaluate("document.title")
|
32
81
|
end
|
33
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
|
+
#
|
34
93
|
def main?
|
35
94
|
@parent_id.nil?
|
36
95
|
end
|
37
96
|
|
38
|
-
|
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
|
+
#
|
109
|
+
def content=(html)
|
39
110
|
evaluate_async(%(
|
40
111
|
document.open();
|
41
112
|
document.write(arguments[0]);
|
@@ -43,29 +114,53 @@ module Ferrum
|
|
43
114
|
arguments[1](true);
|
44
115
|
), @page.timeout, html)
|
45
116
|
end
|
117
|
+
alias set_content content=
|
46
118
|
|
47
|
-
|
48
|
-
|
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!
|
130
|
+
value = @execution_id.borrow(@page.timeout, &:itself)
|
131
|
+
raise NoExecutionContextError if value.instance_of?(Object)
|
132
|
+
|
133
|
+
value
|
49
134
|
end
|
50
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
|
+
#
|
51
142
|
def execution_id
|
52
|
-
|
53
|
-
|
54
|
-
rescue NoExecutionContextError
|
55
|
-
@page.event.reset
|
56
|
-
@page.event.wait(@page.timeout) ? retry : raise
|
57
|
-
end
|
143
|
+
value = @execution_id.value
|
144
|
+
return if value.instance_of?(Object)
|
58
145
|
|
59
|
-
|
60
|
-
@execution_id ||= value
|
146
|
+
value
|
61
147
|
end
|
62
148
|
|
63
|
-
def
|
64
|
-
|
149
|
+
def execution_id=(value)
|
150
|
+
if value.nil?
|
151
|
+
@execution_id.try_take!
|
152
|
+
else
|
153
|
+
@execution_id.try_put!(value)
|
154
|
+
end
|
65
155
|
end
|
66
156
|
|
67
157
|
def inspect
|
68
|
-
|
158
|
+
"#<#{self.class} " \
|
159
|
+
"@id=#{@id.inspect} " \
|
160
|
+
"@parent_id=#{@parent_id.inspect} " \
|
161
|
+
"@name=#{@name.inspect} " \
|
162
|
+
"@state=#{@state.inspect} " \
|
163
|
+
"@execution_id=#{@execution_id.inspect}>"
|
69
164
|
end
|
70
165
|
end
|
71
166
|
end
|
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"]
|
@@ -39,12 +67,12 @@ module Ferrum
|
|
39
67
|
private
|
40
68
|
|
41
69
|
def set_overrides(user_agent: nil, accept_language: nil, platform: nil)
|
42
|
-
options =
|
70
|
+
options = {}
|
43
71
|
options[:userAgent] = user_agent || @page.browser.default_user_agent
|
44
72
|
options[:acceptLanguage] = accept_language if accept_language
|
45
73
|
options[:platform] if platform
|
46
74
|
|
47
|
-
@page.command("Network.setUserAgentOverride", **options)
|
75
|
+
@page.command("Network.setUserAgentOverride", **options) unless options.empty?
|
48
76
|
end
|
49
77
|
end
|
50
78
|
end
|
data/lib/ferrum/keyboard.rb
CHANGED
@@ -4,14 +4,14 @@ require "json"
|
|
4
4
|
|
5
5
|
module Ferrum
|
6
6
|
class Keyboard
|
7
|
-
KEYS = JSON.parse(File.read(File.expand_path("
|
7
|
+
KEYS = JSON.parse(File.read(File.expand_path("keyboard.json", __dir__)))
|
8
8
|
MODIFIERS = { "alt" => 1, "ctrl" => 2, "control" => 2,
|
9
|
-
"meta" => 4, "command" => 4, "shift" => 8 }
|
9
|
+
"meta" => 4, "command" => 4, "shift" => 8 }.freeze
|
10
10
|
KEYS_MAPPING = {
|
11
11
|
cancel: "Cancel", help: "Help", backspace: "Backspace", tab: "Tab",
|
12
12
|
clear: "Clear", return: "Enter", enter: "Enter", shift: "Shift",
|
13
13
|
ctrl: "Control", control: "Control", alt: "Alt", pause: "Pause",
|
14
|
-
escape: "Escape", space: "Space",
|
14
|
+
escape: "Escape", space: "Space", pageup: "PageUp", page_up: "PageUp",
|
15
15
|
pagedown: "PageDown", page_down: "PageDown", end: "End", home: "Home",
|
16
16
|
left: "ArrowLeft", up: "ArrowUp", right: "ArrowRight",
|
17
17
|
down: "ArrowDown", insert: "Insert", delete: "Delete",
|
@@ -23,26 +23,51 @@ module Ferrum
|
|
23
23
|
separator: "NumpadDecimal", subtract: "NumpadSubtract",
|
24
24
|
decimal: "NumpadDecimal", divide: "NumpadDivide", f1: "F1", f2: "F2",
|
25
25
|
f3: "F3", f4: "F4", f5: "F5", f6: "F6", f7: "F7", f8: "F8", f9: "F9",
|
26
|
-
f10: "F10", f11: "F11", f12: "F12", meta: "Meta", command: "Meta"
|
27
|
-
}
|
26
|
+
f10: "F10", f11: "F11", f12: "F12", meta: "Meta", command: "Meta"
|
27
|
+
}.freeze
|
28
28
|
|
29
29
|
def initialize(page)
|
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)
|
@@ -77,33 +114,39 @@ module Ferrum
|
|
77
114
|
pressed_keys.last.push(key)
|
78
115
|
nil
|
79
116
|
else
|
80
|
-
|
81
|
-
|
82
|
-
to_options(
|
117
|
+
key = KEYS.fetch(KEYS_MAPPING[key.to_sym] || key.to_sym)
|
118
|
+
key[:modifiers] = pressed_keys.flatten.map { |k| MODIFIERS[k] }.reduce(0, :|)
|
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|
|
126
|
+
key = KEYS[char] || {}
|
127
|
+
|
87
128
|
if pressed.empty?
|
88
|
-
key = KEYS[char] || {}
|
89
129
|
key = key.merge(text: char, unmodifiedText: char)
|
90
130
|
[to_options(key)]
|
91
131
|
else
|
92
|
-
key = KEYS[char] || {}
|
93
132
|
text = pressed == ["shift"] ? char.upcase : char
|
94
133
|
key = key.merge(
|
95
134
|
text: text,
|
96
135
|
unmodifiedText: text,
|
97
136
|
isKeypad: key["location"] == 3,
|
98
|
-
modifiers: pressed.map { |k| MODIFIERS[k] }.reduce(0, :|)
|
137
|
+
modifiers: pressed.map { |k| MODIFIERS[k] }.reduce(0, :|)
|
99
138
|
)
|
100
139
|
|
101
140
|
modifiers = pressed.map { |k| to_options(KEYS.fetch(KEYS_MAPPING[k.to_sym])) }
|
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
|
data/lib/ferrum/mouse.rb
CHANGED
@@ -10,10 +10,50 @@ module Ferrum
|
|
10
10
|
@x = @y = 0
|
11
11
|
end
|
12
12
|
|
13
|
+
#
|
14
|
+
# Scroll page to a given x, y coordinates.
|
15
|
+
#
|
16
|
+
# @param [Integer] top
|
17
|
+
# The pixel along the horizontal axis of the document that you want
|
18
|
+
# displayed in the upper left.
|
19
|
+
#
|
20
|
+
# @param [Integer] left
|
21
|
+
# The pixel along the vertical axis of the document that you want
|
22
|
+
# displayed in the upper left.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# browser.go_to("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
|
26
|
+
# browser.mouse.scroll_to(0, 400)
|
27
|
+
#
|
13
28
|
def scroll_to(top, left)
|
14
29
|
tap { @page.execute("window.scrollTo(#{top}, #{left})") }
|
15
30
|
end
|
16
31
|
|
32
|
+
#
|
33
|
+
# Click given coordinates, fires mouse move, down and up events.
|
34
|
+
#
|
35
|
+
# @param [Integer] x
|
36
|
+
#
|
37
|
+
# @param [Integer] y
|
38
|
+
#
|
39
|
+
# @param [Float] delay
|
40
|
+
# Delay between mouse down and mouse up events.
|
41
|
+
#
|
42
|
+
# @param [Float] wait
|
43
|
+
#
|
44
|
+
# @param [Hash{Symbol => Object}] options
|
45
|
+
# Additional keyword arguments.
|
46
|
+
#
|
47
|
+
# @option options [:left, :right] :button (:left)
|
48
|
+
# The mouse button to click.
|
49
|
+
#
|
50
|
+
# @option options [Integer] :count (1)
|
51
|
+
#
|
52
|
+
# @option options [Integer] :modifiers
|
53
|
+
# Bitfield for key modifiers. See`keyboard.modifiers`.
|
54
|
+
#
|
55
|
+
# @return [self]
|
56
|
+
#
|
17
57
|
def click(x:, y:, delay: 0, wait: CLICK_WAIT, **options)
|
18
58
|
move(x: x, y: y)
|
19
59
|
down(**options)
|
@@ -24,21 +64,67 @@ module Ferrum
|
|
24
64
|
self
|
25
65
|
end
|
26
66
|
|
67
|
+
#
|
68
|
+
# Mouse down for given coordinates.
|
69
|
+
#
|
70
|
+
# @param [Hash{Symbol => Object}] options
|
71
|
+
# Additional keyword arguments.
|
72
|
+
#
|
73
|
+
# @option options [:left, :right] :button (:left)
|
74
|
+
# The mouse button to click.
|
75
|
+
#
|
76
|
+
# @option options [Integer] :count (1)
|
77
|
+
#
|
78
|
+
# @option options [Integer] :modifiers
|
79
|
+
# Bitfield for key modifiers. See`keyboard.modifiers`.
|
80
|
+
#
|
81
|
+
# @return [self]
|
82
|
+
#
|
27
83
|
def down(**options)
|
28
84
|
tap { mouse_event(type: "mousePressed", **options) }
|
29
85
|
end
|
30
86
|
|
87
|
+
#
|
88
|
+
# Mouse up for given coordinates.
|
89
|
+
#
|
90
|
+
# @param [Hash{Symbol => Object}] options
|
91
|
+
# Additional keyword arguments.
|
92
|
+
#
|
93
|
+
# @option options [:left, :right] :button (:left)
|
94
|
+
# The mouse button to click.
|
95
|
+
#
|
96
|
+
# @option options [Integer] :count (1)
|
97
|
+
#
|
98
|
+
# @option options [Integer] :modifiers
|
99
|
+
# Bitfield for key modifiers. See`keyboard.modifiers`.
|
100
|
+
#
|
101
|
+
# @return [self]
|
102
|
+
#
|
31
103
|
def up(**options)
|
32
104
|
tap { mouse_event(type: "mouseReleased", **options) }
|
33
105
|
end
|
34
106
|
|
107
|
+
#
|
108
|
+
# Mouse move to given x and y.
|
109
|
+
#
|
110
|
+
# @param [Integer] x
|
111
|
+
#
|
112
|
+
# @param [Integer] y
|
113
|
+
#
|
114
|
+
# @param [Integer] steps
|
115
|
+
# Sends intermediate mousemove events.
|
116
|
+
#
|
117
|
+
# @return [self]
|
118
|
+
#
|
35
119
|
def move(x:, y:, steps: 1)
|
36
|
-
from_x
|
37
|
-
|
120
|
+
from_x = @x
|
121
|
+
from_y = @y
|
122
|
+
@x = x
|
123
|
+
@y = y
|
38
124
|
|
39
125
|
steps.times do |i|
|
40
|
-
new_x = from_x + (@x - from_x) * ((i + 1) / steps.to_f)
|
41
|
-
new_y = from_y + (@y - from_y) * ((i + 1) / steps.to_f)
|
126
|
+
new_x = from_x + ((@x - from_x) * ((i + 1) / steps.to_f))
|
127
|
+
new_y = from_y + ((@y - from_y) * ((i + 1) / steps.to_f))
|
42
128
|
|
43
129
|
@page.command("Input.dispatchMouseEvent",
|
44
130
|
slowmoable: true,
|
@@ -61,9 +147,8 @@ module Ferrum
|
|
61
147
|
|
62
148
|
def validate_button(button)
|
63
149
|
button = button.to_s
|
64
|
-
unless VALID_BUTTONS.include?(button)
|
65
|
-
|
66
|
-
end
|
150
|
+
raise "Invalid button: #{button}" unless VALID_BUTTONS.include?(button)
|
151
|
+
|
67
152
|
button
|
68
153
|
end
|
69
154
|
end
|
@@ -6,7 +6,8 @@ module Ferrum
|
|
6
6
|
attr_accessor :request_id, :frame_id, :resource_type
|
7
7
|
|
8
8
|
def initialize(page, params)
|
9
|
-
@page
|
9
|
+
@page = page
|
10
|
+
@params = params
|
10
11
|
@request_id = params["requestId"]
|
11
12
|
@frame_id = params["frameId"]
|
12
13
|
@resource_type = params["resourceType"]
|
@@ -55,7 +56,11 @@ module Ferrum
|
|
55
56
|
end
|
56
57
|
|
57
58
|
def inspect
|
58
|
-
|
59
|
+
"#<#{self.class} " \
|
60
|
+
"@request_id=#{@request_id.inspect} " \
|
61
|
+
"@frame_id=#{@frame_id.inspect} " \
|
62
|
+
"@resource_type=#{@resource_type.inspect} " \
|
63
|
+
"@request=#{@request.inspect}>"
|
59
64
|
end
|
60
65
|
end
|
61
66
|
end
|
@@ -3,48 +3,133 @@
|
|
3
3
|
module Ferrum
|
4
4
|
class Network
|
5
5
|
class Exchange
|
6
|
+
# ID of the request.
|
7
|
+
#
|
8
|
+
# @return String
|
6
9
|
attr_reader :id
|
10
|
+
|
11
|
+
# The intercepted request.
|
12
|
+
#
|
13
|
+
# @return [InterceptedRequest, nil]
|
7
14
|
attr_accessor :intercepted_request
|
8
|
-
attr_accessor :request, :response, :error
|
9
15
|
|
16
|
+
# The request object.
|
17
|
+
#
|
18
|
+
# @return [Request, nil]
|
19
|
+
attr_accessor :request
|
20
|
+
|
21
|
+
# The response object.
|
22
|
+
#
|
23
|
+
# @return [Response, nil]
|
24
|
+
attr_accessor :response
|
25
|
+
|
26
|
+
# The error object.
|
27
|
+
#
|
28
|
+
# @return [Error, nil]
|
29
|
+
attr_accessor :error
|
30
|
+
|
31
|
+
#
|
32
|
+
# Initializes the network exchange.
|
33
|
+
#
|
34
|
+
# @param [Page] page
|
35
|
+
#
|
36
|
+
# @param [String] id
|
37
|
+
#
|
10
38
|
def initialize(page, id)
|
11
|
-
@
|
39
|
+
@id = id
|
40
|
+
@page = page
|
12
41
|
@intercepted_request = nil
|
13
42
|
@request = @response = @error = nil
|
14
43
|
end
|
15
44
|
|
45
|
+
#
|
46
|
+
# Determines if the network exchange was caused by a page navigation
|
47
|
+
# event.
|
48
|
+
#
|
49
|
+
# @param [String] frame_id
|
50
|
+
#
|
51
|
+
# @return [Boolean]
|
52
|
+
#
|
16
53
|
def navigation_request?(frame_id)
|
17
|
-
request.type?(:document) &&
|
18
|
-
request.frame_id == frame_id
|
54
|
+
request.type?(:document) && request&.frame_id == frame_id
|
19
55
|
end
|
20
56
|
|
57
|
+
#
|
58
|
+
# Determines if the network exchange has a request.
|
59
|
+
#
|
60
|
+
# @return [Boolean]
|
61
|
+
#
|
21
62
|
def blank?
|
22
63
|
!request
|
23
64
|
end
|
24
65
|
|
66
|
+
#
|
67
|
+
# Determines if the request was intercepted and blocked.
|
68
|
+
#
|
69
|
+
# @return [Boolean]
|
70
|
+
#
|
25
71
|
def blocked?
|
26
|
-
|
72
|
+
intercepted? && intercepted_request.status?(:aborted)
|
27
73
|
end
|
28
74
|
|
75
|
+
#
|
76
|
+
# Determines if the request was blocked, a response was returned, or if an
|
77
|
+
# error occurred.
|
78
|
+
#
|
79
|
+
# @return [Boolean]
|
80
|
+
#
|
29
81
|
def finished?
|
30
|
-
blocked? || response || error
|
82
|
+
blocked? || !response.nil? || !error.nil?
|
31
83
|
end
|
32
84
|
|
85
|
+
#
|
86
|
+
# Determines if the network exchange is still not finished.
|
87
|
+
#
|
88
|
+
# @return [Boolean]
|
89
|
+
#
|
33
90
|
def pending?
|
34
91
|
!finished?
|
35
92
|
end
|
36
93
|
|
94
|
+
#
|
95
|
+
# Determines if the exchange's request was intercepted.
|
96
|
+
#
|
97
|
+
# @return [Boolean]
|
98
|
+
#
|
99
|
+
def intercepted?
|
100
|
+
!intercepted_request.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Returns request's URL.
|
105
|
+
#
|
106
|
+
# @return [String, nil]
|
107
|
+
#
|
108
|
+
def url
|
109
|
+
request&.url
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Converts the network exchange into a request, response, and error tuple.
|
114
|
+
#
|
115
|
+
# @return [Array]
|
116
|
+
#
|
37
117
|
def to_a
|
38
118
|
[request, response, error]
|
39
119
|
end
|
40
120
|
|
121
|
+
#
|
122
|
+
# Inspects the network exchange.
|
123
|
+
#
|
124
|
+
# @return [String]
|
125
|
+
#
|
41
126
|
def inspect
|
42
|
-
"#<#{self.class} "\
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
127
|
+
"#<#{self.class} " \
|
128
|
+
"@id=#{@id.inspect} " \
|
129
|
+
"@intercepted_request=#{@intercepted_request.inspect} " \
|
130
|
+
"@request=#{@request.inspect} " \
|
131
|
+
"@response=#{@response.inspect} " \
|
132
|
+
"@error=#{@error.inspect}>"
|
48
133
|
end
|
49
134
|
end
|
50
135
|
end
|