puppeteer-ruby 0.0.10

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