playwright-ruby-client 0.0.4 → 0.0.9

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 +120 -6
  3. data/docs/api_coverage.md +351 -0
  4. data/lib/playwright.rb +9 -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 +20 -27
  9. data/lib/playwright/channel_owners/browser_context.rb +51 -0
  10. data/lib/playwright/channel_owners/console_message.rb +0 -4
  11. data/lib/playwright/channel_owners/element_handle.rb +306 -0
  12. data/lib/playwright/channel_owners/frame.rb +473 -7
  13. data/lib/playwright/channel_owners/js_handle.rb +51 -0
  14. data/lib/playwright/channel_owners/page.rb +589 -4
  15. data/lib/playwright/channel_owners/request.rb +98 -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 +17 -1
  20. data/lib/playwright/http_headers.rb +20 -0
  21. data/lib/playwright/input_files.rb +42 -0
  22. data/lib/playwright/input_type.rb +19 -0
  23. data/lib/playwright/input_types/android_input.rb +19 -0
  24. data/lib/playwright/input_types/keyboard.rb +32 -0
  25. data/lib/playwright/input_types/mouse.rb +4 -0
  26. data/lib/playwright/input_types/touchscreen.rb +4 -0
  27. data/lib/playwright/javascript.rb +13 -0
  28. data/lib/playwright/javascript/expression.rb +67 -0
  29. data/lib/playwright/javascript/function.rb +67 -0
  30. data/lib/playwright/javascript/value_parser.rb +75 -0
  31. data/lib/playwright/javascript/value_serializer.rb +54 -0
  32. data/lib/playwright/playwright_api.rb +45 -25
  33. data/lib/playwright/select_option_values.rb +32 -0
  34. data/lib/playwright/timeout_settings.rb +19 -0
  35. data/lib/playwright/url_matcher.rb +19 -0
  36. data/lib/playwright/utils.rb +37 -0
  37. data/lib/playwright/version.rb +1 -1
  38. data/lib/playwright/wait_helper.rb +73 -0
  39. data/lib/playwright_api/accessibility.rb +46 -6
  40. data/lib/playwright_api/android.rb +33 -0
  41. data/lib/playwright_api/android_device.rb +78 -0
  42. data/lib/playwright_api/android_input.rb +25 -0
  43. data/lib/playwright_api/binding_call.rb +18 -0
  44. data/lib/playwright_api/browser.rb +93 -12
  45. data/lib/playwright_api/browser_context.rb +279 -28
  46. data/lib/playwright_api/browser_type.rb +68 -5
  47. data/lib/playwright_api/cdp_session.rb +23 -1
  48. data/lib/playwright_api/chromium_browser_context.rb +26 -0
  49. data/lib/playwright_api/console_message.rb +20 -7
  50. data/lib/playwright_api/dialog.rb +48 -2
  51. data/lib/playwright_api/download.rb +19 -4
  52. data/lib/playwright_api/element_handle.rb +278 -104
  53. data/lib/playwright_api/file_chooser.rb +20 -3
  54. data/lib/playwright_api/frame.rb +452 -147
  55. data/lib/playwright_api/js_handle.rb +78 -19
  56. data/lib/playwright_api/keyboard.rb +99 -9
  57. data/lib/playwright_api/mouse.rb +22 -0
  58. data/lib/playwright_api/page.rb +864 -222
  59. data/lib/playwright_api/playwright.rb +116 -14
  60. data/lib/playwright_api/request.rb +86 -24
  61. data/lib/playwright_api/response.rb +18 -7
  62. data/lib/playwright_api/route.rb +49 -0
  63. data/lib/playwright_api/selectors.rb +28 -2
  64. data/lib/playwright_api/video.rb +8 -0
  65. data/lib/playwright_api/web_socket.rb +0 -8
  66. data/lib/playwright_api/worker.rb +25 -13
  67. data/playwright.gemspec +3 -0
  68. metadata +66 -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,460 @@ 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 wait_for_selector(selector, state: nil, timeout: nil)
148
+ params = { selector: selector, state: state, timeout: timeout }.compact
149
+ resp = @channel.send_message_to_server('waitForSelector', params)
150
+
151
+ ChannelOwners::ElementHandle.from_nullable(resp)
28
152
  end
29
153
 
30
- private
154
+ def checked?(selector, timeout: nil)
155
+ params = { selector: selector, timeout: timeout }.compact
156
+ @channel.send_message_to_server('isChecked', params)
157
+ end
158
+
159
+ def disabled?(selector, timeout: nil)
160
+ params = { selector: selector, timeout: timeout }.compact
161
+ @channel.send_message_to_server('isDisabled', params)
162
+ end
163
+
164
+ def editable?(selector, timeout: nil)
165
+ params = { selector: selector, timeout: timeout }.compact
166
+ @channel.send_message_to_server('isEditable', params)
167
+ end
168
+
169
+ def enabled?(selector, timeout: nil)
170
+ params = { selector: selector, timeout: timeout }.compact
171
+ @channel.send_message_to_server('isEnabled', params)
172
+ end
173
+
174
+ def hidden?(selector, timeout: nil)
175
+ params = { selector: selector, timeout: timeout }.compact
176
+ @channel.send_message_to_server('isHidden', params)
177
+ end
178
+
179
+ def visible?(selector, timeout: nil)
180
+ params = { selector: selector, timeout: timeout }.compact
181
+ @channel.send_message_to_server('isVisible', params)
182
+ end
183
+
184
+ def dispatch_event(selector, type, eventInit: nil, timeout: nil)
185
+ params = {
186
+ selector: selector,
187
+ type: type,
188
+ eventInit: JavaScript::ValueSerializer.new(eventInit).serialize,
189
+ timeout: timeout,
190
+ }.compact
191
+ @channel.send_message_to_server('dispatchEvent', params)
192
+
193
+ nil
194
+ end
195
+
196
+ def eval_on_selector(selector, pageFunction, arg: nil)
197
+ if JavaScript.function?(pageFunction)
198
+ JavaScript::Function.new(pageFunction, arg).eval_on_selector(@channel, selector)
199
+ else
200
+ JavaScript::Expression.new(pageFunction).eval_on_selector(@channel, selector)
201
+ end
202
+ end
203
+
204
+ def eval_on_selector_all(selector, pageFunction, arg: nil)
205
+ if JavaScript.function?(pageFunction)
206
+ JavaScript::Function.new(pageFunction, arg).eval_on_selector_all(@channel, selector)
207
+ else
208
+ JavaScript::Expression.new(pageFunction).eval_on_selector_all(@channel, selector)
209
+ end
210
+ end
211
+
212
+ def content
213
+ @channel.send_message_to_server('content')
214
+ end
215
+
216
+ def set_content(html, timeout: nil, waitUntil: nil)
217
+ params = {
218
+ html: html,
219
+ timeout: timeout,
220
+ waitUntil: waitUntil,
221
+ }.compact
222
+
223
+ @channel.send_message_to_server('setContent', params)
224
+
225
+ nil
226
+ end
227
+
228
+ def name
229
+ @name || ''
230
+ end
231
+
232
+ def url
233
+ @url || ''
234
+ end
235
+
236
+ def child_frames
237
+ @child_frames.to_a
238
+ end
239
+
240
+ def detached?
241
+ @detached
242
+ end
243
+
244
+ def add_script_tag(content: nil, path: nil, type: nil, url: nil)
245
+ params = {
246
+ content: content,
247
+ type: type,
248
+ url: url,
249
+ }.compact
250
+ if path
251
+ params[:content] = "#{File.read(path)}\n//# sourceURL=#{path}"
252
+ end
253
+ resp = @channel.send_message_to_server('addScriptTag', params)
254
+ ChannelOwners::ElementHandle.from(resp)
255
+ end
256
+
257
+ def add_style_tag(content: nil, path: nil, url: nil)
258
+ params = {
259
+ content: content,
260
+ url: url,
261
+ }.compact
262
+ if path
263
+ params[:content] = "#{File.read(path)}\n/*# sourceURL=#{path}*/"
264
+ end
265
+ resp = @channel.send_message_to_server('addStyleTag', params)
266
+ ChannelOwners::ElementHandle.from(resp)
267
+ end
268
+
269
+ def click(
270
+ selector,
271
+ button: nil,
272
+ clickCount: nil,
273
+ delay: nil,
274
+ force: nil,
275
+ modifiers: nil,
276
+ noWaitAfter: nil,
277
+ position: nil,
278
+ timeout: nil)
279
+
280
+ params = {
281
+ selector: selector,
282
+ button: button,
283
+ clickCount: clickCount,
284
+ delay: delay,
285
+ force: force,
286
+ modifiers: modifiers,
287
+ noWaitAfter: noWaitAfter,
288
+ position: position,
289
+ timeout: timeout,
290
+ }.compact
291
+ @channel.send_message_to_server('click', params)
292
+
293
+ nil
294
+ end
295
+
296
+ def dblclick(
297
+ selector,
298
+ button: nil,
299
+ delay: nil,
300
+ force: nil,
301
+ modifiers: nil,
302
+ noWaitAfter: nil,
303
+ position: nil,
304
+ timeout: nil)
305
+
306
+ params = {
307
+ selector: selector,
308
+ button: button,
309
+ delay: delay,
310
+ force: force,
311
+ modifiers: modifiers,
312
+ noWaitAfter: noWaitAfter,
313
+ position: position,
314
+ timeout: timeout,
315
+ }.compact
316
+ @channel.send_message_to_server('dblclick', params)
317
+
318
+ nil
319
+ end
320
+
321
+ def tap_point(
322
+ selector,
323
+ force: nil,
324
+ modifiers: nil,
325
+ noWaitAfter: nil,
326
+ position: nil,
327
+ timeout: nil)
328
+ params = {
329
+ selector: selector,
330
+ force: force,
331
+ modifiers: modifiers,
332
+ noWaitAfter: noWaitAfter,
333
+ position: position,
334
+ timeout: timeout,
335
+ }.compact
336
+ @channel.send_message_to_server('tap', params)
337
+
338
+ nil
339
+ end
340
+
341
+ def fill(selector, value, noWaitAfter: nil, timeout: nil)
342
+ params = {
343
+ selector: selector,
344
+ value: value,
345
+ noWaitAfter: noWaitAfter,
346
+ timeout: timeout,
347
+ }.compact
348
+ @channel.send_message_to_server('fill', params)
349
+
350
+ nil
351
+ end
352
+
353
+ def focus(selector, timeout: nil)
354
+ params = { selector: selector, timeout: timeout }.compact
355
+ @channel.send_message_to_server('focus', params)
356
+ nil
357
+ end
358
+
359
+ def text_content(selector, timeout: nil)
360
+ params = { selector: selector, timeout: timeout }.compact
361
+ @channel.send_message_to_server('textContent', params)
362
+ end
363
+
364
+ def inner_text(selector, timeout: nil)
365
+ params = { selector: selector, timeout: timeout }.compact
366
+ @channel.send_message_to_server('innerText', params)
367
+ end
368
+
369
+ def inner_html(selector, timeout: nil)
370
+ params = { selector: selector, timeout: timeout }.compact
371
+ @channel.send_message_to_server('innerHTML', params)
372
+ end
373
+
374
+ def get_attribute(selector, name, timeout: nil)
375
+ params = {
376
+ selector: selector,
377
+ name: name,
378
+ timeout: timeout,
379
+ }.compact
380
+ @channel.send_message_to_server('getAttribute', params)
381
+ end
382
+
383
+ def hover(
384
+ selector,
385
+ force: nil,
386
+ modifiers: nil,
387
+ position: nil,
388
+ timeout: nil)
389
+ params = {
390
+ selector: selector,
391
+ force: force,
392
+ modifiers: modifiers,
393
+ position: position,
394
+ timeout: timeout,
395
+ }.compact
396
+ @channel.send_message_to_server('hover', params)
397
+
398
+ nil
399
+ end
400
+
401
+ def select_option(selector, values, noWaitAfter: nil, timeout: nil)
402
+ base_params = SelectOptionValues.new(values).as_params
403
+ params = base_params + { selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
404
+ @channel.send_message_to_server('selectOption', params)
405
+
406
+ nil
407
+ end
408
+
409
+ def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
410
+ base_params = InputFiles.new(values).as_params
411
+ params = base_params + { selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
412
+ @channel.send_message_to_server('setInputFiles', params)
413
+
414
+ nil
415
+ end
416
+
417
+ def type(
418
+ selector,
419
+ text,
420
+ delay: nil,
421
+ noWaitAfter: nil,
422
+ timeout: nil)
423
+
424
+ params = {
425
+ selector: selector,
426
+ text: text,
427
+ delay: delay,
428
+ noWaitAfter: noWaitAfter,
429
+ timeout: timeout,
430
+ }.compact
431
+ @channel.send_message_to_server('type', params)
432
+
433
+ nil
434
+ end
435
+
436
+ def press(
437
+ selector,
438
+ key,
439
+ delay: nil,
440
+ noWaitAfter: nil,
441
+ timeout: nil)
442
+
443
+ params = {
444
+ selector: selector,
445
+ key: key,
446
+ delay: delay,
447
+ noWaitAfter: noWaitAfter,
448
+ timeout: timeout,
449
+ }.compact
450
+ @channel.send_message_to_server('press', params)
451
+
452
+ nil
453
+ end
454
+
455
+ def check(selector, force: nil, noWaitAfter: nil, timeout: nil)
456
+ params = {
457
+ selector: selector,
458
+ force: force,
459
+ noWaitAfter: noWaitAfter,
460
+ timeout: timeout,
461
+ }.compact
462
+ @channel.send_message_to_server('check', params)
463
+
464
+ nil
465
+ end
466
+
467
+ def uncheck(selector, force: nil, noWaitAfter: nil, timeout: nil)
468
+ params = {
469
+ selector: selector,
470
+ force: force,
471
+ noWaitAfter: noWaitAfter,
472
+ timeout: timeout,
473
+ }.compact
474
+ @channel.send_message_to_server('uncheck', params)
475
+
476
+ nil
477
+ end
478
+
479
+ def wait_for_function(pageFunction, arg: nil, polling: nil, timeout: nil)
480
+ if polling.is_a?(String) && polling != 'raf'
481
+ raise ArgumentError.new("Unknown polling option: #{polling}")
482
+ end
483
+
484
+ function_or_expression =
485
+ if JavaScript.function?(pageFunction)
486
+ JavaScript::Function.new(pageFunction, arg)
487
+ else
488
+ JavaScript::Expression.new(pageFunction)
489
+ end
490
+
491
+ function_or_expression.wait_for_function(@channel, polling: polling, timeout: timeout)
492
+ end
493
+
494
+ def title
495
+ @channel.send_message_to_server('title')
496
+ end
31
497
 
32
498
  # @param page [Page]
33
499
  # @note This method should be used internally. Accessed via .send method, so keep private!
34
- def update_page_from_page(page)
500
+ private def update_page_from_page(page)
35
501
  @page = page
36
502
  end
37
503
 
38
504
  # @param child [Frame]
39
505
  # @note This method should be used internally. Accessed via .send method, so keep private!
40
- def append_child_frame_from_child(frame)
506
+ private def append_child_frame_from_child(frame)
41
507
  @child_frames << frame
42
508
  end
43
509
  end