puppeteer-ruby 0.44.2 → 0.44.3

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: 122e93a49a21ba31268f981920fb45ddf3bbc4e5b5e37705337c7f0fcfb416d8
4
- data.tar.gz: '0940d06d294a8a069d23fd02dee7a1f00b99a3a0c787b4520d8854be0103f1cf'
3
+ metadata.gz: 9e3ce791016a5018ed76902a9baa63110032fb28681b52d6beeb2e0b629ec0b1
4
+ data.tar.gz: d449886d836e7b2d65e6764e8a73745b53117bc01437a05699efbff911d92dbc
5
5
  SHA512:
6
- metadata.gz: 8253f32cb5b92f3413ba88e5179494f76b7e3c6b7a7b58e1544691586ae771ded9992cb9404f94998e72a958e6da7fbb21df4eb00e04c7446a875b0ce4cb005f
7
- data.tar.gz: 07114ec059413a71c444e467cc61a35428c5091a1cc015b1b33447f1bfae0c543fc419a36020824e40a3ad7d8e79438c7a69770a850578eea1c8e6f3ab6b4aa3
6
+ metadata.gz: 74fce38ce98fffd7a47273e34d6d1b66a8e171656255fe431caf9d78c33fde9c6fc81316253091681521afed796b11a374d123edde4934253a892b9a69947666
7
+ data.tar.gz: 5fa58e64d918f1ff5d59de1c57cbb0a04728e9c9866a0376c8c0bc2d2729277391a631dbfb3a56cc25b7c43f00ac7208e478c5b66bd1d35afb28c32428569d18
data/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
- ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.44.2...main)]
1
+ ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.44.3...main)]
2
2
 
3
- - xxx
3
+ ### 0.44.3 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.44.2...0.44.3)]
4
+
5
+ Bugfix
6
+
7
+ - Revive Firefox automation :tada:
4
8
 
5
9
  ### 0.44.2 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.44.1...0.44.2)]
6
10
 
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
2
  - Puppeteer version: v17.1.3
3
- - puppeteer-ruby version: 0.44.2
3
+ - puppeteer-ruby version: 0.44.3
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -159,8 +159,31 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
159
159
  begin
160
160
  @remote_object.scroll_into_view_if_needed(@client)
161
161
  rescue => err
162
- # Just ignore 'Node does not have a layout object' for backward-compatibility.
163
- raise unless err.message =~ /Node does not have a layout object/
162
+ # Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
163
+ js = <<~JAVASCRIPT
164
+ async (element, pageJavascriptEnabled) => {
165
+ const visibleRatio = async () => {
166
+ return await new Promise(resolve => {
167
+ const observer = new IntersectionObserver(entries => {
168
+ resolve(entries[0].intersectionRatio);
169
+ observer.disconnect();
170
+ });
171
+ observer.observe(element);
172
+ });
173
+ };
174
+ if (!pageJavascriptEnabled || (await visibleRatio()) !== 1.0) {
175
+ element.scrollIntoView({
176
+ block: 'center',
177
+ inline: 'center',
178
+ // @ts-expect-error Chrome still supports behavior: instant but
179
+ // it's not in the spec so TS shouts We don't want to make this
180
+ // breaking change in Puppeteer yet so we'll ignore the line.
181
+ behavior: 'instant',
182
+ });
183
+ }
184
+ }
185
+ JAVASCRIPT
186
+ evaluate(js, page.javascript_enabled?)
164
187
  end
165
188
 
166
189
  # clickpoint is often calculated before scrolling is completed.
@@ -118,12 +118,12 @@ class Puppeteer::ExecutionContext
118
118
  ) # .catch(rewriteError);
119
119
 
120
120
  exception_details = result['exceptionDetails']
121
- remote_object = Puppeteer::RemoteObject.new(result['result'])
122
-
123
121
  if exception_details
124
122
  raise EvaluationError.new("Evaluation failed: #{exception_details}")
125
123
  end
126
124
 
125
+ remote_object = Puppeteer::RemoteObject.new(result['result'])
126
+
127
127
  if @return_by_value
128
128
  remote_object.value
129
129
  else
@@ -66,9 +66,8 @@ class Puppeteer::FirefoxTargetManager
66
66
  end
67
67
 
68
68
  private def remove_session_listeners(session)
69
- return unless @attachment_listener_ids
70
- listener_ids = @attachment_listener_ids.delete(session)
71
- return if listener_ids.empty?
69
+ listener_ids = @attachment_listener_ids&.delete(session)
70
+ return if !listener_ids || listener_ids.empty?
72
71
  session.remove_event_listener(*listener_ids)
73
72
  end
74
73
 
@@ -310,7 +310,7 @@ class Puppeteer::Frame
310
310
 
311
311
  # Ensure loaderId updated.
312
312
  # The order of [Page.lifecycleEvent name="init"] and [Page.frameNavigated] is random... for some reason...
313
- @loader_id = frame_payload['loaderId']
313
+ @loader_id = frame_payload['loaderId'] if frame_payload['loaderId']
314
314
  end
315
315
 
316
316
  # @param url [String]
@@ -57,7 +57,7 @@ class Puppeteer::IsolaatedWorld
57
57
  @frame = frame
58
58
  @timeout_settings = timeout_settings
59
59
  @context_promise = resolvable_future
60
- @wait_tasks = Set.new
60
+ @task_manager = Puppeteer::TaskManager.new
61
61
  @bound_functions = {}
62
62
  @ctx_bindings = Set.new
63
63
  @detached = false
@@ -65,12 +65,7 @@ class Puppeteer::IsolaatedWorld
65
65
  @client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
66
66
  end
67
67
 
68
- attr_reader :frame
69
-
70
- # only used in Puppeteer::WaitTask#initialize
71
- private def _wait_tasks
72
- @wait_tasks
73
- end
68
+ attr_reader :frame, :task_manager
74
69
 
75
70
  # only used in Puppeteer::WaitTask#initialize
76
71
  private def _bound_functions
@@ -84,7 +79,7 @@ class Puppeteer::IsolaatedWorld
84
79
  unless @context_promise.resolved?
85
80
  @context_promise.fulfill(context)
86
81
  end
87
- @wait_tasks.each(&:async_rerun)
82
+ @task_manager.async_rerun_all
88
83
  else
89
84
  raise ArgumentError.new("context should now be nil. Use #delete_context for clearing document.")
90
85
  end
@@ -101,9 +96,7 @@ class Puppeteer::IsolaatedWorld
101
96
 
102
97
  def detach
103
98
  @detached = true
104
- @wait_tasks.each do |wait_task|
105
- wait_task.terminate(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
106
- end
99
+ @task_manager.terminate_all(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
107
100
  end
108
101
 
109
102
  class DetachedError < StandardError; end
@@ -324,6 +324,11 @@ module Puppeteer::Launcher
324
324
  # Make sure opening about:addons will not hit the network
325
325
  'extensions.webservice.discoverURL': "http://#{server}/dummy/discoveryURL",
326
326
 
327
+ # Temporarily force disable BFCache in parent (https://bit.ly/bug-1732263)
328
+ 'fission.bfcacheInParent': false,
329
+ # Force all web content to use a single content process
330
+ 'fission.webContentIsolationStrategy': 0,
331
+
327
332
  # Allow the application to have focus even it runs in the background
328
333
  'focusmanager.testmode': true,
329
334
  # Disable useragent updates
@@ -105,15 +105,10 @@ class Puppeteer::Page
105
105
  else
106
106
  options[:capture_beyond_viewport]
107
107
  end
108
- @from_surface =
109
- if options[:from_surface].nil?
110
- true
111
- else
112
- options[:from_surface]
113
- end
108
+ @from_surface = options[:from_surface]
114
109
  end
115
110
 
116
- attr_reader :type, :quality, :path, :clip, :encoding
111
+ attr_reader :type, :quality, :path, :clip, :encoding, :from_surface
117
112
 
118
113
  def full_page?
119
114
  @full_page
@@ -126,9 +121,5 @@ class Puppeteer::Page
126
121
  def capture_beyond_viewport?
127
122
  @capture_beyond_viewport
128
123
  end
129
-
130
- def from_surface?
131
- @from_surface
132
- end
133
124
  end
134
125
  end
@@ -1104,7 +1104,7 @@ class Puppeteer::Page
1104
1104
  quality: screenshot_options.quality,
1105
1105
  clip: clip,
1106
1106
  captureBeyondViewport: screenshot_options.capture_beyond_viewport?,
1107
- fromSurface: screenshot_options.from_surface?,
1107
+ fromSurface: screenshot_options.from_surface,
1108
1108
  }.compact
1109
1109
  result = @client.send_message('Page.captureScreenshot', screenshot_params)
1110
1110
  reset_default_background_color if should_set_default_background
@@ -7,6 +7,7 @@ class Puppeteer::QueryHandlerManager
7
7
  @query_handlers ||= {
8
8
  aria: Puppeteer::AriaQueryHandler.new,
9
9
  xpath: xpath_handler,
10
+ text: text_query_handler,
10
11
  }
11
12
  end
12
13
 
@@ -51,6 +52,156 @@ class Puppeteer::QueryHandlerManager
51
52
  )
52
53
  end
53
54
 
55
+ private def text_query_handler
56
+ text_content_js = <<~JAVASCRIPT
57
+ const TRIVIAL_VALUE_INPUT_TYPES = new Set(['checkbox', 'image', 'radio']);
58
+
59
+ /**
60
+ * Determines if the node has a non-trivial value property.
61
+ */
62
+ const isNonTrivialValueNode = (node) => {
63
+ if (node instanceof HTMLSelectElement) {
64
+ return true;
65
+ }
66
+ if (node instanceof HTMLTextAreaElement) {
67
+ return true;
68
+ }
69
+ if (
70
+ node instanceof HTMLInputElement &&
71
+ !TRIVIAL_VALUE_INPUT_TYPES.has(node.type)
72
+ ) {
73
+ return true;
74
+ }
75
+ return false;
76
+ };
77
+
78
+ const UNSUITABLE_NODE_NAMES = new Set(['SCRIPT', 'STYLE']);
79
+
80
+ /**
81
+ * Determines whether a given node is suitable for text matching.
82
+ */
83
+ const isSuitableNodeForTextMatching = (node) => {
84
+ return (
85
+ !UNSUITABLE_NODE_NAMES.has(node.nodeName) && !document.head?.contains(node)
86
+ );
87
+ };
88
+
89
+ /**
90
+ * Maps {@link Node}s to their computed {@link TextContent}.
91
+ */
92
+ const textContentCache = new Map();
93
+
94
+ /**
95
+ * Builds the text content of a node using some custom logic.
96
+ *
97
+ * @remarks
98
+ * The primary reason this function exists is due to {@link ShadowRoot}s not having
99
+ * text content.
100
+ *
101
+ * @internal
102
+ */
103
+ const createTextContent = (root) => {
104
+ let value = textContentCache.get(root);
105
+ if (value) {
106
+ return value;
107
+ }
108
+ value = {full: '', immediate: []};
109
+ if (!isSuitableNodeForTextMatching(root)) {
110
+ return value;
111
+ }
112
+ let currentImmediate = '';
113
+ if (isNonTrivialValueNode(root)) {
114
+ value.full = root.value;
115
+ value.immediate.push(root.value);
116
+ } else {
117
+ for (let child = root.firstChild; child; child = child.nextSibling) {
118
+ if (child.nodeType === Node.TEXT_NODE) {
119
+ value.full += child.nodeValue ?? '';
120
+ currentImmediate += child.nodeValue ?? '';
121
+ continue;
122
+ }
123
+ if (currentImmediate) {
124
+ value.immediate.push(currentImmediate);
125
+ }
126
+ currentImmediate = '';
127
+ if (child.nodeType === Node.ELEMENT_NODE) {
128
+ value.full += createTextContent(child).full;
129
+ }
130
+ }
131
+ if (currentImmediate) {
132
+ value.immediate.push(currentImmediate);
133
+ }
134
+ if (root instanceof Element && root.shadowRoot) {
135
+ value.full += createTextContent(root.shadowRoot).full;
136
+ }
137
+ }
138
+ textContentCache.set(root, value);
139
+ return value;
140
+ };
141
+ JAVASCRIPT
142
+
143
+ @text_query_handler ||= Puppeteer::CustomQueryHandler.new(
144
+ query_one: <<~JAVASCRIPT,
145
+ (element, selector) => {
146
+ #{text_content_js}
147
+
148
+ const search = (root) => {
149
+ for (const node of root.childNodes) {
150
+ if (node instanceof Element) {
151
+ let matchedNode;
152
+ if (node.shadowRoot) {
153
+ matchedNode = search(node.shadowRoot);
154
+ } else {
155
+ matchedNode = search(node);
156
+ }
157
+ if (matchedNode) {
158
+ return matchedNode;
159
+ }
160
+ }
161
+ }
162
+ const textContent = createTextContent(root);
163
+ if (textContent.full.includes(selector)) {
164
+ return root;
165
+ }
166
+ return null;
167
+ };
168
+ return search(element);
169
+ }
170
+ JAVASCRIPT
171
+
172
+ query_all: <<~JAVASCRIPT,
173
+ (element, selector) => {
174
+ #{text_content_js}
175
+
176
+ const search = (root) => {
177
+ let results = [];
178
+ for (const node of root.childNodes) {
179
+ if (node instanceof Element) {
180
+ let matchedNodes;
181
+ if (node.shadowRoot) {
182
+ matchedNodes = search(node.shadowRoot);
183
+ } else {
184
+ matchedNodes = search(node);
185
+ }
186
+ results = results.concat(matchedNodes);
187
+ }
188
+ }
189
+ if (results.length > 0) {
190
+ return results;
191
+ }
192
+
193
+ const textContent = createTextContent(root);
194
+ if (textContent.full.includes(selector)) {
195
+ return [root];
196
+ }
197
+ return [];
198
+ };
199
+ return search(element);
200
+ }
201
+ JAVASCRIPT
202
+ )
203
+ end
204
+
54
205
  class Result
55
206
  def initialize(query_handler:, selector:)
56
207
  @query_handler = query_handler
@@ -0,0 +1,24 @@
1
+ class Puppeteer::TaskManager
2
+ def initialize
3
+ @tasks = Set.new
4
+ end
5
+
6
+ def add(task)
7
+ @tasks << task
8
+ end
9
+
10
+ def delete(task)
11
+ @tasks.delete(task)
12
+ end
13
+
14
+ def terminate_all(error)
15
+ @tasks.each do |task|
16
+ task.terminate(error)
17
+ end
18
+ @tasks.clear
19
+ end
20
+
21
+ def async_rerun_all
22
+ Concurrent::Promises.zip(*@tasks.map(&:async_rerun))
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Puppeteer
2
- VERSION = '0.44.2'
2
+ VERSION = '0.44.3'
3
3
  end
@@ -30,7 +30,7 @@ class Puppeteer::WaitTask
30
30
  @args = args
31
31
  @binding_function = binding_function
32
32
  @run_count = 0
33
- @dom_world.send(:_wait_tasks).add(self)
33
+ @dom_world.task_manager.add(self)
34
34
  if binding_function
35
35
  @dom_world.send(:_bound_functions)[binding_function.name] = binding_function
36
36
  end
@@ -117,10 +117,10 @@ class Puppeteer::WaitTask
117
117
 
118
118
  private def cleanup
119
119
  @timeout_cleared = true
120
- @dom_world.send(:_wait_tasks).delete(self)
120
+ @dom_world.task_manager.delete(self)
121
121
  end
122
122
 
123
- private define_async_method :async_rerun
123
+ define_async_method :async_rerun
124
124
 
125
125
  WAIT_FOR_PREDICATE_PAGE_FUNCTION = <<~JAVASCRIPT
126
126
  async function _(root, predicateBody, polling, timeout, ...args) {
data/lib/puppeteer.rb CHANGED
@@ -58,6 +58,7 @@ require 'puppeteer/puppeteer'
58
58
  require 'puppeteer/query_handler_manager'
59
59
  require 'puppeteer/remote_object'
60
60
  require 'puppeteer/target'
61
+ require 'puppeteer/task_manager'
61
62
  require 'puppeteer/tracing'
62
63
  require 'puppeteer/timeout_helper'
63
64
  require 'puppeteer/timeout_settings'
@@ -24,15 +24,15 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency 'concurrent-ruby', '~> 1.1.0'
25
25
  spec.add_dependency 'websocket-driver', '>= 0.6.0'
26
26
  spec.add_dependency 'mime-types', '>= 3.0'
27
- spec.add_development_dependency 'bundler', '~> 2.3.4'
27
+ spec.add_development_dependency 'bundler'
28
28
  spec.add_development_dependency 'chunky_png'
29
29
  spec.add_development_dependency 'dry-inflector'
30
30
  spec.add_development_dependency 'pry-byebug'
31
31
  spec.add_development_dependency 'rake', '~> 13.0.3'
32
32
  spec.add_development_dependency 'rollbar'
33
- spec.add_development_dependency 'rspec', '~> 3.11.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.12.0'
34
34
  spec.add_development_dependency 'rspec_junit_formatter' # for CircleCI.
35
- spec.add_development_dependency 'rubocop', '~> 1.36.0'
35
+ spec.add_development_dependency 'rubocop', '~> 1.42.0'
36
36
  spec.add_development_dependency 'rubocop-rspec'
37
37
  spec.add_development_dependency 'sinatra'
38
38
  spec.add_development_dependency 'webrick'
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.44.2
4
+ version: 0.44.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-06 00:00:00.000000000 Z
11
+ date: 2023-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 2.3.4
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 2.3.4
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: chunky_png
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: 3.11.0
145
+ version: 3.12.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: 3.11.0
152
+ version: 3.12.0
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: rspec_junit_formatter
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -170,14 +170,14 @@ dependencies:
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: 1.36.0
173
+ version: 1.42.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: 1.36.0
180
+ version: 1.42.0
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: rubocop-rspec
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -321,6 +321,7 @@ files:
321
321
  - lib/puppeteer/query_handler_manager.rb
322
322
  - lib/puppeteer/remote_object.rb
323
323
  - lib/puppeteer/target.rb
324
+ - lib/puppeteer/task_manager.rb
324
325
  - lib/puppeteer/timeout_helper.rb
325
326
  - lib/puppeteer/timeout_settings.rb
326
327
  - lib/puppeteer/touch_screen.rb
@@ -350,7 +351,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
350
351
  - !ruby/object:Gem::Version
351
352
  version: '0'
352
353
  requirements: []
353
- rubygems_version: 3.3.7
354
+ rubygems_version: 3.4.1
354
355
  signing_key:
355
356
  specification_version: 4
356
357
  summary: A ruby port of puppeteer