puppeteer-ruby 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +36 -0
- data/.travis.yml +7 -0
- data/Dockerfile +6 -0
- data/Gemfile +6 -0
- data/README.md +41 -0
- data/Rakefile +1 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +15 -0
- data/example.rb +7 -0
- data/lib/puppeteer.rb +192 -0
- data/lib/puppeteer/async_await_behavior.rb +34 -0
- data/lib/puppeteer/browser.rb +240 -0
- data/lib/puppeteer/browser_context.rb +90 -0
- data/lib/puppeteer/browser_fetcher.rb +6 -0
- data/lib/puppeteer/browser_runner.rb +142 -0
- data/lib/puppeteer/cdp_session.rb +78 -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 +447 -0
- data/lib/puppeteer/element_handle.rb +433 -0
- data/lib/puppeteer/emulation_manager.rb +46 -0
- data/lib/puppeteer/errors.rb +4 -0
- data/lib/puppeteer/event_callbackable.rb +88 -0
- data/lib/puppeteer/execution_context.rb +230 -0
- data/lib/puppeteer/frame.rb +278 -0
- data/lib/puppeteer/frame_manager.rb +380 -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 +26 -0
- data/lib/puppeteer/launcher/base.rb +48 -0
- data/lib/puppeteer/launcher/browser_options.rb +41 -0
- data/lib/puppeteer/launcher/chrome.rb +165 -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 +168 -0
- data/lib/puppeteer/mouse.rb +120 -0
- data/lib/puppeteer/network_manager.rb +122 -0
- data/lib/puppeteer/page.rb +1001 -0
- data/lib/puppeteer/page/screenshot_options.rb +78 -0
- data/lib/puppeteer/remote_object.rb +124 -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 +36 -0
- data/lib/puppeteer/wait_task.rb +6 -0
- data/lib/puppeteer/web_socket.rb +117 -0
- data/lib/puppeteer/web_socket_transport.rb +49 -0
- data/puppeteer-ruby.gemspec +29 -0
- metadata +213 -0
@@ -0,0 +1,120 @@
|
|
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 / steps,
|
41
|
+
y: from_y + (@y - from_y) * n / 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
|
+
if delay
|
60
|
+
await_all(
|
61
|
+
async_move(x, y),
|
62
|
+
async_down(button: button, click_count: click_count),
|
63
|
+
)
|
64
|
+
sleep(delay / 1000.0)
|
65
|
+
up(button: button, click_count: click_count)
|
66
|
+
else
|
67
|
+
await_all(
|
68
|
+
async_move(x, y),
|
69
|
+
async_down(button: button, click_count: click_count),
|
70
|
+
async_up(button: button, click_count: click_count),
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param x [number]
|
76
|
+
# @param y [number]
|
77
|
+
# @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
|
78
|
+
# @return [Future]
|
79
|
+
async def async_click(x, y, delay: nil, button: nil, click_count: nil)
|
80
|
+
click(x, y, delay: delay, button: button, click_count: click_count)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
84
|
+
def down(button: nil, click_count: nil)
|
85
|
+
@button = button || Button::LEFT
|
86
|
+
@client.send_message('Input.dispatchMouseEvent',
|
87
|
+
type: 'mousePressed',
|
88
|
+
button: @button,
|
89
|
+
x: @x,
|
90
|
+
y: @y,
|
91
|
+
modifiers: @keyboard.modifiers,
|
92
|
+
clickCount: click_count || 1,
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
97
|
+
# @return [Future]
|
98
|
+
async def async_down(button: nil, click_count: nil)
|
99
|
+
down(button: button, click_count: click_count)
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
103
|
+
def up(button: nil, click_count: nil)
|
104
|
+
@button = Button::NONE
|
105
|
+
@client.send_message('Input.dispatchMouseEvent',
|
106
|
+
type: 'mouseReleased',
|
107
|
+
button: button || Button::LEFT,
|
108
|
+
x: @x,
|
109
|
+
y: @y,
|
110
|
+
modifiers: @keyboard.modifiers,
|
111
|
+
clickCount: click_count || 1,
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
|
116
|
+
# @return [Future]
|
117
|
+
async def async_up(button: nil, click_count: nil)
|
118
|
+
up(button: button, click_count: click_count)
|
119
|
+
end
|
120
|
+
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,1001 @@
|
|
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
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
session = Puppeteer::Connection.from_session(@client).session(event['sessionId'])
|
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
|
+
return 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
|
+
# this._fileChooserInterceptionIsDisabled = false;
|
92
|
+
# this._fileChooserInterceptors = new Set();
|
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('Page.fileChooserOpened', event => this._onFileChooser(event));
|
110
|
+
@target.on_close do
|
111
|
+
emit_event 'Events.Page.Close'
|
112
|
+
@closed = true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def init
|
117
|
+
await_all(
|
118
|
+
@frame_manager.async_init,
|
119
|
+
@client.async_send_message('Target.setAutoAttach', autoAttach: true, waitForDebuggerOnStart: false, flatten: true),
|
120
|
+
@client.async_send_message('Performance.enable'),
|
121
|
+
@client.async_send_message('Log.enable'),
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
125
|
+
# /**
|
126
|
+
# * @param {!Protocol.Page.fileChooserOpenedPayload} event
|
127
|
+
# */
|
128
|
+
# async _onFileChooser(event) {
|
129
|
+
# if (!this._fileChooserInterceptors.size)
|
130
|
+
# return;
|
131
|
+
# const frame = this._frameManager.frame(event.frameId);
|
132
|
+
# const context = await frame.executionContext();
|
133
|
+
# const element = await context._adoptBackendNodeId(event.backendNodeId);
|
134
|
+
# const interceptors = Array.from(this._fileChooserInterceptors);
|
135
|
+
# this._fileChooserInterceptors.clear();
|
136
|
+
# const fileChooser = new FileChooser(this._client, element, event);
|
137
|
+
# for (const interceptor of interceptors)
|
138
|
+
# interceptor.call(null, fileChooser);
|
139
|
+
# }
|
140
|
+
|
141
|
+
# /**
|
142
|
+
# * @param {!{timeout?: number}=} options
|
143
|
+
# * @return !Promise<!FileChooser>}
|
144
|
+
# */
|
145
|
+
# async waitForFileChooser(options = {}) {
|
146
|
+
# if (!this._fileChooserInterceptors.size)
|
147
|
+
# await this._client.send('Page.setInterceptFileChooserDialog', {enabled: true});
|
148
|
+
|
149
|
+
# const {
|
150
|
+
# timeout = this._timeoutSettings.timeout(),
|
151
|
+
# } = options;
|
152
|
+
# let callback;
|
153
|
+
# const promise = new Promise(x => callback = x);
|
154
|
+
# this._fileChooserInterceptors.add(callback);
|
155
|
+
# return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(e => {
|
156
|
+
# this._fileChooserInterceptors.delete(callback);
|
157
|
+
# throw e;
|
158
|
+
# });
|
159
|
+
# }
|
160
|
+
|
161
|
+
|
162
|
+
# /**
|
163
|
+
# * @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options
|
164
|
+
# */
|
165
|
+
# async setGeolocation(options) {
|
166
|
+
# const { longitude, latitude, accuracy = 0} = options;
|
167
|
+
# if (longitude < -180 || longitude > 180)
|
168
|
+
# throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
|
169
|
+
# if (latitude < -90 || latitude > 90)
|
170
|
+
# throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
|
171
|
+
# if (accuracy < 0)
|
172
|
+
# throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
|
173
|
+
# await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy});
|
174
|
+
# }
|
175
|
+
|
176
|
+
attr_reader :target
|
177
|
+
|
178
|
+
def browser
|
179
|
+
@target.browser
|
180
|
+
end
|
181
|
+
|
182
|
+
def browser_context
|
183
|
+
@target.browser_context
|
184
|
+
end
|
185
|
+
|
186
|
+
class TargetCrashedError < StandardError ; end
|
187
|
+
|
188
|
+
private def handle_target_crashed
|
189
|
+
emit_event 'error', TargetCrashedError.new('Page crashed!')
|
190
|
+
end
|
191
|
+
|
192
|
+
private def handle_log_entry_added(event)
|
193
|
+
entry = event["entry"]
|
194
|
+
level = entry["level"]
|
195
|
+
text = entry["text"]
|
196
|
+
source = entry["source"]
|
197
|
+
url = entry["url"]
|
198
|
+
line_number = entry["lineNumber"]
|
199
|
+
|
200
|
+
if_present(entry["args"]) do |args|
|
201
|
+
args.map do |arg|
|
202
|
+
Puppeteer::RemoteObject.new(arg).async_release(@client)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
if source != 'worker'
|
206
|
+
console_message_location = Puppeteer::ConsoleMessage::Location.new(
|
207
|
+
url: url, line_number: line_number)
|
208
|
+
emit_event("Events.Page.Console",
|
209
|
+
Puppeteer::ConsoleMessage.new(level, text, [], console_message_location))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def main_frame
|
214
|
+
@frame_manager.main_frame
|
215
|
+
end
|
216
|
+
|
217
|
+
attr_reader :keyboard, :touch_screen, :coverage, :accessibility
|
218
|
+
|
219
|
+
def frames
|
220
|
+
@frame_manager.frames
|
221
|
+
end
|
222
|
+
|
223
|
+
def workers
|
224
|
+
@workers.values
|
225
|
+
end
|
226
|
+
|
227
|
+
# @param value [Bool]
|
228
|
+
def request_interception=(value)
|
229
|
+
@frame_manager.network_manager.request_interception = value
|
230
|
+
end
|
231
|
+
|
232
|
+
def offline_mode=(enabled)
|
233
|
+
@frame_manager.network_manager.offline_mode = enabled
|
234
|
+
end
|
235
|
+
|
236
|
+
# @param {number} timeout
|
237
|
+
def default_navigation_timeout=(timeout)
|
238
|
+
@timeout_settings.default_navigation_timeout = timeout
|
239
|
+
end
|
240
|
+
|
241
|
+
# @param {number} timeout
|
242
|
+
def default_timeout=(timeout)
|
243
|
+
@timeout_settings.default_timeout = timeout
|
244
|
+
end
|
245
|
+
|
246
|
+
# `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
247
|
+
# @param {string} selector
|
248
|
+
# @return {!Promise<?Puppeteer.ElementHandle>}
|
249
|
+
def S(selector)
|
250
|
+
main_frame.S(selector)
|
251
|
+
end
|
252
|
+
|
253
|
+
# `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
254
|
+
# @param {string} selector
|
255
|
+
# @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
|
256
|
+
def SS(selector)
|
257
|
+
main_frame.SS(selector)
|
258
|
+
end
|
259
|
+
|
260
|
+
# @param {Function|string} pageFunction
|
261
|
+
# @param {!Array<*>} args
|
262
|
+
# @return {!Promise<!Puppeteer.JSHandle>}
|
263
|
+
def evaluate_handle(page_function, *args)
|
264
|
+
context = main_frame.execution_context
|
265
|
+
context.evaluate_handle(page_function, *args)
|
266
|
+
end
|
267
|
+
|
268
|
+
# @param {!Puppeteer.JSHandle} prototypeHandle
|
269
|
+
# @return {!Promise<!Puppeteer.JSHandle>}
|
270
|
+
def query_objects(prototype_handle)
|
271
|
+
context = main_frame.execution_context
|
272
|
+
context.query_objects(prototype_handle)
|
273
|
+
end
|
274
|
+
|
275
|
+
# `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
276
|
+
# @param {string} selector
|
277
|
+
# @param {Function|string} pageFunction
|
278
|
+
# @param {!Array<*>} args
|
279
|
+
# @return {!Promise<(!Object|undefined)>}
|
280
|
+
def Seval(selector, page_function, *args)
|
281
|
+
main_frame.Seval(selector, page_function, *args)
|
282
|
+
end
|
283
|
+
|
284
|
+
# `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
285
|
+
# @param {string} selector
|
286
|
+
# @param {Function|string} pageFunction
|
287
|
+
# @param {!Array<*>} args
|
288
|
+
# @return {!Promise<(!Object|undefined)>}
|
289
|
+
def SSeval(selector, page_function, *args)
|
290
|
+
main_frame.SSeval(selector, page_function, *args)
|
291
|
+
end
|
292
|
+
|
293
|
+
# `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
294
|
+
# @param {string} expression
|
295
|
+
# @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
|
296
|
+
def Sx(expression)
|
297
|
+
main_frame.Sx(expression)
|
298
|
+
end
|
299
|
+
|
300
|
+
# /**
|
301
|
+
# * @param {!Array<string>} urls
|
302
|
+
# * @return {!Promise<!Array<Network.Cookie>>}
|
303
|
+
# */
|
304
|
+
# async cookies(...urls) {
|
305
|
+
# return (await this._client.send('Network.getCookies', {
|
306
|
+
# urls: urls.length ? urls : [this.url()]
|
307
|
+
# })).cookies;
|
308
|
+
# }
|
309
|
+
|
310
|
+
# /**
|
311
|
+
# * @param {Array<Protocol.Network.deleteCookiesParameters>} cookies
|
312
|
+
# */
|
313
|
+
# async deleteCookie(...cookies) {
|
314
|
+
# const pageURL = this.url();
|
315
|
+
# for (const cookie of cookies) {
|
316
|
+
# const item = Object.assign({}, cookie);
|
317
|
+
# if (!cookie.url && pageURL.startsWith('http'))
|
318
|
+
# item.url = pageURL;
|
319
|
+
# await this._client.send('Network.deleteCookies', item);
|
320
|
+
# }
|
321
|
+
# }
|
322
|
+
|
323
|
+
# /**
|
324
|
+
# * @param {Array<Network.CookieParam>} cookies
|
325
|
+
# */
|
326
|
+
# async setCookie(...cookies) {
|
327
|
+
# const pageURL = this.url();
|
328
|
+
# const startsWithHTTP = pageURL.startsWith('http');
|
329
|
+
# const items = cookies.map(cookie => {
|
330
|
+
# const item = Object.assign({}, cookie);
|
331
|
+
# if (!item.url && startsWithHTTP)
|
332
|
+
# item.url = pageURL;
|
333
|
+
# assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`);
|
334
|
+
# assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`);
|
335
|
+
# return item;
|
336
|
+
# });
|
337
|
+
# await this.deleteCookie(...items);
|
338
|
+
# if (items.length)
|
339
|
+
# await this._client.send('Network.setCookies', { cookies: items });
|
340
|
+
# }
|
341
|
+
|
342
|
+
class ScriptTag
|
343
|
+
# @param {!{content?: string, path?: string, type?: string, url?: string}} options
|
344
|
+
def initialize(content: nil, path: nil, type: nil, url: nil)
|
345
|
+
@content = content
|
346
|
+
@path = path
|
347
|
+
@type = type
|
348
|
+
@url = url
|
349
|
+
end
|
350
|
+
attr_reader :content, :path, :type, :url
|
351
|
+
end
|
352
|
+
|
353
|
+
# @param style_tag [Puppeteer::Page::ScriptTag]
|
354
|
+
# @return {!Promise<!ElementHandle>}
|
355
|
+
def add_script_tag(script_tag)
|
356
|
+
main_frame.add_script_tag(script_tag)
|
357
|
+
end
|
358
|
+
|
359
|
+
class StyleTag
|
360
|
+
# @param {!{content?: string, path?: string, url?: string}} options
|
361
|
+
def initialize(content: nil, path: nil, url: nil)
|
362
|
+
@content = content
|
363
|
+
@path = path
|
364
|
+
@url = url
|
365
|
+
end
|
366
|
+
attr_reader :content, :path, :url
|
367
|
+
end
|
368
|
+
|
369
|
+
# @param style_tag [Puppeteer::Page::StyleTag]
|
370
|
+
# @return {!Promise<!ElementHandle>}
|
371
|
+
def add_style_tag(style_tag)
|
372
|
+
main_frame.add_style_tag(style_tag)
|
373
|
+
end
|
374
|
+
|
375
|
+
# /**
|
376
|
+
# * @param {string} name
|
377
|
+
# * @param {Function} puppeteerFunction
|
378
|
+
# */
|
379
|
+
# async exposeFunction(name, puppeteerFunction) {
|
380
|
+
# if (this._pageBindings.has(name))
|
381
|
+
# throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
382
|
+
# this._pageBindings.set(name, puppeteerFunction);
|
383
|
+
|
384
|
+
# const expression = helper.evaluationString(addPageBinding, name);
|
385
|
+
# await this._client.send('Runtime.addBinding', {name: name});
|
386
|
+
# await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: expression});
|
387
|
+
# await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError)));
|
388
|
+
|
389
|
+
# function addPageBinding(bindingName) {
|
390
|
+
# const binding = window[bindingName];
|
391
|
+
# window[bindingName] = (...args) => {
|
392
|
+
# const me = window[bindingName];
|
393
|
+
# let callbacks = me['callbacks'];
|
394
|
+
# if (!callbacks) {
|
395
|
+
# callbacks = new Map();
|
396
|
+
# me['callbacks'] = callbacks;
|
397
|
+
# }
|
398
|
+
# const seq = (me['lastSeq'] || 0) + 1;
|
399
|
+
# me['lastSeq'] = seq;
|
400
|
+
# const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject}));
|
401
|
+
# binding(JSON.stringify({name: bindingName, seq, args}));
|
402
|
+
# return promise;
|
403
|
+
# };
|
404
|
+
# }
|
405
|
+
# }
|
406
|
+
|
407
|
+
# @param username [String?]
|
408
|
+
# @param password [String?]
|
409
|
+
def authenticate(username: nil, password: nil)
|
410
|
+
@frame_manager.network_manager.authenticate(username: username, password: password)
|
411
|
+
end
|
412
|
+
|
413
|
+
# @param headers [Hash]
|
414
|
+
def extra_http_headers=(headers)
|
415
|
+
@frame_manager.network_manager.extra_http_headers = headers
|
416
|
+
end
|
417
|
+
|
418
|
+
# @param user_agent [String]
|
419
|
+
def user_agent=(user_agent)
|
420
|
+
@frame_manager.network_manager.user_agent = user_agent
|
421
|
+
end
|
422
|
+
|
423
|
+
# /**
|
424
|
+
# * @return {!Promise<!Metrics>}
|
425
|
+
# */
|
426
|
+
# async metrics() {
|
427
|
+
# const response = await this._client.send('Performance.getMetrics');
|
428
|
+
# return this._buildMetricsObject(response.metrics);
|
429
|
+
# }
|
430
|
+
|
431
|
+
# /**
|
432
|
+
# * @param {!Protocol.Performance.metricsPayload} event
|
433
|
+
# */
|
434
|
+
# _emitMetrics(event) {
|
435
|
+
# this.emit(Events.Page.Metrics, {
|
436
|
+
# title: event.title,
|
437
|
+
# metrics: this._buildMetricsObject(event.metrics)
|
438
|
+
# });
|
439
|
+
# }
|
440
|
+
|
441
|
+
# /**
|
442
|
+
# * @param {?Array<!Protocol.Performance.Metric>} metrics
|
443
|
+
# * @return {!Metrics}
|
444
|
+
# */
|
445
|
+
# _buildMetricsObject(metrics) {
|
446
|
+
# const result = {};
|
447
|
+
# for (const metric of metrics || []) {
|
448
|
+
# if (supportedMetrics.has(metric.name))
|
449
|
+
# result[metric.name] = metric.value;
|
450
|
+
# }
|
451
|
+
# return result;
|
452
|
+
# }
|
453
|
+
|
454
|
+
# /**
|
455
|
+
# * @param {!Protocol.Runtime.ExceptionDetails} exceptionDetails
|
456
|
+
# */
|
457
|
+
# _handleException(exceptionDetails) {
|
458
|
+
# const message = helper.getExceptionMessage(exceptionDetails);
|
459
|
+
# const err = new Error(message);
|
460
|
+
# err.stack = ''; // Don't report clientside error with a node stack attached
|
461
|
+
# this.emit(Events.Page.PageError, err);
|
462
|
+
# }
|
463
|
+
|
464
|
+
# /**
|
465
|
+
# * @param {!Protocol.Runtime.consoleAPICalledPayload} event
|
466
|
+
# */
|
467
|
+
# async _onConsoleAPI(event) {
|
468
|
+
# if (event.executionContextId === 0) {
|
469
|
+
# // DevTools protocol stores the last 1000 console messages. These
|
470
|
+
# // messages are always reported even for removed execution contexts. In
|
471
|
+
# // this case, they are marked with executionContextId = 0 and are
|
472
|
+
# // reported upon enabling Runtime agent.
|
473
|
+
# //
|
474
|
+
# // Ignore these messages since:
|
475
|
+
# // - there's no execution context we can use to operate with message
|
476
|
+
# // arguments
|
477
|
+
# // - these messages are reported before Puppeteer clients can subscribe
|
478
|
+
# // to the 'console'
|
479
|
+
# // page event.
|
480
|
+
# //
|
481
|
+
# // @see https://github.com/puppeteer/puppeteer/issues/3865
|
482
|
+
# return;
|
483
|
+
# }
|
484
|
+
# const context = this._frameManager.executionContextById(event.executionContextId);
|
485
|
+
# const values = event.args.map(arg => createJSHandle(context, arg));
|
486
|
+
# this._addConsoleMessage(event.type, values, event.stackTrace);
|
487
|
+
# }
|
488
|
+
|
489
|
+
# /**
|
490
|
+
# * @param {!Protocol.Runtime.bindingCalledPayload} event
|
491
|
+
# */
|
492
|
+
# async _onBindingCalled(event) {
|
493
|
+
# const {name, seq, args} = JSON.parse(event.payload);
|
494
|
+
# let expression = null;
|
495
|
+
# try {
|
496
|
+
# const result = await this._pageBindings.get(name)(...args);
|
497
|
+
# expression = helper.evaluationString(deliverResult, name, seq, result);
|
498
|
+
# } catch (error) {
|
499
|
+
# if (error instanceof Error)
|
500
|
+
# expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack);
|
501
|
+
# else
|
502
|
+
# expression = helper.evaluationString(deliverErrorValue, name, seq, error);
|
503
|
+
# }
|
504
|
+
# this._client.send('Runtime.evaluate', { expression, contextId: event.executionContextId }).catch(debugError);
|
505
|
+
|
506
|
+
# /**
|
507
|
+
# * @param {string} name
|
508
|
+
# * @param {number} seq
|
509
|
+
# * @param {*} result
|
510
|
+
# */
|
511
|
+
# function deliverResult(name, seq, result) {
|
512
|
+
# window[name]['callbacks'].get(seq).resolve(result);
|
513
|
+
# window[name]['callbacks'].delete(seq);
|
514
|
+
# }
|
515
|
+
|
516
|
+
# /**
|
517
|
+
# * @param {string} name
|
518
|
+
# * @param {number} seq
|
519
|
+
# * @param {string} message
|
520
|
+
# * @param {string} stack
|
521
|
+
# */
|
522
|
+
# function deliverError(name, seq, message, stack) {
|
523
|
+
# const error = new Error(message);
|
524
|
+
# error.stack = stack;
|
525
|
+
# window[name]['callbacks'].get(seq).reject(error);
|
526
|
+
# window[name]['callbacks'].delete(seq);
|
527
|
+
# }
|
528
|
+
|
529
|
+
# /**
|
530
|
+
# * @param {string} name
|
531
|
+
# * @param {number} seq
|
532
|
+
# * @param {*} value
|
533
|
+
# */
|
534
|
+
# function deliverErrorValue(name, seq, value) {
|
535
|
+
# window[name]['callbacks'].get(seq).reject(value);
|
536
|
+
# window[name]['callbacks'].delete(seq);
|
537
|
+
# }
|
538
|
+
# }
|
539
|
+
|
540
|
+
# /**
|
541
|
+
# * @param {string} type
|
542
|
+
# * @param {!Array<!Puppeteer.JSHandle>} args
|
543
|
+
# * @param {Protocol.Runtime.StackTrace=} stackTrace
|
544
|
+
# */
|
545
|
+
# _addConsoleMessage(type, args, stackTrace) {
|
546
|
+
# if (!this.listenerCount(Events.Page.Console)) {
|
547
|
+
# args.forEach(arg => arg.dispose());
|
548
|
+
# return;
|
549
|
+
# }
|
550
|
+
# const textTokens = [];
|
551
|
+
# for (const arg of args) {
|
552
|
+
# const remoteObject = arg._remoteObject;
|
553
|
+
# if (remoteObject.objectId)
|
554
|
+
# textTokens.push(arg.toString());
|
555
|
+
# else
|
556
|
+
# textTokens.push(helper.valueFromRemoteObject(remoteObject));
|
557
|
+
# }
|
558
|
+
# const location = stackTrace && stackTrace.callFrames.length ? {
|
559
|
+
# url: stackTrace.callFrames[0].url,
|
560
|
+
# lineNumber: stackTrace.callFrames[0].lineNumber,
|
561
|
+
# columnNumber: stackTrace.callFrames[0].columnNumber,
|
562
|
+
# } : {};
|
563
|
+
# const message = new ConsoleMessage(type, textTokens.join(' '), args, location);
|
564
|
+
# this.emit(Events.Page.Console, message);
|
565
|
+
# }
|
566
|
+
|
567
|
+
# _onDialog(event) {
|
568
|
+
# let dialogType = null;
|
569
|
+
# if (event.type === 'alert')
|
570
|
+
# dialogType = Dialog.Type.Alert;
|
571
|
+
# else if (event.type === 'confirm')
|
572
|
+
# dialogType = Dialog.Type.Confirm;
|
573
|
+
# else if (event.type === 'prompt')
|
574
|
+
# dialogType = Dialog.Type.Prompt;
|
575
|
+
# else if (event.type === 'beforeunload')
|
576
|
+
# dialogType = Dialog.Type.BeforeUnload;
|
577
|
+
# assert(dialogType, 'Unknown javascript dialog type: ' + event.type);
|
578
|
+
# const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt);
|
579
|
+
# this.emit(Events.Page.Dialog, dialog);
|
580
|
+
# }
|
581
|
+
|
582
|
+
# @return [String]
|
583
|
+
def url
|
584
|
+
main_frame.url
|
585
|
+
end
|
586
|
+
|
587
|
+
# @return [String]
|
588
|
+
def content
|
589
|
+
main_frame.content
|
590
|
+
end
|
591
|
+
|
592
|
+
# @param {string} html
|
593
|
+
# @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
|
594
|
+
def set_content(html, timeout: nil, wait_until: nil)
|
595
|
+
main_frame.set_content(html, timeout: timeout, wait_until: wait_until)
|
596
|
+
end
|
597
|
+
|
598
|
+
# @param {string} html
|
599
|
+
def content=(html)
|
600
|
+
main_frame.set_content(html)
|
601
|
+
end
|
602
|
+
|
603
|
+
# @param url [String]
|
604
|
+
# @param rederer [String]
|
605
|
+
# @param timeout [number|nil]
|
606
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
607
|
+
def goto(url, referer: nil, timeout: nil, wait_until: nil)
|
608
|
+
main_frame.goto(url, referer: referer, timeout: timeout, wait_until: wait_until)
|
609
|
+
end
|
610
|
+
|
611
|
+
# @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
|
612
|
+
# @return {!Promise<?Puppeteer.Response>}
|
613
|
+
def reload(timeout: nil, wait_until: nil)
|
614
|
+
# const [response] = await Promise.all([
|
615
|
+
# this.waitForNavigation(options),
|
616
|
+
# this._client.send('Page.reload')
|
617
|
+
# ]);
|
618
|
+
# return response;
|
619
|
+
end
|
620
|
+
|
621
|
+
# @param timeout [number|nil]
|
622
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
623
|
+
private def wait_for_navigation(timeout: nil, wait_until: nil)
|
624
|
+
main_frame.wait_for_navigation(timeout: timeout, wait_until: wait_until)
|
625
|
+
end
|
626
|
+
|
627
|
+
# @param timeout [number|nil]
|
628
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
629
|
+
# @return [Future]
|
630
|
+
async def async_wait_for_navigation(timeout: nil, wait_until: nil)
|
631
|
+
wait_for_navigation(timeout: timeout, wait_until: wait_until)
|
632
|
+
end
|
633
|
+
|
634
|
+
# /**
|
635
|
+
# * @param {(string|Function)} urlOrPredicate
|
636
|
+
# * @param {!{timeout?: number}=} options
|
637
|
+
# * @return {!Promise<!Puppeteer.Request>}
|
638
|
+
# */
|
639
|
+
# async waitForRequest(urlOrPredicate, options = {}) {
|
640
|
+
# const {
|
641
|
+
# timeout = this._timeoutSettings.timeout(),
|
642
|
+
# } = options;
|
643
|
+
# return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Request, request => {
|
644
|
+
# if (helper.isString(urlOrPredicate))
|
645
|
+
# return (urlOrPredicate === request.url());
|
646
|
+
# if (typeof urlOrPredicate === 'function')
|
647
|
+
# return !!(urlOrPredicate(request));
|
648
|
+
# return false;
|
649
|
+
# }, timeout, this._sessionClosePromise());
|
650
|
+
# }
|
651
|
+
|
652
|
+
# /**
|
653
|
+
# * @param {(string|Function)} urlOrPredicate
|
654
|
+
# * @param {!{timeout?: number}=} options
|
655
|
+
# * @return {!Promise<!Puppeteer.Response>}
|
656
|
+
# */
|
657
|
+
# async waitForResponse(urlOrPredicate, options = {}) {
|
658
|
+
# const {
|
659
|
+
# timeout = this._timeoutSettings.timeout(),
|
660
|
+
# } = options;
|
661
|
+
# return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Response, response => {
|
662
|
+
# if (helper.isString(urlOrPredicate))
|
663
|
+
# return (urlOrPredicate === response.url());
|
664
|
+
# if (typeof urlOrPredicate === 'function')
|
665
|
+
# return !!(urlOrPredicate(response));
|
666
|
+
# return false;
|
667
|
+
# }, timeout, this._sessionClosePromise());
|
668
|
+
# }
|
669
|
+
|
670
|
+
# @param timeout [number|nil]
|
671
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
672
|
+
def go_back(timeout: nil, wait_until: nil)
|
673
|
+
go(-1, timeout: timeout, wait_until: wait_until)
|
674
|
+
end
|
675
|
+
|
676
|
+
# @param timeout [number|nil]
|
677
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
678
|
+
def go_forward(timeout: nil, wait_until: nil)
|
679
|
+
go(+1, timeout: timeout, wait_until: wait_until)
|
680
|
+
end
|
681
|
+
|
682
|
+
private def go(delta, timeout: nil, wait_until: nil)
|
683
|
+
history = @client.send_message('Page.getNavigationHistory')
|
684
|
+
entries = history['entries']
|
685
|
+
index = history['currentIndex'] + delta
|
686
|
+
if_present(entries[index]) do |entry|
|
687
|
+
await_all(
|
688
|
+
async_wait_for_navigation(timeout: timeout, wait_until: wait_until),
|
689
|
+
@client.async_send_message('Page.navigateToHistoryEntry', entryId: entry['id'])
|
690
|
+
)
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
# @param device [Device]
|
695
|
+
def emulate(device)
|
696
|
+
self.viewport = device.viewport
|
697
|
+
self.user_agent = device.user_agent
|
698
|
+
end
|
699
|
+
|
700
|
+
# @param {boolean} enabled
|
701
|
+
def javascript_enabled=(enabled)
|
702
|
+
return if (@javascript_enabled == enabled)
|
703
|
+
@javascript_enabled = enabled
|
704
|
+
@client.send_message('Emulation.setScriptExecutionDisabled', value: !enabled);
|
705
|
+
end
|
706
|
+
|
707
|
+
# /**
|
708
|
+
# * @param {boolean} enabled
|
709
|
+
# */
|
710
|
+
# async setBypassCSP(enabled) {
|
711
|
+
# await this._client.send('Page.setBypassCSP', { enabled });
|
712
|
+
# }
|
713
|
+
|
714
|
+
# /**
|
715
|
+
# * @param {?string} type
|
716
|
+
# */
|
717
|
+
# async emulateMediaType(type) {
|
718
|
+
# assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type);
|
719
|
+
# await this._client.send('Emulation.setEmulatedMedia', {media: type || ''});
|
720
|
+
# }
|
721
|
+
|
722
|
+
# /**
|
723
|
+
# * @param {?Array<MediaFeature>} features
|
724
|
+
# */
|
725
|
+
# async emulateMediaFeatures(features) {
|
726
|
+
# if (features === null)
|
727
|
+
# await this._client.send('Emulation.setEmulatedMedia', {features: null});
|
728
|
+
# if (Array.isArray(features)) {
|
729
|
+
# features.every(mediaFeature => {
|
730
|
+
# const name = mediaFeature.name;
|
731
|
+
# assert(/^prefers-(?:color-scheme|reduced-motion)$/.test(name), 'Unsupported media feature: ' + name);
|
732
|
+
# return true;
|
733
|
+
# });
|
734
|
+
# await this._client.send('Emulation.setEmulatedMedia', {features: features});
|
735
|
+
# }
|
736
|
+
# }
|
737
|
+
|
738
|
+
# @param timezone_id [String?]
|
739
|
+
def emulate_timezone(timezone_id)
|
740
|
+
@client.send_message('Emulation.setTimezoneOverride', timezoneId: timezoneId || '')
|
741
|
+
rescue => err
|
742
|
+
if err.message.include?('Invalid timezone')
|
743
|
+
raise ArgumentError.new("Invalid timezone ID: #{timezone_id}")
|
744
|
+
else
|
745
|
+
raise err
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
# @param viewport [Viewport]
|
750
|
+
def viewport=(viewport)
|
751
|
+
needs_reload = @emulation_manager.emulate_viewport(viewport)
|
752
|
+
@viewport = viewport
|
753
|
+
reload if needs_reload
|
754
|
+
end
|
755
|
+
|
756
|
+
attr_reader :viewport
|
757
|
+
|
758
|
+
# @param {Function|string} pageFunction
|
759
|
+
# @param {!Array<*>} args
|
760
|
+
# @return {!Promise<*>}
|
761
|
+
def evaluate(page_function, *args)
|
762
|
+
main_frame.evaluate(page_function, *args)
|
763
|
+
end
|
764
|
+
|
765
|
+
# /**
|
766
|
+
# * @param {Function|string} pageFunction
|
767
|
+
# * @param {!Array<*>} args
|
768
|
+
# */
|
769
|
+
# async evaluateOnNewDocument(pageFunction, ...args) {
|
770
|
+
# const source = helper.evaluationString(pageFunction, ...args);
|
771
|
+
# await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
|
772
|
+
# }
|
773
|
+
|
774
|
+
# @param {boolean} enabled
|
775
|
+
def cache_enabled=(enabled)
|
776
|
+
@frame_manager.network_manager.cache_enabled = enabled
|
777
|
+
end
|
778
|
+
|
779
|
+
# @return {!Promise<string>}
|
780
|
+
def title
|
781
|
+
@title
|
782
|
+
end
|
783
|
+
|
784
|
+
# /**
|
785
|
+
# * @param {!ScreenshotOptions=} options
|
786
|
+
# * @return {!Promise<!Buffer|!String>}
|
787
|
+
# */
|
788
|
+
def screenshot(options = {})
|
789
|
+
screenshot_options = ScreenshotOptions.new(options)
|
790
|
+
|
791
|
+
#@screenshot_task_queue.post_task(-> { screenshot_task(screenshot_options.type, screenshot_options) })
|
792
|
+
screenshot_task(screenshot_options.type, screenshot_options)
|
793
|
+
end
|
794
|
+
|
795
|
+
# @param {"png"|"jpeg"} format
|
796
|
+
# @param {!ScreenshotOptions=} options
|
797
|
+
# @return {!Promise<!Buffer|!String>}
|
798
|
+
private def screenshot_task(format, screenshot_options)
|
799
|
+
@client.send_message('Target.activateTarget', targetId: @target.target_id);
|
800
|
+
|
801
|
+
clip = if_present(screenshot_options.clip) do |rect|
|
802
|
+
x = rect[:x].round
|
803
|
+
y = rect[:y].round
|
804
|
+
{ x: x, y: y, width: rect[:width] + rect[:x] - x, height: rect[:height] + rect[:y] - y, scale: 1 }
|
805
|
+
end
|
806
|
+
|
807
|
+
if screenshot_options.full_page?
|
808
|
+
metrics = @client.send_message('Page.getLayoutMetrics')
|
809
|
+
width = metrics['contentSize']['width'].ceil
|
810
|
+
height = metrics['contentSize']['height'].ceil
|
811
|
+
|
812
|
+
# Overwrite clip for full page at all times.
|
813
|
+
clip = { x: 0, y: 0, width: width, height: height, scale: 1 }
|
814
|
+
|
815
|
+
screen_orientation =
|
816
|
+
if @viewport.landscape?
|
817
|
+
{ angle: 90, type: 'landscapePrimary' }
|
818
|
+
else
|
819
|
+
{ angle: 0, type: 'portraitPrimary' }
|
820
|
+
end
|
821
|
+
@client.send_message('Emulation.setDeviceMetricsOverride',
|
822
|
+
mobile: @viewport.mobile?,
|
823
|
+
width: width,
|
824
|
+
height: height,
|
825
|
+
deviceScaleFactor: @viewport.device_scale_factor,
|
826
|
+
screenOrientation: screen_orientation)
|
827
|
+
end
|
828
|
+
|
829
|
+
should_set_default_background = screenshot_options.omit_background? && format == 'png'
|
830
|
+
if should_set_default_background
|
831
|
+
@client.send_message('Emulation.setDefaultBackgroundColorOverride', color: { r: 0, g: 0, b: 0, a: 0 })
|
832
|
+
end
|
833
|
+
screenshot_params = {
|
834
|
+
format: format,
|
835
|
+
quality: screenshot_options.quality,
|
836
|
+
clip: clip,
|
837
|
+
}.compact
|
838
|
+
result = @client.send_message('Page.captureScreenshot', screenshot_params)
|
839
|
+
if should_set_default_background
|
840
|
+
@client.send_message('Emulation.setDefaultBackgroundColorOverride')
|
841
|
+
end
|
842
|
+
|
843
|
+
if screenshot_options.full_page? && @viewport
|
844
|
+
self.viewport = @viewport
|
845
|
+
end
|
846
|
+
|
847
|
+
buffer =
|
848
|
+
if screenshot_options.encoding == 'base64'
|
849
|
+
result['data']
|
850
|
+
else
|
851
|
+
Base64.decode64(result['data'])
|
852
|
+
end
|
853
|
+
|
854
|
+
if screenshot_options.path
|
855
|
+
File.binwrite(screenshot_options.path, buffer)
|
856
|
+
end
|
857
|
+
|
858
|
+
buffer
|
859
|
+
end
|
860
|
+
|
861
|
+
# /**
|
862
|
+
# * @param {!PDFOptions=} options
|
863
|
+
# * @return {!Promise<!Buffer>}
|
864
|
+
# */
|
865
|
+
# async pdf(options = {}) {
|
866
|
+
# const {
|
867
|
+
# scale = 1,
|
868
|
+
# displayHeaderFooter = false,
|
869
|
+
# headerTemplate = '',
|
870
|
+
# footerTemplate = '',
|
871
|
+
# printBackground = false,
|
872
|
+
# landscape = false,
|
873
|
+
# pageRanges = '',
|
874
|
+
# preferCSSPageSize = false,
|
875
|
+
# margin = {},
|
876
|
+
# path = null
|
877
|
+
# } = options;
|
878
|
+
|
879
|
+
# let paperWidth = 8.5;
|
880
|
+
# let paperHeight = 11;
|
881
|
+
# if (options.format) {
|
882
|
+
# const format = Page.PaperFormats[options.format.toLowerCase()];
|
883
|
+
# assert(format, 'Unknown paper format: ' + options.format);
|
884
|
+
# paperWidth = format.width;
|
885
|
+
# paperHeight = format.height;
|
886
|
+
# } else {
|
887
|
+
# paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
|
888
|
+
# paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
|
889
|
+
# }
|
890
|
+
|
891
|
+
# const marginTop = convertPrintParameterToInches(margin.top) || 0;
|
892
|
+
# const marginLeft = convertPrintParameterToInches(margin.left) || 0;
|
893
|
+
# const marginBottom = convertPrintParameterToInches(margin.bottom) || 0;
|
894
|
+
# const marginRight = convertPrintParameterToInches(margin.right) || 0;
|
895
|
+
|
896
|
+
# const result = await this._client.send('Page.printToPDF', {
|
897
|
+
# transferMode: 'ReturnAsStream',
|
898
|
+
# landscape,
|
899
|
+
# displayHeaderFooter,
|
900
|
+
# headerTemplate,
|
901
|
+
# footerTemplate,
|
902
|
+
# printBackground,
|
903
|
+
# scale,
|
904
|
+
# paperWidth,
|
905
|
+
# paperHeight,
|
906
|
+
# marginTop,
|
907
|
+
# marginBottom,
|
908
|
+
# marginLeft,
|
909
|
+
# marginRight,
|
910
|
+
# pageRanges,
|
911
|
+
# preferCSSPageSize
|
912
|
+
# });
|
913
|
+
# return await helper.readProtocolStream(this._client, result.stream, path);
|
914
|
+
# }
|
915
|
+
|
916
|
+
# @param {!{runBeforeUnload: (boolean|undefined)}=} options
|
917
|
+
def close
|
918
|
+
# assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
919
|
+
# const runBeforeUnload = !!options.runBeforeUnload;
|
920
|
+
# if (runBeforeUnload) {
|
921
|
+
# await this._client.send('Page.close');
|
922
|
+
# } else {
|
923
|
+
# await this._client._connection.send('Target.closeTarget', { targetId: this._target._targetId });
|
924
|
+
# await this._target._isClosedPromise;
|
925
|
+
# }
|
926
|
+
end
|
927
|
+
|
928
|
+
# @return [boolean]
|
929
|
+
def closed?
|
930
|
+
@closed
|
931
|
+
end
|
932
|
+
|
933
|
+
attr_reader :mouse
|
934
|
+
|
935
|
+
# @param {string} selector
|
936
|
+
# @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
|
937
|
+
def click(selector, delay: nil, button: nil, click_count: nil)
|
938
|
+
main_frame.click(selector, delay: delay, button: button, click_count: click_count)
|
939
|
+
end
|
940
|
+
|
941
|
+
# @param {string} selector
|
942
|
+
def focus(selector)
|
943
|
+
main_frame.focus(selector)
|
944
|
+
end
|
945
|
+
|
946
|
+
# @param {string} selector
|
947
|
+
def hover(selector)
|
948
|
+
main_frame.hover(selector)
|
949
|
+
end
|
950
|
+
|
951
|
+
# @param {string} selector
|
952
|
+
# @param {!Array<string>} values
|
953
|
+
# @return {!Promise<!Array<string>>}
|
954
|
+
def select(selector, *values)
|
955
|
+
main_frame.select(selector, *values)
|
956
|
+
end
|
957
|
+
|
958
|
+
# @param {string} selector
|
959
|
+
def tap(selector)
|
960
|
+
main_frame.tap(selector)
|
961
|
+
end
|
962
|
+
|
963
|
+
# @param {string} selector
|
964
|
+
# @param {string} text
|
965
|
+
# @param {{delay: (number|undefined)}=} options
|
966
|
+
def type(selector, text, delay: nil)
|
967
|
+
main_frame.type(selector, text, delay: delay)
|
968
|
+
end
|
969
|
+
|
970
|
+
# /**
|
971
|
+
# * @param {(string|number|Function)} selectorOrFunctionOrTimeout
|
972
|
+
# * @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options
|
973
|
+
# * @param {!Array<*>} args
|
974
|
+
# * @return {!Promise<!Puppeteer.JSHandle>}
|
975
|
+
# */
|
976
|
+
# waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
|
977
|
+
# return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
978
|
+
# }
|
979
|
+
|
980
|
+
# @param {string} selector
|
981
|
+
# @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
|
982
|
+
# @return {!Promise<?Puppeteer.ElementHandle>}
|
983
|
+
def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
|
984
|
+
main_frame.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
|
985
|
+
end
|
986
|
+
|
987
|
+
# @param {string} xpath
|
988
|
+
# @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
|
989
|
+
# @return {!Promise<?Puppeteer.ElementHandle>}
|
990
|
+
def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
|
991
|
+
main_frame.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
|
992
|
+
end
|
993
|
+
|
994
|
+
# @param {Function|string} pageFunction
|
995
|
+
# @param {!{polling?: string|number, timeout?: number}=} options
|
996
|
+
# @param {!Array<*>} args
|
997
|
+
# @return {!Promise<!Puppeteer.JSHandle>}
|
998
|
+
def wait_for_function(page_function, options = {}, *args)
|
999
|
+
main_frame.wait_for_function(page_function, options, *args)
|
1000
|
+
end
|
1001
|
+
end
|