puppeteer-ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +36 -0
  5. data/.travis.yml +7 -0
  6. data/Dockerfile +6 -0
  7. data/Gemfile +6 -0
  8. data/README.md +41 -0
  9. data/Rakefile +1 -0
  10. data/bin/console +11 -0
  11. data/bin/setup +8 -0
  12. data/docker-compose.yml +15 -0
  13. data/example.rb +7 -0
  14. data/lib/puppeteer.rb +192 -0
  15. data/lib/puppeteer/async_await_behavior.rb +34 -0
  16. data/lib/puppeteer/browser.rb +240 -0
  17. data/lib/puppeteer/browser_context.rb +90 -0
  18. data/lib/puppeteer/browser_fetcher.rb +6 -0
  19. data/lib/puppeteer/browser_runner.rb +142 -0
  20. data/lib/puppeteer/cdp_session.rb +78 -0
  21. data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
  22. data/lib/puppeteer/connection.rb +254 -0
  23. data/lib/puppeteer/console_message.rb +24 -0
  24. data/lib/puppeteer/debug_print.rb +20 -0
  25. data/lib/puppeteer/device.rb +12 -0
  26. data/lib/puppeteer/devices.rb +885 -0
  27. data/lib/puppeteer/dom_world.rb +447 -0
  28. data/lib/puppeteer/element_handle.rb +433 -0
  29. data/lib/puppeteer/emulation_manager.rb +46 -0
  30. data/lib/puppeteer/errors.rb +4 -0
  31. data/lib/puppeteer/event_callbackable.rb +88 -0
  32. data/lib/puppeteer/execution_context.rb +230 -0
  33. data/lib/puppeteer/frame.rb +278 -0
  34. data/lib/puppeteer/frame_manager.rb +380 -0
  35. data/lib/puppeteer/if_present.rb +18 -0
  36. data/lib/puppeteer/js_handle.rb +142 -0
  37. data/lib/puppeteer/keyboard.rb +183 -0
  38. data/lib/puppeteer/keyboard/key_description.rb +19 -0
  39. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
  40. data/lib/puppeteer/launcher.rb +26 -0
  41. data/lib/puppeteer/launcher/base.rb +48 -0
  42. data/lib/puppeteer/launcher/browser_options.rb +41 -0
  43. data/lib/puppeteer/launcher/chrome.rb +165 -0
  44. data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
  45. data/lib/puppeteer/launcher/launch_options.rb +68 -0
  46. data/lib/puppeteer/lifecycle_watcher.rb +168 -0
  47. data/lib/puppeteer/mouse.rb +120 -0
  48. data/lib/puppeteer/network_manager.rb +122 -0
  49. data/lib/puppeteer/page.rb +1001 -0
  50. data/lib/puppeteer/page/screenshot_options.rb +78 -0
  51. data/lib/puppeteer/remote_object.rb +124 -0
  52. data/lib/puppeteer/target.rb +150 -0
  53. data/lib/puppeteer/timeout_settings.rb +15 -0
  54. data/lib/puppeteer/touch_screen.rb +43 -0
  55. data/lib/puppeteer/version.rb +3 -0
  56. data/lib/puppeteer/viewport.rb +36 -0
  57. data/lib/puppeteer/wait_task.rb +6 -0
  58. data/lib/puppeteer/web_socket.rb +117 -0
  59. data/lib/puppeteer/web_socket_transport.rb +49 -0
  60. data/puppeteer-ruby.gemspec +29 -0
  61. 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