ferrum 0.12 → 0.14
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 +28 -22
- data/lib/ferrum/browser/client.rb +6 -5
- data/lib/ferrum/browser/command.rb +9 -6
- data/lib/ferrum/browser/options/base.rb +1 -4
- data/lib/ferrum/browser/options/chrome.rb +22 -10
- data/lib/ferrum/browser/options/firefox.rb +3 -6
- data/lib/ferrum/browser/options.rb +84 -0
- data/lib/ferrum/browser/process.rb +6 -7
- data/lib/ferrum/browser/version_info.rb +71 -0
- data/lib/ferrum/browser/web_socket.rb +1 -1
- data/lib/ferrum/browser/xvfb.rb +1 -1
- data/lib/ferrum/browser.rb +184 -64
- data/lib/ferrum/context.rb +3 -2
- data/lib/ferrum/contexts.rb +2 -2
- data/lib/ferrum/cookies/cookie.rb +183 -0
- data/lib/ferrum/cookies.rb +122 -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 +91 -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 +104 -5
- data/lib/ferrum/network/intercepted_request.rb +3 -12
- data/lib/ferrum/network/request.rb +58 -19
- data/lib/ferrum/network/request_params.rb +57 -0
- data/lib/ferrum/network/response.rb +106 -4
- data/lib/ferrum/network.rb +193 -8
- data/lib/ferrum/node.rb +21 -1
- data/lib/ferrum/page/animation.rb +16 -0
- data/lib/ferrum/page/frames.rb +66 -11
- data/lib/ferrum/page/screenshot.rb +97 -0
- data/lib/ferrum/page/tracing.rb +26 -0
- data/lib/ferrum/page.rb +158 -45
- data/lib/ferrum/proxy.rb +91 -2
- data/lib/ferrum/target.rb +6 -4
- data/lib/ferrum/version.rb +1 -1
- metadata +7 -101
data/lib/ferrum/page/frames.rb
CHANGED
@@ -5,12 +5,54 @@ require "ferrum/frame"
|
|
5
5
|
module Ferrum
|
6
6
|
class Page
|
7
7
|
module Frames
|
8
|
+
# The page's main frame, the top of the tree and the parent of all frames.
|
9
|
+
#
|
10
|
+
# @return [Frame]
|
8
11
|
attr_reader :main_frame
|
9
12
|
|
13
|
+
#
|
14
|
+
# Returns all the frames current page have.
|
15
|
+
#
|
16
|
+
# @return [Array<Frame>]
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# browser.go_to("https://www.w3schools.com/tags/tag_frame.asp")
|
20
|
+
# browser.frames # =>
|
21
|
+
# # [
|
22
|
+
# # #<Ferrum::Frame
|
23
|
+
# # @id="C6D104CE454A025FBCF22B98DE612B12"
|
24
|
+
# # @parent_id=nil @name=nil @state=:stopped_loading @execution_id=1>,
|
25
|
+
# # #<Ferrum::Frame
|
26
|
+
# # @id="C09C4E4404314AAEAE85928EAC109A93"
|
27
|
+
# # @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=2>,
|
28
|
+
# # #<Ferrum::Frame
|
29
|
+
# # @id="2E9C7F476ED09D87A42F2FEE3C6FBC3C"
|
30
|
+
# # @parent_id="C6D104CE454A025FBCF22B98DE612B12" @state=:stopped_loading @execution_id=3>,
|
31
|
+
# # ...
|
32
|
+
# # ]
|
33
|
+
#
|
10
34
|
def frames
|
11
35
|
@frames.values
|
12
36
|
end
|
13
37
|
|
38
|
+
#
|
39
|
+
# Find frame by given options.
|
40
|
+
#
|
41
|
+
# @param [String] id
|
42
|
+
# Unique frame's id that browser provides.
|
43
|
+
#
|
44
|
+
# @param [String] name
|
45
|
+
# Frame's name if there's one.
|
46
|
+
#
|
47
|
+
# @param [String] execution_id
|
48
|
+
# Frame's context execution id.
|
49
|
+
#
|
50
|
+
# @return [Frame, nil]
|
51
|
+
# The matching frame.
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# browser.frame_by(id: "C6D104CE454A025FBCF22B98DE612B12")
|
55
|
+
#
|
14
56
|
def frame_by(id: nil, name: nil, execution_id: nil)
|
15
57
|
if id
|
16
58
|
@frames[id]
|
@@ -25,6 +67,7 @@ module Ferrum
|
|
25
67
|
|
26
68
|
def frames_subscribe
|
27
69
|
subscribe_frame_attached
|
70
|
+
subscribe_frame_detached
|
28
71
|
subscribe_frame_started_loading
|
29
72
|
subscribe_frame_navigated
|
30
73
|
subscribe_frame_stopped_loading
|
@@ -43,14 +86,26 @@ module Ferrum
|
|
43
86
|
def subscribe_frame_attached
|
44
87
|
on("Page.frameAttached") do |params|
|
45
88
|
parent_frame_id, frame_id = params.values_at("parentFrameId", "frameId")
|
46
|
-
@frames
|
89
|
+
@frames.put_if_absent(frame_id, Frame.new(frame_id, self, parent_frame_id))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def subscribe_frame_detached
|
94
|
+
on("Page.frameDetached") do |params|
|
95
|
+
frame = @frames[params["frameId"]]
|
96
|
+
|
97
|
+
if frame&.main?
|
98
|
+
frame.execution_id = nil
|
99
|
+
else
|
100
|
+
@frames.delete(params["frameId"])
|
101
|
+
end
|
47
102
|
end
|
48
103
|
end
|
49
104
|
|
50
105
|
def subscribe_frame_started_loading
|
51
106
|
on("Page.frameStartedLoading") do |params|
|
52
107
|
frame = @frames[params["frameId"]]
|
53
|
-
frame.state = :started_loading
|
108
|
+
frame.state = :started_loading if frame
|
54
109
|
@event.reset
|
55
110
|
end
|
56
111
|
end
|
@@ -59,8 +114,11 @@ module Ferrum
|
|
59
114
|
on("Page.frameNavigated") do |params|
|
60
115
|
frame_id, name = params["frame"]&.values_at("id", "name")
|
61
116
|
frame = @frames[frame_id]
|
62
|
-
|
63
|
-
frame
|
117
|
+
|
118
|
+
if frame
|
119
|
+
frame.state = :navigated
|
120
|
+
frame.name = name
|
121
|
+
end
|
64
122
|
end
|
65
123
|
end
|
66
124
|
|
@@ -107,14 +165,12 @@ module Ferrum
|
|
107
165
|
root_frame = command("Page.getFrameTree").dig("frameTree", "frame", "id")
|
108
166
|
if frame_id == root_frame
|
109
167
|
@main_frame.id = frame_id
|
110
|
-
@frames
|
168
|
+
@frames.put_if_absent(frame_id, @main_frame)
|
111
169
|
end
|
112
170
|
end
|
113
171
|
|
114
|
-
frame = @frames
|
172
|
+
frame = @frames.fetch_or_store(frame_id, Frame.new(frame_id, self))
|
115
173
|
frame.execution_id = context_id
|
116
|
-
|
117
|
-
@frames[frame_id] ||= frame
|
118
174
|
end
|
119
175
|
end
|
120
176
|
|
@@ -128,13 +184,12 @@ module Ferrum
|
|
128
184
|
|
129
185
|
def subscribe_execution_contexts_cleared
|
130
186
|
on("Runtime.executionContextsCleared") do
|
131
|
-
@frames.
|
132
|
-
@main_frame.execution_id = nil
|
187
|
+
@frames.each_value { |f| f.execution_id = nil }
|
133
188
|
end
|
134
189
|
end
|
135
190
|
|
136
191
|
def idling?
|
137
|
-
@frames.all? { |
|
192
|
+
@frames.values.all? { |f| f.state == :stopped_loading }
|
138
193
|
end
|
139
194
|
end
|
140
195
|
end
|
@@ -26,6 +26,51 @@ module Ferrum
|
|
26
26
|
A6: { width: 4.13, height: 5.83 }
|
27
27
|
}.freeze
|
28
28
|
|
29
|
+
#
|
30
|
+
# Saves screenshot on a disk or returns it as base64.
|
31
|
+
#
|
32
|
+
# @param [Hash{Symbol => Object}] opts
|
33
|
+
#
|
34
|
+
# @option opts [String] :path
|
35
|
+
# The path to save a screenshot on the disk. `:encoding` will be set to
|
36
|
+
# `:binary` automatically.
|
37
|
+
#
|
38
|
+
# @option opts [:base64, :binary] :encoding
|
39
|
+
# The encoding the image should be returned in.
|
40
|
+
#
|
41
|
+
# @option opts ["jpeg", "png"] :format
|
42
|
+
# The format the image should be returned in.
|
43
|
+
#
|
44
|
+
# @option opts [Integer] :quality
|
45
|
+
# The image quality. **Note:** 0-100 works for jpeg only.
|
46
|
+
#
|
47
|
+
# @option opts [Boolean] :full
|
48
|
+
# Whether you need full page screenshot or a viewport.
|
49
|
+
#
|
50
|
+
# @option opts [String] :selector
|
51
|
+
# CSS selector for the given element.
|
52
|
+
#
|
53
|
+
# @option opts [Float] :scale
|
54
|
+
# Zoom in/out.
|
55
|
+
#
|
56
|
+
# @option opts [Ferrum::RGBA] :background_color
|
57
|
+
# Sets the background color.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# browser.go_to("https://google.com/")
|
61
|
+
#
|
62
|
+
# @example Save on the disk in PNG:
|
63
|
+
# browser.screenshot(path: "google.png") # => 134660
|
64
|
+
#
|
65
|
+
# @example Save on the disk in JPG:
|
66
|
+
# browser.screenshot(path: "google.jpg") # => 30902
|
67
|
+
#
|
68
|
+
# @example Save to Base64 the whole page not only viewport and reduce quality:
|
69
|
+
# browser.screenshot(full: true, quality: 60) # "iVBORw0KGgoAAAANS...
|
70
|
+
#
|
71
|
+
# @example Save with specific background color:
|
72
|
+
# browser.screenshot(background_color: Ferrum::RGBA.new(0, 0, 0, 0.0))
|
73
|
+
#
|
29
74
|
def screenshot(**opts)
|
30
75
|
path, encoding = common_options(**opts)
|
31
76
|
options = screenshot_options(path, **opts)
|
@@ -36,6 +81,42 @@ module Ferrum
|
|
36
81
|
save_file(path, bin)
|
37
82
|
end
|
38
83
|
|
84
|
+
#
|
85
|
+
# Saves PDF on a disk or returns it as Base64.
|
86
|
+
#
|
87
|
+
# @param [Hash{Symbol => Object}] opts
|
88
|
+
#
|
89
|
+
# @option opts [String] :path
|
90
|
+
# The path to save a screenshot on the disk. `:encoding` will be set to
|
91
|
+
# `:binary` automatically.
|
92
|
+
#
|
93
|
+
# @option opts [:base64, :binary] :encoding
|
94
|
+
# The encoding the image should be returned in.
|
95
|
+
#
|
96
|
+
# @option opts [Boolean] :landscape (false)
|
97
|
+
# Page orientation.
|
98
|
+
#
|
99
|
+
# @option opts [Float] :scale
|
100
|
+
# Zoom in/out.
|
101
|
+
#
|
102
|
+
# @option opts [:letter, :legal, :tabloid, :ledger, :A0, :A1, :A2, :A3, :A4, :A5, :A6] :format
|
103
|
+
# The standard paper size.
|
104
|
+
#
|
105
|
+
# @option opts [Float] :paper_width
|
106
|
+
# Sets the paper's width.
|
107
|
+
#
|
108
|
+
# @option opts [Float] :paper_height
|
109
|
+
# Sets the paper's height.
|
110
|
+
#
|
111
|
+
# @note
|
112
|
+
# See other [native options](https://chromedevtools.github.io/devtools-protocol/tot/Page#method-printToPDF) you
|
113
|
+
# can pass.
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# browser.go_to("https://google.com/")
|
117
|
+
# # Save to disk as a PDF
|
118
|
+
# browser.pdf(path: "google.pdf", paper_width: 1.0, paper_height: 1.0) # => true
|
119
|
+
#
|
39
120
|
def pdf(**opts)
|
40
121
|
path, encoding = common_options(**opts)
|
41
122
|
options = pdf_options(**opts).merge(transferMode: "ReturnAsStream")
|
@@ -43,6 +124,16 @@ module Ferrum
|
|
43
124
|
stream_to(path: path, encoding: encoding, handle: handle)
|
44
125
|
end
|
45
126
|
|
127
|
+
#
|
128
|
+
# Saves MHTML on a disk or returns it as a string.
|
129
|
+
#
|
130
|
+
# @param [String, nil] path
|
131
|
+
# The path to save a file on the disk.
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# browser.go_to("https://google.com/")
|
135
|
+
# browser.mhtml(path: "google.mhtml") # => 87742
|
136
|
+
#
|
46
137
|
def mhtml(path: nil)
|
47
138
|
data = command("Page.captureSnapshot", format: :mhtml).fetch("data")
|
48
139
|
return data if path.nil?
|
@@ -56,6 +147,12 @@ module Ferrum
|
|
56
147
|
JS
|
57
148
|
end
|
58
149
|
|
150
|
+
def device_pixel_ratio
|
151
|
+
evaluate <<~JS
|
152
|
+
window.devicePixelRatio
|
153
|
+
JS
|
154
|
+
end
|
155
|
+
|
59
156
|
def document_size
|
60
157
|
evaluate <<~JS
|
61
158
|
[document.documentElement.scrollWidth,
|
data/lib/ferrum/page/tracing.rb
CHANGED
@@ -19,6 +19,32 @@ module Ferrum
|
|
19
19
|
@subscribed_tracing_complete = false
|
20
20
|
end
|
21
21
|
|
22
|
+
#
|
23
|
+
# Accepts block, records trace and by default returns trace data from `Tracing.tracingComplete` event as output.
|
24
|
+
#
|
25
|
+
# @param [String, nil] path
|
26
|
+
# Save data on the disk.
|
27
|
+
#
|
28
|
+
# @param [:binary, :base64] encoding
|
29
|
+
# Encode output as Base64 or plain text.
|
30
|
+
#
|
31
|
+
# @param [Float, nil] timeout
|
32
|
+
# Wait until file streaming finishes in the specified time or raise
|
33
|
+
# error.
|
34
|
+
#
|
35
|
+
# @param [Boolean] screenshots
|
36
|
+
# capture screenshots in the trace.
|
37
|
+
#
|
38
|
+
# @param [Hash{String => Object}] trace_config
|
39
|
+
# config for [trace](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#type-TraceConfig),
|
40
|
+
# for categories see [getCategories](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#method-getCategories),
|
41
|
+
# only one trace config can be active at a time per browser.
|
42
|
+
#
|
43
|
+
# @return [String, true]
|
44
|
+
# The trace data from the `Tracing.tracingComplete` event.
|
45
|
+
# When `path` is specified returns `true` and stores trace data into
|
46
|
+
# file.
|
47
|
+
#
|
22
48
|
def record(path: nil, encoding: :binary, timeout: nil, trace_config: nil, screenshots: false)
|
23
49
|
@path = path
|
24
50
|
@encoding = encoding
|
data/lib/ferrum/page.rb
CHANGED
@@ -35,7 +35,7 @@ module Ferrum
|
|
35
35
|
extend Forwardable
|
36
36
|
delegate %i[at_css at_xpath css xpath
|
37
37
|
current_url current_title url title body doctype content=
|
38
|
-
execution_id evaluate evaluate_on evaluate_async execute evaluate_func
|
38
|
+
execution_id execution_id! evaluate evaluate_on evaluate_async execute evaluate_func
|
39
39
|
add_script_tag add_style_tag] => :main_frame
|
40
40
|
|
41
41
|
include Animation
|
@@ -43,23 +43,47 @@ module Ferrum
|
|
43
43
|
include Frames
|
44
44
|
include Stream
|
45
45
|
|
46
|
-
attr_accessor :referrer
|
47
|
-
attr_reader :target_id, :browser,
|
48
|
-
:headers, :cookies, :network,
|
49
|
-
:mouse, :keyboard, :event,
|
50
|
-
:tracing
|
46
|
+
attr_accessor :referrer, :timeout
|
47
|
+
attr_reader :target_id, :browser, :event, :tracing
|
51
48
|
|
52
|
-
|
53
|
-
|
49
|
+
# Mouse object.
|
50
|
+
#
|
51
|
+
# @return [Mouse]
|
52
|
+
attr_reader :mouse
|
53
|
+
|
54
|
+
# Keyboard object.
|
55
|
+
#
|
56
|
+
# @return [Keyboard]
|
57
|
+
attr_reader :keyboard
|
58
|
+
|
59
|
+
# Network object.
|
60
|
+
#
|
61
|
+
# @return [Network]
|
62
|
+
attr_reader :network
|
63
|
+
|
64
|
+
# Headers object.
|
65
|
+
#
|
66
|
+
# @return [Headers]
|
67
|
+
attr_reader :headers
|
68
|
+
|
69
|
+
# Cookie store.
|
70
|
+
#
|
71
|
+
# @return [Cookies]
|
72
|
+
attr_reader :cookies
|
73
|
+
|
74
|
+
def initialize(target_id, browser, proxy: nil)
|
75
|
+
@frames = Concurrent::Map.new
|
54
76
|
@main_frame = Frame.new(nil, self)
|
55
77
|
@browser = browser
|
56
78
|
@target_id = target_id
|
79
|
+
@timeout = @browser.timeout
|
57
80
|
@event = Event.new.tap(&:set)
|
81
|
+
self.proxy = proxy
|
58
82
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
83
|
+
@client = Browser::Client.new(ws_url, self,
|
84
|
+
logger: @browser.options.logger,
|
85
|
+
ws_max_receive_size: @browser.options.ws_max_receive_size,
|
86
|
+
id_starts_with: 1000)
|
63
87
|
|
64
88
|
@mouse = Mouse.new(self)
|
65
89
|
@keyboard = Keyboard.new(self)
|
@@ -72,30 +96,31 @@ module Ferrum
|
|
72
96
|
prepare_page
|
73
97
|
end
|
74
98
|
|
75
|
-
def timeout
|
76
|
-
@browser.timeout
|
77
|
-
end
|
78
|
-
|
79
99
|
def context
|
80
100
|
@browser.contexts.find_by(target_id: target_id)
|
81
101
|
end
|
82
102
|
|
103
|
+
#
|
104
|
+
# Navigates the page to a URL.
|
105
|
+
#
|
106
|
+
# @param [String, nil] url
|
107
|
+
# The URL to navigate to. The url should include scheme unless you set
|
108
|
+
# `{Browser#base_url = url}` when configuring driver.
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# browser.go_to("https://github.com/")
|
112
|
+
#
|
83
113
|
def go_to(url = nil)
|
84
114
|
options = { url: combine_url!(url) }
|
85
115
|
options.merge!(referrer: referrer) if referrer
|
86
116
|
response = command("Page.navigate", wait: GOTO_WAIT, **options)
|
87
|
-
|
88
|
-
if
|
89
|
-
net::ERR_NAME_RESOLUTION_FAILED
|
90
|
-
net::ERR_INTERNET_DISCONNECTED
|
91
|
-
net::ERR_CONNECTION_TIMED_OUT].include?(response["errorText"])
|
92
|
-
raise StatusError, options[:url]
|
93
|
-
end
|
117
|
+
error_text = response["errorText"]
|
118
|
+
raise StatusError.new(options[:url], "Request to #{options[:url]} failed (#{error_text})") if error_text
|
94
119
|
|
95
120
|
response["frameId"]
|
96
121
|
rescue TimeoutError
|
97
|
-
if @browser.pending_connection_errors
|
98
|
-
pendings = network.traffic.select(&:pending?).map
|
122
|
+
if @browser.options.pending_connection_errors
|
123
|
+
pendings = network.traffic.select(&:pending?).map(&:url).compact
|
99
124
|
raise PendingConnectionsError.new(options[:url], pendings) unless pendings.empty?
|
100
125
|
end
|
101
126
|
end
|
@@ -120,34 +145,87 @@ module Ferrum
|
|
120
145
|
command("Emulation.setDeviceMetricsOverride", slowmoable: true,
|
121
146
|
width: width,
|
122
147
|
height: height,
|
123
|
-
deviceScaleFactor:
|
124
|
-
mobile: false
|
125
|
-
fitWindow: false)
|
148
|
+
deviceScaleFactor: 0,
|
149
|
+
mobile: false)
|
126
150
|
end
|
127
151
|
|
152
|
+
#
|
153
|
+
# The current position of the browser window.
|
154
|
+
#
|
155
|
+
# @return [(Integer, Integer)]
|
156
|
+
# The left, top coordinates of the browser window.
|
157
|
+
#
|
158
|
+
# @example
|
159
|
+
# browser.position # => [10, 20]
|
160
|
+
#
|
128
161
|
def position
|
129
162
|
@browser.command("Browser.getWindowBounds", windowId: window_id).fetch("bounds").values_at("left", "top")
|
130
163
|
end
|
131
164
|
|
165
|
+
#
|
166
|
+
# Sets the position of the browser window.
|
167
|
+
#
|
168
|
+
# @param [Hash{Symbol => Object}] options
|
169
|
+
#
|
170
|
+
# @option options [Integer] :left
|
171
|
+
# The number of pixels from the left-hand side of the screen.
|
172
|
+
#
|
173
|
+
# @option options [Integer] :top
|
174
|
+
# The number of pixels from the top of the screen.
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# browser.position = { left: 10, top: 20 }
|
178
|
+
#
|
132
179
|
def position=(options)
|
133
180
|
@browser.command("Browser.setWindowBounds",
|
134
181
|
windowId: window_id,
|
135
182
|
bounds: { left: options[:left], top: options[:top] })
|
136
183
|
end
|
137
184
|
|
185
|
+
#
|
186
|
+
# Reloads the current page.
|
187
|
+
#
|
188
|
+
# @example
|
189
|
+
# browser.go_to("https://github.com/")
|
190
|
+
# browser.refresh
|
191
|
+
#
|
138
192
|
def refresh
|
139
193
|
command("Page.reload", wait: timeout, slowmoable: true)
|
140
194
|
end
|
141
195
|
alias reload refresh
|
142
196
|
|
197
|
+
#
|
198
|
+
# Stop all navigations and loading pending resources on the page.
|
199
|
+
#
|
200
|
+
# @example
|
201
|
+
# browser.go_to("https://github.com/")
|
202
|
+
# browser.stop
|
203
|
+
#
|
143
204
|
def stop
|
144
205
|
command("Page.stopLoading", slowmoable: true)
|
145
206
|
end
|
146
207
|
|
208
|
+
#
|
209
|
+
# Navigates to the previous URL in the browser's history.
|
210
|
+
#
|
211
|
+
# @example
|
212
|
+
# browser.go_to("https://github.com/")
|
213
|
+
# browser.at_xpath("//a").click
|
214
|
+
# browser.back
|
215
|
+
#
|
147
216
|
def back
|
148
217
|
history_navigate(delta: -1)
|
149
218
|
end
|
150
219
|
|
220
|
+
#
|
221
|
+
# Navigates to the next URL in the browser's history.
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
# browser.go_to("https://github.com/")
|
225
|
+
# browser.at_xpath("//a").click
|
226
|
+
# browser.back
|
227
|
+
# browser.forward
|
228
|
+
#
|
151
229
|
def forward
|
152
230
|
history_navigate(delta: 1)
|
153
231
|
end
|
@@ -158,6 +236,20 @@ module Ferrum
|
|
158
236
|
@event.set
|
159
237
|
end
|
160
238
|
|
239
|
+
#
|
240
|
+
# Enables/disables CSP bypass.
|
241
|
+
#
|
242
|
+
# @param [Boolean] enabled
|
243
|
+
#
|
244
|
+
# @return [Boolean]
|
245
|
+
#
|
246
|
+
# @example
|
247
|
+
# browser.bypass_csp # => true
|
248
|
+
# browser.go_to("https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md")
|
249
|
+
# browser.refresh
|
250
|
+
# browser.add_script_tag(content: "window.__injected = 42")
|
251
|
+
# browser.evaluate("window.__injected") # => 42
|
252
|
+
#
|
161
253
|
def bypass_csp(enabled: true)
|
162
254
|
command("Page.setBypassCSP", enabled: enabled)
|
163
255
|
enabled
|
@@ -173,16 +265,16 @@ module Ferrum
|
|
173
265
|
|
174
266
|
def command(method, wait: 0, slowmoable: false, **params)
|
175
267
|
iteration = @event.reset if wait.positive?
|
176
|
-
sleep(@browser.slowmo) if slowmoable && @browser.slowmo.positive?
|
268
|
+
sleep(@browser.options.slowmo) if slowmoable && @browser.options.slowmo.positive?
|
177
269
|
result = @client.command(method, params)
|
178
270
|
|
179
271
|
if wait.positive?
|
180
|
-
@event.wait(wait)
|
181
272
|
# Wait a bit after command and check if iteration has
|
182
273
|
# changed which means there was some network event for
|
183
274
|
# the main frame and it started to load new content.
|
275
|
+
@event.wait(wait)
|
184
276
|
if iteration != @event.iteration
|
185
|
-
set = @event.wait(
|
277
|
+
set = @event.wait(timeout)
|
186
278
|
raise TimeoutError unless set
|
187
279
|
end
|
188
280
|
end
|
@@ -218,19 +310,31 @@ module Ferrum
|
|
218
310
|
@client.subscribed?(event)
|
219
311
|
end
|
220
312
|
|
313
|
+
def use_proxy?
|
314
|
+
@proxy_host && @proxy_port
|
315
|
+
end
|
316
|
+
|
317
|
+
def use_authorized_proxy?
|
318
|
+
use_proxy? && @proxy_user && @proxy_password
|
319
|
+
end
|
320
|
+
|
321
|
+
def document_node_id
|
322
|
+
command("DOM.getDocument", depth: 0).dig("root", "nodeId")
|
323
|
+
end
|
324
|
+
|
221
325
|
private
|
222
326
|
|
223
327
|
def subscribe
|
224
328
|
frames_subscribe
|
225
329
|
network.subscribe
|
226
330
|
|
227
|
-
if @browser.logger
|
331
|
+
if @browser.options.logger
|
228
332
|
on("Runtime.consoleAPICalled") do |params|
|
229
|
-
params["args"].each { |r| @browser.logger.puts(r["value"]) }
|
333
|
+
params["args"].each { |r| @browser.options.logger.puts(r["value"]) }
|
230
334
|
end
|
231
335
|
end
|
232
336
|
|
233
|
-
if @browser.js_errors
|
337
|
+
if @browser.options.js_errors
|
234
338
|
on("Runtime.exceptionThrown") do |params|
|
235
339
|
# FIXME: https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
|
236
340
|
Thread.main.raise JavaScriptError.new(
|
@@ -243,7 +347,7 @@ module Ferrum
|
|
243
347
|
on(:dialog) do |dialog, _index, total|
|
244
348
|
if total == 1
|
245
349
|
warn "Dialog was shown but you didn't provide `on(:dialog)` callback, accepting it by default. " \
|
246
|
-
"Please take a look at https://github.com/rubycdp/ferrum#
|
350
|
+
"Please take a look at https://github.com/rubycdp/ferrum#dialogs"
|
247
351
|
dialog.accept
|
248
352
|
end
|
249
353
|
end
|
@@ -257,21 +361,22 @@ module Ferrum
|
|
257
361
|
command("Log.enable")
|
258
362
|
command("Network.enable")
|
259
363
|
|
260
|
-
if
|
261
|
-
|
262
|
-
|
364
|
+
if use_authorized_proxy?
|
365
|
+
network.authorize(user: @proxy_user,
|
366
|
+
password: @proxy_password,
|
367
|
+
type: :proxy) do |request, _index, _total|
|
263
368
|
request.continue
|
264
369
|
end
|
265
370
|
end
|
266
371
|
|
267
|
-
if @browser.options
|
268
|
-
unless Pathname.new(@browser.options
|
372
|
+
if @browser.options.save_path
|
373
|
+
unless Pathname.new(@browser.options.save_path).absolute?
|
269
374
|
raise Error, "supply absolute path for `:save_path` option"
|
270
375
|
end
|
271
376
|
|
272
377
|
@browser.command("Browser.setDownloadBehavior",
|
273
378
|
browserContextId: context.id,
|
274
|
-
downloadPath: browser.options
|
379
|
+
downloadPath: @browser.options.save_path,
|
275
380
|
behavior: "allow", eventsEnabled: true)
|
276
381
|
end
|
277
382
|
|
@@ -285,7 +390,8 @@ module Ferrum
|
|
285
390
|
resize(width: width, height: height)
|
286
391
|
|
287
392
|
response = command("Page.getNavigationHistory")
|
288
|
-
|
393
|
+
transition_type = response.dig("entries", 0, "transitionType")
|
394
|
+
return if transition_type == "auto_toplevel"
|
289
395
|
|
290
396
|
# If we create page by clicking links, submitting forms and so on it
|
291
397
|
# opens a new window for which `frameStoppedLoading` event never
|
@@ -303,7 +409,7 @@ module Ferrum
|
|
303
409
|
# We also evaluate script just in case because
|
304
410
|
# `Page.addScriptToEvaluateOnNewDocument` doesn't work in popups.
|
305
411
|
command("Runtime.evaluate", expression: extension,
|
306
|
-
|
412
|
+
executionContextId: execution_id!,
|
307
413
|
returnByValue: true)
|
308
414
|
end
|
309
415
|
end
|
@@ -333,8 +439,15 @@ module Ferrum
|
|
333
439
|
(nil_or_relative ? @browser.base_url.join(url.to_s) : url).to_s
|
334
440
|
end
|
335
441
|
|
336
|
-
def
|
337
|
-
|
442
|
+
def ws_url
|
443
|
+
"ws://#{@browser.process.host}:#{@browser.process.port}/devtools/page/#{@target_id}"
|
444
|
+
end
|
445
|
+
|
446
|
+
def proxy=(options)
|
447
|
+
@proxy_host = options&.[](:host) || @browser.options.proxy&.[](:host)
|
448
|
+
@proxy_port = options&.[](:port) || @browser.options.proxy&.[](:port)
|
449
|
+
@proxy_user = options&.[](:user) || @browser.options.proxy&.[](:user)
|
450
|
+
@proxy_password = options&.[](:password) || @browser.options.proxy&.[](:password)
|
338
451
|
end
|
339
452
|
end
|
340
453
|
end
|