puppeteer-ruby 0.29.0 → 0.31.4

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.
data/lib/puppeteer.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'concurrent'
2
2
 
3
- class Puppeteer; end
3
+ module Puppeteer; end
4
4
 
5
5
  require 'puppeteer/env'
6
6
 
@@ -43,6 +43,7 @@ require 'puppeteer/lifecycle_watcher'
43
43
  require 'puppeteer/mouse'
44
44
  require 'puppeteer/network_manager'
45
45
  require 'puppeteer/page'
46
+ require 'puppeteer/puppeteer'
46
47
  require 'puppeteer/query_handler_manager'
47
48
  require 'puppeteer/remote_object'
48
49
  require 'puppeteer/request'
@@ -59,9 +60,9 @@ require 'puppeteer/web_socket_transport'
59
60
  require 'puppeteer/element_handle'
60
61
 
61
62
  # ref: https://github.com/puppeteer/puppeteer/blob/master/lib/Puppeteer.js
62
- class Puppeteer
63
- def self.method_missing(method, *args, **kwargs, &block)
64
- @puppeteer ||= Puppeteer.new(
63
+ module Puppeteer
64
+ module_function def method_missing(method, *args, **kwargs, &block)
65
+ @puppeteer ||= ::Puppeteer::Puppeteer.new(
65
66
  project_root: __dir__,
66
67
  preferred_revision: '706915',
67
68
  is_puppeteer_core: true,
@@ -73,167 +74,4 @@ class Puppeteer
73
74
  @puppeteer.public_send(method, *args, **kwargs, &block)
74
75
  end
75
76
  end
76
-
77
- # @param project_root [String]
78
- # @param prefereed_revision [String]
79
- # @param is_puppeteer_core [String]
80
- def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
81
- @project_root = project_root
82
- @preferred_revision = preferred_revision
83
- @is_puppeteer_core = is_puppeteer_core
84
- end
85
-
86
- # @param product [String]
87
- # @param executable_path [String]
88
- # @param ignore_default_args [Array<String>|nil]
89
- # @param handle_SIGINT [Boolean]
90
- # @param handle_SIGTERM [Boolean]
91
- # @param handle_SIGHUP [Boolean]
92
- # @param timeout [Integer]
93
- # @param dumpio [Boolean]
94
- # @param env [Hash]
95
- # @param pipe [Boolean]
96
- # @param args [Array<String>]
97
- # @param user_data_dir [String]
98
- # @param devtools [Boolean]
99
- # @param headless [Boolean]
100
- # @param ignore_https_errors [Boolean]
101
- # @param default_viewport [Puppeteer::Viewport|nil]
102
- # @param slow_mo [Integer]
103
- # @return [Puppeteer::Browser]
104
- def launch(
105
- product: nil,
106
- executable_path: nil,
107
- ignore_default_args: nil,
108
- handle_SIGINT: nil,
109
- handle_SIGTERM: nil,
110
- handle_SIGHUP: nil,
111
- timeout: nil,
112
- dumpio: nil,
113
- env: nil,
114
- pipe: nil,
115
- args: nil,
116
- user_data_dir: nil,
117
- devtools: nil,
118
- headless: nil,
119
- ignore_https_errors: nil,
120
- default_viewport: nil,
121
- slow_mo: nil
122
- )
123
- options = {
124
- executable_path: executable_path,
125
- ignore_default_args: ignore_default_args,
126
- handle_SIGINT: handle_SIGINT,
127
- handle_SIGTERM: handle_SIGTERM,
128
- handle_SIGHUP: handle_SIGHUP,
129
- timeout: timeout,
130
- dumpio: dumpio,
131
- env: env,
132
- pipe: pipe,
133
- args: args,
134
- user_data_dir: user_data_dir,
135
- devtools: devtools,
136
- headless: headless,
137
- ignore_https_errors: ignore_https_errors,
138
- default_viewport: default_viewport,
139
- slow_mo: slow_mo,
140
- }
141
-
142
- @product_name ||= product
143
- browser = launcher.launch(options)
144
- if block_given?
145
- begin
146
- yield(browser)
147
- ensure
148
- browser.close
149
- end
150
- else
151
- browser
152
- end
153
- end
154
-
155
- # @param browser_ws_endpoint [String]
156
- # @param browser_url [String]
157
- # @param transport [Puppeteer::WebSocketTransport]
158
- # @param ignore_https_errors [Boolean]
159
- # @param default_viewport [Puppeteer::Viewport|nil]
160
- # @param slow_mo [Integer]
161
- # @return [Puppeteer::Browser]
162
- def connect(
163
- browser_ws_endpoint: nil,
164
- browser_url: nil,
165
- transport: nil,
166
- ignore_https_errors: nil,
167
- default_viewport: nil,
168
- slow_mo: nil
169
- )
170
- options = {
171
- browser_ws_endpoint: browser_ws_endpoint,
172
- browser_url: browser_url,
173
- transport: transport,
174
- ignore_https_errors: ignore_https_errors,
175
- default_viewport: default_viewport,
176
- slow_mo: slow_mo,
177
- }.compact
178
- browser = launcher.connect(options)
179
- if block_given?
180
- begin
181
- yield(browser)
182
- ensure
183
- browser.disconnect
184
- end
185
- else
186
- browser
187
- end
188
- end
189
-
190
- # @return [String]
191
- def executable_path
192
- launcher.executable_path
193
- end
194
-
195
- private def launcher
196
- @launcher ||= Puppeteer::Launcher.new(
197
- project_root: @project_root,
198
- preferred_revision: @preferred_revision,
199
- is_puppeteer_core: @is_puppeteer_core,
200
- product: @product_name,
201
- )
202
- end
203
-
204
- # @return [String]
205
- def product
206
- launcher.product
207
- end
208
-
209
- # @return [Puppeteer::Devices]
210
- def devices
211
- Puppeteer::Devices
212
- end
213
-
214
- # # @return {Object}
215
- # def errors
216
- # # ???
217
- # end
218
-
219
- # @param args [Array<String>]
220
- # @param user_data_dir [String]
221
- # @param devtools [Boolean]
222
- # @param headless [Boolean]
223
- # @return [Array<String>]
224
- def default_args(args: nil, user_data_dir: nil, devtools: nil, headless: nil)
225
- options = {
226
- args: args,
227
- user_data_dir: user_data_dir,
228
- devtools: devtools,
229
- headless: headless,
230
- }.compact
231
- launcher.default_args(options)
232
- end
233
-
234
- # @param {!BrowserFetcher.Options=} options
235
- # @return {!BrowserFetcher}
236
- def createBrowserFetcher(options = {})
237
- BrowserFetcher.new(@project_root, options)
238
- end
239
77
  end
@@ -111,7 +111,7 @@ class Puppeteer::BrowserRunner
111
111
  end
112
112
  end
113
113
 
114
- if @launch_options.handle_SIGHUP?
114
+ if @launch_options.handle_SIGHUP? && !Puppeteer.env.windows?
115
115
  trap(:HUP) do
116
116
  close
117
117
  end
@@ -273,6 +273,16 @@ class Puppeteer::Connection
273
273
  def create_session(target_info)
274
274
  result = send_message('Target.attachToTarget', targetId: target_info.target_id, flatten: true)
275
275
  session_id = result['sessionId']
276
- @sessions[session_id]
276
+
277
+ # Target.attachedToTarget is often notified after the result of Target.attachToTarget.
278
+ # D, [2020-04-04T23:04:30.736311 #91875] DEBUG -- : RECV << {"id"=>2, "result"=>{"sessionId"=>"DA002F8A95B04710502CB40D8430B95A"}}
279
+ # D, [2020-04-04T23:04:30.736649 #91875] DEBUG -- : RECV << {"method"=>"Target.attachedToTarget", "params"=>{"sessionId"=>"DA002F8A95B04710502CB40D8430B95A", "targetInfo"=>{"targetId"=>"EBAB949A7DE63F12CB94268AD3A9976B", "type"=>"page", "title"=>"about:blank", "url"=>"about:blank", "attached"=>true, "browserContextId"=>"46D23767E9B79DD9E589101121F6DADD"}, "waitingForDebugger"=>false}}
280
+ # So we have to wait for "Target.attachedToTarget" a bit.
281
+ 20.times do
282
+ if @sessions[session_id]
283
+ return @sessions[session_id]
284
+ end
285
+ sleep 0.1
286
+ end
277
287
  end
278
288
  end
@@ -126,12 +126,13 @@ class Puppeteer::DOMWorld
126
126
  execution_context.evaluate(page_function, *args)
127
127
  end
128
128
 
129
- # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
129
+ # `$()` in JavaScript.
130
130
  # @param {string} selector
131
131
  # @return {!Promise<?Puppeteer.ElementHandle>}
132
- def S(selector)
133
- document.S(selector)
132
+ def query_selector(selector)
133
+ document.query_selector(selector)
134
134
  end
135
+ alias_method :S, :query_selector
135
136
 
136
137
  private def evaluate_document
137
138
  # sometimes execution_context.evaluate_handle('document') returns null object.
@@ -158,30 +159,33 @@ class Puppeteer::DOMWorld
158
159
  document.Sx(expression)
159
160
  end
160
161
 
161
- # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
162
+ # `$eval()` in JavaScript.
162
163
  # @param {string} selector
163
164
  # @param {Function|string} pageFunction
164
165
  # @param {!Array<*>} args
165
166
  # @return {!Promise<(!Object|undefined)>}
166
- def Seval(selector, page_function, *args)
167
- document.Seval(selector, page_function, *args)
167
+ def eval_on_selector(selector, page_function, *args)
168
+ document.eval_on_selector(selector, page_function, *args)
168
169
  end
170
+ alias_method :Seval, :eval_on_selector
169
171
 
170
- # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
172
+ # `$$eval()` in JavaScript.
171
173
  # @param {string} selector
172
174
  # @param {Function|string} pageFunction
173
175
  # @param {!Array<*>} args
174
176
  # @return {!Promise<(!Object|undefined)>}
175
- def SSeval(selector, page_function, *args)
176
- document.SSeval(selector, page_function, *args)
177
+ def eval_on_selector_all(selector, page_function, *args)
178
+ document.eval_on_selector_all(selector, page_function, *args)
177
179
  end
180
+ alias_method :SSeval, :eval_on_selector_all
178
181
 
179
- # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
182
+ # `$$()` in JavaScript.
180
183
  # @param {string} selector
181
184
  # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
182
- def SS(selector)
183
- document.SS(selector)
185
+ def query_selector_all(selector)
186
+ document.query_selector_all(selector)
184
187
  end
188
+ alias_method :SS, :query_selector_all
185
189
 
186
190
  # @return [String]
187
191
  def content
@@ -226,144 +230,127 @@ class Puppeteer::DOMWorld
226
230
  end
227
231
  end
228
232
 
229
- # /**
230
- # * @param {!{url?: string, path?: string, content?: string, type?: string}} options
231
- # * @return {!Promise<!Puppeteer.ElementHandle>}
232
- # */
233
- # async addScriptTag(options) {
234
- # const {
235
- # url = null,
236
- # path = null,
237
- # content = null,
238
- # type = ''
239
- # } = options;
240
- # if (url !== null) {
241
- # try {
242
- # const context = await this.executionContext();
243
- # return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
244
- # } catch (error) {
245
- # throw new Error(`Loading script from ${url} failed`);
246
- # }
247
- # }
248
-
249
- # if (path !== null) {
250
- # let contents = await readFileAsync(path, 'utf8');
251
- # contents += '//# sourceURL=' + path.replace(/\n/g, '');
252
- # const context = await this.executionContext();
253
- # return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
254
- # }
255
-
256
- # if (content !== null) {
257
- # const context = await this.executionContext();
258
- # return (await context.evaluateHandle(addScriptContent, content, type)).asElement();
259
- # }
260
-
261
- # throw new Error('Provide an object with a `url`, `path` or `content` property');
262
-
263
- # /**
264
- # * @param {string} url
265
- # * @param {string} type
266
- # * @return {!Promise<!HTMLElement>}
267
- # */
268
- # async function addScriptUrl(url, type) {
269
- # const script = document.createElement('script');
270
- # script.src = url;
271
- # if (type)
272
- # script.type = type;
273
- # const promise = new Promise((res, rej) => {
274
- # script.onload = res;
275
- # script.onerror = rej;
276
- # });
277
- # document.head.appendChild(script);
278
- # await promise;
279
- # return script;
280
- # }
281
-
282
- # /**
283
- # * @param {string} content
284
- # * @param {string} type
285
- # * @return {!HTMLElement}
286
- # */
287
- # function addScriptContent(content, type = 'text/javascript') {
288
- # const script = document.createElement('script');
289
- # script.type = type;
290
- # script.text = content;
291
- # let error = null;
292
- # script.onerror = e => error = e;
293
- # document.head.appendChild(script);
294
- # if (error)
295
- # throw error;
296
- # return script;
297
- # }
298
- # }
233
+ # @param url [String?]
234
+ # @param path [String?]
235
+ # @param content [String?]
236
+ # @param type [String?]
237
+ def add_script_tag(url: nil, path: nil, content: nil, type: nil)
238
+ if url
239
+ begin
240
+ return execution_context.
241
+ evaluate_handle(ADD_SCRIPT_URL, url, type || '').
242
+ as_element
243
+ rescue Puppeteer::ExecutionContext::EvaluationError # for Chrome
244
+ raise "Loading script from #{url} failed"
245
+ rescue Puppeteer::Connection::ProtocolError # for Firefox
246
+ raise "Loading script from #{url} failed"
247
+ end
248
+ end
299
249
 
300
- # /**
301
- # * @param {!{url?: string, path?: string, content?: string}} options
302
- # * @return {!Promise<!Puppeteer.ElementHandle>}
303
- # */
304
- # async addStyleTag(options) {
305
- # const {
306
- # url = null,
307
- # path = null,
308
- # content = null
309
- # } = options;
310
- # if (url !== null) {
311
- # try {
312
- # const context = await this.executionContext();
313
- # return (await context.evaluateHandle(addStyleUrl, url)).asElement();
314
- # } catch (error) {
315
- # throw new Error(`Loading style from ${url} failed`);
316
- # }
317
- # }
318
-
319
- # if (path !== null) {
320
- # let contents = await readFileAsync(path, 'utf8');
321
- # contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
322
- # const context = await this.executionContext();
323
- # return (await context.evaluateHandle(addStyleContent, contents)).asElement();
324
- # }
325
-
326
- # if (content !== null) {
327
- # const context = await this.executionContext();
328
- # return (await context.evaluateHandle(addStyleContent, content)).asElement();
329
- # }
330
-
331
- # throw new Error('Provide an object with a `url`, `path` or `content` property');
332
-
333
- # /**
334
- # * @param {string} url
335
- # * @return {!Promise<!HTMLElement>}
336
- # */
337
- # async function addStyleUrl(url) {
338
- # const link = document.createElement('link');
339
- # link.rel = 'stylesheet';
340
- # link.href = url;
341
- # const promise = new Promise((res, rej) => {
342
- # link.onload = res;
343
- # link.onerror = rej;
344
- # });
345
- # document.head.appendChild(link);
346
- # await promise;
347
- # return link;
348
- # }
349
-
350
- # /**
351
- # * @param {string} content
352
- # * @return {!Promise<!HTMLElement>}
353
- # */
354
- # async function addStyleContent(content) {
355
- # const style = document.createElement('style');
356
- # style.type = 'text/css';
357
- # style.appendChild(document.createTextNode(content));
358
- # const promise = new Promise((res, rej) => {
359
- # style.onload = res;
360
- # style.onerror = rej;
361
- # });
362
- # document.head.appendChild(style);
363
- # await promise;
364
- # return style;
365
- # }
366
- # }
250
+ if path
251
+ contents = File.read(path)
252
+ contents += "//# sourceURL=#{path.gsub(/\n/, '')}"
253
+ return execution_context.
254
+ evaluate_handle(ADD_SCRIPT_CONTENT, contents, type || '').
255
+ as_element
256
+ end
257
+
258
+ if content
259
+ return execution_context.
260
+ evaluate_handle(ADD_SCRIPT_CONTENT, content, type || '').
261
+ as_element
262
+ end
263
+
264
+ raise ArgumentError.new('Provide an object with a `url`, `path` or `content` property')
265
+ end
266
+
267
+ ADD_SCRIPT_URL = <<~JAVASCRIPT
268
+ async (url, type) => {
269
+ const script = document.createElement('script');
270
+ script.src = url;
271
+ if (type)
272
+ script.type = type;
273
+ const promise = new Promise((res, rej) => {
274
+ script.onload = res;
275
+ script.onerror = rej;
276
+ });
277
+ document.head.appendChild(script);
278
+ await promise;
279
+ return script;
280
+ }
281
+ JAVASCRIPT
282
+
283
+ ADD_SCRIPT_CONTENT = <<~JAVASCRIPT
284
+ (content, type) => {
285
+ if (type === undefined) type = 'text/javascript';
286
+ const script = document.createElement('script');
287
+ script.type = type;
288
+ script.text = content;
289
+ let error = null;
290
+ script.onerror = e => error = e;
291
+ document.head.appendChild(script);
292
+ if (error)
293
+ throw error;
294
+ return script;
295
+ }
296
+ JAVASCRIPT
297
+
298
+ # @param url [String?]
299
+ # @param path [String?]
300
+ # @param content [String?]
301
+ def add_style_tag(url: nil, path: nil, content: nil)
302
+ if url
303
+ begin
304
+ return execution_context.evaluate_handle(ADD_STYLE_URL, url).as_element
305
+ rescue Puppeteer::ExecutionContext::EvaluationError # for Chrome
306
+ raise "Loading style from #{url} failed"
307
+ rescue Puppeteer::Connection::ProtocolError # for Firefox
308
+ raise "Loading style from #{url} failed"
309
+ end
310
+ end
311
+
312
+ if path
313
+ contents = File.read(path)
314
+ contents += "/*# sourceURL=#{path.gsub(/\n/, '')}*/"
315
+ return execution_context.evaluate_handle(ADD_STYLE_CONTENT, contents).as_element
316
+ end
317
+
318
+ if content
319
+ return execution_context.evaluate_handle(ADD_STYLE_CONTENT, content).as_element
320
+ end
321
+
322
+ raise ArgumentError.new('Provide an object with a `url`, `path` or `content` property')
323
+ end
324
+
325
+ ADD_STYLE_URL = <<~JAVASCRIPT
326
+ async (url) => {
327
+ const link = document.createElement('link');
328
+ link.rel = 'stylesheet';
329
+ link.href = url;
330
+ const promise = new Promise((res, rej) => {
331
+ link.onload = res;
332
+ link.onerror = rej;
333
+ });
334
+ document.head.appendChild(link);
335
+ await promise;
336
+ return link;
337
+ }
338
+ JAVASCRIPT
339
+
340
+ ADD_STYLE_CONTENT = <<~JAVASCRIPT
341
+ async (content) => {
342
+ const style = document.createElement('style');
343
+ style.type = 'text/css';
344
+ style.appendChild(document.createTextNode(content));
345
+ const promise = new Promise((res, rej) => {
346
+ style.onload = res;
347
+ style.onerror = rej;
348
+ });
349
+ document.head.appendChild(style);
350
+ await promise;
351
+ return style;
352
+ }
353
+ JAVASCRIPT
367
354
 
368
355
  class ElementNotFoundError < StandardError
369
356
  def initialize(selector)
@@ -376,14 +363,14 @@ class Puppeteer::DOMWorld
376
363
  # @param button [String] "left"|"right"|"middle"
377
364
  # @param click_count [Number]
378
365
  def click(selector, delay: nil, button: nil, click_count: nil)
379
- handle = S(selector) or raise ElementNotFoundError.new(selector)
366
+ handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
380
367
  handle.click(delay: delay, button: button, click_count: click_count)
381
368
  handle.dispose
382
369
  end
383
370
 
384
371
  # @param selector [String]
385
372
  def focus(selector)
386
- handle = S(selector) or raise ElementNotFoundError.new(selector)
373
+ handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
387
374
  handle.focus
388
375
  handle.dispose
389
376
  end
@@ -401,7 +388,7 @@ class Puppeteer::DOMWorld
401
388
  # @param selector [String]
402
389
  # @return [Array<String>]
403
390
  def select(selector, *values)
404
- handle = S(selector) or raise ElementNotFoundError.new(selector)
391
+ handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
405
392
  result = handle.select(*values)
406
393
  handle.dispose
407
394
 
@@ -410,7 +397,7 @@ class Puppeteer::DOMWorld
410
397
 
411
398
  # @param selector [String]
412
399
  def tap(selector)
413
- handle = S(selector) or raise ElementNotFoundError.new(selector)
400
+ handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
414
401
  handle.tap
415
402
  handle.dispose
416
403
  end
@@ -419,7 +406,7 @@ class Puppeteer::DOMWorld
419
406
  # @param text [String]
420
407
  # @param delay [Number]
421
408
  def type_text(selector, text, delay: nil)
422
- handle = S(selector) or raise ElementNotFoundError.new(selector)
409
+ handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
423
410
  handle.type_text(text, delay: delay)
424
411
  handle.dispose
425
412
  end