playwright-ruby-client 0.0.3 → 0.0.8

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +119 -12
  3. data/docs/api_coverage.md +354 -0
  4. data/lib/playwright.rb +8 -0
  5. data/lib/playwright/channel_owner.rb +16 -2
  6. data/lib/playwright/channel_owners/android.rb +10 -1
  7. data/lib/playwright/channel_owners/android_device.rb +163 -0
  8. data/lib/playwright/channel_owners/browser.rb +22 -29
  9. data/lib/playwright/channel_owners/browser_context.rb +43 -0
  10. data/lib/playwright/channel_owners/console_message.rb +21 -0
  11. data/lib/playwright/channel_owners/element_handle.rb +314 -0
  12. data/lib/playwright/channel_owners/frame.rb +466 -7
  13. data/lib/playwright/channel_owners/js_handle.rb +55 -0
  14. data/lib/playwright/channel_owners/page.rb +353 -5
  15. data/lib/playwright/channel_owners/request.rb +90 -0
  16. data/lib/playwright/channel_owners/webkit_browser.rb +1 -1
  17. data/lib/playwright/connection.rb +15 -14
  18. data/lib/playwright/errors.rb +1 -1
  19. data/lib/playwright/event_emitter.rb +13 -0
  20. data/lib/playwright/input_files.rb +42 -0
  21. data/lib/playwright/input_type.rb +19 -0
  22. data/lib/playwright/input_types/android_input.rb +19 -0
  23. data/lib/playwright/input_types/keyboard.rb +32 -0
  24. data/lib/playwright/input_types/mouse.rb +4 -0
  25. data/lib/playwright/input_types/touchscreen.rb +4 -0
  26. data/lib/playwright/javascript.rb +13 -0
  27. data/lib/playwright/javascript/expression.rb +67 -0
  28. data/lib/playwright/javascript/function.rb +67 -0
  29. data/lib/playwright/javascript/value_parser.rb +75 -0
  30. data/lib/playwright/javascript/value_serializer.rb +54 -0
  31. data/lib/playwright/playwright_api.rb +45 -25
  32. data/lib/playwright/select_option_values.rb +32 -0
  33. data/lib/playwright/timeout_settings.rb +19 -0
  34. data/lib/playwright/url_matcher.rb +19 -0
  35. data/lib/playwright/utils.rb +37 -0
  36. data/lib/playwright/version.rb +1 -1
  37. data/lib/playwright/wait_helper.rb +73 -0
  38. data/lib/playwright_api/accessibility.rb +60 -6
  39. data/lib/playwright_api/android.rb +33 -0
  40. data/lib/playwright_api/android_device.rb +78 -0
  41. data/lib/playwright_api/android_input.rb +25 -0
  42. data/lib/playwright_api/binding_call.rb +18 -0
  43. data/lib/playwright_api/browser.rb +136 -44
  44. data/lib/playwright_api/browser_context.rb +378 -51
  45. data/lib/playwright_api/browser_type.rb +137 -55
  46. data/lib/playwright_api/cdp_session.rb +32 -7
  47. data/lib/playwright_api/chromium_browser_context.rb +31 -0
  48. data/lib/playwright_api/console_message.rb +27 -7
  49. data/lib/playwright_api/dialog.rb +47 -3
  50. data/lib/playwright_api/download.rb +29 -5
  51. data/lib/playwright_api/element_handle.rb +429 -143
  52. data/lib/playwright_api/file_chooser.rb +13 -2
  53. data/lib/playwright_api/frame.rb +633 -179
  54. data/lib/playwright_api/js_handle.rb +97 -17
  55. data/lib/playwright_api/keyboard.rb +152 -24
  56. data/lib/playwright_api/mouse.rb +28 -3
  57. data/lib/playwright_api/page.rb +1183 -317
  58. data/lib/playwright_api/playwright.rb +174 -13
  59. data/lib/playwright_api/request.rb +115 -30
  60. data/lib/playwright_api/response.rb +22 -3
  61. data/lib/playwright_api/route.rb +63 -4
  62. data/lib/playwright_api/selectors.rb +29 -7
  63. data/lib/playwright_api/touchscreen.rb +2 -1
  64. data/lib/playwright_api/video.rb +11 -1
  65. data/lib/playwright_api/web_socket.rb +5 -5
  66. data/lib/playwright_api/worker.rb +29 -5
  67. data/playwright.gemspec +3 -0
  68. metadata +68 -2
@@ -2,9 +2,8 @@ module Playwright
2
2
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_frame.py
3
3
  define_channel_owner :Frame do
4
4
  def after_initialize
5
- @event_emitter = Object.new.extend(EventEmitter)
6
5
  if @initializer['parentFrame']
7
- @parent_frame = self.from(@initializer['parentFrame'])
6
+ @parent_frame = ChannelOwners::Frame.from(@initializer['parentFrame'])
8
7
  @parent_frame.send(:append_child_frame_from_child, self)
9
8
  end
10
9
  @name = @initializer['name']
@@ -12,9 +11,36 @@ module Playwright
12
11
  @detached = false
13
12
  @child_frames = Set.new
14
13
  @load_states = Set.new(@initializer['loadStates'])
14
+ @event_emitter = Object.new.extend(EventEmitter)
15
+
16
+ @channel.on('loadstate', ->(params) {
17
+ on_load_state(add: params['add'], remove: params['remove'])
18
+ })
19
+ @channel.on('navigated', method(:on_frame_navigated))
20
+ end
21
+
22
+ attr_reader :page, :parent_frame
23
+ attr_writer :detached
24
+
25
+ private def on_load_state(add:, remove:)
26
+ if add
27
+ @load_states << add
28
+ @event_emitter.emit('loadstate', add)
29
+ end
30
+ if remove
31
+ @load_states.delete(remove)
32
+ end
15
33
  end
16
34
 
17
- attr_reader :page
35
+ private def on_frame_navigated(event)
36
+ @url = event['url']
37
+ @name = event['name']
38
+ @event_emitter.emit('navigated', event)
39
+
40
+ unless event['error']
41
+ @page&.emit('framenavigated', self)
42
+ end
43
+ end
18
44
 
19
45
  def goto(url, timeout: nil, waitUntil: nil, referer: nil)
20
46
  params = {
@@ -24,20 +50,453 @@ module Playwright
24
50
  referer: referer
25
51
  }.compact
26
52
  resp = @channel.send_message_to_server('goto', params)
27
- ChannelOwners::Response.from(resp)
53
+ ChannelOwners::Response.from_nullable(resp)
54
+ end
55
+
56
+ private def setup_navigation_wait_helper(timeout:)
57
+ WaitHelper.new.tap do |helper|
58
+ helper.reject_on_event(@page, Events::Page::Close, AlreadyClosedError.new)
59
+ helper.reject_on_event(@page, Events::Page::Crash, CrashedError.new)
60
+ helper.reject_on_event(@page, Events::Page::FrameDetached, FrameAlreadyDetachedError.new)
61
+ helper.reject_on_timeout(timeout, "Timeout #{timeout}ms exceeded.")
62
+ end
63
+ end
64
+
65
+ def expect_navigation(timeout: nil, url: nil, waitUntil: nil, &block)
66
+ option_wait_until = waitUntil || 'load'
67
+ option_timeout = timeout || @page.send(:timeout_settings).navigation_timeout
68
+ time_start = Time.now
69
+
70
+ wait_helper = setup_navigation_wait_helper(timeout: option_timeout)
71
+
72
+ predicate =
73
+ if url
74
+ matcher = UrlMatcher.new(url)
75
+ ->(event) { event['error'] || matcher.match?(event['url']) }
76
+ else
77
+ ->(_) { true }
78
+ end
79
+
80
+ wait_helper.wait_for_event(@event_emitter, 'navigated', predicate: predicate)
81
+
82
+ block&.call
83
+
84
+ event = wait_helper.promise.value!
85
+ if event['error']
86
+ raise event['error']
87
+ end
88
+
89
+ unless @load_states.include?(option_wait_until)
90
+ elapsed_time = Time.now - time_start
91
+ if elapsed_time < option_timeout
92
+ wait_for_load_state(state: option_wait_until, timeout: option_timeout - elapsed_time)
93
+ end
94
+ end
95
+
96
+ request_json = event.dig('newDocument', 'request')
97
+ request = ChannelOwners::Request.from_nullable(request_json)
98
+ request&.response
99
+ end
100
+
101
+ def wait_for_load_state(state: nil, timeout: nil)
102
+ option_state = state || 'load'
103
+ option_timeout = timeout || @page.send(:timeout_settings).navigation_timeout
104
+ unless %w(load domcontentloaded networkidle).include?(option_state)
105
+ raise ArgumentError.new('state: expected one of (load|domcontentloaded|networkidle)')
106
+ end
107
+ if @load_states.include?(option_state)
108
+ return
109
+ end
110
+
111
+ wait_helper = setup_navigation_wait_helper(timeout: option_timeout)
112
+
113
+ predicate = ->(state) { state == option_state }
114
+ wait_helper.wait_for_event(@event_emitter, 'loadstate', predicate: predicate)
115
+ wait_helper.promise.value!
116
+
117
+ nil
118
+ end
119
+
120
+ def evaluate(pageFunction, arg: nil)
121
+ if JavaScript.function?(pageFunction)
122
+ JavaScript::Function.new(pageFunction, arg).evaluate(@channel)
123
+ else
124
+ JavaScript::Expression.new(pageFunction).evaluate(@channel)
125
+ end
126
+ end
127
+
128
+ def evaluate_handle(pageFunction, arg: nil)
129
+ if JavaScript.function?(pageFunction)
130
+ JavaScript::Function.new(pageFunction, arg).evaluate_handle(@channel)
131
+ else
132
+ JavaScript::Expression.new(pageFunction).evaluate_handle(@channel)
133
+ end
134
+ end
135
+
136
+ def query_selector(selector)
137
+ resp = @channel.send_message_to_server('querySelector', selector: selector)
138
+ ChannelOwners::ElementHandle.from_nullable(resp)
139
+ end
140
+
141
+ def query_selector_all(selector)
142
+ @channel.send_message_to_server('querySelectorAll', selector: selector).map do |el|
143
+ ChannelOwners::ElementHandle.from(el)
144
+ end
145
+ end
146
+
147
+ def checked?(selector, timeout: nil)
148
+ params = { selector: selector, timeout: timeout }.compact
149
+ @channel.send_message_to_server('isChecked', params)
150
+ end
151
+
152
+ def disabled?(selector, timeout: nil)
153
+ params = { selector: selector, timeout: timeout }.compact
154
+ @channel.send_message_to_server('isDisabled', params)
155
+ end
156
+
157
+ def editable?(selector, timeout: nil)
158
+ params = { selector: selector, timeout: timeout }.compact
159
+ @channel.send_message_to_server('isEditable', params)
160
+ end
161
+
162
+ def enabled?(selector, timeout: nil)
163
+ params = { selector: selector, timeout: timeout }.compact
164
+ @channel.send_message_to_server('isEnabled', params)
165
+ end
166
+
167
+ def hidden?(selector, timeout: nil)
168
+ params = { selector: selector, timeout: timeout }.compact
169
+ @channel.send_message_to_server('isHidden', params)
170
+ end
171
+
172
+ def visible?(selector, timeout: nil)
173
+ params = { selector: selector, timeout: timeout }.compact
174
+ @channel.send_message_to_server('isVisible', params)
175
+ end
176
+
177
+ def dispatch_event(selector, type, eventInit: nil, timeout: nil)
178
+ params = {
179
+ selector: selector,
180
+ type: type,
181
+ eventInit: JavaScript::ValueSerializer.new(eventInit).serialize,
182
+ timeout: timeout,
183
+ }.compact
184
+ @channel.send_message_to_server('dispatchEvent', params)
185
+
186
+ nil
187
+ end
188
+
189
+ def eval_on_selector(selector, pageFunction, arg: nil)
190
+ if JavaScript.function?(pageFunction)
191
+ JavaScript::Function.new(pageFunction, arg).eval_on_selector(@channel, selector)
192
+ else
193
+ JavaScript::Expression.new(pageFunction).eval_on_selector(@channel, selector)
194
+ end
195
+ end
196
+
197
+ def eval_on_selector_all(selector, pageFunction, arg: nil)
198
+ if JavaScript.function?(pageFunction)
199
+ JavaScript::Function.new(pageFunction, arg).eval_on_selector_all(@channel, selector)
200
+ else
201
+ JavaScript::Expression.new(pageFunction).eval_on_selector_all(@channel, selector)
202
+ end
203
+ end
204
+
205
+ def content
206
+ @channel.send_message_to_server('content')
207
+ end
208
+
209
+ def set_content(html, timeout: nil, waitUntil: nil)
210
+ params = {
211
+ html: html,
212
+ timeout: timeout,
213
+ waitUntil: waitUntil,
214
+ }.compact
215
+
216
+ @channel.send_message_to_server('setContent', params)
217
+
218
+ nil
219
+ end
220
+
221
+ def name
222
+ @name || ''
223
+ end
224
+
225
+ def url
226
+ @url || ''
227
+ end
228
+
229
+ def child_frames
230
+ @child_frames.to_a
231
+ end
232
+
233
+ def detached?
234
+ @detached
235
+ end
236
+
237
+ def add_script_tag(content: nil, path: nil, type: nil, url: nil)
238
+ params = {
239
+ content: content,
240
+ type: type,
241
+ url: url,
242
+ }.compact
243
+ if path
244
+ params[:content] = "#{File.read(path)}\n//# sourceURL=#{path}"
245
+ end
246
+ resp = @channel.send_message_to_server('addScriptTag', params)
247
+ ChannelOwners::ElementHandle.from(resp)
248
+ end
249
+
250
+ def add_style_tag(content: nil, path: nil, url: nil)
251
+ params = {
252
+ content: content,
253
+ url: url,
254
+ }.compact
255
+ if path
256
+ params[:content] = "#{File.read(path)}\n/*# sourceURL=#{path}*/"
257
+ end
258
+ resp = @channel.send_message_to_server('addStyleTag', params)
259
+ ChannelOwners::ElementHandle.from(resp)
260
+ end
261
+
262
+ def click(
263
+ selector,
264
+ button: nil,
265
+ clickCount: nil,
266
+ delay: nil,
267
+ force: nil,
268
+ modifiers: nil,
269
+ noWaitAfter: nil,
270
+ position: nil,
271
+ timeout: nil)
272
+
273
+ params = {
274
+ selector: selector,
275
+ button: button,
276
+ clickCount: clickCount,
277
+ delay: delay,
278
+ force: force,
279
+ modifiers: modifiers,
280
+ noWaitAfter: noWaitAfter,
281
+ position: position,
282
+ timeout: timeout,
283
+ }.compact
284
+ @channel.send_message_to_server('click', params)
285
+
286
+ nil
287
+ end
288
+
289
+ def dblclick(
290
+ selector,
291
+ button: nil,
292
+ delay: nil,
293
+ force: nil,
294
+ modifiers: nil,
295
+ noWaitAfter: nil,
296
+ position: nil,
297
+ timeout: nil)
298
+
299
+ params = {
300
+ selector: selector,
301
+ button: button,
302
+ delay: delay,
303
+ force: force,
304
+ modifiers: modifiers,
305
+ noWaitAfter: noWaitAfter,
306
+ position: position,
307
+ timeout: timeout,
308
+ }.compact
309
+ @channel.send_message_to_server('dblclick', params)
310
+
311
+ nil
312
+ end
313
+
314
+ def tap_point(
315
+ selector,
316
+ force: nil,
317
+ modifiers: nil,
318
+ noWaitAfter: nil,
319
+ position: nil,
320
+ timeout: nil)
321
+ params = {
322
+ selector: selector,
323
+ force: force,
324
+ modifiers: modifiers,
325
+ noWaitAfter: noWaitAfter,
326
+ position: position,
327
+ timeout: timeout,
328
+ }.compact
329
+ @channel.send_message_to_server('tap', params)
330
+
331
+ nil
332
+ end
333
+
334
+ def fill(selector, value, noWaitAfter: nil, timeout: nil)
335
+ params = {
336
+ selector: selector,
337
+ value: value,
338
+ noWaitAfter: noWaitAfter,
339
+ timeout: timeout,
340
+ }.compact
341
+ @channel.send_message_to_server('fill', params)
342
+
343
+ nil
344
+ end
345
+
346
+ def focus(selector, timeout: nil)
347
+ params = { selector: selector, timeout: timeout }.compact
348
+ @channel.send_message_to_server('focus', params)
349
+ nil
350
+ end
351
+
352
+ def text_content(selector, timeout: nil)
353
+ params = { selector: selector, timeout: timeout }.compact
354
+ @channel.send_message_to_server('textContent', params)
355
+ end
356
+
357
+ def inner_text(selector, timeout: nil)
358
+ params = { selector: selector, timeout: timeout }.compact
359
+ @channel.send_message_to_server('innerText', params)
360
+ end
361
+
362
+ def inner_html(selector, timeout: nil)
363
+ params = { selector: selector, timeout: timeout }.compact
364
+ @channel.send_message_to_server('innerHTML', params)
365
+ end
366
+
367
+ def get_attribute(selector, name, timeout: nil)
368
+ params = {
369
+ selector: selector,
370
+ name: name,
371
+ timeout: timeout,
372
+ }.compact
373
+ @channel.send_message_to_server('getAttribute', params)
374
+ end
375
+
376
+ def hover(
377
+ selector,
378
+ force: nil,
379
+ modifiers: nil,
380
+ position: nil,
381
+ timeout: nil)
382
+ params = {
383
+ selector: selector,
384
+ force: force,
385
+ modifiers: modifiers,
386
+ position: position,
387
+ timeout: timeout,
388
+ }.compact
389
+ @channel.send_message_to_server('hover', params)
390
+
391
+ nil
392
+ end
393
+
394
+ def select_option(selector, values, noWaitAfter: nil, timeout: nil)
395
+ base_params = SelectOptionValues.new(values).as_params
396
+ params = base_params + { selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
397
+ @channel.send_message_to_server('selectOption', params)
398
+
399
+ nil
28
400
  end
29
401
 
30
- private
402
+ def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
403
+ base_params = InputFiles.new(values).as_params
404
+ params = base_params + { selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
405
+ @channel.send_message_to_server('setInputFiles', params)
406
+
407
+ nil
408
+ end
409
+
410
+ def type(
411
+ selector,
412
+ text,
413
+ delay: nil,
414
+ noWaitAfter: nil,
415
+ timeout: nil)
416
+
417
+ params = {
418
+ selector: selector,
419
+ text: text,
420
+ delay: delay,
421
+ noWaitAfter: noWaitAfter,
422
+ timeout: timeout,
423
+ }.compact
424
+ @channel.send_message_to_server('type', params)
425
+
426
+ nil
427
+ end
428
+
429
+ def press(
430
+ selector,
431
+ key,
432
+ delay: nil,
433
+ noWaitAfter: nil,
434
+ timeout: nil)
435
+
436
+ params = {
437
+ selector: selector,
438
+ key: key,
439
+ delay: delay,
440
+ noWaitAfter: noWaitAfter,
441
+ timeout: timeout,
442
+ }.compact
443
+ @channel.send_message_to_server('press', params)
444
+
445
+ nil
446
+ end
447
+
448
+ def check(selector, force: nil, noWaitAfter: nil, timeout: nil)
449
+ params = {
450
+ selector: selector,
451
+ force: force,
452
+ noWaitAfter: noWaitAfter,
453
+ timeout: timeout,
454
+ }.compact
455
+ @channel.send_message_to_server('check', params)
456
+
457
+ nil
458
+ end
459
+
460
+ def uncheck(selector, force: nil, noWaitAfter: nil, timeout: nil)
461
+ params = {
462
+ selector: selector,
463
+ force: force,
464
+ noWaitAfter: noWaitAfter,
465
+ timeout: timeout,
466
+ }.compact
467
+ @channel.send_message_to_server('uncheck', params)
468
+
469
+ nil
470
+ end
471
+
472
+ def wait_for_function(pageFunction, arg: nil, polling: nil, timeout: nil)
473
+ if polling.is_a?(String) && polling != 'raf'
474
+ raise ArgumentError.new("Unknown polling option: #{polling}")
475
+ end
476
+
477
+ function_or_expression =
478
+ if JavaScript.function?(pageFunction)
479
+ JavaScript::Function.new(pageFunction, arg)
480
+ else
481
+ JavaScript::Expression.new(pageFunction)
482
+ end
483
+
484
+ function_or_expression.wait_for_function(@channel, polling: polling, timeout: timeout)
485
+ end
486
+
487
+ def title
488
+ @channel.send_message_to_server('title')
489
+ end
31
490
 
32
491
  # @param page [Page]
33
492
  # @note This method should be used internally. Accessed via .send method, so keep private!
34
- def update_page_from_page(page)
493
+ private def update_page_from_page(page)
35
494
  @page = page
36
495
  end
37
496
 
38
497
  # @param child [Frame]
39
498
  # @note This method should be used internally. Accessed via .send method, so keep private!
40
- def append_child_frame_from_child(frame)
499
+ private def append_child_frame_from_child(frame)
41
500
  @child_frames << frame
42
501
  end
43
502
  end