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.
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