puppeteer-ruby 0.37.3 → 0.40.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84ede7b4fc083303903e960a08d2a21b44866b2b47662489005e09e6e955df01
4
- data.tar.gz: aba2c71b916d6779560a200fff6685cb9f4ba83dc2b04e092926fb121add3155
3
+ metadata.gz: af966d033f8cff7c99c121805ffe7dde6f4d73cfe0fa9f34937b9eedbeafd9b8
4
+ data.tar.gz: 3578653749391fd953144e21160beb6baee5eb6463b43b8cecafcf3f3433bda7
5
5
  SHA512:
6
- metadata.gz: 3b6a5cc66476c8954ab1ae942df3be6ad161ffe3aeb9b1ea51bde681f6cd9cf32d61d251980ab5d7f0907923b5ac42871cd7e99fbb55f0c00205ee231190f595
7
- data.tar.gz: 7329fc6a4f8d1933efee34180c6d051e73613da2189b2af5226629d681c9f4b52abadf5eb548f45cb3af37d3ab1f093067b5e67e10e8c3cde09815a4fa3a0e4c
6
+ metadata.gz: 9ae375c2763cd2fd597f72b0e82ccae84eba414cb4e1b7b3e0067c250c869020118f58fc1d74e0bfe0bb512318501fcedd5c1a25f3725de44a29e01393227765
7
+ data.tar.gz: 4dec50af05864ec0410e4d99bbec2cdccfbf06412eb21b3f923be4de892244510ba4a5d553dafc04b62d6de6381caf852238a9e971d2ba2d30c3009e78c9e048
data/.rubocop.yml CHANGED
@@ -149,6 +149,8 @@ Style/MethodCallWithArgsParentheses:
149
149
  # utils
150
150
  - debug_print
151
151
  - debug_puts
152
+ - exit
153
+ - puts
152
154
 
153
155
  Style/MethodCallWithoutArgsParentheses:
154
156
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,7 +1,31 @@
1
- ### master [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.3...master)]
1
+ ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.39.0...main)]
2
2
 
3
3
  - xxx
4
4
 
5
+ ### 0.39.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.38.0...0.39.0)]
6
+
7
+ New features:
8
+
9
+ - Puppeteer 12.0 functionalities
10
+
11
+ ### 0.38.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.4...0.38.0)]
12
+
13
+ New features:
14
+
15
+ - Puppeteer 11.0 functionalities
16
+ * OOP iframe support
17
+ * Customize debugging port
18
+ * HTTPRequest#initiator
19
+ * Customize temp directory for user data
20
+ * Add webp to screenshot quality option allow list
21
+
22
+
23
+ ### 0.37.4 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.3...0.37.4)]
24
+
25
+ Bugfix:
26
+
27
+ - Fix ElementHandle#screenshot to work.
28
+
5
29
  ### 0.37.3 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.2...0.37.3)]
6
30
 
7
31
  Improvement:
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
- - Puppeteer version: v12.0.0
3
- - puppeteer-ruby version: 0.37.3
2
+ - Puppeteer version: v13.0.1
3
+ - puppeteer-ruby version: 0.40.0
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -134,7 +134,7 @@
134
134
  * viewport
135
135
  * ~~waitFor~~
136
136
  * waitForFileChooser => `#wait_for_file_chooser`
137
- * ~~waitForFrame~~
137
+ * waitForFrame => `#wait_for_frame`
138
138
  * waitForFunction => `#wait_for_function`
139
139
  * waitForNavigation => `#wait_for_navigation`
140
140
  * ~~waitForNetworkIdle~~
@@ -227,7 +227,7 @@
227
227
  * goto
228
228
  * hover
229
229
  * isDetached => `#detached?`
230
- * ~~isOOPFrame~~
230
+ * isOOPFrame => `#oop_frame?`
231
231
  * name
232
232
  * parentFrame => `#parent_frame`
233
233
  * select
@@ -308,7 +308,7 @@
308
308
  * finalizeInterceptions => `#finalize_interceptions`
309
309
  * frame
310
310
  * headers
311
- * ~~initiator~~
311
+ * initiator
312
312
  * isNavigationRequest => `#navigation_request?`
313
313
  * method
314
314
  * postData => `#post_data`
@@ -360,7 +360,7 @@
360
360
 
361
361
  * connection
362
362
  * detach
363
- * ~~id~~
363
+ * id
364
364
  * send
365
365
 
366
366
  ## Coverage
@@ -7,10 +7,10 @@ class Puppeteer::AriaQueryHandler
7
7
  private def parse_aria_selector(selector)
8
8
  known_attributes = %w(name role)
9
9
  query_options = {}
10
- attribute_regexp = /\[\s*(?<attribute>\w+)\s*=\s*"(?<value>\\.|[^"\\]*)"\s*\]/
10
+ attribute_regexp = /\[\s*(?<attribute>\w+)\s*=\s*(?<quote>"|')(?<value>\\.|.*?(?=\k<quote>))\k<quote>\s*\]/
11
11
  default_name = selector.gsub(attribute_regexp) do
12
12
  attribute = $1.strip
13
- value = $2
13
+ value = $3
14
14
  unless known_attributes.include?(attribute)
15
15
  raise ArgumentError.new("Unkown aria attribute \"#{attribute}\" in selector")
16
16
  end
@@ -36,10 +36,11 @@ class Puppeteer::AriaQueryHandler
36
36
  end
37
37
  end
38
38
 
39
- def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil)
39
+ def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil, root: nil)
40
+ # addHandlerToWorld
40
41
  binding_function = Puppeteer::DOMWorld::BindingFunction.new(
41
42
  name: 'ariaQuerySelector',
42
- proc: -> (selector) { query_one(dom_world.send(:document), selector) },
43
+ proc: -> (sel) { query_one(root || dom_world.send(:document), sel) },
43
44
  )
44
45
  dom_world.send(:wait_for_selector_in_page,
45
46
  '(_, selector) => globalThis.ariaQuerySelector(selector)',
@@ -47,7 +48,9 @@ class Puppeteer::AriaQueryHandler
47
48
  visible: visible,
48
49
  hidden: hidden,
49
50
  timeout: timeout,
50
- binding_function: binding_function)
51
+ binding_function: binding_function,
52
+ root: root,
53
+ )
51
54
  end
52
55
 
53
56
  def query_all(element, selector)
@@ -4,13 +4,17 @@ require 'timeout'
4
4
 
5
5
  # https://github.com/puppeteer/puppeteer/blob/master/lib/Launcher.js
6
6
  class Puppeteer::BrowserRunner
7
+ include Puppeteer::DebugPrint
8
+
7
9
  # @param {string} executablePath
8
10
  # @param {!Array<string>} processArguments
9
11
  # @param {string=} tempDirectory
10
- def initialize(executable_path, process_arguments, temp_directory)
12
+ def initialize(for_firefox, executable_path, process_arguments, user_data_dir, using_temp_user_data_dir)
13
+ @for_firefox = for_firefox
11
14
  @executable_path = executable_path
12
15
  @process_arguments = process_arguments
13
- @temp_directory = temp_directory
16
+ @user_data_dir = user_data_dir
17
+ @using_temp_user_data_dir = using_temp_user_data_dir
14
18
  @proc = nil
15
19
  @connection = nil
16
20
  @closed = true
@@ -90,8 +94,8 @@ class Puppeteer::BrowserRunner
90
94
  @process_closing = -> {
91
95
  @proc.dispose
92
96
  @closed = true
93
- if @temp_directory
94
- FileUtils.rm_rf(@temp_directory)
97
+ if @using_temp_user_data_dir
98
+ FileUtils.rm_rf(@user_data_dir)
95
99
  end
96
100
  }
97
101
  at_exit do
@@ -122,7 +126,7 @@ class Puppeteer::BrowserRunner
122
126
  def close
123
127
  return if @closed
124
128
 
125
- if @temp_directory
129
+ if @using_temp_user_data_dir && !@for_firefox
126
130
  kill
127
131
  elsif @connection
128
132
  begin
@@ -137,11 +141,18 @@ class Puppeteer::BrowserRunner
137
141
 
138
142
  # @return {Promise}
139
143
  def kill
140
- if @temp_directory
141
- FileUtils.rm_rf(@temp_directory)
142
- end
143
- unless @closed
144
- @proc.kill
144
+ # If the process failed to launch (for example if the browser executable path
145
+ # is invalid), then the process does not get a pid assigned. A call to
146
+ # `proc.kill` would error, as the `pid` to-be-killed can not be found.
147
+ @proc&.kill
148
+
149
+ # Attempt to remove temporary profile directory to avoid littering.
150
+ begin
151
+ if @using_temp_user_data_dir
152
+ FileUtils.rm_rf(@user_data_dir)
153
+ end
154
+ rescue => err
155
+ debug_puts(err)
145
156
  end
146
157
  end
147
158
 
@@ -15,6 +15,11 @@ class Puppeteer::CDPSession
15
15
  @session_id = session_id
16
16
  end
17
17
 
18
+ # @internal
19
+ def id
20
+ @session_id
21
+ end
22
+
18
23
  attr_reader :connection
19
24
 
20
25
  # @param method [String]
@@ -21,12 +21,12 @@ class Puppeteer::CustomQueryHandler
21
21
  nil
22
22
  end
23
23
 
24
- def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil)
24
+ def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil, root: nil)
25
25
  unless @query_one
26
26
  raise NotImplementedError.new("#{self.class}##{__method__} is not implemented.")
27
27
  end
28
28
 
29
- dom_world.send(:wait_for_selector_in_page, @query_one, selector, visible: visible, hidden: hidden, timeout: timeout)
29
+ dom_world.send(:wait_for_selector_in_page, @query_one, selector, visible: visible, hidden: hidden, timeout: timeout, root: root)
30
30
  end
31
31
 
32
32
  def query_all(element, selector)
@@ -45,10 +45,14 @@ class Puppeteer::DOMWorld
45
45
  end
46
46
  end
47
47
 
48
- # @param {!Puppeteer.FrameManager} frameManager
49
- # @param {!Puppeteer.Frame} frame
50
- # @param {!Puppeteer.TimeoutSettings} timeoutSettings
51
- def initialize(frame_manager, frame, timeout_settings)
48
+ # @param client [Puppeteer::CDPSession]
49
+ # @param frame_manager [Puppeteer::FrameManager]
50
+ # @param frame [Puppeteer::Frame]
51
+ # @param timeout_settings [Puppeteer::TimeoutSettings]
52
+ def initialize(client, frame_manager, frame, timeout_settings)
53
+ # Keep own reference to client because it might differ from the FrameManager's
54
+ # client for OOP iframes.
55
+ @client = client
52
56
  @frame_manager = frame_manager
53
57
  @frame = frame
54
58
  @timeout_settings = timeout_settings
@@ -58,7 +62,7 @@ class Puppeteer::DOMWorld
58
62
  @ctx_bindings = Set.new
59
63
  @detached = false
60
64
 
61
- frame_manager.client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
65
+ @client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
62
66
  end
63
67
 
64
68
  attr_reader :frame
@@ -416,10 +420,10 @@ class Puppeteer::DOMWorld
416
420
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
417
421
  # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
418
422
  # @param timeout [Integer]
419
- def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
423
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil, root: nil)
420
424
  # call wait_for_selector_in_page with custom query selector.
421
425
  query_selector_manager = Puppeteer::QueryHandlerManager.instance
422
- query_selector_manager.detect_query_handler(selector).wait_for(self, visible: visible, hidden: hidden, timeout: timeout)
426
+ query_selector_manager.detect_query_handler(selector).wait_for(self, visible: visible, hidden: hidden, timeout: timeout, root: root)
423
427
  end
424
428
 
425
429
  private def binding_identifier(name, context)
@@ -493,10 +497,11 @@ class Puppeteer::DOMWorld
493
497
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
494
498
  # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
495
499
  # @param timeout [Integer]
496
- private def wait_for_selector_in_page(query_one, selector, visible: nil, hidden: nil, timeout: nil, binding_function: nil)
500
+ private def wait_for_selector_in_page(query_one, selector, visible: nil, hidden: nil, timeout: nil, root: nil, binding_function: nil)
497
501
  option_wait_for_visible = visible || false
498
502
  option_wait_for_hidden = hidden || false
499
503
  option_timeout = timeout || @timeout_settings.timeout
504
+ option_root = root
500
505
 
501
506
  polling =
502
507
  if option_wait_for_visible || option_wait_for_hidden
@@ -507,11 +512,11 @@ class Puppeteer::DOMWorld
507
512
  title = "selector #{selector}#{option_wait_for_hidden ? 'to be hidden' : ''}"
508
513
 
509
514
  selector_predicate = make_predicate_string(
510
- predicate_arg_def: '(selector, waitForVisible, waitForHidden)',
515
+ predicate_arg_def: '(root, selector, waitForVisible, waitForHidden)',
511
516
  predicate_query_handler: query_one,
512
517
  async: true,
513
518
  predicate_body: <<~JAVASCRIPT
514
- const node = await predicateQueryHandler(document, selector)
519
+ const node = await predicateQueryHandler(root, selector)
515
520
  return checkWaitForOptions(node, waitForVisible, waitForHidden);
516
521
  JAVASCRIPT
517
522
  )
@@ -523,6 +528,7 @@ class Puppeteer::DOMWorld
523
528
  polling: polling,
524
529
  timeout: option_timeout,
525
530
  args: [selector, option_wait_for_visible, option_wait_for_hidden],
531
+ root: option_root,
526
532
  binding_function: binding_function,
527
533
  )
528
534
  handle = wait_task.await_promise
@@ -537,10 +543,11 @@ class Puppeteer::DOMWorld
537
543
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
538
544
  # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
539
545
  # @param timeout [Integer]
540
- def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
546
+ def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil, root: nil)
541
547
  option_wait_for_visible = visible || false
542
548
  option_wait_for_hidden = hidden || false
543
549
  option_timeout = timeout || @timeout_settings.timeout
550
+ option_root = root
544
551
 
545
552
  polling =
546
553
  if option_wait_for_visible || option_wait_for_hidden
@@ -551,9 +558,9 @@ class Puppeteer::DOMWorld
551
558
  title = "XPath #{xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"
552
559
 
553
560
  xpath_predicate = make_predicate_string(
554
- predicate_arg_def: '(selector, waitForVisible, waitForHidden)',
561
+ predicate_arg_def: '(root, selector, waitForVisible, waitForHidden)',
555
562
  predicate_body: <<~JAVASCRIPT
556
- const node = document.evaluate(selector, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
563
+ const node = document.evaluate(selector, root, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
557
564
  return checkWaitForOptions(node, waitForVisible, waitForHidden);
558
565
  JAVASCRIPT
559
566
  )
@@ -565,6 +572,7 @@ class Puppeteer::DOMWorld
565
572
  polling: polling,
566
573
  timeout: option_timeout,
567
574
  args: [xpath, option_wait_for_visible, option_wait_for_hidden],
575
+ root: option_root,
568
576
  )
569
577
  handle = wait_task.await_promise
570
578
  unless handle.as_element
@@ -20,6 +20,60 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
20
20
  @disposed = false
21
21
  end
22
22
 
23
+ def inspect
24
+ values = %i[context remote_object page disposed].map do |sym|
25
+ value = instance_variable_get(:"@#{sym}")
26
+ "@#{sym}=#{value}"
27
+ end
28
+ "#<Puppeteer::ElementHandle #{values.join(' ')}>"
29
+ end
30
+
31
+ #
32
+ # Wait for the `selector` to appear within the element. If at the moment of calling the
33
+ # method the `selector` already exists, the method will return immediately. If
34
+ # the `selector` doesn't appear after the `timeout` milliseconds of waiting, the
35
+ # function will throw.
36
+ #
37
+ # This method does not work across navigations or if the element is detached from DOM.
38
+ #
39
+ # @param selector - A
40
+ # {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
41
+ # of an element to wait for
42
+ # @param options - Optional waiting parameters
43
+ # @returns Promise which resolves when element specified by selector string
44
+ # is added to DOM. Resolves to `null` if waiting for hidden: `true` and
45
+ # selector is not found in DOM.
46
+ # @remarks
47
+ # The optional parameters in `options` are:
48
+ #
49
+ # - `visible`: wait for the selected element to be present in DOM and to be
50
+ # visible, i.e. to not have `display: none` or `visibility: hidden` CSS
51
+ # properties. Defaults to `false`.
52
+ #
53
+ # - `hidden`: wait for the selected element to not be found in the DOM or to be hidden,
54
+ # i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to
55
+ # `false`.
56
+ #
57
+ # - `timeout`: maximum time to wait in milliseconds. Defaults to `30000`
58
+ # (30 seconds). Pass `0` to disable timeout. The default value can be changed
59
+ # by using the {@link Page.setDefaultTimeout} method.
60
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
61
+ frame = @context.frame
62
+
63
+ secondary_world = frame.secondary_world
64
+ adopted_root = secondary_world.execution_context.adopt_element_handle(self)
65
+ handle = secondary_world.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout, root: adopted_root)
66
+ adopted_root.dispose
67
+ return nil unless handle
68
+
69
+ main_world = frame.main_world
70
+ result = main_world.execution_context.adopt_element_handle(handle)
71
+ handle.dispose
72
+ result
73
+ end
74
+
75
+ define_async_method :async_wait_for_selector
76
+
23
77
  def as_element
24
78
  self
25
79
  end
@@ -323,7 +377,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
323
377
  end
324
378
  end
325
379
 
326
- def screenshot(options = {})
380
+ def screenshot(type: nil, path: nil, full_page: nil, clip: nil, quality: nil, omit_background: nil, encoding: nil)
327
381
  needs_viewport_reset = false
328
382
 
329
383
  box = bounding_box
@@ -358,14 +412,16 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
358
412
  page_x = layout_metrics["layoutViewport"]["pageX"]
359
413
  page_y = layout_metrics["layoutViewport"]["pageY"]
360
414
 
361
- clip = {
362
- x: page_x + box.x,
363
- y: page_y + box.y,
364
- width: box.width,
365
- height: box.height,
366
- }
415
+ if clip.nil?
416
+ clip = {
417
+ x: page_x + box.x,
418
+ y: page_y + box.y,
419
+ width: box.width,
420
+ height: box.height,
421
+ }
422
+ end
367
423
 
368
- @page.screenshot({ clip: clip }.merge(options))
424
+ @page.screenshot(type: type, path: path, full_page: full_page, clip: clip, quality: quality, omit_background: omit_background, encoding: encoding)
369
425
  ensure
370
426
  if needs_viewport_reset
371
427
  @page.viewport = viewport
@@ -81,6 +81,7 @@ module NetworkManagerEmittedEvents ; end
81
81
 
82
82
  {
83
83
  Request: EventsDefinitionUtils.symbol('NetworkManager.Request'),
84
+ RequestServedFromCache: EventsDefinitionUtils.symbol('NetworkManager.RequestServedFromCache'),
84
85
  Response: EventsDefinitionUtils.symbol('NetworkManager.Response'),
85
86
  RequestFailed: EventsDefinitionUtils.symbol('NetworkManager.RequestFailed'),
86
87
  RequestFinished: EventsDefinitionUtils.symbol('NetworkManager.RequestFinished'),
@@ -1,25 +1,47 @@
1
1
  class Puppeteer::Frame
2
2
  using Puppeteer::DefineAsyncMethod
3
3
 
4
- # @param {!FrameManager} frameManager
5
- # @param {!Puppeteer.CDPSession} client
6
- # @param {?Frame} parentFrame
7
- # @param {string} frameId
8
- def initialize(frame_manager, client, parent_frame, frame_id)
4
+ # @param frame_manager [Puppeteer::FrameManager]
5
+ # @param parent_frame [Puppeteer::Frame|nil]
6
+ # @param frame_id [String]
7
+ # @param client [Puppeteer::CDPSession]
8
+ def initialize(frame_manager, parent_frame, frame_id, client)
9
9
  @frame_manager = frame_manager
10
- @client = client
11
10
  @parent_frame = parent_frame
12
11
  @id = frame_id
13
12
  @detached = false
14
13
 
15
14
  @loader_id = ''
16
15
  @lifecycle_events = Set.new
17
- @main_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
18
- @secondary_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
19
16
  @child_frames = Set.new
20
17
  if parent_frame
21
18
  parent_frame._child_frames << self
22
19
  end
20
+
21
+ update_client(client)
22
+ end
23
+
24
+ def inspect
25
+ values = %i[id parent_frame detached loader_id lifecycle_events child_frames].map do |sym|
26
+ value = instance_variable_get(:"@#{sym}")
27
+ "@#{sym}=#{value}"
28
+ end
29
+ "#<Puppeteer::Frame #{values.join(' ')}>"
30
+ end
31
+
32
+ def _client
33
+ @client
34
+ end
35
+
36
+ # @param client [Puppeteer::CDPSession]
37
+ private def update_client(client)
38
+ @client = client
39
+ @main_world = Puppeteer::DOMWorld.new(@client, @frame_manager, self, @frame_manager.timeout_settings)
40
+ @secondary_world = Puppeteer::DOMWorld.new(@client, @frame_manager, self, @frame_manager.timeout_settings)
41
+ end
42
+
43
+ def oop_frame?
44
+ @client != @frame_manager.client
23
45
  end
24
46
 
25
47
  attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :secondary_world