puppeteer-ruby 0.43.0 → 0.43.1

Sign up to get free protection for your applications and to get access to all the features.
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