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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +25 -2
- data/.github/workflows/reviewdog.yml +1 -1
- data/.rubocop.yml +49 -3
- data/Dockerfile +9 -0
- data/README.md +3 -1
- data/docker-compose.yml +34 -0
- data/lib/puppeteer.rb +16 -12
- data/lib/puppeteer/browser.rb +36 -9
- data/lib/puppeteer/browser_context.rb +35 -5
- data/lib/puppeteer/browser_runner.rb +1 -1
- data/lib/puppeteer/connection.rb +6 -1
- data/lib/puppeteer/debug_print.rb +2 -2
- data/lib/puppeteer/define_async_method.rb +1 -1
- data/lib/puppeteer/dom_world.rb +78 -52
- data/lib/puppeteer/element_handle.rb +49 -20
- data/lib/puppeteer/env.rb +23 -0
- data/lib/puppeteer/frame.rb +17 -31
- data/lib/puppeteer/js_handle.rb +37 -27
- data/lib/puppeteer/keyboard.rb +3 -2
- data/lib/puppeteer/launcher.rb +11 -1
- data/lib/puppeteer/launcher/base.rb +14 -4
- data/lib/puppeteer/launcher/chrome.rb +1 -1
- data/lib/puppeteer/launcher/firefox.rb +392 -0
- data/lib/puppeteer/mouse.rb +16 -0
- data/lib/puppeteer/network_manager.rb +163 -5
- data/lib/puppeteer/page.rb +195 -125
- data/lib/puppeteer/page/pdf_options.rb +166 -0
- data/lib/puppeteer/remote_object.rb +28 -1
- data/lib/puppeteer/request.rb +330 -0
- data/lib/puppeteer/response.rb +113 -0
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +1 -1
- data/lib/puppeteer/web_socket.rb +7 -0
- data/puppeteer-ruby.gemspec +2 -1
- metadata +25 -4
@@ -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#{
|
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
|
data/lib/puppeteer/connection.rb
CHANGED
@@ -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
|
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
|
4
|
+
if Puppeteer.env.debug?
|
5
5
|
def debug_puts(*args, **kwargs)
|
6
|
-
@__debug_logger ||= Logger.new(
|
6
|
+
@__debug_logger ||= Logger.new($stdout)
|
7
7
|
@__debug_logger.debug(*args, **kwargs)
|
8
8
|
end
|
9
9
|
|
data/lib/puppeteer/dom_world.rb
CHANGED
@@ -132,45 +132,48 @@ class Puppeteer::DOMWorld
|
|
132
132
|
document.SS(selector)
|
133
133
|
end
|
134
134
|
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
#
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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:
|
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
|
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 =
|
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
|
-
#
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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
|
data/lib/puppeteer/frame.rb
CHANGED
@@ -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
|
118
|
-
# @param
|
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.
|
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
|
266
|
-
# @param
|
267
|
-
# @param
|
268
|
-
# @
|
269
|
-
|
270
|
-
|
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
|