puppeteer-ruby 0.0.14 → 0.0.19

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.
@@ -156,6 +156,6 @@ class Puppeteer::BrowserRunner
156
156
  end
157
157
  end
158
158
  rescue Timeout::Error
159
- raise Puppeteer::TimeoutError.new("Timed out after #{timeout} ms while trying to connect to the browser! Only Chrome at revision r#{preferredRevision} is guaranteed to work.")
159
+ raise Puppeteer::TimeoutError.new("Timed out after #{timeout} ms while trying to connect to the browser! Only Chrome at revision r#{preferred_revision} is guaranteed to work.")
160
160
  end
161
161
  end
@@ -49,13 +49,18 @@ class Puppeteer::Connection
49
49
  async_handle_message(message)
50
50
  end
51
51
  @transport.on_close do |reason, code|
52
- handle_close(reason, code)
52
+ handle_close
53
53
  end
54
54
 
55
55
  @sessions = {}
56
56
  @closed = false
57
57
  end
58
58
 
59
+ # used only in Browser#connected?
60
+ def closed?
61
+ @closed
62
+ end
63
+
59
64
  private def sleep_before_handling_message(message)
60
65
  # Puppeteer doesn't handle any Network monitoring responses.
61
66
  # So we don't have to sleep.
@@ -1,9 +1,9 @@
1
1
  require 'logger'
2
2
 
3
3
  module Puppeteer::DebugPrint
4
- if ['1', 'true'].include?(ENV['DEBUG'])
4
+ if Puppeteer.env.debug?
5
5
  def debug_puts(*args, **kwargs)
6
- @__debug_logger ||= Logger.new(STDOUT)
6
+ @__debug_logger ||= Logger.new($stdout)
7
7
  @__debug_logger.debug(*args, **kwargs)
8
8
  end
9
9
 
@@ -14,7 +14,7 @@ module Puppeteer::DefineAsyncMethod
14
14
  Concurrent::Promises.future do
15
15
  original_method.bind(self).call(*args)
16
16
  rescue => err
17
- Logger.new(STDERR).warn(err)
17
+ Logger.new($stderr).warn(err)
18
18
  raise err
19
19
  end
20
20
  end
@@ -132,45 +132,48 @@ class Puppeteer::DOMWorld
132
132
  document.SS(selector)
133
133
  end
134
134
 
135
- # /**
136
- # * @return {!Promise<String>}
137
- # */
138
- # async content() {
139
- # return await this.evaluate(() => {
140
- # let retVal = '';
141
- # if (document.doctype)
142
- # retVal = new XMLSerializer().serializeToString(document.doctype);
143
- # if (document.documentElement)
144
- # retVal += document.documentElement.outerHTML;
145
- # return retVal;
146
- # });
147
- # }
135
+ # @return [String]
136
+ def content
137
+ evaluate <<-JAVASCRIPT
138
+ () => {
139
+ let retVal = '';
140
+ if (document.doctype)
141
+ retVal = new XMLSerializer().serializeToString(document.doctype);
142
+ if (document.documentElement)
143
+ retVal += document.documentElement.outerHTML;
144
+ return retVal;
145
+ }
146
+ JAVASCRIPT
147
+ end
148
148
 
149
- # /**
150
- # * @param {string} html
151
- # * @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
152
- # */
153
- # async setContent(html, options = {}) {
154
- # const {
155
- # waitUntil = ['load'],
156
- # timeout = this._timeoutSettings.navigationTimeout(),
157
- # } = options;
158
- # // We rely upon the fact that document.open() will reset frame lifecycle with "init"
159
- # // lifecycle event. @see https://crrev.com/608658
160
- # await this.evaluate(html => {
161
- # document.open();
162
- # document.write(html);
163
- # document.close();
164
- # }, html);
165
- # const watcher = new LifecycleWatcher(this._frameManager, this._frame, waitUntil, timeout);
166
- # const error = await Promise.race([
167
- # watcher.timeoutOrTerminationPromise(),
168
- # watcher.lifecyclePromise(),
169
- # ]);
170
- # watcher.dispose();
171
- # if (error)
172
- # throw error;
173
- # }
149
+ # @param html [String]
150
+ # @param timeout [Integer]
151
+ # @param wait_until [String|Array<String>]
152
+ def set_content(html, timeout: nil, wait_until: nil)
153
+ option_wait_until = [wait_until || 'load'].flatten
154
+ option_timeout = @timeout_settings.navigation_timeout
155
+
156
+ # We rely upon the fact that document.open() will reset frame lifecycle with "init"
157
+ # lifecycle event. @see https://crrev.com/608658
158
+ js = <<-JAVASCRIPT
159
+ (html) => {
160
+ document.open();
161
+ document.write(html);
162
+ document.close();
163
+ }
164
+ JAVASCRIPT
165
+ evaluate(js, html)
166
+
167
+ watcher = Puppeteer::LifecycleWatcher.new(@frame_manager, @frame, option_wait_until, option_timeout)
168
+ begin
169
+ await_any(
170
+ watcher.timeout_or_termination_promise,
171
+ watcher.lifecycle_promise,
172
+ )
173
+ ensure
174
+ watcher.dispose
175
+ end
176
+ end
174
177
 
175
178
  # /**
176
179
  # * @param {!{url?: string, path?: string, content?: string, type?: string}} options
@@ -311,25 +314,28 @@ class Puppeteer::DOMWorld
311
314
  # }
312
315
  # }
313
316
 
317
+ class ElementNotFoundError < StandardError
318
+ def initialize(selector)
319
+ super("No node found for selector: #{selector}")
320
+ end
321
+ end
322
+
314
323
  # @param selector [String]
315
324
  # @param delay [Number]
316
325
  # @param button [String] "left"|"right"|"middle"
317
326
  # @param click_count [Number]
318
327
  def click(selector, delay: nil, button: nil, click_count: nil)
319
- handle = S(selector)
328
+ handle = S(selector) or raise ElementNotFoundError.new(selector)
320
329
  handle.click(delay: delay, button: button, click_count: click_count)
321
330
  handle.dispose
322
331
  end
323
332
 
324
- # /**
325
- # * @param {string} selector
326
- # */
327
- # async focus(selector) {
328
- # const handle = await this.$(selector);
329
- # assert(handle, 'No node found for selector: ' + selector);
330
- # await handle.focus();
331
- # await handle.dispose();
332
- # }
333
+ # @param selector [String]
334
+ def focus(selector)
335
+ handle = S(selector) or raise ElementNotFoundError.new(selector)
336
+ handle.focus
337
+ handle.dispose
338
+ end
333
339
 
334
340
  # /**
335
341
  # * @param {string} selector
@@ -344,7 +350,7 @@ class Puppeteer::DOMWorld
344
350
  # @param selector [String]
345
351
  # @return [Array<String>]
346
352
  def select(selector, *values)
347
- handle = S(selector)
353
+ handle = S(selector) or raise ElementNotFoundError.new(selector)
348
354
  result = handle.select(*values)
349
355
  handle.dispose
350
356
 
@@ -353,7 +359,7 @@ class Puppeteer::DOMWorld
353
359
 
354
360
  # @param selector [String]
355
361
  def tap(selector)
356
- handle = S(selector)
362
+ handle = S(selector) or raise ElementNotFoundError.new(selector)
357
363
  handle.tap
358
364
  handle.dispose
359
365
  end
@@ -362,7 +368,7 @@ class Puppeteer::DOMWorld
362
368
  # @param text [String]
363
369
  # @param delay [Number]
364
370
  def type_text(selector, text, delay: nil)
365
- handle = S(selector)
371
+ handle = S(selector) or raise ElementNotFoundError.new(selector)
366
372
  handle.type_text(text, delay: delay)
367
373
  handle.dispose
368
374
  end
@@ -396,6 +402,26 @@ class Puppeteer::DOMWorld
396
402
  # return new WaitTask(this, pageFunction, 'function', polling, timeout, ...args).promise;
397
403
  # }
398
404
 
405
+ # @param page_function [String]
406
+ # @param args [Array]
407
+ # @param polling [Integer|String]
408
+ # @param timeout [Integer]
409
+ # @return [Puppeteer::JSHandle]
410
+ def wait_for_function(page_function, args: [], polling: nil, timeout: nil)
411
+ option_polling = polling || 'raf'
412
+ option_timeout = timeout || @timeout_settings.timeout
413
+
414
+ Puppeteer::WaitTask.new(
415
+ dom_world: self,
416
+ predicate_body: page_function,
417
+ title: 'function',
418
+ polling: option_polling,
419
+ timeout: option_timeout,
420
+ args: args,
421
+ ).await_promise
422
+ end
423
+
424
+
399
425
  # @return [String]
400
426
  def title
401
427
  evaluate('() => document.title')
@@ -421,7 +447,7 @@ class Puppeteer::DOMWorld
421
447
 
422
448
  wait_task = Puppeteer::WaitTask.new(
423
449
  dom_world: self,
424
- predicate_body: "return (#{PREDICATE})(...args)",
450
+ predicate_body: PREDICATE,
425
451
  title: title,
426
452
  polling: polling,
427
453
  timeout: option_timeout,
@@ -3,6 +3,7 @@ require_relative './element_handle/box_model'
3
3
  require_relative './element_handle/point'
4
4
 
5
5
  class Puppeteer::ElementHandle < Puppeteer::JSHandle
6
+ include Puppeteer::DebugPrint
6
7
  include Puppeteer::IfPresent
7
8
  using Puppeteer::DefineAsyncMethod
8
9
 
@@ -23,7 +24,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
23
24
  end
24
25
 
25
26
  def content_frame
26
- node_info = @remote_object.node_info
27
+ node_info = @remote_object.node_info(@client)
27
28
  frame_id = node_info['node']['frameId']
28
29
  if frame_id.is_a?(String)
29
30
  @frame_manager.frame(frame_id)
@@ -42,7 +43,24 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
42
43
  if (element.nodeType !== Node.ELEMENT_NODE)
43
44
  return 'Node is not of type HTMLElement';
44
45
 
45
- element.scrollIntoViewIfNeeded({block: 'center', inline: 'center', behavior: 'instant'});
46
+ if (element.scrollIntoViewIfNeeded) {
47
+ element.scrollIntoViewIfNeeded({block: 'center', inline: 'center', behavior: 'instant'});
48
+ } else {
49
+ // force-scroll if page's javascript is disabled.
50
+ if (!pageJavascriptEnabled) {
51
+ element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
52
+ return false;
53
+ }
54
+ const visibleRatio = await new Promise(resolve => {
55
+ const observer = new IntersectionObserver(entries => {
56
+ resolve(entries[0].intersectionRatio);
57
+ observer.disconnect();
58
+ });
59
+ observer.observe(element);
60
+ });
61
+ if (visibleRatio !== 1.0)
62
+ element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
63
+ }
46
64
  return false;
47
65
  }
48
66
  JAVASCRIPT
@@ -62,7 +80,14 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
62
80
  end
63
81
 
64
82
  def clickable_point
65
- result = @remote_object.content_quads(@client)
83
+ result =
84
+ begin
85
+ @remote_object.content_quads(@client)
86
+ rescue => err
87
+ debug_puts(err)
88
+ nil
89
+ end
90
+
66
91
  if !result || result["quads"].empty?
67
92
  raise ElementNotVisibleError.new
68
93
  end
@@ -208,10 +233,11 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
208
233
  define_async_method :async_type_text
209
234
 
210
235
  # @param key [String]
236
+ # @param text [String]
211
237
  # @param delay [number|nil]
212
- def press(key, delay: nil)
238
+ def press(key, delay: nil, text: nil)
213
239
  focus
214
- @page.keyboard.press(key, delay: delay)
240
+ @page.keyboard.press(key, delay: delay, text: text)
215
241
  end
216
242
 
217
243
  define_async_method :async_press
@@ -379,21 +405,24 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
379
405
 
380
406
  define_async_method :async_Sx
381
407
 
382
- # /**
383
- # * @returns {!Promise<boolean>}
384
- # */
385
- # isIntersectingViewport() {
386
- # return this.evaluate(async element => {
387
- # const visibleRatio = await new Promise(resolve => {
388
- # const observer = new IntersectionObserver(entries => {
389
- # resolve(entries[0].intersectionRatio);
390
- # observer.disconnect();
391
- # });
392
- # observer.observe(element);
393
- # });
394
- # return visibleRatio > 0;
395
- # });
396
- # }
408
+ # in JS, #isIntersectingViewport.
409
+ # @return [Boolean]
410
+ def intersecting_viewport?
411
+ js = <<~JAVASCRIPT
412
+ async element => {
413
+ const visibleRatio = await new Promise(resolve => {
414
+ const observer = new IntersectionObserver(entries => {
415
+ resolve(entries[0].intersectionRatio);
416
+ observer.disconnect();
417
+ });
418
+ observer.observe(element);
419
+ });
420
+ return visibleRatio > 0;
421
+ }
422
+ JAVASCRIPT
423
+
424
+ evaluate(js)
425
+ end
397
426
 
398
427
  # @param quad [Array<Point>]
399
428
  private def compute_quad_area(quad)
@@ -0,0 +1,23 @@
1
+ class Puppeteer::Env
2
+ # indicates whether DEBUG=1 is specified.
3
+ #
4
+ # @return [Boolean]
5
+ def debug?
6
+ ['1', 'true'].include?(ENV['DEBUG'].to_s)
7
+ end
8
+
9
+ def ci?
10
+ ['1', 'true'].include?(ENV['CI'].to_s)
11
+ end
12
+
13
+ # check if running on macOS
14
+ def darwin?
15
+ RUBY_PLATFORM.include?('darwin')
16
+ end
17
+ end
18
+
19
+ class Puppeteer
20
+ def self.env
21
+ Puppeteer::Env.new
22
+ end
23
+ end
@@ -110,12 +110,14 @@ class Puppeteer::Frame
110
110
 
111
111
  define_async_method :async_SS
112
112
 
113
+ # @return [String]
113
114
  def content
114
115
  @secondary_world.content
115
116
  end
116
117
 
117
- # @param {string} html
118
- # @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
118
+ # @param html [String]
119
+ # @param timeout [Integer]
120
+ # @param wait_until [String|Array<String>]
119
121
  def set_content(html, timeout: nil, wait_until: nil)
120
122
  @secondary_world.set_content(html, timeout: timeout, wait_until: wait_until)
121
123
  end
@@ -140,7 +142,7 @@ class Puppeteer::Frame
140
142
  end
141
143
 
142
144
  def child_frames
143
- @child_frames.dup
145
+ @child_frames.to_a
144
146
  end
145
147
 
146
148
  def detached?
@@ -206,28 +208,6 @@ class Puppeteer::Frame
206
208
 
207
209
  define_async_method :async_type_text
208
210
 
209
- # /**
210
- # * @param {(string|number|Function)} selectorOrFunctionOrTimeout
211
- # * @param {!Object=} options
212
- # * @param {!Array<*>} args
213
- # * @return {!Promise<?Puppeteer.JSHandle>}
214
- # */
215
- # waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
216
- # const xPathPattern = '//';
217
-
218
- # if (helper.isString(selectorOrFunctionOrTimeout)) {
219
- # const string = /** @type {string} */ (selectorOrFunctionOrTimeout);
220
- # if (string.startsWith(xPathPattern))
221
- # return this.waitForXPath(string, options);
222
- # return this.waitForSelector(string, options);
223
- # }
224
- # if (helper.isNumber(selectorOrFunctionOrTimeout))
225
- # return new Promise(fulfill => setTimeout(fulfill, /** @type {number} */ (selectorOrFunctionOrTimeout)));
226
- # if (typeof selectorOrFunctionOrTimeout === 'function')
227
- # return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);
228
- # return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
229
- # }
230
-
231
211
  # @param selector [String]
232
212
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
233
213
  # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
@@ -245,6 +225,11 @@ class Puppeteer::Frame
245
225
 
246
226
  define_async_method :async_wait_for_selector
247
227
 
228
+ # @param milliseconds [Integer] the number of milliseconds to wait.
229
+ def wait_for_timeout(milliseconds)
230
+ sleep(milliseconds / 1000.0)
231
+ end
232
+
248
233
  # @param xpath [String]
249
234
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
250
235
  # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
@@ -262,12 +247,13 @@ class Puppeteer::Frame
262
247
 
263
248
  define_async_method :async_wait_for_xpath
264
249
 
265
- # @param {Function|string} pageFunction
266
- # @param {!{polling?: string|number, timeout?: number}=} options
267
- # @param {!Array<*>} args
268
- # @return {!Promise<!Puppeteer.JSHandle>}
269
- def wait_for_function(page_function, options = {}, *args)
270
- @main_world.wait_for_function(page_function, options, *args)
250
+ # @param page_function [String]
251
+ # @param args [Integer|Array]
252
+ # @param polling [String]
253
+ # @param timeout [Integer]
254
+ # @return [Puppeteer::JSHandle]
255
+ def wait_for_function(page_function, args: [], polling: nil, timeout: nil)
256
+ @main_world.wait_for_function(page_function, args: args, polling: polling, timeout: timeout)
271
257
  end
272
258
 
273
259
  define_async_method :async_wait_for_function