puppeteer-ruby 0.0.10

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.
Files changed (161) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +71 -0
  3. data/.github/stale.yml +16 -0
  4. data/.gitignore +19 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +302 -0
  7. data/.travis.yml +7 -0
  8. data/Dockerfile +6 -0
  9. data/Gemfile +6 -0
  10. data/README.md +54 -0
  11. data/Rakefile +1 -0
  12. data/bin/console +11 -0
  13. data/bin/setup +8 -0
  14. data/docker-compose.yml +15 -0
  15. data/docs/Puppeteer.html +2020 -0
  16. data/docs/Puppeteer/AsyncAwaitBehavior.html +105 -0
  17. data/docs/Puppeteer/Browser.html +2148 -0
  18. data/docs/Puppeteer/BrowserContext.html +809 -0
  19. data/docs/Puppeteer/BrowserFetcher.html +214 -0
  20. data/docs/Puppeteer/BrowserRunner.html +914 -0
  21. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +477 -0
  22. data/docs/Puppeteer/CDPSession.html +813 -0
  23. data/docs/Puppeteer/CDPSession/Error.html +124 -0
  24. data/docs/Puppeteer/ConcurrentRubyUtils.html +430 -0
  25. data/docs/Puppeteer/Connection.html +960 -0
  26. data/docs/Puppeteer/Connection/MessageCallback.html +434 -0
  27. data/docs/Puppeteer/Connection/ProtocolError.html +216 -0
  28. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +217 -0
  29. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +244 -0
  30. data/docs/Puppeteer/ConsoleMessage.html +565 -0
  31. data/docs/Puppeteer/ConsoleMessage/Location.html +433 -0
  32. data/docs/Puppeteer/DOMWorld.html +2219 -0
  33. data/docs/Puppeteer/DOMWorld/DetachedError.html +124 -0
  34. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +124 -0
  35. data/docs/Puppeteer/DebugPrint.html +233 -0
  36. data/docs/Puppeteer/Device.html +470 -0
  37. data/docs/Puppeteer/Devices.html +139 -0
  38. data/docs/Puppeteer/ElementHandle.html +2542 -0
  39. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +206 -0
  40. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +206 -0
  41. data/docs/Puppeteer/ElementHandle/Point.html +492 -0
  42. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +124 -0
  43. data/docs/Puppeteer/EmulationManager.html +454 -0
  44. data/docs/Puppeteer/EventCallbackable.html +433 -0
  45. data/docs/Puppeteer/EventCallbackable/EventListeners.html +435 -0
  46. data/docs/Puppeteer/ExecutionContext.html +998 -0
  47. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +124 -0
  48. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +357 -0
  49. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +389 -0
  50. data/docs/Puppeteer/FileChooser.html +455 -0
  51. data/docs/Puppeteer/Frame.html +3677 -0
  52. data/docs/Puppeteer/FrameManager.html +2410 -0
  53. data/docs/Puppeteer/FrameManager/NavigationError.html +124 -0
  54. data/docs/Puppeteer/IfPresent.html +222 -0
  55. data/docs/Puppeteer/JSHandle.html +1352 -0
  56. data/docs/Puppeteer/Keyboard.html +1557 -0
  57. data/docs/Puppeteer/Keyboard/KeyDefinition.html +831 -0
  58. data/docs/Puppeteer/Keyboard/KeyDescription.html +603 -0
  59. data/docs/Puppeteer/Launcher.html +237 -0
  60. data/docs/Puppeteer/Launcher/Base.html +385 -0
  61. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +124 -0
  62. data/docs/Puppeteer/Launcher/BrowserOptions.html +441 -0
  63. data/docs/Puppeteer/Launcher/Chrome.html +669 -0
  64. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +382 -0
  65. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +531 -0
  66. data/docs/Puppeteer/Launcher/LaunchOptions.html +893 -0
  67. data/docs/Puppeteer/LifecycleWatcher.html +834 -0
  68. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +363 -0
  69. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +206 -0
  70. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +124 -0
  71. data/docs/Puppeteer/Mouse.html +1105 -0
  72. data/docs/Puppeteer/Mouse/Button.html +136 -0
  73. data/docs/Puppeteer/NetworkManager.html +901 -0
  74. data/docs/Puppeteer/NetworkManager/Credentials.html +385 -0
  75. data/docs/Puppeteer/Page.html +5970 -0
  76. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
  77. data/docs/Puppeteer/Page/ScreenshotOptions.html +845 -0
  78. data/docs/Puppeteer/Page/ScriptTag.html +555 -0
  79. data/docs/Puppeteer/Page/StyleTag.html +448 -0
  80. data/docs/Puppeteer/Page/TargetCrashedError.html +124 -0
  81. data/docs/Puppeteer/RemoteObject.html +1087 -0
  82. data/docs/Puppeteer/Target.html +1336 -0
  83. data/docs/Puppeteer/Target/InitializeFailure.html +124 -0
  84. data/docs/Puppeteer/Target/TargetInfo.html +729 -0
  85. data/docs/Puppeteer/TimeoutError.html +135 -0
  86. data/docs/Puppeteer/TimeoutSettings.html +496 -0
  87. data/docs/Puppeteer/TouchScreen.html +464 -0
  88. data/docs/Puppeteer/Viewport.html +837 -0
  89. data/docs/Puppeteer/WaitTask.html +637 -0
  90. data/docs/Puppeteer/WaitTask/TerminatedError.html +124 -0
  91. data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
  92. data/docs/Puppeteer/WebSocket.html +673 -0
  93. data/docs/Puppeteer/WebSocket/DriverImpl.html +412 -0
  94. data/docs/Puppeteer/WebSocketTransport.html +600 -0
  95. data/docs/Puppeteer/WebSocktTransportError.html +124 -0
  96. data/docs/_index.html +823 -0
  97. data/docs/class_list.html +51 -0
  98. data/docs/css/common.css +1 -0
  99. data/docs/css/full_list.css +58 -0
  100. data/docs/css/style.css +496 -0
  101. data/docs/file.README.html +123 -0
  102. data/docs/file_list.html +56 -0
  103. data/docs/frames.html +17 -0
  104. data/docs/index.html +123 -0
  105. data/docs/js/app.js +314 -0
  106. data/docs/js/full_list.js +216 -0
  107. data/docs/js/jquery.js +4 -0
  108. data/docs/method_list.html +4075 -0
  109. data/docs/top-level-namespace.html +126 -0
  110. data/lib/puppeteer.rb +200 -0
  111. data/lib/puppeteer/async_await_behavior.rb +38 -0
  112. data/lib/puppeteer/browser.rb +259 -0
  113. data/lib/puppeteer/browser_context.rb +90 -0
  114. data/lib/puppeteer/browser_fetcher.rb +6 -0
  115. data/lib/puppeteer/browser_runner.rb +161 -0
  116. data/lib/puppeteer/cdp_session.rb +100 -0
  117. data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
  118. data/lib/puppeteer/connection.rb +254 -0
  119. data/lib/puppeteer/console_message.rb +24 -0
  120. data/lib/puppeteer/debug_print.rb +20 -0
  121. data/lib/puppeteer/device.rb +12 -0
  122. data/lib/puppeteer/devices.rb +885 -0
  123. data/lib/puppeteer/dom_world.rb +484 -0
  124. data/lib/puppeteer/element_handle.rb +433 -0
  125. data/lib/puppeteer/element_handle/bounding_box.rb +12 -0
  126. data/lib/puppeteer/element_handle/box_model.rb +19 -0
  127. data/lib/puppeteer/element_handle/point.rb +26 -0
  128. data/lib/puppeteer/emulation_manager.rb +46 -0
  129. data/lib/puppeteer/errors.rb +2 -0
  130. data/lib/puppeteer/event_callbackable.rb +88 -0
  131. data/lib/puppeteer/execution_context.rb +254 -0
  132. data/lib/puppeteer/file_chooser.rb +29 -0
  133. data/lib/puppeteer/frame.rb +286 -0
  134. data/lib/puppeteer/frame_manager.rb +378 -0
  135. data/lib/puppeteer/if_present.rb +18 -0
  136. data/lib/puppeteer/js_handle.rb +142 -0
  137. data/lib/puppeteer/keyboard.rb +183 -0
  138. data/lib/puppeteer/keyboard/key_description.rb +19 -0
  139. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
  140. data/lib/puppeteer/launcher.rb +25 -0
  141. data/lib/puppeteer/launcher/base.rb +48 -0
  142. data/lib/puppeteer/launcher/browser_options.rb +41 -0
  143. data/lib/puppeteer/launcher/chrome.rb +211 -0
  144. data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
  145. data/lib/puppeteer/launcher/launch_options.rb +68 -0
  146. data/lib/puppeteer/lifecycle_watcher.rb +171 -0
  147. data/lib/puppeteer/mouse.rb +123 -0
  148. data/lib/puppeteer/network_manager.rb +122 -0
  149. data/lib/puppeteer/page.rb +1065 -0
  150. data/lib/puppeteer/page/screenshot_options.rb +78 -0
  151. data/lib/puppeteer/remote_object.rb +143 -0
  152. data/lib/puppeteer/target.rb +150 -0
  153. data/lib/puppeteer/timeout_settings.rb +15 -0
  154. data/lib/puppeteer/touch_screen.rb +43 -0
  155. data/lib/puppeteer/version.rb +3 -0
  156. data/lib/puppeteer/viewport.rb +54 -0
  157. data/lib/puppeteer/wait_task.rb +188 -0
  158. data/lib/puppeteer/web_socket.rb +122 -0
  159. data/lib/puppeteer/web_socket_transport.rb +49 -0
  160. data/puppeteer-ruby.gemspec +32 -0
  161. metadata +355 -0
@@ -0,0 +1,484 @@
1
+ require 'thread'
2
+
3
+ # https://github.com/puppeteer/puppeteer/blob/master/src/DOMWorld.js
4
+ class Puppeteer::DOMWorld
5
+ using Puppeteer::AsyncAwaitBehavior
6
+
7
+ # @param {!Puppeteer.FrameManager} frameManager
8
+ # @param {!Puppeteer.Frame} frame
9
+ # @param {!Puppeteer.TimeoutSettings} timeoutSettings
10
+ def initialize(frame_manager, frame, timeout_settings)
11
+ @frame_manager = frame_manager
12
+ @frame = frame
13
+ @timeout_settings = timeout_settings
14
+ @context_promise = resolvable_future
15
+ @pending_destroy = []
16
+ @wait_tasks = Set.new
17
+ @detached = false
18
+ end
19
+
20
+ attr_reader :frame
21
+
22
+ # only used in Puppeteer::WaitTask#initialize
23
+ def _wait_tasks
24
+ @wait_tasks
25
+ end
26
+
27
+ # @param {?Puppeteer.ExecutionContext} context
28
+ def context=(context)
29
+ # D, [2020-04-12T22:45:03.938754 #46154] DEBUG -- : RECV << {"method"=>"Runtime.executionContextCreated", "params"=>{"context"=>{"id"=>3, "origin"=>"https://github.com", "name"=>"", "auxData"=>{"isDefault"=>true, "type"=>"default", "frameId"=>"3AD7F1E82BCBA88BFE31D03BC49FF6CB"}}}, "sessionId"=>"636CEF0C4FEAFC4FE815E9E7B5F7BA68"}
30
+ # D, [2020-04-12T22:45:03.938856 #46154] DEBUG -- : RECV << {"method"=>"Runtime.executionContextCreated", "params"=>{"context"=>{"id"=>4, "origin"=>"://", "name"=>"__puppeteer_utility_world__", "auxData"=>{"isDefault"=>false, "type"=>"isolated", "frameId"=>"3AD7F1E82BCBA88BFE31D03BC49FF6CB"}}}, "sessionId"=>"636CEF0C4FEAFC4FE815E9E7B5F7BA68"}
31
+ # D, [2020-04-12T22:45:03.938960 #46154] DEBUG -- : RECV << {"method"=>"Runtime.executionContextDestroyed", "params"=>{"executionContextId"=>1}, "sessionId"=>"636CEF0C4FEAFC4FE815E9E7B5F7BA68"}
32
+ # D, [2020-04-12T22:45:03.939110 #46154] DEBUG -- : RECV << {"method"=>"Page.frameNavigated", "params"=>{"frame"=>{"id"=>"3AD7F1E82BCBA88BFE31D03BC49FF6CB", "loaderId"=>"301B349884E582986C502CBE020966DF", "url"=>"https://github.com/", "securityOrigin"=>"https://github.com", "mimeType"=>"text/html"}}, "sessionId"=>"636CEF0C4FEAFC4FE815E9E7B5F7BA68"}
33
+ # D, [2020-04-12T22:45:03.939793 #46154] DEBUG -- : RECV << {"method"=>"Runtime.executionContextDestroyed", "params"=>{"executionContextId"=>2}, "sessionId"=>"636CEF0C4FEAFC4FE815E9E7B5F7BA68"}
34
+ # executionContextDestroyed is often notified after executionContextCreated.
35
+
36
+ if context
37
+ if @context_promise.fulfilled?
38
+ @pending_destroy << context._context_id
39
+ @document = nil
40
+ @context_promise = resolvable_future
41
+ end
42
+ @context_promise.fulfill(context)
43
+ @wait_tasks.each(&:async_rerun)
44
+ else
45
+ raise ArgumentError.new("context should now be nil. Use #delete_context for clearing document.")
46
+ end
47
+ end
48
+
49
+ def delete_context(execution_context_id)
50
+ if @pending_destroy.include?(execution_context_id)
51
+ @pending_destroy.delete(execution_context_id)
52
+ else
53
+ @document = nil
54
+ @context_promise = resolvable_future
55
+ end
56
+ end
57
+
58
+ def has_context?
59
+ @context_promise.resolved?
60
+ end
61
+
62
+ def detach
63
+ @detached = true
64
+ @wait_tasks.each do |wait_task|
65
+ wait_task.terminate(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
66
+ end
67
+ end
68
+
69
+ class DetachedError < StandardError; end
70
+
71
+ # @return {!Promise<!Puppeteer.ExecutionContext>}
72
+ def execution_context
73
+ if @detached
74
+ raise DetachedError.new("Execution Context is not available in detached frame \"#{@frame.url}\" (are you trying to evaluate?)")
75
+ end
76
+ @context_promise.value!
77
+ end
78
+
79
+ # @param {Function|string} pageFunction
80
+ # @param {!Array<*>} args
81
+ # @return {!Promise<!Puppeteer.JSHandle>}
82
+ def evaluate_handle(page_function, *args)
83
+ execution_context.evaluate_handle(page_function, *args)
84
+ end
85
+
86
+ # @param {Function|string} pageFunction
87
+ # @param {!Array<*>} args
88
+ # @return {!Promise<*>}
89
+ def evaluate(page_function, *args)
90
+ execution_context.evaluate(page_function, *args)
91
+ end
92
+
93
+ # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
94
+ # @param {string} selector
95
+ # @return {!Promise<?Puppeteer.ElementHandle>}
96
+ def S(selector)
97
+ document.S(selector)
98
+ end
99
+
100
+ class DocumentEvaluationError < StandardError; end
101
+
102
+ private def evaluate_document
103
+ # sometimes execution_context.evaluate_handle('document') returns null object.
104
+ # D, [2020-04-24T02:17:51.023631 #220] DEBUG -- : RECV << {"id"=>20, "result"=>{"result"=>{"type"=>"object", "subtype"=>"null", "value"=>nil}}, "sessionId"=>"78E9CF1E14D81294E320E7C20E5CDE06"}
105
+ # retry if so.
106
+ 5.times do
107
+ handle = execution_context.evaluate_handle('document')
108
+ return handle if handle.is_a?(Puppeteer::ElementHandle)
109
+ end
110
+ raise DocumentEvaluationError.new("'document' object cannot be evaluated as an Element")
111
+ end
112
+
113
+ private def document
114
+ @document ||= evaluate_document.as_element
115
+ end
116
+
117
+ # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
118
+ # @param {string} expression
119
+ # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
120
+ def Sx(expression)
121
+ document.Sx(expression)
122
+ end
123
+
124
+ # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
125
+ # @param {string} selector
126
+ # @param {Function|string} pageFunction
127
+ # @param {!Array<*>} args
128
+ # @return {!Promise<(!Object|undefined)>}
129
+ def Seval(selector, page_function, *args)
130
+ document.Seval(selector, page_function, *args)
131
+ end
132
+
133
+ # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
134
+ # @param {string} selector
135
+ # @param {Function|string} pageFunction
136
+ # @param {!Array<*>} args
137
+ # @return {!Promise<(!Object|undefined)>}
138
+ def SSeval(selector, page_function, *args)
139
+ document.SSeval(selector, page_function, *args)
140
+ end
141
+
142
+ # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
143
+ # @param {string} selector
144
+ # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
145
+ def SS(selector)
146
+ document.SS(selector)
147
+ end
148
+
149
+ # /**
150
+ # * @return {!Promise<String>}
151
+ # */
152
+ # async content() {
153
+ # return await this.evaluate(() => {
154
+ # let retVal = '';
155
+ # if (document.doctype)
156
+ # retVal = new XMLSerializer().serializeToString(document.doctype);
157
+ # if (document.documentElement)
158
+ # retVal += document.documentElement.outerHTML;
159
+ # return retVal;
160
+ # });
161
+ # }
162
+
163
+ # /**
164
+ # * @param {string} html
165
+ # * @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
166
+ # */
167
+ # async setContent(html, options = {}) {
168
+ # const {
169
+ # waitUntil = ['load'],
170
+ # timeout = this._timeoutSettings.navigationTimeout(),
171
+ # } = options;
172
+ # // We rely upon the fact that document.open() will reset frame lifecycle with "init"
173
+ # // lifecycle event. @see https://crrev.com/608658
174
+ # await this.evaluate(html => {
175
+ # document.open();
176
+ # document.write(html);
177
+ # document.close();
178
+ # }, html);
179
+ # const watcher = new LifecycleWatcher(this._frameManager, this._frame, waitUntil, timeout);
180
+ # const error = await Promise.race([
181
+ # watcher.timeoutOrTerminationPromise(),
182
+ # watcher.lifecyclePromise(),
183
+ # ]);
184
+ # watcher.dispose();
185
+ # if (error)
186
+ # throw error;
187
+ # }
188
+
189
+ # /**
190
+ # * @param {!{url?: string, path?: string, content?: string, type?: string}} options
191
+ # * @return {!Promise<!Puppeteer.ElementHandle>}
192
+ # */
193
+ # async addScriptTag(options) {
194
+ # const {
195
+ # url = null,
196
+ # path = null,
197
+ # content = null,
198
+ # type = ''
199
+ # } = options;
200
+ # if (url !== null) {
201
+ # try {
202
+ # const context = await this.executionContext();
203
+ # return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
204
+ # } catch (error) {
205
+ # throw new Error(`Loading script from ${url} failed`);
206
+ # }
207
+ # }
208
+
209
+ # if (path !== null) {
210
+ # let contents = await readFileAsync(path, 'utf8');
211
+ # contents += '//# sourceURL=' + path.replace(/\n/g, '');
212
+ # const context = await this.executionContext();
213
+ # return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
214
+ # }
215
+
216
+ # if (content !== null) {
217
+ # const context = await this.executionContext();
218
+ # return (await context.evaluateHandle(addScriptContent, content, type)).asElement();
219
+ # }
220
+
221
+ # throw new Error('Provide an object with a `url`, `path` or `content` property');
222
+
223
+ # /**
224
+ # * @param {string} url
225
+ # * @param {string} type
226
+ # * @return {!Promise<!HTMLElement>}
227
+ # */
228
+ # async function addScriptUrl(url, type) {
229
+ # const script = document.createElement('script');
230
+ # script.src = url;
231
+ # if (type)
232
+ # script.type = type;
233
+ # const promise = new Promise((res, rej) => {
234
+ # script.onload = res;
235
+ # script.onerror = rej;
236
+ # });
237
+ # document.head.appendChild(script);
238
+ # await promise;
239
+ # return script;
240
+ # }
241
+
242
+ # /**
243
+ # * @param {string} content
244
+ # * @param {string} type
245
+ # * @return {!HTMLElement}
246
+ # */
247
+ # function addScriptContent(content, type = 'text/javascript') {
248
+ # const script = document.createElement('script');
249
+ # script.type = type;
250
+ # script.text = content;
251
+ # let error = null;
252
+ # script.onerror = e => error = e;
253
+ # document.head.appendChild(script);
254
+ # if (error)
255
+ # throw error;
256
+ # return script;
257
+ # }
258
+ # }
259
+
260
+ # /**
261
+ # * @param {!{url?: string, path?: string, content?: string}} options
262
+ # * @return {!Promise<!Puppeteer.ElementHandle>}
263
+ # */
264
+ # async addStyleTag(options) {
265
+ # const {
266
+ # url = null,
267
+ # path = null,
268
+ # content = null
269
+ # } = options;
270
+ # if (url !== null) {
271
+ # try {
272
+ # const context = await this.executionContext();
273
+ # return (await context.evaluateHandle(addStyleUrl, url)).asElement();
274
+ # } catch (error) {
275
+ # throw new Error(`Loading style from ${url} failed`);
276
+ # }
277
+ # }
278
+
279
+ # if (path !== null) {
280
+ # let contents = await readFileAsync(path, 'utf8');
281
+ # contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
282
+ # const context = await this.executionContext();
283
+ # return (await context.evaluateHandle(addStyleContent, contents)).asElement();
284
+ # }
285
+
286
+ # if (content !== null) {
287
+ # const context = await this.executionContext();
288
+ # return (await context.evaluateHandle(addStyleContent, content)).asElement();
289
+ # }
290
+
291
+ # throw new Error('Provide an object with a `url`, `path` or `content` property');
292
+
293
+ # /**
294
+ # * @param {string} url
295
+ # * @return {!Promise<!HTMLElement>}
296
+ # */
297
+ # async function addStyleUrl(url) {
298
+ # const link = document.createElement('link');
299
+ # link.rel = 'stylesheet';
300
+ # link.href = url;
301
+ # const promise = new Promise((res, rej) => {
302
+ # link.onload = res;
303
+ # link.onerror = rej;
304
+ # });
305
+ # document.head.appendChild(link);
306
+ # await promise;
307
+ # return link;
308
+ # }
309
+
310
+ # /**
311
+ # * @param {string} content
312
+ # * @return {!Promise<!HTMLElement>}
313
+ # */
314
+ # async function addStyleContent(content) {
315
+ # const style = document.createElement('style');
316
+ # style.type = 'text/css';
317
+ # style.appendChild(document.createTextNode(content));
318
+ # const promise = new Promise((res, rej) => {
319
+ # style.onload = res;
320
+ # style.onerror = rej;
321
+ # });
322
+ # document.head.appendChild(style);
323
+ # await promise;
324
+ # return style;
325
+ # }
326
+ # }
327
+
328
+ # @param selector [String]
329
+ # @param delay [Number]
330
+ # @param button [String] "left"|"right"|"middle"
331
+ # @param click_count [Number]
332
+ def click(selector, delay: nil, button: nil, click_count: nil)
333
+ handle = S(selector)
334
+ handle.click(delay: delay, button: button, click_count: click_count)
335
+ handle.dispose
336
+ end
337
+
338
+ # /**
339
+ # * @param {string} selector
340
+ # */
341
+ # async focus(selector) {
342
+ # const handle = await this.$(selector);
343
+ # assert(handle, 'No node found for selector: ' + selector);
344
+ # await handle.focus();
345
+ # await handle.dispose();
346
+ # }
347
+
348
+ # /**
349
+ # * @param {string} selector
350
+ # */
351
+ # async hover(selector) {
352
+ # const handle = await this.$(selector);
353
+ # assert(handle, 'No node found for selector: ' + selector);
354
+ # await handle.hover();
355
+ # await handle.dispose();
356
+ # }
357
+
358
+ # @param selector [String]
359
+ # @return [Array<String>]
360
+ def select(selector, *values)
361
+ handle = S(selector)
362
+ result = handle.select(*values)
363
+ handle.dispose
364
+
365
+ result
366
+ end
367
+
368
+ # @param selector [String]
369
+ def tap(selector)
370
+ handle = S(selector)
371
+ handle.tap
372
+ handle.dispose
373
+ end
374
+
375
+ # @param selector [String]
376
+ # @param text [String]
377
+ # @param delay [Number]
378
+ def type_text(selector, text, delay: nil)
379
+ handle = S(selector)
380
+ handle.type_text(text, delay: delay)
381
+ handle.dispose
382
+ end
383
+
384
+ # @param selector [String]
385
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
386
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
387
+ # @param timeout [Integer]
388
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
389
+ wait_for_selector_or_xpath(selector, false, visible: visible, hidden: hidden, timeout: timeout)
390
+ end
391
+
392
+ # @param xpath [String]
393
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
394
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
395
+ # @param timeout [Integer]
396
+ def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
397
+ wait_for_selector_or_xpath(xpath, true, visible: visible, hidden: hidden, timeout: timeout)
398
+ end
399
+
400
+ # /**
401
+ # * @param {Function|string} pageFunction
402
+ # * @param {!{polling?: string|number, timeout?: number}=} options
403
+ # * @return {!Promise<!Puppeteer.JSHandle>}
404
+ # */
405
+ # waitForFunction(pageFunction, options = {}, ...args) {
406
+ # const {
407
+ # polling = 'raf',
408
+ # timeout = this._timeoutSettings.timeout(),
409
+ # } = options;
410
+ # return new WaitTask(this, pageFunction, 'function', polling, timeout, ...args).promise;
411
+ # }
412
+
413
+ # /**
414
+ # * @return {!Promise<string>}
415
+ # */
416
+ # async title() {
417
+ # return this.evaluate(() => document.title);
418
+ # }
419
+
420
+ # @param selector_or_xpath [String]
421
+ # @param is_xpath [Boolean]
422
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
423
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
424
+ # @param timeout [Integer]
425
+ private def wait_for_selector_or_xpath(selector_or_xpath, is_xpath, visible: nil, hidden: nil, timeout: nil)
426
+ option_wait_for_visible = visible || false
427
+ option_wait_for_hidden = hidden || false
428
+ option_timeout = timeout || @timeout_settings.timeout
429
+
430
+ polling =
431
+ if option_wait_for_visible || option_wait_for_hidden
432
+ 'raf'
433
+ else
434
+ 'mutation'
435
+ end
436
+ title = "#{is_xpath ? :XPath : :selector} #{selector_or_xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"
437
+
438
+ wait_task = Puppeteer::WaitTask.new(
439
+ dom_world: self,
440
+ predicate_body: "return (#{PREDICATE})(...args)",
441
+ title: title,
442
+ polling: polling,
443
+ timeout: option_timeout,
444
+ args: [selector_or_xpath, is_xpath, option_wait_for_visible, option_wait_for_hidden],
445
+ )
446
+ handle = wait_task.await_promise
447
+ unless handle.as_element
448
+ handle.dispose
449
+ return nil
450
+ end
451
+ handle.as_element
452
+ end
453
+
454
+ PREDICATE = <<~JAVASCRIPT
455
+ /**
456
+ * @param {string} selectorOrXPath
457
+ * @param {boolean} isXPath
458
+ * @param {boolean} waitForVisible
459
+ * @param {boolean} waitForHidden
460
+ * @return {?Node|boolean}
461
+ */
462
+ function _(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
463
+ const node = isXPath
464
+ ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
465
+ : document.querySelector(selectorOrXPath);
466
+ if (!node)
467
+ return waitForHidden;
468
+ if (!waitForVisible && !waitForHidden)
469
+ return node;
470
+ const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
471
+ const style = window.getComputedStyle(element);
472
+ const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
473
+ const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
474
+ return success ? node : null;
475
+ /**
476
+ * @return {boolean}
477
+ */
478
+ function hasVisibleBoundingBox() {
479
+ const rect = element.getBoundingClientRect();
480
+ return !!(rect.top || rect.bottom || rect.width || rect.height);
481
+ }
482
+ }
483
+ JAVASCRIPT
484
+ end