puppeteer-ruby 0.29.0 → 0.31.4

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