puppeteer-ruby 0.0.10
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 +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
|