puppeteer-ruby 0.0.2
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/.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
|