puppeteer-ruby 0.0.25 → 0.30.0
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 +32 -22
- data/.github/ISSUE_TEMPLATE/bug_report.md +17 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- data/.github/workflows/docs.yml +2 -2
- data/.github/workflows/reviewdog.yml +1 -1
- data/CHANGELOG.md +93 -0
- data/Dockerfile +1 -1
- data/README.md +25 -9
- data/lib/puppeteer.rb +3 -0
- data/lib/puppeteer/aria_query_handler.rb +71 -0
- data/lib/puppeteer/browser.rb +0 -2
- data/lib/puppeteer/concurrent_ruby_utils.rb +6 -3
- data/lib/puppeteer/custom_query_handler.rb +51 -0
- data/lib/puppeteer/define_async_method.rb +15 -6
- data/lib/puppeteer/dom_world.rb +252 -91
- data/lib/puppeteer/element_handle.rb +28 -33
- data/lib/puppeteer/execution_context.rb +12 -0
- data/lib/puppeteer/frame.rb +20 -16
- data/lib/puppeteer/launcher/chrome.rb +4 -1
- data/lib/puppeteer/page.rb +60 -29
- data/lib/puppeteer/page/screenshot_options.rb +2 -2
- data/lib/puppeteer/page/screenshot_task_queue.rb +13 -0
- data/lib/puppeteer/query_handler_manager.rb +65 -0
- data/lib/puppeteer/remote_object.rb +12 -0
- data/lib/puppeteer/target.rb +2 -4
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +16 -4
- data/puppeteer-ruby.gemspec +6 -4
- metadata +45 -10
@@ -314,33 +314,23 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
314
314
|
end
|
315
315
|
end
|
316
316
|
|
317
|
-
|
317
|
+
private def query_handler_manager
|
318
|
+
Puppeteer::QueryHandlerManager.instance
|
319
|
+
end
|
320
|
+
|
321
|
+
# `$()` in JavaScript.
|
318
322
|
# @param selector [String]
|
319
|
-
def
|
320
|
-
|
321
|
-
'(element, selector) => element.querySelector(selector)',
|
322
|
-
selector,
|
323
|
-
)
|
324
|
-
element = handle.as_element
|
325
|
-
|
326
|
-
if element
|
327
|
-
return element
|
328
|
-
end
|
329
|
-
handle.dispose
|
330
|
-
nil
|
323
|
+
def query_selector(selector)
|
324
|
+
query_handler_manager.detect_query_handler(selector).query_one(self)
|
331
325
|
end
|
326
|
+
alias_method :S, :query_selector
|
332
327
|
|
333
|
-
# `$$()` in JavaScript.
|
328
|
+
# `$$()` in JavaScript.
|
334
329
|
# @param selector [String]
|
335
|
-
def
|
336
|
-
|
337
|
-
'(element, selector) => element.querySelectorAll(selector)',
|
338
|
-
selector,
|
339
|
-
)
|
340
|
-
properties = handles.properties
|
341
|
-
handles.dispose
|
342
|
-
properties.values.map(&:as_element).compact
|
330
|
+
def query_selector_all(selector)
|
331
|
+
query_handler_manager.detect_query_handler(selector).query_all(self)
|
343
332
|
end
|
333
|
+
alias_method :SS, :query_selector_all
|
344
334
|
|
345
335
|
class ElementNotFoundError < StandardError
|
346
336
|
def initialize(selector)
|
@@ -348,12 +338,12 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
348
338
|
end
|
349
339
|
end
|
350
340
|
|
351
|
-
# `$eval()` in JavaScript.
|
341
|
+
# `$eval()` in JavaScript.
|
352
342
|
# @param selector [String]
|
353
343
|
# @param page_function [String]
|
354
344
|
# @return [Object]
|
355
|
-
def
|
356
|
-
element_handle =
|
345
|
+
def eval_on_selector(selector, page_function, *args)
|
346
|
+
element_handle = query_selector(selector)
|
357
347
|
unless element_handle
|
358
348
|
raise ElementNotFoundError.new(selector)
|
359
349
|
end
|
@@ -362,25 +352,24 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
362
352
|
|
363
353
|
result
|
364
354
|
end
|
355
|
+
alias_method :Seval, :eval_on_selector
|
365
356
|
|
366
|
-
define_async_method :
|
357
|
+
define_async_method :async_eval_on_selector
|
367
358
|
|
368
|
-
# `$$eval()` in JavaScript.
|
359
|
+
# `$$eval()` in JavaScript.
|
369
360
|
# @param selector [String]
|
370
361
|
# @param page_function [String]
|
371
362
|
# @return [Object]
|
372
|
-
def
|
373
|
-
handles =
|
374
|
-
'(element, selector) => Array.from(element.querySelectorAll(selector))',
|
375
|
-
selector,
|
376
|
-
)
|
363
|
+
def eval_on_selector_all(selector, page_function, *args)
|
364
|
+
handles = query_handler_manager.detect_query_handler(selector).query_all_array(self)
|
377
365
|
result = handles.evaluate(page_function, *args)
|
378
366
|
handles.dispose
|
379
367
|
|
380
368
|
result
|
381
369
|
end
|
370
|
+
alias_method :SSeval, :eval_on_selector_all
|
382
371
|
|
383
|
-
define_async_method :
|
372
|
+
define_async_method :async_eval_on_selector_all
|
384
373
|
|
385
374
|
# `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
386
375
|
# @param expression [String]
|
@@ -430,4 +419,10 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
430
419
|
# https://en.wikipedia.org/wiki/Polygon#Simple_polygons
|
431
420
|
quad.zip(quad.rotate).map { |p1, p2| (p1.x * p2.y - p2.x * p1.y) / 2 }.reduce(:+).abs
|
432
421
|
end
|
422
|
+
|
423
|
+
# used in AriaQueryHandler
|
424
|
+
def query_ax_tree(accessible_name: nil, role: nil)
|
425
|
+
@remote_object.query_ax_tree(@client,
|
426
|
+
accessible_name: accessible_name, role: role)
|
427
|
+
end
|
433
428
|
end
|
@@ -12,10 +12,21 @@ class Puppeteer::ExecutionContext
|
|
12
12
|
@client = client
|
13
13
|
@world = world
|
14
14
|
@context_id = context_payload['id']
|
15
|
+
@context_name = context_payload['name']
|
15
16
|
end
|
16
17
|
|
17
18
|
attr_reader :client, :world
|
18
19
|
|
20
|
+
# only used in DOMWorld
|
21
|
+
private def _context_id
|
22
|
+
@context_id
|
23
|
+
end
|
24
|
+
|
25
|
+
# only used in DOMWorld::BindingFunction#add_binding_to_context
|
26
|
+
private def _context_name
|
27
|
+
@context_name
|
28
|
+
end
|
29
|
+
|
19
30
|
# @return [Puppeteer::Frame]
|
20
31
|
def frame
|
21
32
|
if_present(@world) do |world|
|
@@ -223,6 +234,7 @@ class Puppeteer::ExecutionContext
|
|
223
234
|
remote_object: Puppeteer::RemoteObject.new(response["object"]),
|
224
235
|
)
|
225
236
|
end
|
237
|
+
private define_async_method :async_adopt_backend_node_id
|
226
238
|
|
227
239
|
# @param element_handle [Puppeteer::ElementHandle]
|
228
240
|
# @return [Puppeteer::ElementHandle]
|
data/lib/puppeteer/frame.rb
CHANGED
@@ -61,14 +61,15 @@ class Puppeteer::Frame
|
|
61
61
|
|
62
62
|
define_async_method :async_evaluate
|
63
63
|
|
64
|
-
# `$()` in JavaScript.
|
64
|
+
# `$()` in JavaScript.
|
65
65
|
# @param {string} selector
|
66
66
|
# @return {!Promise<?Puppeteer.ElementHandle>}
|
67
|
-
def
|
68
|
-
@main_world.
|
67
|
+
def query_selector(selector)
|
68
|
+
@main_world.query_selector(selector)
|
69
69
|
end
|
70
|
+
alias_method :S, :query_selector
|
70
71
|
|
71
|
-
define_async_method :
|
72
|
+
define_async_method :async_query_selector
|
72
73
|
|
73
74
|
# `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
74
75
|
# @param {string} expression
|
@@ -79,36 +80,39 @@ class Puppeteer::Frame
|
|
79
80
|
|
80
81
|
define_async_method :async_Sx
|
81
82
|
|
82
|
-
# `$eval()` in JavaScript.
|
83
|
+
# `$eval()` in JavaScript.
|
83
84
|
# @param {string} selector
|
84
85
|
# @param {Function|string} pageFunction
|
85
86
|
# @param {!Array<*>} args
|
86
87
|
# @return {!Promise<(!Object|undefined)>}
|
87
|
-
def
|
88
|
-
@main_world.
|
88
|
+
def eval_on_selector(selector, page_function, *args)
|
89
|
+
@main_world.eval_on_selector(selector, page_function, *args)
|
89
90
|
end
|
91
|
+
alias_method :Seval, :eval_on_selector
|
90
92
|
|
91
|
-
define_async_method :
|
93
|
+
define_async_method :async_eval_on_selector
|
92
94
|
|
93
|
-
# `$$eval()` in JavaScript.
|
95
|
+
# `$$eval()` in JavaScript.
|
94
96
|
# @param {string} selector
|
95
97
|
# @param {Function|string} pageFunction
|
96
98
|
# @param {!Array<*>} args
|
97
99
|
# @return {!Promise<(!Object|undefined)>}
|
98
|
-
def
|
99
|
-
@main_world.
|
100
|
+
def eval_on_selector_all(selector, page_function, *args)
|
101
|
+
@main_world.eval_on_selector_all(selector, page_function, *args)
|
100
102
|
end
|
103
|
+
alias_method :SSeval, :eval_on_selector_all
|
101
104
|
|
102
|
-
define_async_method :
|
105
|
+
define_async_method :async_eval_on_selector_all
|
103
106
|
|
104
|
-
# `$$()` in JavaScript.
|
107
|
+
# `$$()` in JavaScript.
|
105
108
|
# @param {string} selector
|
106
109
|
# @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
|
107
|
-
def
|
108
|
-
@main_world.
|
110
|
+
def query_selector_all(selector)
|
111
|
+
@main_world.query_selector_all(selector)
|
109
112
|
end
|
113
|
+
alias_method :SS, :query_selector_all
|
110
114
|
|
111
|
-
define_async_method :
|
115
|
+
define_async_method :async_query_selector_all
|
112
116
|
|
113
117
|
# @return [String]
|
114
118
|
def content
|
@@ -92,7 +92,7 @@ module Puppeteer::Launcher
|
|
92
92
|
'--disable-default-apps',
|
93
93
|
'--disable-dev-shm-usage',
|
94
94
|
'--disable-extensions',
|
95
|
-
'--disable-features=
|
95
|
+
'--disable-features=Translate',
|
96
96
|
'--disable-hang-monitor',
|
97
97
|
'--disable-ipc-flooding-protection',
|
98
98
|
'--disable-popup-blocking',
|
@@ -105,6 +105,9 @@ module Puppeteer::Launcher
|
|
105
105
|
'--enable-automation',
|
106
106
|
'--password-store=basic',
|
107
107
|
'--use-mock-keychain',
|
108
|
+
# TODO(sadym): remove '--enable-blink-features=IdleDetection'
|
109
|
+
# once IdleDetection is turned on by default.
|
110
|
+
'--enable-blink-features=IdleDetection',
|
108
111
|
]
|
109
112
|
|
110
113
|
if chrome_arg_options.user_data_dir
|
data/lib/puppeteer/page.rb
CHANGED
@@ -3,6 +3,7 @@ require "stringio"
|
|
3
3
|
|
4
4
|
require_relative './page/pdf_options'
|
5
5
|
require_relative './page/screenshot_options'
|
6
|
+
require_relative './page/screenshot_task_queue'
|
6
7
|
|
7
8
|
class Puppeteer::Page
|
8
9
|
include Puppeteer::EventCallbackable
|
@@ -13,10 +14,9 @@ class Puppeteer::Page
|
|
13
14
|
# @param {!Puppeteer.Target} target
|
14
15
|
# @param {boolean} ignoreHTTPSErrors
|
15
16
|
# @param {?Puppeteer.Viewport} defaultViewport
|
16
|
-
# @param {!Puppeteer.TaskQueue} screenshotTaskQueue
|
17
17
|
# @return {!Promise<!Page>}
|
18
|
-
def self.create(client, target, ignore_https_errors, default_viewport
|
19
|
-
page = Puppeteer::Page.new(client, target, ignore_https_errors
|
18
|
+
def self.create(client, target, ignore_https_errors, default_viewport)
|
19
|
+
page = Puppeteer::Page.new(client, target, ignore_https_errors)
|
20
20
|
page.init
|
21
21
|
if default_viewport
|
22
22
|
page.viewport = default_viewport
|
@@ -27,8 +27,7 @@ class Puppeteer::Page
|
|
27
27
|
# @param {!Puppeteer.CDPSession} client
|
28
28
|
# @param {!Puppeteer.Target} target
|
29
29
|
# @param {boolean} ignoreHTTPSErrors
|
30
|
-
|
31
|
-
def initialize(client, target, ignore_https_errors, screenshot_task_queue)
|
30
|
+
def initialize(client, target, ignore_https_errors)
|
32
31
|
@closed = false
|
33
32
|
@client = client
|
34
33
|
@target = target
|
@@ -43,7 +42,7 @@ class Puppeteer::Page
|
|
43
42
|
@page_bindings = {}
|
44
43
|
# @coverage = Coverage.new(client)
|
45
44
|
@javascript_enabled = true
|
46
|
-
@screenshot_task_queue =
|
45
|
+
@screenshot_task_queue = ScreenshotTaskQueue.new
|
47
46
|
|
48
47
|
@workers = {}
|
49
48
|
@client.on_event('Target.attachedToTarget') do |event|
|
@@ -277,23 +276,25 @@ class Puppeteer::Page
|
|
277
276
|
@timeout_settings.default_timeout = timeout
|
278
277
|
end
|
279
278
|
|
280
|
-
# `$()` in JavaScript.
|
279
|
+
# `$()` in JavaScript.
|
281
280
|
# @param {string} selector
|
282
281
|
# @return {!Promise<?Puppeteer.ElementHandle>}
|
283
|
-
def
|
284
|
-
main_frame.
|
282
|
+
def query_selector(selector)
|
283
|
+
main_frame.query_selector(selector)
|
285
284
|
end
|
285
|
+
alias_method :S, :query_selector
|
286
286
|
|
287
|
-
define_async_method :
|
287
|
+
define_async_method :async_query_selector
|
288
288
|
|
289
|
-
# `$$()` in JavaScript.
|
289
|
+
# `$$()` in JavaScript.
|
290
290
|
# @param {string} selector
|
291
291
|
# @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
|
292
|
-
def
|
293
|
-
main_frame.
|
292
|
+
def query_selector_all(selector)
|
293
|
+
main_frame.query_selector_all(selector)
|
294
294
|
end
|
295
|
+
alias_method :SS, :query_selector_all
|
295
296
|
|
296
|
-
define_async_method :
|
297
|
+
define_async_method :async_query_selector_all
|
297
298
|
|
298
299
|
# @param {Function|string} pageFunction
|
299
300
|
# @param {!Array<*>} args
|
@@ -312,25 +313,27 @@ class Puppeteer::Page
|
|
312
313
|
context.query_objects(prototype_handle)
|
313
314
|
end
|
314
315
|
|
315
|
-
# `$eval()` in JavaScript.
|
316
|
+
# `$eval()` in JavaScript.
|
316
317
|
# @param selector [String]
|
317
318
|
# @param page_function [String]
|
318
319
|
# @return [Object]
|
319
|
-
def
|
320
|
-
main_frame.
|
320
|
+
def eval_on_selector(selector, page_function, *args)
|
321
|
+
main_frame.eval_on_selector(selector, page_function, *args)
|
321
322
|
end
|
323
|
+
alias_method :Seval, :eval_on_selector
|
322
324
|
|
323
|
-
define_async_method :
|
325
|
+
define_async_method :async_eval_on_selector
|
324
326
|
|
325
|
-
# `$$eval()` in JavaScript.
|
327
|
+
# `$$eval()` in JavaScript.
|
326
328
|
# @param selector [String]
|
327
329
|
# @param page_function [String]
|
328
330
|
# @return [Object]
|
329
|
-
def
|
330
|
-
main_frame.
|
331
|
+
def eval_on_selector_all(selector, page_function, *args)
|
332
|
+
main_frame.eval_on_selector_all(selector, page_function, *args)
|
331
333
|
end
|
334
|
+
alias_method :SSeval, :eval_on_selector_all
|
332
335
|
|
333
|
-
define_async_method :
|
336
|
+
define_async_method :async_eval_on_selector_all
|
334
337
|
|
335
338
|
# `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
|
336
339
|
# @param {string} expression
|
@@ -830,6 +833,21 @@ class Puppeteer::Page
|
|
830
833
|
end
|
831
834
|
end
|
832
835
|
|
836
|
+
# @param is_user_active [Boolean]
|
837
|
+
# @param is_screen_unlocked [Boolean]
|
838
|
+
def emulate_idle_state(is_user_active: nil, is_screen_unlocked: nil)
|
839
|
+
overrides = {
|
840
|
+
isUserActive: is_user_active,
|
841
|
+
isScreenUnlocked: is_screen_unlocked,
|
842
|
+
}.compact
|
843
|
+
|
844
|
+
if overrides.empty?
|
845
|
+
@client.send_message('Emulation.clearIdleOverride')
|
846
|
+
else
|
847
|
+
@client.send_message('Emulation.setIdleOverride', overrides)
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
833
851
|
# @param viewport [Viewport]
|
834
852
|
def viewport=(viewport)
|
835
853
|
needs_reload = @emulation_manager.emulate_viewport(viewport)
|
@@ -867,15 +885,28 @@ class Puppeteer::Page
|
|
867
885
|
main_frame.title
|
868
886
|
end
|
869
887
|
|
870
|
-
#
|
871
|
-
#
|
872
|
-
#
|
873
|
-
#
|
874
|
-
|
888
|
+
# @param type [String] "png"|"jpeg"
|
889
|
+
# @param path [String]
|
890
|
+
# @param full_page [Boolean]
|
891
|
+
# @param clip [Hash]
|
892
|
+
# @param quality [Integer]
|
893
|
+
# @param omit_background [Boolean]
|
894
|
+
# @param encoding [String]
|
895
|
+
def screenshot(type: nil, path: nil, full_page: nil, clip: nil, quality: nil, omit_background: nil, encoding: nil)
|
896
|
+
options = {
|
897
|
+
type: type,
|
898
|
+
path: path,
|
899
|
+
full_page: full_page,
|
900
|
+
clip: clip,
|
901
|
+
quality: quality,
|
902
|
+
omit_background: omit_background,
|
903
|
+
encoding: encoding,
|
904
|
+
}.compact
|
875
905
|
screenshot_options = ScreenshotOptions.new(options)
|
876
906
|
|
877
|
-
|
878
|
-
|
907
|
+
@screenshot_task_queue.post_task do
|
908
|
+
screenshot_task(screenshot_options.type, screenshot_options)
|
909
|
+
end
|
879
910
|
end
|
880
911
|
|
881
912
|
# @param {"png"|"jpeg"} format
|
@@ -32,14 +32,14 @@ class Puppeteer::Page
|
|
32
32
|
@type ||= 'png'
|
33
33
|
|
34
34
|
if options[:quality]
|
35
|
-
unless @type == '
|
35
|
+
unless @type == 'jpeg'
|
36
36
|
raise ArgumentError.new("options.quality is unsupported for the #{@type} screenshots")
|
37
37
|
end
|
38
38
|
unless options[:quality].is_a?(Numeric)
|
39
39
|
raise ArgumentError.new("Expected options.quality to be a number but found #{options[:quality].class}")
|
40
40
|
end
|
41
41
|
quality = options[:quality].to_i
|
42
|
-
unless (0..100).include?(
|
42
|
+
unless (0..100).include?(quality)
|
43
43
|
raise ArgumentError.new("Expected options.quality to be between 0 and 100 (inclusive), got #{quality}")
|
44
44
|
end
|
45
45
|
@quality = quality
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Puppeteer::Page
|
2
|
+
class ScreenshotTaskQueue
|
3
|
+
def initialize
|
4
|
+
@chain = Concurrent::Promises.fulfilled_future(nil)
|
5
|
+
end
|
6
|
+
|
7
|
+
def post_task(&block)
|
8
|
+
result = @chain.then { block.call }
|
9
|
+
@chain = result.rescue { nil }
|
10
|
+
result.value!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
class Puppeteer::QueryHandlerManager
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
def query_handlers
|
7
|
+
@query_handlers ||= {
|
8
|
+
aria: Puppeteer::AriaQueryHandler.new,
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
private def default_handler
|
13
|
+
@default_handler ||= Puppeteer::CustomQueryHandler.new(
|
14
|
+
query_one: '(element, selector) => element.querySelector(selector)',
|
15
|
+
query_all: '(element, selector) => element.querySelectorAll(selector)',
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
class Result
|
20
|
+
def initialize(query_handler:, selector:)
|
21
|
+
@query_handler = query_handler
|
22
|
+
@selector = selector
|
23
|
+
end
|
24
|
+
|
25
|
+
def query_one(element_handle)
|
26
|
+
@query_handler.query_one(element_handle, @selector)
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait_for(dom_world, visible:, hidden:, timeout:)
|
30
|
+
@query_handler.wait_for(dom_world, @selector, visible: visible, hidden: hidden, timeout: timeout)
|
31
|
+
end
|
32
|
+
|
33
|
+
def query_all(element_handle)
|
34
|
+
@query_handler.query_all(element_handle, @selector)
|
35
|
+
end
|
36
|
+
|
37
|
+
def query_all_array(element_handle)
|
38
|
+
@query_handler.query_all_array(element_handle, @selector)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def detect_query_handler(selector)
|
43
|
+
unless /^[a-zA-Z]+\// =~ selector
|
44
|
+
return Result.new(
|
45
|
+
query_handler: default_handler,
|
46
|
+
selector: selector,
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
chunk = selector.split("/")
|
51
|
+
name = chunk.shift
|
52
|
+
updated_selector = chunk.join("/")
|
53
|
+
|
54
|
+
query_handler = query_handlers[name.to_sym]
|
55
|
+
|
56
|
+
unless query_handler
|
57
|
+
raise ArgumentError.new("Query set to use \"#{name}\", but no query handler of that name was found")
|
58
|
+
end
|
59
|
+
|
60
|
+
Result.new(
|
61
|
+
query_handler: query_handler,
|
62
|
+
selector: updated_selector,
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|