playwright-ruby-client 0.0.5 → 0.1.0

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +114 -2
  3. data/docs/api_coverage.md +351 -0
  4. data/lib/playwright.rb +7 -1
  5. data/lib/playwright/android_input_impl.rb +23 -0
  6. data/lib/playwright/api_implementation.rb +18 -0
  7. data/lib/playwright/channel.rb +7 -0
  8. data/lib/playwright/channel_owner.rb +3 -2
  9. data/lib/playwright/channel_owners/android.rb +10 -1
  10. data/lib/playwright/channel_owners/android_device.rb +163 -0
  11. data/lib/playwright/channel_owners/browser.rb +13 -13
  12. data/lib/playwright/channel_owners/browser_context.rb +9 -1
  13. data/lib/playwright/channel_owners/download.rb +27 -0
  14. data/lib/playwright/channel_owners/element_handle.rb +306 -0
  15. data/lib/playwright/channel_owners/frame.rb +371 -19
  16. data/lib/playwright/channel_owners/js_handle.rb +51 -0
  17. data/lib/playwright/channel_owners/page.rb +416 -19
  18. data/lib/playwright/channel_owners/request.rb +98 -0
  19. data/lib/playwright/channel_owners/webkit_browser.rb +1 -1
  20. data/lib/playwright/connection.rb +9 -6
  21. data/lib/playwright/errors.rb +2 -2
  22. data/lib/playwright/event_emitter.rb +8 -1
  23. data/lib/playwright/event_emitter_proxy.rb +49 -0
  24. data/lib/playwright/file_chooser_impl.rb +23 -0
  25. data/lib/playwright/http_headers.rb +20 -0
  26. data/lib/playwright/input_files.rb +42 -0
  27. data/lib/playwright/javascript/expression.rb +37 -0
  28. data/lib/playwright/javascript/function.rb +37 -0
  29. data/lib/playwright/javascript/value_parser.rb +1 -1
  30. data/lib/playwright/javascript/value_serializer.rb +11 -11
  31. data/lib/playwright/keyboard_impl.rb +36 -0
  32. data/lib/playwright/mouse_impl.rb +7 -0
  33. data/lib/playwright/playwright_api.rb +84 -29
  34. data/lib/playwright/select_option_values.rb +32 -0
  35. data/lib/playwright/timeout_settings.rb +2 -2
  36. data/lib/playwright/touchscreen_impl.rb +7 -0
  37. data/lib/playwright/url_matcher.rb +19 -0
  38. data/lib/playwright/utils.rb +18 -0
  39. data/lib/playwright/version.rb +1 -1
  40. data/lib/playwright/wait_helper.rb +1 -1
  41. data/lib/playwright_api/accessibility.rb +46 -6
  42. data/lib/playwright_api/android.rb +37 -0
  43. data/lib/playwright_api/android_device.rb +82 -0
  44. data/lib/playwright_api/android_input.rb +25 -0
  45. data/lib/playwright_api/binding_call.rb +10 -6
  46. data/lib/playwright_api/browser.rb +85 -18
  47. data/lib/playwright_api/browser_context.rb +269 -37
  48. data/lib/playwright_api/browser_type.rb +60 -11
  49. data/lib/playwright_api/cdp_session.rb +23 -1
  50. data/lib/playwright_api/chromium_browser_context.rb +18 -6
  51. data/lib/playwright_api/console_message.rb +14 -15
  52. data/lib/playwright_api/dialog.rb +48 -2
  53. data/lib/playwright_api/download.rb +47 -10
  54. data/lib/playwright_api/element_handle.rb +269 -110
  55. data/lib/playwright_api/file_chooser.rb +23 -7
  56. data/lib/playwright_api/frame.rb +439 -154
  57. data/lib/playwright_api/js_handle.rb +69 -24
  58. data/lib/playwright_api/keyboard.rb +99 -9
  59. data/lib/playwright_api/mouse.rb +22 -0
  60. data/lib/playwright_api/page.rb +856 -229
  61. data/lib/playwright_api/playwright.rb +108 -20
  62. data/lib/playwright_api/request.rb +77 -29
  63. data/lib/playwright_api/response.rb +10 -13
  64. data/lib/playwright_api/route.rb +49 -0
  65. data/lib/playwright_api/selectors.rb +20 -8
  66. data/lib/playwright_api/video.rb +8 -0
  67. data/lib/playwright_api/web_socket.rb +0 -8
  68. data/lib/playwright_api/worker.rb +25 -13
  69. data/playwright.gemspec +1 -0
  70. metadata +33 -2
@@ -53,6 +53,70 @@ module Playwright
53
53
  ChannelOwners::Response.from_nullable(resp)
54
54
  end
55
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
+
56
120
  def evaluate(pageFunction, arg: nil)
57
121
  if JavaScript.function?(pageFunction)
58
122
  JavaScript::Function.new(pageFunction, arg).evaluate(@channel)
@@ -69,6 +133,82 @@ module Playwright
69
133
  end
70
134
  end
71
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)
152
+ end
153
+
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
+
72
212
  def content
73
213
  @channel.send_message_to_server('content')
74
214
  end
@@ -85,18 +225,201 @@ module Playwright
85
225
  nil
86
226
  end
87
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
+
88
353
  def focus(selector, timeout: nil)
89
354
  params = { selector: selector, timeout: timeout }.compact
90
355
  @channel.send_message_to_server('focus', params)
91
356
  nil
92
357
  end
93
358
 
94
- def type_text(
95
- selector,
96
- text,
97
- delay: nil,
98
- noWaitAfter: nil,
99
- timeout: nil)
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
+ file_payloads = InputFiles.new(files).as_params
411
+ params = { files: file_payloads, 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)
100
423
 
101
424
  params = {
102
425
  selector: selector,
@@ -105,16 +428,17 @@ module Playwright
105
428
  noWaitAfter: noWaitAfter,
106
429
  timeout: timeout,
107
430
  }.compact
108
-
109
431
  @channel.send_message_to_server('type', params)
432
+
433
+ nil
110
434
  end
111
435
 
112
436
  def press(
113
- selector,
114
- key,
115
- delay: nil,
116
- noWaitAfter: nil,
117
- timeout: nil)
437
+ selector,
438
+ key,
439
+ delay: nil,
440
+ noWaitAfter: nil,
441
+ timeout: nil)
118
442
 
119
443
  params = {
120
444
  selector: selector,
@@ -123,20 +447,48 @@ module Playwright
123
447
  noWaitAfter: noWaitAfter,
124
448
  timeout: timeout,
125
449
  }.compact
126
-
127
450
  @channel.send_message_to_server('press', params)
451
+
452
+ nil
128
453
  end
129
454
 
130
- def name
131
- @name || ''
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
132
465
  end
133
466
 
134
- def url
135
- @url || ''
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
136
477
  end
137
478
 
138
- def child_frames
139
- @child_frames.to_a
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)
140
492
  end
141
493
 
142
494
  def title
@@ -1,4 +1,55 @@
1
1
  module Playwright
2
2
  define_channel_owner :JSHandle do
3
+ def after_initialize
4
+ @preview = @initializer['preview']
5
+ @channel.on('previewUpdated', method(:on_preview_updated))
6
+ end
7
+
8
+ def to_s
9
+ @preview
10
+ end
11
+
12
+ private def on_preview_updated(preview)
13
+ @preview = preview
14
+ end
15
+
16
+ def evaluate(pageFunction, arg: nil)
17
+ if JavaScript.function?(pageFunction)
18
+ JavaScript::Function.new(pageFunction, arg).evaluate(@channel)
19
+ else
20
+ JavaScript::Expression.new(pageFunction).evaluate(@channel)
21
+ end
22
+ end
23
+
24
+ def evaluate_handle(pageFunction, arg: nil)
25
+ if JavaScript.function?(pageFunction)
26
+ JavaScript::Function.new(pageFunction, arg).evaluate_handle(@channel)
27
+ else
28
+ JavaScript::Expression.new(pageFunction).evaluate_handle(@channel)
29
+ end
30
+ end
31
+
32
+ def get_properties
33
+ resp = @channel.send_message_to_server('getPropertyList')
34
+ resp.map { |prop| [prop['name'], ChannelOwner.from(prop['value'])] }.to_h
35
+ end
36
+
37
+ def get_property(name)
38
+ resp = @channel.send_message_to_server('getProperty', name: name)
39
+ ChannelOwner.from(resp)
40
+ end
41
+
42
+ def as_element
43
+ nil
44
+ end
45
+
46
+ def dispose
47
+ @channel.send_message_to_server('dispose')
48
+ end
49
+
50
+ def json_value
51
+ value = @channel.send_message_to_server('jsonValue')
52
+ JavaScript::ValueParser.new(value).parse
53
+ end
3
54
  end
4
55
  end