puppeteer-ruby 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +71 -0
- data/.github/stale.yml +16 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +302 -0
- data/.travis.yml +7 -0
- data/Dockerfile +6 -0
- data/Gemfile +6 -0
- data/README.md +54 -0
- data/Rakefile +1 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +15 -0
- data/docs/Puppeteer.html +2020 -0
- data/docs/Puppeteer/AsyncAwaitBehavior.html +105 -0
- data/docs/Puppeteer/Browser.html +2148 -0
- data/docs/Puppeteer/BrowserContext.html +809 -0
- data/docs/Puppeteer/BrowserFetcher.html +214 -0
- data/docs/Puppeteer/BrowserRunner.html +914 -0
- data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +477 -0
- data/docs/Puppeteer/CDPSession.html +813 -0
- data/docs/Puppeteer/CDPSession/Error.html +124 -0
- data/docs/Puppeteer/ConcurrentRubyUtils.html +430 -0
- data/docs/Puppeteer/Connection.html +960 -0
- data/docs/Puppeteer/Connection/MessageCallback.html +434 -0
- data/docs/Puppeteer/Connection/ProtocolError.html +216 -0
- data/docs/Puppeteer/Connection/RequestDebugPrinter.html +217 -0
- data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +244 -0
- data/docs/Puppeteer/ConsoleMessage.html +565 -0
- data/docs/Puppeteer/ConsoleMessage/Location.html +433 -0
- data/docs/Puppeteer/DOMWorld.html +2219 -0
- data/docs/Puppeteer/DOMWorld/DetachedError.html +124 -0
- data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +124 -0
- data/docs/Puppeteer/DebugPrint.html +233 -0
- data/docs/Puppeteer/Device.html +470 -0
- data/docs/Puppeteer/Devices.html +139 -0
- data/docs/Puppeteer/ElementHandle.html +2542 -0
- data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +206 -0
- data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +206 -0
- data/docs/Puppeteer/ElementHandle/Point.html +492 -0
- data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +124 -0
- data/docs/Puppeteer/EmulationManager.html +454 -0
- data/docs/Puppeteer/EventCallbackable.html +433 -0
- data/docs/Puppeteer/EventCallbackable/EventListeners.html +435 -0
- data/docs/Puppeteer/ExecutionContext.html +998 -0
- data/docs/Puppeteer/ExecutionContext/EvaluationError.html +124 -0
- data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +357 -0
- data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +389 -0
- data/docs/Puppeteer/FileChooser.html +455 -0
- data/docs/Puppeteer/Frame.html +3677 -0
- data/docs/Puppeteer/FrameManager.html +2410 -0
- data/docs/Puppeteer/FrameManager/NavigationError.html +124 -0
- data/docs/Puppeteer/IfPresent.html +222 -0
- data/docs/Puppeteer/JSHandle.html +1352 -0
- data/docs/Puppeteer/Keyboard.html +1557 -0
- data/docs/Puppeteer/Keyboard/KeyDefinition.html +831 -0
- data/docs/Puppeteer/Keyboard/KeyDescription.html +603 -0
- data/docs/Puppeteer/Launcher.html +237 -0
- data/docs/Puppeteer/Launcher/Base.html +385 -0
- data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +124 -0
- data/docs/Puppeteer/Launcher/BrowserOptions.html +441 -0
- data/docs/Puppeteer/Launcher/Chrome.html +669 -0
- data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +382 -0
- data/docs/Puppeteer/Launcher/ChromeArgOptions.html +531 -0
- data/docs/Puppeteer/Launcher/LaunchOptions.html +893 -0
- data/docs/Puppeteer/LifecycleWatcher.html +834 -0
- data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +363 -0
- data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +206 -0
- data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +124 -0
- data/docs/Puppeteer/Mouse.html +1105 -0
- data/docs/Puppeteer/Mouse/Button.html +136 -0
- data/docs/Puppeteer/NetworkManager.html +901 -0
- data/docs/Puppeteer/NetworkManager/Credentials.html +385 -0
- data/docs/Puppeteer/Page.html +5970 -0
- data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
- data/docs/Puppeteer/Page/ScreenshotOptions.html +845 -0
- data/docs/Puppeteer/Page/ScriptTag.html +555 -0
- data/docs/Puppeteer/Page/StyleTag.html +448 -0
- data/docs/Puppeteer/Page/TargetCrashedError.html +124 -0
- data/docs/Puppeteer/RemoteObject.html +1087 -0
- data/docs/Puppeteer/Target.html +1336 -0
- data/docs/Puppeteer/Target/InitializeFailure.html +124 -0
- data/docs/Puppeteer/Target/TargetInfo.html +729 -0
- data/docs/Puppeteer/TimeoutError.html +135 -0
- data/docs/Puppeteer/TimeoutSettings.html +496 -0
- data/docs/Puppeteer/TouchScreen.html +464 -0
- data/docs/Puppeteer/Viewport.html +837 -0
- data/docs/Puppeteer/WaitTask.html +637 -0
- data/docs/Puppeteer/WaitTask/TerminatedError.html +124 -0
- data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
- data/docs/Puppeteer/WebSocket.html +673 -0
- data/docs/Puppeteer/WebSocket/DriverImpl.html +412 -0
- data/docs/Puppeteer/WebSocketTransport.html +600 -0
- data/docs/Puppeteer/WebSocktTransportError.html +124 -0
- data/docs/_index.html +823 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +496 -0
- data/docs/file.README.html +123 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +123 -0
- data/docs/js/app.js +314 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +4075 -0
- data/docs/top-level-namespace.html +126 -0
- data/lib/puppeteer.rb +200 -0
- data/lib/puppeteer/async_await_behavior.rb +38 -0
- data/lib/puppeteer/browser.rb +259 -0
- data/lib/puppeteer/browser_context.rb +90 -0
- data/lib/puppeteer/browser_fetcher.rb +6 -0
- data/lib/puppeteer/browser_runner.rb +161 -0
- data/lib/puppeteer/cdp_session.rb +100 -0
- data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
- data/lib/puppeteer/connection.rb +254 -0
- data/lib/puppeteer/console_message.rb +24 -0
- data/lib/puppeteer/debug_print.rb +20 -0
- data/lib/puppeteer/device.rb +12 -0
- data/lib/puppeteer/devices.rb +885 -0
- data/lib/puppeteer/dom_world.rb +484 -0
- data/lib/puppeteer/element_handle.rb +433 -0
- data/lib/puppeteer/element_handle/bounding_box.rb +12 -0
- data/lib/puppeteer/element_handle/box_model.rb +19 -0
- data/lib/puppeteer/element_handle/point.rb +26 -0
- data/lib/puppeteer/emulation_manager.rb +46 -0
- data/lib/puppeteer/errors.rb +2 -0
- data/lib/puppeteer/event_callbackable.rb +88 -0
- data/lib/puppeteer/execution_context.rb +254 -0
- data/lib/puppeteer/file_chooser.rb +29 -0
- data/lib/puppeteer/frame.rb +286 -0
- data/lib/puppeteer/frame_manager.rb +378 -0
- data/lib/puppeteer/if_present.rb +18 -0
- data/lib/puppeteer/js_handle.rb +142 -0
- data/lib/puppeteer/keyboard.rb +183 -0
- data/lib/puppeteer/keyboard/key_description.rb +19 -0
- data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
- data/lib/puppeteer/launcher.rb +25 -0
- data/lib/puppeteer/launcher/base.rb +48 -0
- data/lib/puppeteer/launcher/browser_options.rb +41 -0
- data/lib/puppeteer/launcher/chrome.rb +211 -0
- data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
- data/lib/puppeteer/launcher/launch_options.rb +68 -0
- data/lib/puppeteer/lifecycle_watcher.rb +171 -0
- data/lib/puppeteer/mouse.rb +123 -0
- data/lib/puppeteer/network_manager.rb +122 -0
- data/lib/puppeteer/page.rb +1065 -0
- data/lib/puppeteer/page/screenshot_options.rb +78 -0
- data/lib/puppeteer/remote_object.rb +143 -0
- data/lib/puppeteer/target.rb +150 -0
- data/lib/puppeteer/timeout_settings.rb +15 -0
- data/lib/puppeteer/touch_screen.rb +43 -0
- data/lib/puppeteer/version.rb +3 -0
- data/lib/puppeteer/viewport.rb +54 -0
- data/lib/puppeteer/wait_task.rb +188 -0
- data/lib/puppeteer/web_socket.rb +122 -0
- data/lib/puppeteer/web_socket_transport.rb +49 -0
- data/puppeteer-ruby.gemspec +32 -0
- metadata +355 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
# https://github.com/puppeteer/puppeteer/blob/master/lib/LifecycleWatcher.js
|
4
|
+
class Puppeteer::LifecycleWatcher
|
5
|
+
include Puppeteer::IfPresent
|
6
|
+
|
7
|
+
class ExpectedLifecycle
|
8
|
+
PUPPETEER_TO_PROTOCOL_LIFECYCLE = {
|
9
|
+
'load' => 'load',
|
10
|
+
'domcontentloaded' => 'DOMContentLoaded',
|
11
|
+
'networkidle0' => 'networkIdle',
|
12
|
+
'networkidle2' => 'networkAlmostIdle',
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(wait_until)
|
16
|
+
if wait_until.is_a?(Enumerable)
|
17
|
+
@wait_until = wait_until.map do |value|
|
18
|
+
unless PUPPETEER_TO_PROTOCOL_LIFECYCLE.has_key?(value.to_s)
|
19
|
+
raise ArgumentError.new("Unknown value for options.waitUntil: #{value}")
|
20
|
+
end
|
21
|
+
value.to_s
|
22
|
+
end
|
23
|
+
elsif wait_until.is_a?(String)
|
24
|
+
unless PUPPETEER_TO_PROTOCOL_LIFECYCLE.has_key?(wait_until)
|
25
|
+
raise ArgumentError.new("Unknown value for options.waitUntil: #{wait_until}")
|
26
|
+
end
|
27
|
+
@wait_until = [wait_until]
|
28
|
+
else
|
29
|
+
raise ArgumentError.new('wait_until should be a Array<String> or String')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private def expected_lifecycle
|
34
|
+
@expected_lifecycle ||= @wait_until.map do |value|
|
35
|
+
PUPPETEER_TO_PROTOCOL_LIFECYCLE[value]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Check if navigation lifecycle has experienced the expected_lifecycle events.
|
40
|
+
#
|
41
|
+
# @param frame [Puppeteer::Frame]
|
42
|
+
def completed?(frame)
|
43
|
+
if expected_lifecycle.any? { |event| !frame.lifecycle_events.include?(event) }
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
if frame.child_frames.any? { |child| !completed?(child) }
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class FrameDetachedError < StandardError
|
54
|
+
def initialize
|
55
|
+
super('Navigating frame was detached')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
class TerminatedError < StandardError; end
|
59
|
+
|
60
|
+
# * @param {!Puppeteer.FrameManager} frameManager
|
61
|
+
# * @param {!Puppeteer.Frame} frame
|
62
|
+
# * @param {string|!Array<string>} waitUntil
|
63
|
+
# * @param {number} timeout
|
64
|
+
def initialize(frame_manager, frame, wait_until, timeout)
|
65
|
+
@expected_lifecycle = ExpectedLifecycle.new(wait_until)
|
66
|
+
@frame_manager = frame_manager
|
67
|
+
@frame = frame
|
68
|
+
@initial_loader_id = frame.loader_id
|
69
|
+
@timeout = timeout
|
70
|
+
|
71
|
+
@listener_ids = {}
|
72
|
+
@listener_ids['client'] = @frame_manager.client.add_event_listener('Events.CDPSession.Disconnected') do
|
73
|
+
terminate(TerminatedError.new('Navigation failed because browser has disconnected!'))
|
74
|
+
end
|
75
|
+
@listener_ids['frame_manager'] = [
|
76
|
+
@frame_manager.add_event_listener('Events.FrameManager.LifecycleEvent') do |_|
|
77
|
+
check_lifecycle_complete
|
78
|
+
end,
|
79
|
+
@frame_manager.add_event_listener('Events.FrameManager.FrameNavigatedWithinDocument', &method(:navigated_within_document)),
|
80
|
+
@frame_manager.add_event_listener('Events.FrameManager.FrameDetached', &method(:handle_frame_detached)),
|
81
|
+
]
|
82
|
+
@listener_ids['network_manager'] = @frame_manager.network_manager.add_event_listener('Events.NetworkManager.Request', &method(:handle_request))
|
83
|
+
|
84
|
+
@same_document_navigation_promise = resolvable_future
|
85
|
+
@lifecycle_promise = resolvable_future
|
86
|
+
@new_document_navigation_promise = resolvable_future
|
87
|
+
@termination_promise = resolvable_future
|
88
|
+
check_lifecycle_complete
|
89
|
+
end
|
90
|
+
|
91
|
+
# @param [Puppeteer::Request] request
|
92
|
+
def handle_request(request)
|
93
|
+
return if request.frame != @frame || !request.navigation_request?
|
94
|
+
@navigation_request = request
|
95
|
+
end
|
96
|
+
|
97
|
+
# @param frame [Puppeteer::Frame]
|
98
|
+
def handle_frame_detached(frame)
|
99
|
+
if @frame == frame
|
100
|
+
@termination_promise.reject(FrameDetachedError.new)
|
101
|
+
return
|
102
|
+
end
|
103
|
+
check_lifecycle_complete
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Puppeteer::Response]
|
107
|
+
def navigation_response
|
108
|
+
if_present(@navigation_request) do |request|
|
109
|
+
request.response
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param error [TerminatedError]
|
114
|
+
private def terminate(error)
|
115
|
+
@termination_promise.reject(error)
|
116
|
+
end
|
117
|
+
|
118
|
+
attr_reader(
|
119
|
+
:same_document_navigation_promise,
|
120
|
+
:new_document_navigation_promise,
|
121
|
+
:lifecycle_promise,
|
122
|
+
)
|
123
|
+
|
124
|
+
def timeout_or_termination_promise
|
125
|
+
if @timeout > 0
|
126
|
+
future do
|
127
|
+
Timeout.timeout(@timeout / 1000.0) do
|
128
|
+
@termination_promise.value!
|
129
|
+
end
|
130
|
+
rescue Timeout::Error
|
131
|
+
raise Puppeteer::FrameManager::NavigationError.new("Navigation timeout of #{@timeout}ms exceeded")
|
132
|
+
end
|
133
|
+
else
|
134
|
+
@termination_promise
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param frame [Puppeteer::Frame]
|
139
|
+
private def navigated_within_document(frame)
|
140
|
+
return if frame != @frame
|
141
|
+
@has_same_document_navigation = true
|
142
|
+
check_lifecycle_complete
|
143
|
+
end
|
144
|
+
|
145
|
+
private def check_lifecycle_complete
|
146
|
+
# We expect navigation to commit.
|
147
|
+
return unless @expected_lifecycle.completed?(@frame)
|
148
|
+
@lifecycle_promise.fulfill(true) if @lifecycle_promise.pending?
|
149
|
+
if @frame.loader_id == @initial_loader_id && !@has_same_document_navigation
|
150
|
+
return
|
151
|
+
end
|
152
|
+
if @has_same_document_navigation && @same_document_navigation_promise.pending?
|
153
|
+
@same_document_navigation_promise.fulfill(true)
|
154
|
+
end
|
155
|
+
if @frame.loader_id != @initial_loader_id && @new_document_navigation_promise.pending?
|
156
|
+
@new_document_navigation_promise.fulfill(true)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def dispose
|
161
|
+
if_present(@listener_ids['client']) do |id|
|
162
|
+
@frame_manager.client.remove_event_listener(id)
|
163
|
+
end
|
164
|
+
if_present(@listener_ids['frame_manager']) do |ids|
|
165
|
+
@frame_manager.remove_event_listener(*ids)
|
166
|
+
end
|
167
|
+
if_present(@listener_ids['network_manager']) do |id|
|
168
|
+
@frame_manager.network_manager.remove_event_listener(id)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
class Puppeteer::Mouse
|
2
|
+
using Puppeteer::AsyncAwaitBehavior
|
3
|
+
|
4
|
+
module Button
|
5
|
+
NONE = 'none'
|
6
|
+
LEFT = 'left'
|
7
|
+
RIGHT = 'right'
|
8
|
+
MIDDLE = 'middle'
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param {Puppeteer.CDPSession} client
|
12
|
+
# @param keyboard [Puppeteer::Keyboard]
|
13
|
+
def initialize(client, keyboard)
|
14
|
+
@client = client
|
15
|
+
@keyboard = keyboard
|
16
|
+
|
17
|
+
@x = 0
|
18
|
+
@y = 0
|
19
|
+
@button = Button::NONE
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param x [number]
|
23
|
+
# @param y [number]
|
24
|
+
# @param steps [number]
|
25
|
+
def move(x, y, steps: nil)
|
26
|
+
move_steps = (steps || 1).to_i
|
27
|
+
|
28
|
+
from_x = @x
|
29
|
+
from_y = @y
|
30
|
+
@x = x
|
31
|
+
@y = y
|
32
|
+
|
33
|
+
return if move_steps <= 0
|
34
|
+
|
35
|
+
move_steps.times do |i|
|
36
|
+
n = i + 1
|
37
|
+
@client.send_message('Input.dispatchMouseEvent',
|
38
|
+
type: 'mouseMoved',
|
39
|
+
button: @button,
|
40
|
+
x: from_x + (@x - from_x) * n / move_steps,
|
41
|
+
y: from_y + (@y - from_y) * n / move_steps,
|
42
|
+
modifiers: @keyboard.modifiers,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param x [number]
|
48
|
+
# @param y [number]
|
49
|
+
# @param steps [number]
|
50
|
+
# @return [Future]
|
51
|
+
async def async_move(x, y, steps: nil)
|
52
|
+
move(x, y, steps: steps)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param x [number]
|
56
|
+
# @param y [number]
|
57
|
+
# @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
|
58
|
+
def click(x, y, delay: nil, button: nil, click_count: nil)
|
59
|
+
# await_all(async_move, async_down, async_up) often breaks the order of CDP commands.
|
60
|
+
# D, [2020-04-15T17:09:47.895895 #88683] DEBUG -- : RECV << {"id"=>23, "result"=>{"layoutViewport"=>{"pageX"=>0, "pageY"=>1, "clientWidth"=>375, "clientHeight"=>667}, "visualViewport"=>{"offsetX"=>0, "offsetY"=>0, "pageX"=>0, "pageY"=>1, "clientWidth"=>375, "clientHeight"=>667, "scale"=>1, "zoom"=>1}, "contentSize"=>{"x"=>0, "y"=>0, "width"=>375, "height"=>2007}}, "sessionId"=>"0B09EA5E18DEE403E525B3E7FCD7E225"}
|
61
|
+
# D, [2020-04-15T17:09:47.898422 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mouseReleased","button":"left","x":0,"y":0,"modifiers":0,"clickCount":1},"id":24}
|
62
|
+
# D, [2020-04-15T17:09:47.899711 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mousePressed","button":"left","x":0,"y":0,"modifiers":0,"clickCount":1},"id":25}
|
63
|
+
# D, [2020-04-15T17:09:47.900237 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mouseMoved","button":"left","x":187,"y":283,"modifiers":0},"id":26}
|
64
|
+
# So we execute move in advance.
|
65
|
+
move(x, y)
|
66
|
+
if delay
|
67
|
+
down(button: button, click_count: click_count)
|
68
|
+
sleep(delay / 1000.0)
|
69
|
+
up(button: button, click_count: click_count)
|
70
|
+
else
|
71
|
+
await_all(
|
72
|
+
async_down(button: button, click_count: click_count),
|
73
|
+
async_up(button: button, click_count: click_count),
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param x [number]
|
79
|
+
# @param y [number]
|
80
|
+
# @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
|
81
|
+
# @return [Future]
|
82
|
+
async def async_click(x, y, delay: nil, button: nil, click_count: nil)
|
83
|
+
click(x, y, delay: delay, button: button, click_count: click_count)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
87
|
+
def down(button: nil, click_count: nil)
|
88
|
+
@button = button || Button::LEFT
|
89
|
+
@client.send_message('Input.dispatchMouseEvent',
|
90
|
+
type: 'mousePressed',
|
91
|
+
button: @button,
|
92
|
+
x: @x,
|
93
|
+
y: @y,
|
94
|
+
modifiers: @keyboard.modifiers,
|
95
|
+
clickCount: click_count || 1,
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
100
|
+
# @return [Future]
|
101
|
+
async def async_down(button: nil, click_count: nil)
|
102
|
+
down(button: button, click_count: click_count)
|
103
|
+
end
|
104
|
+
|
105
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
106
|
+
def up(button: nil, click_count: nil)
|
107
|
+
@button = Button::NONE
|
108
|
+
@client.send_message('Input.dispatchMouseEvent',
|
109
|
+
type: 'mouseReleased',
|
110
|
+
button: button || Button::LEFT,
|
111
|
+
x: @x,
|
112
|
+
y: @y,
|
113
|
+
modifiers: @keyboard.modifiers,
|
114
|
+
clickCount: click_count || 1,
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
119
|
+
# @return [Future]
|
120
|
+
async def async_up(button: nil, click_count: nil)
|
121
|
+
up(button: button, click_count: click_count)
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
class Puppeteer::NetworkManager
|
2
|
+
include Puppeteer::EventCallbackable
|
3
|
+
|
4
|
+
class Credentials
|
5
|
+
# @param username [String|NilClass]
|
6
|
+
# @param password [String|NilClass]
|
7
|
+
def initialize(username:, password:)
|
8
|
+
@username = username
|
9
|
+
@password = password
|
10
|
+
end
|
11
|
+
attr_reader :username, :password
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param {!Puppeteer.CDPSession} client
|
15
|
+
# @param {boolean} ignoreHTTPSErrors
|
16
|
+
# @param {!Puppeteer.FrameManager} frameManager
|
17
|
+
def initialize(client, ignore_https_errors, frame_manager)
|
18
|
+
@client = client
|
19
|
+
@ignore_https_errors = ignore_https_errors
|
20
|
+
@frame_manager = frame_manager
|
21
|
+
|
22
|
+
# @type {!Map<string, !Request>}
|
23
|
+
@request_id_to_request = {}
|
24
|
+
|
25
|
+
# @type {!Map<string, !Protocol.Network.requestWillBeSentPayload>}
|
26
|
+
@request_id_to_request_with_be_sent_event
|
27
|
+
|
28
|
+
@extra_http_headers = {}
|
29
|
+
|
30
|
+
@offline = false
|
31
|
+
|
32
|
+
# /** @type {!Set<string>} */
|
33
|
+
# this._attemptedAuthentications = new Set();
|
34
|
+
@user_request_interception_enabled = false
|
35
|
+
@protocol_request_interception_enabled = false
|
36
|
+
@user_cache_disabled = false
|
37
|
+
# /** @type {!Map<string, string>} */
|
38
|
+
# this._requestIdToInterceptionId = new Map();
|
39
|
+
end
|
40
|
+
|
41
|
+
def init
|
42
|
+
@client.send_message('Network.enable')
|
43
|
+
if @ignore_https_errors
|
44
|
+
@client.send_message('Security.setIgnoreCertificateErrors', ignore: true)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param username [String|NilClass]
|
49
|
+
# @param password [String|NilClass]
|
50
|
+
def authenticate(username:, password:)
|
51
|
+
@credentials = Credentials.new(username: username, password: password)
|
52
|
+
update_protocol_request_interception
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param {!Object<string, string>} extraHTTPHeaders
|
56
|
+
def extra_http_headers=(headers)
|
57
|
+
new_extra_http_headers = {}
|
58
|
+
headers.each do |key, value|
|
59
|
+
unless value.is_a?(String)
|
60
|
+
raise ArgumentError.new("Expected value of header \"#{key}\" to be String, but \"#{value}\" is found.")
|
61
|
+
end
|
62
|
+
new_extra_http_headers[key.downcase] = value
|
63
|
+
end
|
64
|
+
@extra_http_headers = new_extra_http_headers
|
65
|
+
@client.send_message('Network.setExtraHTTPHeaders', headers: new_extra_http_headers)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return {!Object<string, string>}
|
69
|
+
def extra_http_headers
|
70
|
+
@extra_http_headers.dup
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param value [TrueClass|FalseClass]
|
74
|
+
def offline_mode=(value)
|
75
|
+
return if @offline == value
|
76
|
+
@offline = value
|
77
|
+
@client.send_message('Network.emulateNetworkConditions',
|
78
|
+
offline: @offline,
|
79
|
+
# values of 0 remove any active throttling. crbug.com/456324#c9
|
80
|
+
latency: 0,
|
81
|
+
downloadThroughput: -1,
|
82
|
+
uploadThroughput: -1,
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param user_agent [String]
|
87
|
+
def user_agent=(user_agent)
|
88
|
+
@client.send_message('Network.setUserAgentOverride', userAgent: user_agent)
|
89
|
+
end
|
90
|
+
|
91
|
+
def cache_enabled=(enabled)
|
92
|
+
@user_cache_disabled = !enabled
|
93
|
+
update_protocol_cache_disabled
|
94
|
+
end
|
95
|
+
|
96
|
+
def request_interception=(enabled)
|
97
|
+
@user_request_interception_enabled = enabled
|
98
|
+
update_protocol_request_interception
|
99
|
+
end
|
100
|
+
|
101
|
+
private def update_protocol_request_interception
|
102
|
+
enabled = @user_request_interception_enabled || !@credentials.nil?
|
103
|
+
return if @protocol_request_interception_enabled == enabled
|
104
|
+
@protocol_request_interception_enabled = enabled
|
105
|
+
|
106
|
+
if enabled
|
107
|
+
update_protocol_cache_disabled
|
108
|
+
@client.send_message('Fetch.enable',
|
109
|
+
handleAuthRequests: true,
|
110
|
+
patterns: [{ urlPattern: '*' }],
|
111
|
+
)
|
112
|
+
else
|
113
|
+
update_protocol_cache_disabled
|
114
|
+
@client.async_send_message('Fetch.disable')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private def update_protocol_cache_disabled
|
119
|
+
cache_disabled = @user_cache_disabled || @protocol_request_interception_enabled
|
120
|
+
@client.send_message('Network.setCacheDisabled', cacheDisabled: cache_disabled)
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,1065 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
require_relative './page/screenshot_options'
|
4
|
+
|
5
|
+
class Puppeteer::Page
|
6
|
+
include Puppeteer::EventCallbackable
|
7
|
+
include Puppeteer::IfPresent
|
8
|
+
using Puppeteer::AsyncAwaitBehavior
|
9
|
+
|
10
|
+
# @param {!Puppeteer.CDPSession} client
|
11
|
+
# @param {!Puppeteer.Target} target
|
12
|
+
# @param {boolean} ignoreHTTPSErrors
|
13
|
+
# @param {?Puppeteer.Viewport} defaultViewport
|
14
|
+
# @param {!Puppeteer.TaskQueue} screenshotTaskQueue
|
15
|
+
# @return {!Promise<!Page>}
|
16
|
+
def self.create(client, target, ignore_https_errors, default_viewport, screenshot_task_queue)
|
17
|
+
page = Puppeteer::Page.new(client, target, ignore_https_errors, screenshot_task_queue)
|
18
|
+
page.init
|
19
|
+
if default_viewport
|
20
|
+
page.viewport = default_viewport
|
21
|
+
end
|
22
|
+
page
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param {!Puppeteer.CDPSession} client
|
26
|
+
# @param {!Puppeteer.Target} target
|
27
|
+
# @param {boolean} ignoreHTTPSErrors
|
28
|
+
# @param {!Puppeteer.TaskQueue} screenshotTaskQueue
|
29
|
+
def initialize(client, target, ignore_https_errors, screenshot_task_queue)
|
30
|
+
@closed = false
|
31
|
+
@client = client
|
32
|
+
@target = target
|
33
|
+
@keyboard = Puppeteer::Keyboard.new(client)
|
34
|
+
@mouse = Puppeteer::Mouse.new(client, @keyboard)
|
35
|
+
@timeout_settings = Puppeteer::TimeoutSettings.new
|
36
|
+
@touchscreen = Puppeteer::TouchScreen.new(client, @keyboard)
|
37
|
+
# @accessibility = Accessibility.new(client)
|
38
|
+
@frame_manager = Puppeteer::FrameManager.new(client, self, ignore_https_errors, @timeout_settings)
|
39
|
+
@emulation_manager = Puppeteer::EmulationManager.new(client)
|
40
|
+
# @tracing = Tracing.new(client)
|
41
|
+
@page_bindings = {}
|
42
|
+
# @coverage = Coverage.new(client)
|
43
|
+
@javascript_enabled = true
|
44
|
+
@screenshot_task_queue = screenshot_task_queue
|
45
|
+
|
46
|
+
@workers = {}
|
47
|
+
@client.on_event 'Target.attachedToTarget' do |event|
|
48
|
+
if event['targetInfo']['type'] != 'worker'
|
49
|
+
# If we don't detach from service workers, they will never die.
|
50
|
+
await @client.send_message('Target.detachFromTarget', sessionId: event['sessionId'])
|
51
|
+
next
|
52
|
+
end
|
53
|
+
|
54
|
+
session = Puppeteer::Connection.from_session(@client).session(event['sessionId']) # rubocop:disable Lint/UselessAssignment
|
55
|
+
# const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
|
56
|
+
# this._workers.set(event.sessionId, worker);
|
57
|
+
# this.emit(Events.Page.WorkerCreated, worker);
|
58
|
+
end
|
59
|
+
@client.on_event 'Target.detachedFromTarget' do |event|
|
60
|
+
session_id = event['sessionId']
|
61
|
+
worker = @workers[session_id]
|
62
|
+
next unless worker
|
63
|
+
|
64
|
+
emit_event('Events.Page.WorkerDestroyed', worker)
|
65
|
+
@workers.delete(session_id)
|
66
|
+
end
|
67
|
+
|
68
|
+
@frame_manager.on_event 'Events.FrameManager.FrameAttached' do |event|
|
69
|
+
emit_event 'Events.Page.FrameAttached', event
|
70
|
+
end
|
71
|
+
@frame_manager.on_event 'Events.FrameManager.FrameDetached' do |event|
|
72
|
+
emit_event 'Events.Page.FrameDetached', event
|
73
|
+
end
|
74
|
+
@frame_manager.on_event 'Events.FrameManager.FrameNavigated' do |event|
|
75
|
+
emit_event 'Events.Page.FrameNavigated', event
|
76
|
+
end
|
77
|
+
|
78
|
+
network_manager = @frame_manager.network_manager
|
79
|
+
network_manager.on_event 'Events.NetworkManager.Request' do |event|
|
80
|
+
emit_event 'Events.Page.Request', event
|
81
|
+
end
|
82
|
+
network_manager.on_event 'Events.NetworkManager.Response' do |event|
|
83
|
+
emit_event 'Events.Page.Response', event
|
84
|
+
end
|
85
|
+
network_manager.on_event 'Events.NetworkManager.RequestFailed' do |event|
|
86
|
+
emit_event 'Events.Page.RequestFailed', event
|
87
|
+
end
|
88
|
+
network_manager.on_event 'Events.NetworkManager.RequestFinished' do |event|
|
89
|
+
emit_event 'Events.Page.RequestFinished', event
|
90
|
+
end
|
91
|
+
@file_chooser_interception_is_disabled = false
|
92
|
+
@file_chooser_interceptors = Set.new
|
93
|
+
|
94
|
+
@client.on_event 'Page.domContentEventFired' do |event|
|
95
|
+
emit_event 'Events.Page.DOMContentLoaded'
|
96
|
+
end
|
97
|
+
@client.on_event 'Page.loadEventFired' do |event|
|
98
|
+
emit_event 'Events.Page.Load'
|
99
|
+
end
|
100
|
+
# client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
|
101
|
+
# client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
|
102
|
+
# client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
|
103
|
+
# client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
|
104
|
+
# client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
|
105
|
+
# client.on('Performance.metrics', event => this._emitMetrics(event));
|
106
|
+
@client.on_event 'Log.entryAdded' do |event|
|
107
|
+
handle_log_entry_added(event)
|
108
|
+
end
|
109
|
+
@client.on_event 'Page.fileChooserOpened' do |event|
|
110
|
+
handle_file_chooser(event)
|
111
|
+
end
|
112
|
+
@target.is_closed_promise.then do
|
113
|
+
emit_event 'Events.Page.Close'
|
114
|
+
@closed = true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def init
|
119
|
+
await_all(
|
120
|
+
@frame_manager.async_init,
|
121
|
+
@client.async_send_message('Target.setAutoAttach', autoAttach: true, waitForDebuggerOnStart: false, flatten: true),
|
122
|
+
@client.async_send_message('Performance.enable'),
|
123
|
+
@client.async_send_message('Log.enable'),
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
def handle_file_chooser(event)
|
128
|
+
return if @file_chooser_interceptors.empty?
|
129
|
+
|
130
|
+
frame = @frame_manager.frame(event['frameId'])
|
131
|
+
context = frame.execution_context
|
132
|
+
element = context.adopt_backend_node_id(event['backendNodeId'])
|
133
|
+
interceptors = @file_chooser_interceptors.to_a
|
134
|
+
@file_chooser_interceptors.clear
|
135
|
+
file_chooser = Puppeteer::FileChooser.new(element, event)
|
136
|
+
interceptors.each do |promise|
|
137
|
+
promise.fulfill(file_chooser)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class FileChooserTimeoutError < StandardError
|
142
|
+
def initialize(timeout:)
|
143
|
+
super("waiting for filechooser failed: timeout #{timeout}ms exceeded")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# @param timeout [Integer]
|
148
|
+
# @return [Puppeteer::FileChooser]
|
149
|
+
def wait_for_file_chooser(timeout: nil)
|
150
|
+
if @file_chooser_interceptors.empty?
|
151
|
+
@client.send_message('Page.setInterceptFileChooserDialog', enabled: true)
|
152
|
+
end
|
153
|
+
|
154
|
+
option_timeout = timeout || @timeout_settings.timeout
|
155
|
+
promise = resolvable_future
|
156
|
+
@file_chooser_interceptors << promise
|
157
|
+
|
158
|
+
begin
|
159
|
+
Timeout.timeout(option_timeout / 1000.0) do
|
160
|
+
promise.value!
|
161
|
+
end
|
162
|
+
rescue Timeout::Error
|
163
|
+
raise FileChooserTimeoutError.new(timeout: option_timeout)
|
164
|
+
ensure
|
165
|
+
@file_chooser_interceptors.delete(promise)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# @param timeout [Integer]
|
170
|
+
# @return [Future<Puppeteer::FileChooser>]
|
171
|
+
async def async_wait_for_file_chooser(timeout: nil)
|
172
|
+
wait_for_file_chooser(timeout: timeout)
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
# /**
|
177
|
+
# * @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options
|
178
|
+
# */
|
179
|
+
# async setGeolocation(options) {
|
180
|
+
# const { longitude, latitude, accuracy = 0} = options;
|
181
|
+
# if (longitude < -180 || longitude > 180)
|
182
|
+
# throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
|
183
|
+
# if (latitude < -90 || latitude > 90)
|
184
|
+
# throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
|
185
|
+
# if (accuracy < 0)
|
186
|
+
# throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
|
187
|
+
# await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy});
|
188
|
+
# }
|
189
|
+
|
190
|
+
attr_reader :javascript_enabled, :target
|
191
|
+
|
192
|
+
def browser
|
193
|
+
@target.browser
|
194
|
+
end
|
195
|
+
|
196
|
+
def browser_context
|
197
|
+
@target.browser_context
|
198
|
+
end
|
199
|
+
|
200
|
+
class TargetCrashedError < StandardError; end
|
201
|
+
|
202
|
+
private def handle_target_crashed
|
203
|
+
emit_event 'error', TargetCrashedError.new('Page crashed!')
|
204
|
+
end
|
205
|
+
|
206
|
+
private def handle_log_entry_added(event)
|
207
|
+
entry = event['entry']
|
208
|
+
level = entry['level']
|
209
|
+
text = entry['text']
|
210
|
+
source = entry['source']
|
211
|
+
url = entry['url']
|
212
|
+
line_number = entry['lineNumber']
|
213
|
+
|
214
|
+
if_present(entry['args']) do |args|
|
215
|
+
args.map do |arg|
|
216
|
+
Puppeteer::RemoteObject.new(arg).async_release(@client)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
if source != 'worker'
|
220
|
+
console_message_location = Puppeteer::ConsoleMessage::Location.new(
|
221
|
+
url: url,
|
222
|
+
line_number: line_number,
|
223
|
+
)
|
224
|
+
emit_event('Events.Page.Console',
|
225
|
+
Puppeteer::ConsoleMessage.new(level, text, [], console_message_location))
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def main_frame
|
230
|
+
@frame_manager.main_frame
|
231
|
+
end
|
232
|
+
|
233
|
+
attr_reader :keyboard, :touch_screen, :coverage, :accessibility
|
234
|
+
|
235
|
+
def frames
|
236
|
+
@frame_manager.frames
|
237
|
+
end
|
238
|
+
|
239
|
+
def workers
|
240
|
+
@workers.values
|
241
|
+
end
|
242
|
+
|
243
|
+
# @param value [Bool]
|
244
|
+
def request_interception=(value)
|
245
|
+
@frame_manager.network_manager.request_interception = value
|
246
|
+
end
|
247
|
+
|
248
|
+
def offline_mode=(enabled)
|
249
|
+
@frame_manager.network_manager.offline_mode = enabled
|
250
|
+
end
|
251
|
+
|
252
|
+
# @param {number} timeout
|
253
|
+
def default_navigation_timeout=(timeout)
|
254
|
+
@timeout_settings.default_navigation_timeout = timeout
|
255
|
+
end
|
256
|
+
|
257
|
+
# @param {number} timeout
|
258
|
+
def default_timeout=(timeout)
|
259
|
+
@timeout_settings.default_timeout = timeout
|
260
|
+
end
|
261
|
+
|
262
|
+
# `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
263
|
+
# @param {string} selector
|
264
|
+
# @return {!Promise<?Puppeteer.ElementHandle>}
|
265
|
+
def S(selector)
|
266
|
+
main_frame.S(selector)
|
267
|
+
end
|
268
|
+
|
269
|
+
# `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
270
|
+
# @param {string} selector
|
271
|
+
# @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
|
272
|
+
def SS(selector)
|
273
|
+
main_frame.SS(selector)
|
274
|
+
end
|
275
|
+
|
276
|
+
# @param {Function|string} pageFunction
|
277
|
+
# @param {!Array<*>} args
|
278
|
+
# @return {!Promise<!Puppeteer.JSHandle>}
|
279
|
+
def evaluate_handle(page_function, *args)
|
280
|
+
context = main_frame.execution_context
|
281
|
+
context.evaluate_handle(page_function, *args)
|
282
|
+
end
|
283
|
+
|
284
|
+
# @param {!Puppeteer.JSHandle} prototypeHandle
|
285
|
+
# @return {!Promise<!Puppeteer.JSHandle>}
|
286
|
+
def query_objects(prototype_handle)
|
287
|
+
context = main_frame.execution_context
|
288
|
+
context.query_objects(prototype_handle)
|
289
|
+
end
|
290
|
+
|
291
|
+
# `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
292
|
+
# @param selector [String]
|
293
|
+
# @param page_function [String]
|
294
|
+
# @return [Object]
|
295
|
+
def Seval(selector, page_function, *args)
|
296
|
+
main_frame.Seval(selector, page_function, *args)
|
297
|
+
end
|
298
|
+
|
299
|
+
# `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
300
|
+
# @param selector [String]
|
301
|
+
# @param page_function [String]
|
302
|
+
# @return [Future]
|
303
|
+
async def async_Seval(selector, page_function, *args)
|
304
|
+
Seval(selector, page_function, *args)
|
305
|
+
end
|
306
|
+
|
307
|
+
# `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
308
|
+
# @param selector [String]
|
309
|
+
# @param page_function [String]
|
310
|
+
# @return [Object]
|
311
|
+
def SSeval(selector, page_function, *args)
|
312
|
+
main_frame.SSeval(selector, page_function, *args)
|
313
|
+
end
|
314
|
+
|
315
|
+
# `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
316
|
+
# @param selector [String]
|
317
|
+
# @param page_function [String]
|
318
|
+
# @return [Future]
|
319
|
+
async def async_SSeval(selector, page_function, *args)
|
320
|
+
SSeval(selector, page_function, *args)
|
321
|
+
end
|
322
|
+
|
323
|
+
# `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
324
|
+
# @param {string} expression
|
325
|
+
# @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
|
326
|
+
def Sx(expression)
|
327
|
+
main_frame.Sx(expression)
|
328
|
+
end
|
329
|
+
|
330
|
+
# /**
|
331
|
+
# * @param {!Array<string>} urls
|
332
|
+
# * @return {!Promise<!Array<Network.Cookie>>}
|
333
|
+
# */
|
334
|
+
# async cookies(...urls) {
|
335
|
+
# return (await this._client.send('Network.getCookies', {
|
336
|
+
# urls: urls.length ? urls : [this.url()]
|
337
|
+
# })).cookies;
|
338
|
+
# }
|
339
|
+
|
340
|
+
# /**
|
341
|
+
# * @param {Array<Protocol.Network.deleteCookiesParameters>} cookies
|
342
|
+
# */
|
343
|
+
# async deleteCookie(...cookies) {
|
344
|
+
# const pageURL = this.url();
|
345
|
+
# for (const cookie of cookies) {
|
346
|
+
# const item = Object.assign({}, cookie);
|
347
|
+
# if (!cookie.url && pageURL.startsWith('http'))
|
348
|
+
# item.url = pageURL;
|
349
|
+
# await this._client.send('Network.deleteCookies', item);
|
350
|
+
# }
|
351
|
+
# }
|
352
|
+
|
353
|
+
# /**
|
354
|
+
# * @param {Array<Network.CookieParam>} cookies
|
355
|
+
# */
|
356
|
+
# async setCookie(...cookies) {
|
357
|
+
# const pageURL = this.url();
|
358
|
+
# const startsWithHTTP = pageURL.startsWith('http');
|
359
|
+
# const items = cookies.map(cookie => {
|
360
|
+
# const item = Object.assign({}, cookie);
|
361
|
+
# if (!item.url && startsWithHTTP)
|
362
|
+
# item.url = pageURL;
|
363
|
+
# assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
|
364
|
+
# assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
|
365
|
+
# return item;
|
366
|
+
# });
|
367
|
+
# await this.deleteCookie(...items);
|
368
|
+
# if (items.length)
|
369
|
+
# await this._client.send('Network.setCookies', { cookies: items });
|
370
|
+
# }
|
371
|
+
|
372
|
+
class ScriptTag
|
373
|
+
# @param {!{content?: string, path?: string, type?: string, url?: string}} options
|
374
|
+
def initialize(content: nil, path: nil, type: nil, url: nil)
|
375
|
+
@content = content
|
376
|
+
@path = path
|
377
|
+
@type = type
|
378
|
+
@url = url
|
379
|
+
end
|
380
|
+
attr_reader :content, :path, :type, :url
|
381
|
+
end
|
382
|
+
|
383
|
+
# @param style_tag [Puppeteer::Page::ScriptTag]
|
384
|
+
# @return {!Promise<!ElementHandle>}
|
385
|
+
def add_script_tag(script_tag)
|
386
|
+
main_frame.add_script_tag(script_tag)
|
387
|
+
end
|
388
|
+
|
389
|
+
class StyleTag
|
390
|
+
# @param {!{content?: string, path?: string, url?: string}} options
|
391
|
+
def initialize(content: nil, path: nil, url: nil)
|
392
|
+
@content = content
|
393
|
+
@path = path
|
394
|
+
@url = url
|
395
|
+
end
|
396
|
+
attr_reader :content, :path, :url
|
397
|
+
end
|
398
|
+
|
399
|
+
# @param style_tag [Puppeteer::Page::StyleTag]
|
400
|
+
# @return {!Promise<!ElementHandle>}
|
401
|
+
def add_style_tag(style_tag)
|
402
|
+
main_frame.add_style_tag(style_tag)
|
403
|
+
end
|
404
|
+
|
405
|
+
# /**
|
406
|
+
# * @param {string} name
|
407
|
+
# * @param {Function} puppeteerFunction
|
408
|
+
# */
|
409
|
+
# async exposeFunction(name, puppeteerFunction) {
|
410
|
+
# if (this._pageBindings.has(name))
|
411
|
+
# throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
412
|
+
# this._pageBindings.set(name, puppeteerFunction);
|
413
|
+
|
414
|
+
# const expression = helper.evaluationString(addPageBinding, name);
|
415
|
+
# await this._client.send('Runtime.addBinding', {name: name});
|
416
|
+
# await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: expression});
|
417
|
+
# await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError)));
|
418
|
+
|
419
|
+
# function addPageBinding(bindingName) {
|
420
|
+
# const binding = window[bindingName];
|
421
|
+
# window[bindingName] = (...args) => {
|
422
|
+
# const me = window[bindingName];
|
423
|
+
# let callbacks = me['callbacks'];
|
424
|
+
# if (!callbacks) {
|
425
|
+
# callbacks = new Map();
|
426
|
+
# me['callbacks'] = callbacks;
|
427
|
+
# }
|
428
|
+
# const seq = (me['lastSeq'] || 0) + 1;
|
429
|
+
# me['lastSeq'] = seq;
|
430
|
+
# const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject}));
|
431
|
+
# binding(JSON.stringify({name: bindingName, seq, args}));
|
432
|
+
# return promise;
|
433
|
+
# };
|
434
|
+
# }
|
435
|
+
# }
|
436
|
+
|
437
|
+
# @param username [String?]
|
438
|
+
# @param password [String?]
|
439
|
+
def authenticate(username: nil, password: nil)
|
440
|
+
@frame_manager.network_manager.authenticate(username: username, password: password)
|
441
|
+
end
|
442
|
+
|
443
|
+
# @param headers [Hash]
|
444
|
+
def extra_http_headers=(headers)
|
445
|
+
@frame_manager.network_manager.extra_http_headers = headers
|
446
|
+
end
|
447
|
+
|
448
|
+
# @param user_agent [String]
|
449
|
+
def user_agent=(user_agent)
|
450
|
+
@frame_manager.network_manager.user_agent = user_agent
|
451
|
+
end
|
452
|
+
|
453
|
+
# /**
|
454
|
+
# * @return {!Promise<!Metrics>}
|
455
|
+
# */
|
456
|
+
# async metrics() {
|
457
|
+
# const response = await this._client.send('Performance.getMetrics');
|
458
|
+
# return this._buildMetricsObject(response.metrics);
|
459
|
+
# }
|
460
|
+
|
461
|
+
# /**
|
462
|
+
# * @param {!Protocol.Performance.metricsPayload} event
|
463
|
+
# */
|
464
|
+
# _emitMetrics(event) {
|
465
|
+
# this.emit(Events.Page.Metrics, {
|
466
|
+
# title: event.title,
|
467
|
+
# metrics: this._buildMetricsObject(event.metrics)
|
468
|
+
# });
|
469
|
+
# }
|
470
|
+
|
471
|
+
# /**
|
472
|
+
# * @param {?Array<!Protocol.Performance.Metric>} metrics
|
473
|
+
# * @return {!Metrics}
|
474
|
+
# */
|
475
|
+
# _buildMetricsObject(metrics) {
|
476
|
+
# const result = {};
|
477
|
+
# for (const metric of metrics || []) {
|
478
|
+
# if (supportedMetrics.has(metric.name))
|
479
|
+
# result[metric.name] = metric.value;
|
480
|
+
# }
|
481
|
+
# return result;
|
482
|
+
# }
|
483
|
+
|
484
|
+
# /**
|
485
|
+
# * @param {!Protocol.Runtime.ExceptionDetails} exceptionDetails
|
486
|
+
# */
|
487
|
+
# _handleException(exceptionDetails) {
|
488
|
+
# const message = helper.getExceptionMessage(exceptionDetails);
|
489
|
+
# const err = new Error(message);
|
490
|
+
# err.stack = ''; // Don't report clientside error with a node stack attached
|
491
|
+
# this.emit(Events.Page.PageError, err);
|
492
|
+
# }
|
493
|
+
|
494
|
+
# /**
|
495
|
+
# * @param {!Protocol.Runtime.consoleAPICalledPayload} event
|
496
|
+
# */
|
497
|
+
# async _onConsoleAPI(event) {
|
498
|
+
# if (event.executionContextId === 0) {
|
499
|
+
# // DevTools protocol stores the last 1000 console messages. These
|
500
|
+
# // messages are always reported even for removed execution contexts. In
|
501
|
+
# // this case, they are marked with executionContextId = 0 and are
|
502
|
+
# // reported upon enabling Runtime agent.
|
503
|
+
# //
|
504
|
+
# // Ignore these messages since:
|
505
|
+
# // - there's no execution context we can use to operate with message
|
506
|
+
# // arguments
|
507
|
+
# // - these messages are reported before Puppeteer clients can subscribe
|
508
|
+
# // to the 'console'
|
509
|
+
# // page event.
|
510
|
+
# //
|
511
|
+
# // @see https://github.com/puppeteer/puppeteer/issues/3865
|
512
|
+
# return;
|
513
|
+
# }
|
514
|
+
# const context = this._frameManager.executionContextById(event.executionContextId);
|
515
|
+
# const values = event.args.map(arg => createJSHandle(context, arg));
|
516
|
+
# this._addConsoleMessage(event.type, values, event.stackTrace);
|
517
|
+
# }
|
518
|
+
|
519
|
+
# /**
|
520
|
+
# * @param {!Protocol.Runtime.bindingCalledPayload} event
|
521
|
+
# */
|
522
|
+
# async _onBindingCalled(event) {
|
523
|
+
# const {name, seq, args} = JSON.parse(event.payload);
|
524
|
+
# let expression = null;
|
525
|
+
# try {
|
526
|
+
# const result = await this._pageBindings.get(name)(...args);
|
527
|
+
# expression = helper.evaluationString(deliverResult, name, seq, result);
|
528
|
+
# } catch (error) {
|
529
|
+
# if (error instanceof Error)
|
530
|
+
# expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack);
|
531
|
+
# else
|
532
|
+
# expression = helper.evaluationString(deliverErrorValue, name, seq, error);
|
533
|
+
# }
|
534
|
+
# this._client.send('Runtime.evaluate', { expression, contextId: event.executionContextId }).catch(debugError);
|
535
|
+
|
536
|
+
# /**
|
537
|
+
# * @param {string} name
|
538
|
+
# * @param {number} seq
|
539
|
+
# * @param {*} result
|
540
|
+
# */
|
541
|
+
# function deliverResult(name, seq, result) {
|
542
|
+
# window[name]['callbacks'].get(seq).resolve(result);
|
543
|
+
# window[name]['callbacks'].delete(seq);
|
544
|
+
# }
|
545
|
+
|
546
|
+
# /**
|
547
|
+
# * @param {string} name
|
548
|
+
# * @param {number} seq
|
549
|
+
# * @param {string} message
|
550
|
+
# * @param {string} stack
|
551
|
+
# */
|
552
|
+
# function deliverError(name, seq, message, stack) {
|
553
|
+
# const error = new Error(message);
|
554
|
+
# error.stack = stack;
|
555
|
+
# window[name]['callbacks'].get(seq).reject(error);
|
556
|
+
# window[name]['callbacks'].delete(seq);
|
557
|
+
# }
|
558
|
+
|
559
|
+
# /**
|
560
|
+
# * @param {string} name
|
561
|
+
# * @param {number} seq
|
562
|
+
# * @param {*} value
|
563
|
+
# */
|
564
|
+
# function deliverErrorValue(name, seq, value) {
|
565
|
+
# window[name]['callbacks'].get(seq).reject(value);
|
566
|
+
# window[name]['callbacks'].delete(seq);
|
567
|
+
# }
|
568
|
+
# }
|
569
|
+
|
570
|
+
# /**
|
571
|
+
# * @param {string} type
|
572
|
+
# * @param {!Array<!Puppeteer.JSHandle>} args
|
573
|
+
# * @param {Protocol.Runtime.StackTrace=} stackTrace
|
574
|
+
# */
|
575
|
+
# _addConsoleMessage(type, args, stackTrace) {
|
576
|
+
# if (!this.listenerCount(Events.Page.Console)) {
|
577
|
+
# args.forEach(arg => arg.dispose());
|
578
|
+
# return;
|
579
|
+
# }
|
580
|
+
# const textTokens = [];
|
581
|
+
# for (const arg of args) {
|
582
|
+
# const remoteObject = arg._remoteObject;
|
583
|
+
# if (remoteObject.objectId)
|
584
|
+
# textTokens.push(arg.toString());
|
585
|
+
# else
|
586
|
+
# textTokens.push(helper.valueFromRemoteObject(remoteObject));
|
587
|
+
# }
|
588
|
+
# const location = stackTrace && stackTrace.callFrames.length ? {
|
589
|
+
# url: stackTrace.callFrames[0].url,
|
590
|
+
# lineNumber: stackTrace.callFrames[0].lineNumber,
|
591
|
+
# columnNumber: stackTrace.callFrames[0].columnNumber,
|
592
|
+
# } : {};
|
593
|
+
# const message = new ConsoleMessage(type, textTokens.join(' '), args, location);
|
594
|
+
# this.emit(Events.Page.Console, message);
|
595
|
+
# }
|
596
|
+
|
597
|
+
# _onDialog(event) {
|
598
|
+
# let dialogType = null;
|
599
|
+
# if (event.type === 'alert')
|
600
|
+
# dialogType = Dialog.Type.Alert;
|
601
|
+
# else if (event.type === 'confirm')
|
602
|
+
# dialogType = Dialog.Type.Confirm;
|
603
|
+
# else if (event.type === 'prompt')
|
604
|
+
# dialogType = Dialog.Type.Prompt;
|
605
|
+
# else if (event.type === 'beforeunload')
|
606
|
+
# dialogType = Dialog.Type.BeforeUnload;
|
607
|
+
# assert(dialogType, 'Unknown javascript dialog type: ' + event.type);
|
608
|
+
# const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt);
|
609
|
+
# this.emit(Events.Page.Dialog, dialog);
|
610
|
+
# }
|
611
|
+
|
612
|
+
# @return [String]
|
613
|
+
def url
|
614
|
+
main_frame.url
|
615
|
+
end
|
616
|
+
|
617
|
+
# @return [String]
|
618
|
+
def content
|
619
|
+
main_frame.content
|
620
|
+
end
|
621
|
+
|
622
|
+
# @param {string} html
|
623
|
+
# @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
|
624
|
+
def set_content(html, timeout: nil, wait_until: nil)
|
625
|
+
main_frame.set_content(html, timeout: timeout, wait_until: wait_until)
|
626
|
+
end
|
627
|
+
|
628
|
+
# @param {string} html
|
629
|
+
def content=(html)
|
630
|
+
main_frame.set_content(html)
|
631
|
+
end
|
632
|
+
|
633
|
+
# @param url [String]
|
634
|
+
# @param rederer [String]
|
635
|
+
# @param timeout [number|nil]
|
636
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
637
|
+
def goto(url, referer: nil, timeout: nil, wait_until: nil)
|
638
|
+
main_frame.goto(url, referer: referer, timeout: timeout, wait_until: wait_until)
|
639
|
+
end
|
640
|
+
|
641
|
+
# @param timeout [number|nil]
|
642
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
643
|
+
# @return [Puppeteer::Response]
|
644
|
+
def reload(timeout: nil, wait_until: nil)
|
645
|
+
await_all(
|
646
|
+
async_wait_for_navigation(timeout: timeout, wait_until: wait_until),
|
647
|
+
@client.async_send_message('Page.reload'),
|
648
|
+
).first
|
649
|
+
end
|
650
|
+
|
651
|
+
# @param timeout [number|nil]
|
652
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
653
|
+
private def wait_for_navigation(timeout: nil, wait_until: nil)
|
654
|
+
main_frame.wait_for_navigation(timeout: timeout, wait_until: wait_until)
|
655
|
+
end
|
656
|
+
|
657
|
+
# @param timeout [number|nil]
|
658
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
659
|
+
# @return [Future]
|
660
|
+
async def async_wait_for_navigation(timeout: nil, wait_until: nil)
|
661
|
+
wait_for_navigation(timeout: timeout, wait_until: wait_until)
|
662
|
+
end
|
663
|
+
|
664
|
+
# /**
|
665
|
+
# * @param {(string|Function)} urlOrPredicate
|
666
|
+
# * @param {!{timeout?: number}=} options
|
667
|
+
# * @return {!Promise<!Puppeteer.Request>}
|
668
|
+
# */
|
669
|
+
# async waitForRequest(urlOrPredicate, options = {}) {
|
670
|
+
# const {
|
671
|
+
# timeout = this._timeoutSettings.timeout(),
|
672
|
+
# } = options;
|
673
|
+
# return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Request, request => {
|
674
|
+
# if (helper.isString(urlOrPredicate))
|
675
|
+
# return (urlOrPredicate === request.url());
|
676
|
+
# if (typeof urlOrPredicate === 'function')
|
677
|
+
# return !!(urlOrPredicate(request));
|
678
|
+
# return false;
|
679
|
+
# }, timeout, this._sessionClosePromise());
|
680
|
+
# }
|
681
|
+
|
682
|
+
# /**
|
683
|
+
# * @param {(string|Function)} urlOrPredicate
|
684
|
+
# * @param {!{timeout?: number}=} options
|
685
|
+
# * @return {!Promise<!Puppeteer.Response>}
|
686
|
+
# */
|
687
|
+
# async waitForResponse(urlOrPredicate, options = {}) {
|
688
|
+
# const {
|
689
|
+
# timeout = this._timeoutSettings.timeout(),
|
690
|
+
# } = options;
|
691
|
+
# return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Response, response => {
|
692
|
+
# if (helper.isString(urlOrPredicate))
|
693
|
+
# return (urlOrPredicate === response.url());
|
694
|
+
# if (typeof urlOrPredicate === 'function')
|
695
|
+
# return !!(urlOrPredicate(response));
|
696
|
+
# return false;
|
697
|
+
# }, timeout, this._sessionClosePromise());
|
698
|
+
# }
|
699
|
+
|
700
|
+
# @param timeout [number|nil]
|
701
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
702
|
+
def go_back(timeout: nil, wait_until: nil)
|
703
|
+
go(-1, timeout: timeout, wait_until: wait_until)
|
704
|
+
end
|
705
|
+
|
706
|
+
# @param timeout [number|nil]
|
707
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
708
|
+
def go_forward(timeout: nil, wait_until: nil)
|
709
|
+
go(+1, timeout: timeout, wait_until: wait_until)
|
710
|
+
end
|
711
|
+
|
712
|
+
private def go(delta, timeout: nil, wait_until: nil)
|
713
|
+
history = @client.send_message('Page.getNavigationHistory')
|
714
|
+
entries = history['entries']
|
715
|
+
index = history['currentIndex'] + delta
|
716
|
+
if_present(entries[index]) do |entry|
|
717
|
+
await_all(
|
718
|
+
async_wait_for_navigation(timeout: timeout, wait_until: wait_until),
|
719
|
+
@client.async_send_message('Page.navigateToHistoryEntry', entryId: entry['id']),
|
720
|
+
)
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
# @param device [Device]
|
725
|
+
def emulate(device)
|
726
|
+
self.viewport = device.viewport
|
727
|
+
self.user_agent = device.user_agent
|
728
|
+
end
|
729
|
+
|
730
|
+
# @param {boolean} enabled
|
731
|
+
def javascript_enabled=(enabled)
|
732
|
+
return if @javascript_enabled == enabled
|
733
|
+
@javascript_enabled = enabled
|
734
|
+
@client.send_message('Emulation.setScriptExecutionDisabled', value: !enabled)
|
735
|
+
end
|
736
|
+
|
737
|
+
# /**
|
738
|
+
# * @param {boolean} enabled
|
739
|
+
# */
|
740
|
+
# async setBypassCSP(enabled) {
|
741
|
+
# await this._client.send('Page.setBypassCSP', { enabled });
|
742
|
+
# }
|
743
|
+
|
744
|
+
# /**
|
745
|
+
# * @param {?string} type
|
746
|
+
# */
|
747
|
+
# async emulateMediaType(type) {
|
748
|
+
# assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type);
|
749
|
+
# await this._client.send('Emulation.setEmulatedMedia', {media: type || ''});
|
750
|
+
# }
|
751
|
+
|
752
|
+
# /**
|
753
|
+
# * @param {?Array<MediaFeature>} features
|
754
|
+
# */
|
755
|
+
# async emulateMediaFeatures(features) {
|
756
|
+
# if (features === null)
|
757
|
+
# await this._client.send('Emulation.setEmulatedMedia', {features: null});
|
758
|
+
# if (Array.isArray(features)) {
|
759
|
+
# features.every(mediaFeature => {
|
760
|
+
# const name = mediaFeature.name;
|
761
|
+
# assert(/^prefers-(?:color-scheme|reduced-motion)$/.test(name), 'Unsupported media feature: ' + name);
|
762
|
+
# return true;
|
763
|
+
# });
|
764
|
+
# await this._client.send('Emulation.setEmulatedMedia', {features: features});
|
765
|
+
# }
|
766
|
+
# }
|
767
|
+
|
768
|
+
# @param timezone_id [String?]
|
769
|
+
def emulate_timezone(timezone_id)
|
770
|
+
@client.send_message('Emulation.setTimezoneOverride', timezoneId: timezoneId || '')
|
771
|
+
rescue => err
|
772
|
+
if err.message.include?('Invalid timezone')
|
773
|
+
raise ArgumentError.new("Invalid timezone ID: #{timezone_id}")
|
774
|
+
else
|
775
|
+
raise err
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
# @param viewport [Viewport]
|
780
|
+
def viewport=(viewport)
|
781
|
+
needs_reload = @emulation_manager.emulate_viewport(viewport)
|
782
|
+
@viewport = viewport
|
783
|
+
reload if needs_reload
|
784
|
+
end
|
785
|
+
|
786
|
+
attr_reader :viewport
|
787
|
+
|
788
|
+
# @param {Function|string} pageFunction
|
789
|
+
# @param {!Array<*>} args
|
790
|
+
# @return {!Promise<*>}
|
791
|
+
def evaluate(page_function, *args)
|
792
|
+
main_frame.evaluate(page_function, *args)
|
793
|
+
end
|
794
|
+
|
795
|
+
# /**
|
796
|
+
# * @param {Function|string} pageFunction
|
797
|
+
# * @param {!Array<*>} args
|
798
|
+
# */
|
799
|
+
# async evaluateOnNewDocument(pageFunction, ...args) {
|
800
|
+
# const source = helper.evaluationString(pageFunction, ...args);
|
801
|
+
# await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
|
802
|
+
# }
|
803
|
+
|
804
|
+
# @param {boolean} enabled
|
805
|
+
def cache_enabled=(enabled)
|
806
|
+
@frame_manager.network_manager.cache_enabled = enabled
|
807
|
+
end
|
808
|
+
|
809
|
+
# @return {!Promise<string>}
|
810
|
+
def title
|
811
|
+
@title
|
812
|
+
end
|
813
|
+
|
814
|
+
# /**
|
815
|
+
# * @param {!ScreenshotOptions=} options
|
816
|
+
# * @return {!Promise<!Buffer|!String>}
|
817
|
+
# */
|
818
|
+
def screenshot(options = {})
|
819
|
+
screenshot_options = ScreenshotOptions.new(options)
|
820
|
+
|
821
|
+
# @screenshot_task_queue.post_task(-> { screenshot_task(screenshot_options.type, screenshot_options) })
|
822
|
+
screenshot_task(screenshot_options.type, screenshot_options)
|
823
|
+
end
|
824
|
+
|
825
|
+
# @param {"png"|"jpeg"} format
|
826
|
+
# @param {!ScreenshotOptions=} options
|
827
|
+
# @return {!Promise<!Buffer|!String>}
|
828
|
+
private def screenshot_task(format, screenshot_options)
|
829
|
+
@client.send_message('Target.activateTarget', targetId: @target.target_id)
|
830
|
+
|
831
|
+
clip = if_present(screenshot_options.clip) do |rect|
|
832
|
+
x = rect[:x].round
|
833
|
+
y = rect[:y].round
|
834
|
+
{ x: x, y: y, width: rect[:width] + rect[:x] - x, height: rect[:height] + rect[:y] - y, scale: 1 }
|
835
|
+
end
|
836
|
+
|
837
|
+
if screenshot_options.full_page?
|
838
|
+
metrics = @client.send_message('Page.getLayoutMetrics')
|
839
|
+
width = metrics['contentSize']['width'].ceil
|
840
|
+
height = metrics['contentSize']['height'].ceil
|
841
|
+
|
842
|
+
# Overwrite clip for full page at all times.
|
843
|
+
clip = { x: 0, y: 0, width: width, height: height, scale: 1 }
|
844
|
+
|
845
|
+
screen_orientation =
|
846
|
+
if @viewport.landscape?
|
847
|
+
{ angle: 90, type: 'landscapePrimary' }
|
848
|
+
else
|
849
|
+
{ angle: 0, type: 'portraitPrimary' }
|
850
|
+
end
|
851
|
+
@client.send_message('Emulation.setDeviceMetricsOverride',
|
852
|
+
mobile: @viewport.mobile?,
|
853
|
+
width: width,
|
854
|
+
height: height,
|
855
|
+
deviceScaleFactor: @viewport.device_scale_factor,
|
856
|
+
screenOrientation: screen_orientation)
|
857
|
+
end
|
858
|
+
|
859
|
+
should_set_default_background = screenshot_options.omit_background? && format == 'png'
|
860
|
+
if should_set_default_background
|
861
|
+
@client.send_message('Emulation.setDefaultBackgroundColorOverride', color: { r: 0, g: 0, b: 0, a: 0 })
|
862
|
+
end
|
863
|
+
screenshot_params = {
|
864
|
+
format: format,
|
865
|
+
quality: screenshot_options.quality,
|
866
|
+
clip: clip,
|
867
|
+
}.compact
|
868
|
+
result = @client.send_message('Page.captureScreenshot', screenshot_params)
|
869
|
+
if should_set_default_background
|
870
|
+
@client.send_message('Emulation.setDefaultBackgroundColorOverride')
|
871
|
+
end
|
872
|
+
|
873
|
+
if screenshot_options.full_page? && @viewport
|
874
|
+
self.viewport = @viewport
|
875
|
+
end
|
876
|
+
|
877
|
+
buffer =
|
878
|
+
if screenshot_options.encoding == 'base64'
|
879
|
+
result['data']
|
880
|
+
else
|
881
|
+
Base64.decode64(result['data'])
|
882
|
+
end
|
883
|
+
|
884
|
+
if screenshot_options.path
|
885
|
+
File.binwrite(screenshot_options.path, buffer)
|
886
|
+
end
|
887
|
+
|
888
|
+
buffer
|
889
|
+
end
|
890
|
+
|
891
|
+
# /**
|
892
|
+
# * @param {!PDFOptions=} options
|
893
|
+
# * @return {!Promise<!Buffer>}
|
894
|
+
# */
|
895
|
+
# async pdf(options = {}) {
|
896
|
+
# const {
|
897
|
+
# scale = 1,
|
898
|
+
# displayHeaderFooter = false,
|
899
|
+
# headerTemplate = '',
|
900
|
+
# footerTemplate = '',
|
901
|
+
# printBackground = false,
|
902
|
+
# landscape = false,
|
903
|
+
# pageRanges = '',
|
904
|
+
# preferCSSPageSize = false,
|
905
|
+
# margin = {},
|
906
|
+
# path = null
|
907
|
+
# } = options;
|
908
|
+
|
909
|
+
# let paperWidth = 8.5;
|
910
|
+
# let paperHeight = 11;
|
911
|
+
# if (options.format) {
|
912
|
+
# const format = Page.PaperFormats[options.format.toLowerCase()];
|
913
|
+
# assert(format, 'Unknown paper format: ' + options.format);
|
914
|
+
# paperWidth = format.width;
|
915
|
+
# paperHeight = format.height;
|
916
|
+
# } else {
|
917
|
+
# paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
|
918
|
+
# paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
|
919
|
+
# }
|
920
|
+
|
921
|
+
# const marginTop = convertPrintParameterToInches(margin.top) || 0;
|
922
|
+
# const marginLeft = convertPrintParameterToInches(margin.left) || 0;
|
923
|
+
# const marginBottom = convertPrintParameterToInches(margin.bottom) || 0;
|
924
|
+
# const marginRight = convertPrintParameterToInches(margin.right) || 0;
|
925
|
+
|
926
|
+
# const result = await this._client.send('Page.printToPDF', {
|
927
|
+
# transferMode: 'ReturnAsStream',
|
928
|
+
# landscape,
|
929
|
+
# displayHeaderFooter,
|
930
|
+
# headerTemplate,
|
931
|
+
# footerTemplate,
|
932
|
+
# printBackground,
|
933
|
+
# scale,
|
934
|
+
# paperWidth,
|
935
|
+
# paperHeight,
|
936
|
+
# marginTop,
|
937
|
+
# marginBottom,
|
938
|
+
# marginLeft,
|
939
|
+
# marginRight,
|
940
|
+
# pageRanges,
|
941
|
+
# preferCSSPageSize
|
942
|
+
# });
|
943
|
+
# return await helper.readProtocolStream(this._client, result.stream, path);
|
944
|
+
# }
|
945
|
+
|
946
|
+
# @param {!{runBeforeUnload: (boolean|undefined)}=} options
|
947
|
+
def close
|
948
|
+
# assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
949
|
+
# const runBeforeUnload = !!options.runBeforeUnload;
|
950
|
+
# if (runBeforeUnload) {
|
951
|
+
# await this._client.send('Page.close');
|
952
|
+
# } else {
|
953
|
+
# await this._client._connection.send('Target.closeTarget', { targetId: this._target._targetId });
|
954
|
+
# await this._target._isClosedPromise;
|
955
|
+
# }
|
956
|
+
end
|
957
|
+
|
958
|
+
# @return [boolean]
|
959
|
+
def closed?
|
960
|
+
@closed
|
961
|
+
end
|
962
|
+
|
963
|
+
attr_reader :mouse
|
964
|
+
|
965
|
+
# @param selector [String]
|
966
|
+
# @param delay [Number]
|
967
|
+
# @param button [String] "left"|"right"|"middle"
|
968
|
+
# @param click_count [Number]
|
969
|
+
def click(selector, delay: nil, button: nil, click_count: nil)
|
970
|
+
main_frame.click(selector, delay: delay, button: button, click_count: click_count)
|
971
|
+
end
|
972
|
+
|
973
|
+
# @param selector [String]
|
974
|
+
# @param delay [Number]
|
975
|
+
# @param button [String] "left"|"right"|"middle"
|
976
|
+
# @param click_count [Number]
|
977
|
+
# @return [Future]
|
978
|
+
async def async_click(selector, delay: nil, button: nil, click_count: nil)
|
979
|
+
click(selector, delay: delay, button: button, click_count: click_count)
|
980
|
+
end
|
981
|
+
|
982
|
+
# @param {string} selector
|
983
|
+
def focus(selector)
|
984
|
+
main_frame.focus(selector)
|
985
|
+
end
|
986
|
+
|
987
|
+
# @param {string} selector
|
988
|
+
def hover(selector)
|
989
|
+
main_frame.hover(selector)
|
990
|
+
end
|
991
|
+
|
992
|
+
# @param {string} selector
|
993
|
+
# @param {!Array<string>} values
|
994
|
+
# @return {!Promise<!Array<string>>}
|
995
|
+
def select(selector, *values)
|
996
|
+
main_frame.select(selector, *values)
|
997
|
+
end
|
998
|
+
|
999
|
+
# @param selector [String]
|
1000
|
+
def tap(selector)
|
1001
|
+
main_frame.tap(selector)
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
# @param selector [String]
|
1005
|
+
async def async_tap(selector)
|
1006
|
+
tap(selector)
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# @param selector [String]
|
1010
|
+
# @param text [String]
|
1011
|
+
# @param delay [Number]
|
1012
|
+
def type_text(selector, text, delay: nil)
|
1013
|
+
main_frame.type_text(selector, text, delay: delay)
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# /**
|
1017
|
+
# * @param {(string|number|Function)} selectorOrFunctionOrTimeout
|
1018
|
+
# * @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options
|
1019
|
+
# * @param {!Array<*>} args
|
1020
|
+
# * @return {!Promise<!Puppeteer.JSHandle>}
|
1021
|
+
# */
|
1022
|
+
# waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
|
1023
|
+
# return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
1024
|
+
# }
|
1025
|
+
|
1026
|
+
# @param selector [String]
|
1027
|
+
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
1028
|
+
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
1029
|
+
# @param timeout [Integer]
|
1030
|
+
def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
|
1031
|
+
main_frame.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
# @param selector [String]
|
1035
|
+
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
1036
|
+
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
1037
|
+
# @param timeout [Integer]
|
1038
|
+
async def async_wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
|
1039
|
+
wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
# @param xpath [String]
|
1043
|
+
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
1044
|
+
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
1045
|
+
# @param timeout [Integer]
|
1046
|
+
def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
|
1047
|
+
main_frame.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
# @param xpath [String]
|
1051
|
+
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
1052
|
+
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
1053
|
+
# @param timeout [Integer]
|
1054
|
+
async def async_wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
|
1055
|
+
wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
# @param {Function|string} pageFunction
|
1059
|
+
# @param {!{polling?: string|number, timeout?: number}=} options
|
1060
|
+
# @param {!Array<*>} args
|
1061
|
+
# @return {!Promise<!Puppeteer.JSHandle>}
|
1062
|
+
def wait_for_function(page_function, options = {}, *args)
|
1063
|
+
main_frame.wait_for_function(page_function, options, *args)
|
1064
|
+
end
|
1065
|
+
end
|