puppeteer-ruby 0.43.0 → 0.43.1

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: da17d477ad97a3197fa0eacf237e784204439efdca104e2a304d5cfb58c449d3
4
- data.tar.gz: f56c151a8d7ebd8ac0ff71feafe5a90dcdd14ed997aaa9ea8e198ddd345b93dd
3
+ metadata.gz: e723d5efd8074f8c70f1b94de1399f35ec39eca88fd86d1991b036db32d783a8
4
+ data.tar.gz: 463781ad3737b5db0f09b2ba9ec2ed8f62863991c011291437230c512671e0ba
5
5
  SHA512:
6
- metadata.gz: 3db33eea6388dd6c743395c9e3cd583d95a3f2147627ae371390e40bf6269f32d689687e8adf0fdaef8b99134b6bad2ee55061ca66c037407ec681ee62b61188
7
- data.tar.gz: 2b169890fd8c5dde6850fff2439e5d43c03fa1b65e906ce443389c0599cf31d0e88af1ca0203c12cff90b2789c14af7a992d1c6b95caf7e78b9dadbf4d4a0f09
6
+ metadata.gz: 46462c2894970d02792d5021088252fce262f41f81531449a73d9d40745ede1742fe187cde65cae64222ba817366500d347ca310aa658147040e4fbfa5d1b5db
7
+ data.tar.gz: d5b6e7ae1e0efc6c1e69e7bb136f90dfdc1c2370c29ec4bf9a6877c22bbe38cd6a81f698f086dce3157800bdfc7bfcb06f7eb35b518434870fd95456dc1fbe3c
data/CHANGELOG.md CHANGED
@@ -1,7 +1,11 @@
1
- ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.43.0...main)]
1
+ ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.43.1...main)]
2
2
 
3
3
  - xxx
4
4
 
5
+ ### 0.43.1 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.43.0...0.43.1)]
6
+
7
+ - Port Puppeteer v16.1 features, including bugfix and XPath query handler.
8
+
5
9
  ### 0.43.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.42.0...0.43.0)]
6
10
 
7
11
  - Port Puppeteer v16.0 features. Increasing stability.
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
2
  - Puppeteer version: v16.2.0
3
- - puppeteer-ruby version: 0.43.0
3
+ - puppeteer-ruby version: 0.43.1
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -196,7 +196,7 @@ class Puppeteer::Browser
196
196
  session: session,
197
197
  browser_context: context,
198
198
  target_manager: @target_manager,
199
- session_factory: -> { @connection.create_session(target_info) },
199
+ session_factory: -> (auto_attach_emulated) { @connection.create_session(target_info, auto_attach_emulated: auto_attach_emulated) },
200
200
  ignore_https_errors: @ignore_https_errors,
201
201
  default_viewport: @default_viewport,
202
202
  is_page_target_callback: @is_page_target_callback,
@@ -1,4 +1,5 @@
1
1
  class Puppeteer::ChromeTargetManager
2
+ include Puppeteer::DebugPrint
2
3
  include Puppeteer::EventCallbackable
3
4
 
4
5
  def initialize(connection:, target_factory:, target_filter_callback:)
@@ -33,20 +34,34 @@ class Puppeteer::ChromeTargetManager
33
34
  )
34
35
 
35
36
  setup_attachment_listeners(@connection)
36
- @connection.async_send_message('Target.setDiscoverTargets', discover: true)
37
+ @connection.async_send_message('Target.setDiscoverTargets', {
38
+ discover: true,
39
+ filter: [
40
+ { type: 'tab', exclude: true },
41
+ {},
42
+ ],
43
+ }).then do
44
+ store_existing_targets_for_init
45
+ end.rescue do |err|
46
+ debug_puts(err)
47
+ end
37
48
  end
38
49
 
39
- def init
50
+ private def store_existing_targets_for_init
40
51
  @discovered_targets_by_target_id.each do |target_id, target_info|
41
- if @target_filter_callback.call(target_info)
52
+ if @target_filter_callback.call(target_info) && target_info.type != 'browser'
42
53
  @target_ids_for_init << target_id
43
54
  end
44
55
  end
56
+ end
57
+
58
+ def init
45
59
  @connection.send_message('Target.setAutoAttach', {
46
60
  waitForDebuggerOnStart: true,
47
61
  flatten: true,
48
62
  autoAttach: true,
49
63
  })
64
+ finish_initialization_if_ready
50
65
  @initialize_promise.value!
51
66
  end
52
67
 
@@ -114,7 +129,7 @@ class Puppeteer::ChromeTargetManager
114
129
  # Special case (https://crbug.com/1338156): currently, shared_workers
115
130
  # don't get auto-attached. This should be removed once the auto-attach
116
131
  # works.
117
- @connection.create_session(target_info)
132
+ @connection.create_session(target_info, auto_attach_emulated: true)
118
133
  end
119
134
  end
120
135
 
@@ -170,6 +185,8 @@ class Puppeteer::ChromeTargetManager
170
185
  end
171
186
  }
172
187
 
188
+ return unless @connection.auto_attached?(target_info.target_id)
189
+
173
190
  # Special case for service workers: being attached to service workers will
174
191
  # prevent them from ever being destroyed. Therefore, we silently detach
175
192
  # from service workers unless the connection was manually created via
@@ -197,6 +214,8 @@ class Puppeteer::ChromeTargetManager
197
214
  return
198
215
  end
199
216
 
217
+ is_existing_target = @attached_targets_by_target_id.has_key?(target_info.target_id)
218
+
200
219
  target = @attached_targets_by_target_id[target_info.target_id] || @target_factory.call(target_info, session)
201
220
  setup_attachment_listeners(session)
202
221
 
@@ -218,11 +237,10 @@ class Puppeteer::ChromeTargetManager
218
237
  end
219
238
 
220
239
  @target_ids_for_init.delete(target.target_id)
221
- future { emit_event(TargetManagerEmittedEvents::TargetAvailable, target) }
222
-
223
- if @target_ids_for_init.empty?
224
- @initialize_promise.fulfill(nil) unless @initialize_promise.resolved?
240
+ unless is_existing_target
241
+ future { emit_event(TargetManagerEmittedEvents::TargetAvailable, target) }
225
242
  end
243
+ finish_initialization_if_ready
226
244
 
227
245
  future do
228
246
  # TODO: the browser might be shutting down here. What do we do with the error?
@@ -239,8 +257,8 @@ class Puppeteer::ChromeTargetManager
239
257
  end
240
258
  end
241
259
 
242
- private def finish_initialization_if_ready(target_id)
243
- @target_ids_for_init.delete(target_id)
260
+ private def finish_initialization_if_ready(target_id = nil)
261
+ @target_ids_for_init.delete(target_id) if target_id
244
262
  if @target_ids_for_init.empty?
245
263
  @initialize_promise.fulfill(nil) unless @initialize_promise.resolved?
246
264
  end
@@ -319,15 +319,18 @@ class Puppeteer::Connection
319
319
  end
320
320
 
321
321
  def auto_attached?(target_id)
322
- @manually_attached.include?(target_id)
322
+ !@manually_attached.include?(target_id)
323
323
  end
324
324
 
325
325
  # @param {Protocol.Target.TargetInfo} targetInfo
326
326
  # @return [CDPSession]
327
- def create_session(target_info)
328
- @manually_attached << target_info.target_id
327
+ def create_session(target_info, auto_attach_emulated: false)
328
+ unless auto_attach_emulated
329
+ @manually_attached << target_info.target_id
330
+ end
329
331
  result = send_message('Target.attachToTarget', targetId: target_info.target_id, flatten: true)
330
332
  session_id = result['sessionId']
333
+ @manually_attached.delete(target_info.target_id)
331
334
  @sessions[session_id]
332
335
  end
333
336
  end
@@ -539,49 +539,6 @@ class Puppeteer::DOMWorld
539
539
  handle.as_element
540
540
  end
541
541
 
542
- # @param xpath [String]
543
- # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
544
- # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
545
- # @param timeout [Integer]
546
- def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil, root: nil)
547
- option_wait_for_visible = visible || false
548
- option_wait_for_hidden = hidden || false
549
- option_timeout = timeout || @timeout_settings.timeout
550
- option_root = root
551
-
552
- polling =
553
- if option_wait_for_visible || option_wait_for_hidden
554
- 'raf'
555
- else
556
- 'mutation'
557
- end
558
- title = "XPath #{xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"
559
-
560
- xpath_predicate = make_predicate_string(
561
- predicate_arg_def: '(root, selector, waitForVisible, waitForHidden)',
562
- predicate_body: <<~JAVASCRIPT
563
- const node = document.evaluate(selector, root, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
564
- return checkWaitForOptions(node, waitForVisible, waitForHidden);
565
- JAVASCRIPT
566
- )
567
-
568
- wait_task = Puppeteer::WaitTask.new(
569
- dom_world: self,
570
- predicate_body: xpath_predicate,
571
- title: title,
572
- polling: polling,
573
- timeout: option_timeout,
574
- args: [xpath, option_wait_for_visible, option_wait_for_hidden],
575
- root: option_root,
576
- )
577
- handle = wait_task.await_promise
578
- unless handle.as_element
579
- handle.dispose
580
- return nil
581
- end
582
- handle.as_element
583
- end
584
-
585
542
  # @param page_function [String]
586
543
  # @param args [Array]
587
544
  # @param polling [Integer|String]
@@ -125,28 +125,14 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
125
125
  # Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default
126
126
  # value can be changed by using the {@link Page.setDefaultTimeout} method.
127
127
  def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
128
- frame = @context.frame
129
-
130
- secondary_world = frame.secondary_world
131
- adopted_root = secondary_world.execution_context.adopt_element_handle(self)
132
128
  param_xpath =
133
129
  if xpath.start_with?('//')
134
130
  ".#{xpath}"
135
131
  else
136
132
  xpath
137
133
  end
138
- unless param_xpath.start_with?('.//')
139
- adopted_root.dispose
140
- raise ArgumentError.new("Unsupported xpath expression: #{xpath}")
141
- end
142
- handle = secondary_world.wait_for_xpath(param_xpath, visible: visible, hidden: hidden, timeout: timeout, root: adopted_root)
143
- adopted_root.dispose
144
- return nil unless handle
145
134
 
146
- main_world = frame.main_world
147
- result = main_world.execution_context.adopt_element_handle(handle)
148
- handle.dispose
149
- result
135
+ wait_for_selector("xpath/#{param_xpath}", visible: visible, hidden: hidden, timeout: timeout)
150
136
  end
151
137
 
152
138
  define_async_method :async_wait_for_xpath
@@ -623,21 +609,14 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
623
609
  # @param expression [String]
624
610
  # @return [Array<ElementHandle>]
625
611
  def Sx(expression)
626
- fn = <<~JAVASCRIPT
627
- (element, expression) => {
628
- const document = element.ownerDocument || element;
629
- const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
630
- const array = [];
631
- let item;
632
- while ((item = iterator.iterateNext()))
633
- array.push(item);
634
- return array;
635
- }
636
- JAVASCRIPT
637
- handles = evaluate_handle(fn, expression)
638
- properties = handles.properties
639
- handles.dispose
640
- properties.values.map(&:as_element).compact
612
+ param_xpath =
613
+ if expression.start_with?('//')
614
+ ".#{expression}"
615
+ else
616
+ expression
617
+ end
618
+
619
+ query_selector_all("xpath/#{param_xpath}")
641
620
  end
642
621
 
643
622
  define_async_method :async_Sx
@@ -106,7 +106,14 @@ class Puppeteer::Frame
106
106
  # @param {string} expression
107
107
  # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
108
108
  def Sx(expression)
109
- @main_world.Sx(expression)
109
+ param_xpath =
110
+ if expression.start_with?('//')
111
+ ".#{expression}"
112
+ else
113
+ expression
114
+ end
115
+
116
+ query_selector_all("xpath/#{param_xpath}")
110
117
  end
111
118
 
112
119
  define_async_method :async_Sx
@@ -274,14 +281,14 @@ class Puppeteer::Frame
274
281
  # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
275
282
  # @param timeout [Integer]
276
283
  def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
277
- handle = @secondary_world.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
278
- if !handle
279
- return nil
280
- end
281
- main_execution_context = @main_world.execution_context
282
- result = main_execution_context.adopt_element_handle(handle)
283
- handle.dispose
284
- result
284
+ param_xpath =
285
+ if xpath.start_with?('//')
286
+ ".#{xpath}"
287
+ else
288
+ xpath
289
+ end
290
+
291
+ wait_for_selector("xpath/#{param_xpath}", visible: visible, hidden: hidden, timeout: timeout)
285
292
  end
286
293
 
287
294
  define_async_method :async_wait_for_xpath
@@ -144,13 +144,13 @@ class Puppeteer::FrameManager
144
144
  watcher.same_document_navigation_promise
145
145
  end,
146
146
  )
147
+
148
+ watcher.navigation_response
147
149
  rescue Puppeteer::TimeoutError => err
148
150
  raise NavigationError.new(err)
149
151
  ensure
150
152
  watcher.dispose
151
153
  end
152
-
153
- watcher.navigation_response
154
154
  end
155
155
 
156
156
  # @param timeout [number|nil]
@@ -168,13 +168,13 @@ class Puppeteer::FrameManager
168
168
  watcher.same_document_navigation_promise,
169
169
  watcher.new_document_navigation_promise,
170
170
  )
171
+
172
+ watcher.navigation_response
171
173
  rescue Puppeteer::TimeoutError => err
172
174
  raise NavigationError.new(err)
173
175
  ensure
174
176
  watcher.dispose
175
177
  end
176
-
177
- watcher.navigation_response
178
178
  end
179
179
 
180
180
  # @param event [Hash]
@@ -81,7 +81,10 @@ class Puppeteer::LifecycleWatcher
81
81
  @frame_manager.add_event_listener(FrameManagerEmittedEvents::FrameSwapped, &method(:handle_frame_swapped)),
82
82
  @frame_manager.add_event_listener(FrameManagerEmittedEvents::FrameDetached, &method(:handle_frame_detached)),
83
83
  ]
84
- @listener_ids['network_manager'] = @frame_manager.network_manager.add_event_listener(NetworkManagerEmittedEvents::Request, &method(:handle_request))
84
+ @listener_ids['network_manager'] = [
85
+ @frame_manager.network_manager.add_event_listener(NetworkManagerEmittedEvents::Request, &method(:handle_request)),
86
+ @frame_manager.network_manager.add_event_listener(NetworkManagerEmittedEvents::Response, &method(:handle_response)),
87
+ ]
85
88
 
86
89
  @same_document_navigation_promise = resolvable_future
87
90
  @lifecycle_promise = resolvable_future
@@ -90,10 +93,24 @@ class Puppeteer::LifecycleWatcher
90
93
  check_lifecycle_complete
91
94
  end
92
95
 
96
+ class AnotherRequestReceivedError < StandardError ; end
97
+
93
98
  # @param [Puppeteer::HTTPRequest] request
94
99
  def handle_request(request)
95
100
  return if request.frame != @frame || !request.navigation_request?
96
101
  @navigation_request = request
102
+ @navigation_response_received&.reject(AnotherRequestReceivedError.new('New navigation request was received'))
103
+ @navigation_response_received = resolvable_future
104
+ if request.response && !@navigation_response_received.resolved?
105
+ @navigation_response_received.fulfill(nil)
106
+ end
107
+ end
108
+
109
+ # @param [Puppeteer::HTTPResponse] response
110
+ def handle_response(response)
111
+ return if @navigation_request&.internal&.request_id != response.request.internal.request_id
112
+
113
+ @navigation_response_received.fulfill(nil) unless @navigation_response_received.resolved?
97
114
  end
98
115
 
99
116
  # @param frame [Puppeteer::Frame]
@@ -107,6 +124,8 @@ class Puppeteer::LifecycleWatcher
107
124
 
108
125
  # @return [Puppeteer::HTTPResponse]
109
126
  def navigation_response
127
+ # Continue with a possibly null response.
128
+ @navigation_response_received.value! rescue nil
110
129
  if_present(@navigation_request) do |request|
111
130
  request.response
112
131
  end
@@ -175,8 +194,8 @@ class Puppeteer::LifecycleWatcher
175
194
  if_present(@listener_ids['frame_manager']) do |ids|
176
195
  @frame_manager.remove_event_listener(*ids)
177
196
  end
178
- if_present(@listener_ids['network_manager']) do |id|
179
- @frame_manager.network_manager.remove_event_listener(id)
197
+ if_present(@listener_ids['network_manager']) do |ids|
198
+ @frame_manager.network_manager.remove_event_listener(*ids)
180
199
  end
181
200
  end
182
201
  end
@@ -6,6 +6,7 @@ class Puppeteer::QueryHandlerManager
6
6
  def query_handlers
7
7
  @query_handlers ||= {
8
8
  aria: Puppeteer::AriaQueryHandler.new,
9
+ xpath: xpath_handler,
9
10
  }
10
11
  end
11
12
 
@@ -16,6 +17,40 @@ class Puppeteer::QueryHandlerManager
16
17
  )
17
18
  end
18
19
 
20
+ private def xpath_handler
21
+ @xpath_handler ||= Puppeteer::CustomQueryHandler.new(
22
+ query_one: <<~JAVASCRIPT,
23
+ (element, selector) => {
24
+ const doc = element.ownerDocument || document;
25
+ const result = doc.evaluate(
26
+ selector,
27
+ element,
28
+ null,
29
+ XPathResult.FIRST_ORDERED_NODE_TYPE
30
+ );
31
+ return result.singleNodeValue;
32
+ }
33
+ JAVASCRIPT
34
+ query_all: <<~JAVASCRIPT,
35
+ (element, selector) => {
36
+ const doc = element.ownerDocument || document;
37
+ const iterator = doc.evaluate(
38
+ selector,
39
+ element,
40
+ null,
41
+ XPathResult.ORDERED_NODE_ITERATOR_TYPE
42
+ );
43
+ const array = [];
44
+ let item;
45
+ while ((item = iterator.iterateNext())) {
46
+ array.push(item);
47
+ }
48
+ return array;
49
+ }
50
+ JAVASCRIPT
51
+ )
52
+ end
53
+
19
54
  class Result
20
55
  def initialize(query_handler:, selector:)
21
56
  @query_handler = query_handler
@@ -93,7 +93,7 @@ class Puppeteer::Target
93
93
  end
94
94
 
95
95
  def create_cdp_session
96
- @session_factory.call
96
+ @session_factory.call(false)
97
97
  end
98
98
 
99
99
  def target_manager
@@ -102,7 +102,7 @@ class Puppeteer::Target
102
102
 
103
103
  def page
104
104
  if @is_page_target_callback.call(@target_info) && @page.nil?
105
- client = @session || @session_factory.call
105
+ client = @session || @session_factory.call(true)
106
106
  @page = Puppeteer::Page.create(client, self, @ignore_https_errors, @default_viewport)
107
107
  end
108
108
  @page
@@ -1,3 +1,3 @@
1
1
  module Puppeteer
2
- VERSION = '0.43.0'
2
+ VERSION = '0.43.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppeteer-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.43.0
4
+ version: 0.43.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-13 00:00:00.000000000 Z
11
+ date: 2022-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby