puppeteer-ruby 0.44.2 → 0.44.3

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: 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