playwright-ruby-client 0.3.0 → 0.5.6

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -5
  3. data/docs/api_coverage.md +14 -25
  4. data/lib/playwright.rb +47 -9
  5. data/lib/playwright/channel.rb +19 -9
  6. data/lib/playwright/channel_owners/artifact.rb +30 -0
  7. data/lib/playwright/channel_owners/browser.rb +21 -0
  8. data/lib/playwright/channel_owners/browser_context.rb +5 -0
  9. data/lib/playwright/channel_owners/browser_type.rb +28 -0
  10. data/lib/playwright/channel_owners/element_handle.rb +7 -7
  11. data/lib/playwright/channel_owners/frame.rb +26 -5
  12. data/lib/playwright/channel_owners/js_handle.rb +2 -2
  13. data/lib/playwright/channel_owners/page.rb +78 -15
  14. data/lib/playwright/channel_owners/playwright.rb +22 -29
  15. data/lib/playwright/channel_owners/stream.rb +15 -0
  16. data/lib/playwright/connection.rb +11 -32
  17. data/lib/playwright/download.rb +27 -0
  18. data/lib/playwright/errors.rb +6 -0
  19. data/lib/playwright/events.rb +2 -5
  20. data/lib/playwright/keyboard_impl.rb +1 -1
  21. data/lib/playwright/mouse_impl.rb +41 -0
  22. data/lib/playwright/playwright_api.rb +5 -3
  23. data/lib/playwright/route_handler_entry.rb +1 -9
  24. data/lib/playwright/select_option_values.rb +31 -22
  25. data/lib/playwright/transport.rb +29 -7
  26. data/lib/playwright/url_matcher.rb +1 -1
  27. data/lib/playwright/utils.rb +8 -0
  28. data/lib/playwright/version.rb +1 -1
  29. data/lib/playwright/video.rb +51 -0
  30. data/lib/playwright/wait_helper.rb +2 -2
  31. data/lib/playwright_api/accessibility.rb +39 -1
  32. data/lib/playwright_api/android.rb +10 -10
  33. data/lib/playwright_api/android_device.rb +10 -9
  34. data/lib/playwright_api/browser.rb +83 -8
  35. data/lib/playwright_api/browser_context.rb +157 -9
  36. data/lib/playwright_api/browser_type.rb +35 -4
  37. data/lib/playwright_api/console_message.rb +6 -6
  38. data/lib/playwright_api/dialog.rb +28 -8
  39. data/lib/playwright_api/element_handle.rb +111 -37
  40. data/lib/playwright_api/file_chooser.rb +5 -0
  41. data/lib/playwright_api/frame.rb +228 -37
  42. data/lib/playwright_api/js_handle.rb +26 -3
  43. data/lib/playwright_api/keyboard.rb +48 -1
  44. data/lib/playwright_api/mouse.rb +26 -5
  45. data/lib/playwright_api/page.rb +454 -46
  46. data/lib/playwright_api/playwright.rb +26 -9
  47. data/lib/playwright_api/request.rb +34 -6
  48. data/lib/playwright_api/response.rb +6 -6
  49. data/lib/playwright_api/route.rb +30 -6
  50. data/lib/playwright_api/selectors.rb +32 -6
  51. data/lib/playwright_api/touchscreen.rb +1 -1
  52. data/lib/playwright_api/worker.rb +25 -1
  53. data/playwright.gemspec +4 -2
  54. metadata +37 -14
  55. data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
  56. data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
  57. data/lib/playwright/channel_owners/download.rb +0 -27
  58. data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
  59. data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
  60. data/lib/playwright_api/binding_call.rb +0 -32
  61. data/lib/playwright_api/chromium_browser_context.rb +0 -59
  62. data/lib/playwright_api/download.rb +0 -95
  63. data/lib/playwright_api/video.rb +0 -24
@@ -98,6 +98,15 @@ module Playwright
98
98
  request&.response
99
99
  end
100
100
 
101
+ def wait_for_url(url, timeout: nil, waitUntil: nil)
102
+ matcher = UrlMatcher.new(url)
103
+ if matcher.match?(@url)
104
+ wait_for_load_state(state: waitUntil, timeout: timeout)
105
+ else
106
+ expect_navigation(timeout: timeout, url: url, waitUntil: waitUntil)
107
+ end
108
+ end
109
+
101
110
  def wait_for_load_state(state: nil, timeout: nil)
102
111
  option_state = state || 'load'
103
112
  option_timeout = timeout || @page.send(:timeout_settings).navigation_timeout
@@ -412,10 +421,8 @@ module Playwright
412
421
  value: value,
413
422
  label: label,
414
423
  ).as_params
415
- params = base_params + { selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
424
+ params = base_params.merge({ selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
416
425
  @channel.send_message_to_server('selectOption', params)
417
-
418
- nil
419
426
  end
420
427
 
421
428
  def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
@@ -464,11 +471,18 @@ module Playwright
464
471
  nil
465
472
  end
466
473
 
467
- def check(selector, force: nil, noWaitAfter: nil, timeout: nil)
474
+ def check(
475
+ selector,
476
+ force: nil,
477
+ noWaitAfter: nil,
478
+ position: nil,
479
+ timeout: nil)
480
+
468
481
  params = {
469
482
  selector: selector,
470
483
  force: force,
471
484
  noWaitAfter: noWaitAfter,
485
+ position: position,
472
486
  timeout: timeout,
473
487
  }.compact
474
488
  @channel.send_message_to_server('check', params)
@@ -476,11 +490,18 @@ module Playwright
476
490
  nil
477
491
  end
478
492
 
479
- def uncheck(selector, force: nil, noWaitAfter: nil, timeout: nil)
493
+ def uncheck(
494
+ selector,
495
+ force: nil,
496
+ noWaitAfter: nil,
497
+ position: nil,
498
+ timeout: nil)
499
+
480
500
  params = {
481
501
  selector: selector,
482
502
  force: force,
483
503
  noWaitAfter: noWaitAfter,
504
+ position: position,
484
505
  timeout: timeout,
485
506
  }.compact
486
507
  @channel.send_message_to_server('uncheck', params)
@@ -9,8 +9,8 @@ module Playwright
9
9
  @preview
10
10
  end
11
11
 
12
- private def on_preview_updated(preview)
13
- @preview = preview
12
+ private def on_preview_updated(params)
13
+ @preview = params['preview']
14
14
  end
15
15
 
16
16
  def evaluate(pageFunction, arg: nil)
@@ -28,6 +28,7 @@ module Playwright
28
28
  @main_frame.send(:update_page_from_page, self)
29
29
  @frames = Set.new
30
30
  @frames << @main_frame
31
+ @opener = ChannelOwners::Page.from_nullable(@initializer['opener'])
31
32
 
32
33
  @channel.once('close', ->(_) { on_close })
33
34
  @channel.on('console', ->(params) {
@@ -37,9 +38,7 @@ module Playwright
37
38
  @channel.on('crash', ->(_) { emit(Events::Page::Crash) })
38
39
  @channel.on('dialog', method(:on_dialog))
39
40
  @channel.on('domcontentloaded', ->(_) { emit(Events::Page::DOMContentLoaded) })
40
- @channel.on('download', ->(params) {
41
- emit(Events::Page::Download, ChannelOwners::Download.from(params['download']))
42
- })
41
+ @channel.on('download', method(:on_download))
43
42
  @channel.on('fileChooser', ->(params) {
44
43
  chooser = FileChooserImpl.new(
45
44
  page: self,
@@ -57,9 +56,6 @@ module Playwright
57
56
  @channel.on('pageError', ->(params) {
58
57
  emit(Events::Page::PageError, Error.parse(params['error']['error']))
59
58
  })
60
- @channel.on('popup', ->(params) {
61
- emit(Events::Page::Popup, ChannelOwners::Page.from(params['page']))
62
- })
63
59
  @channel.on('request', ->(params) {
64
60
  emit(Events::Page::Request, ChannelOwners::Request.from(params['request']))
65
61
  })
@@ -82,9 +78,7 @@ module Playwright
82
78
  @channel.on('route', ->(params) {
83
79
  on_route(ChannelOwners::Route.from(params['route']), ChannelOwners::Request.from(params['request']))
84
80
  })
85
- @channel.on('video', ->(params) {
86
- video.send(:update_relative_path, params['relativePath'])
87
- })
81
+ @channel.on('video', method(:on_video))
88
82
  @channel.on('webSocket', ->(params) {
89
83
  emit(Events::Page::WebSocket, ChannelOwners::WebSocket.from(params['webSocket']))
90
84
  })
@@ -149,6 +143,20 @@ module Playwright
149
143
  end
150
144
  end
151
145
 
146
+ private def on_download(params)
147
+ download = Download.new(
148
+ url: params['url'],
149
+ suggested_filename: params['suggestedFilename'],
150
+ artifact: ChannelOwners::Artifact.from(params['artifact']),
151
+ )
152
+ emit(Events::Page::Download, download)
153
+ end
154
+
155
+ private def on_video(params)
156
+ artifact = ChannelOwners::Artifact.from(params['artifact'])
157
+ video.send(:set_artifact, artifact)
158
+ end
159
+
152
160
  # @override
153
161
  def on(event, callback)
154
162
  if event == Events::Page::FileChooser && listener_count(event) == 0
@@ -178,8 +186,17 @@ module Playwright
178
186
  end
179
187
 
180
188
  def opener
181
- resp = @channel.send_message_to_server('opener')
182
- ChannelOwners::Page.from(resp)
189
+ if @opener&.closed?
190
+ nil
191
+ else
192
+ @opener
193
+ end
194
+ end
195
+
196
+ private def emit_popup_event_from_browser_context
197
+ if @opener && !@opener.closed?
198
+ @opener.emit(Events::Page::Popup, self)
199
+ end
183
200
  end
184
201
 
185
202
  def frame(name: nil, url: nil)
@@ -326,6 +343,10 @@ module Playwright
326
343
  @main_frame.wait_for_load_state(state: state, timeout: timeout)
327
344
  end
328
345
 
346
+ def wait_for_url(url, timeout: nil, waitUntil: nil)
347
+ @main_frame.wait_for_url(url, timeout: timeout, waitUntil: waitUntil)
348
+ end
349
+
329
350
  def go_back(timeout: nil, waitUntil: nil)
330
351
  params = { timeout: timeout, waitUntil: waitUntil }.compact
331
352
  resp = @channel.send_message_to_server('goBack', params)
@@ -578,12 +599,24 @@ module Playwright
578
599
  @main_frame.press(selector, key, delay: delay, noWaitAfter: noWaitAfter, timeout: timeout)
579
600
  end
580
601
 
581
- def check(selector, force: nil, noWaitAfter: nil, timeout: nil)
582
- @main_frame.check(selector, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
602
+ def check(
603
+ selector,
604
+ force: nil,
605
+ noWaitAfter: nil,
606
+ position: nil,
607
+ timeout: nil)
608
+
609
+ @main_frame.check(selector, force: force, noWaitAfter: noWaitAfter, position: position, timeout: timeout)
583
610
  end
584
611
 
585
- def uncheck(selector, force: nil, noWaitAfter: nil, timeout: nil)
586
- @main_frame.uncheck(selector, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
612
+ def uncheck(
613
+ selector,
614
+ force: nil,
615
+ noWaitAfter: nil,
616
+ position: nil,
617
+ timeout: nil)
618
+
619
+ @main_frame.uncheck(selector, force: force, noWaitAfter: noWaitAfter, position: position, timeout: timeout)
587
620
  end
588
621
 
589
622
  def wait_for_function(pageFunction, arg: nil, polling: nil, timeout: nil)
@@ -629,6 +662,36 @@ module Playwright
629
662
  decoded_binary
630
663
  end
631
664
 
665
+ def video
666
+ return nil unless @browser_context.send(:has_record_video_option?)
667
+ @video ||= Video.new(self)
668
+ end
669
+
670
+ def start_js_coverage(resetOnNavigation: nil, reportAnonymousScripts: nil)
671
+ params = {
672
+ resetOnNavigation: resetOnNavigation,
673
+ reportAnonymousScripts: reportAnonymousScripts,
674
+ }.compact
675
+
676
+ @channel.send_message_to_server('startJSCoverage', params)
677
+ end
678
+
679
+ def stop_js_coverage
680
+ @channel.send_message_to_server('stopJSCoverage')
681
+ end
682
+
683
+ def start_css_coverage(resetOnNavigation: nil, reportAnonymousScripts: nil)
684
+ params = {
685
+ resetOnNavigation: resetOnNavigation,
686
+ }.compact
687
+
688
+ @channel.send_message_to_server('startCSSCoverage', params)
689
+ end
690
+
691
+ def stop_css_coverage
692
+ @channel.send_message_to_server('stopCSSCoverage')
693
+ end
694
+
632
695
  class CrashedError < StandardError
633
696
  def initialize
634
697
  super('Page crashed')
@@ -24,38 +24,31 @@ module Playwright
24
24
  @selectors ||= ::Playwright::ChannelOwners::Selectors.from(@initializer['selectors'])
25
25
  end
26
26
 
27
- class DeviceDescriptor
28
- class Viewport
29
- def initialize(hash)
30
- @width = hash['width']
31
- @heirhgt = hash['height']
32
- end
33
- attr_reader :width, :height
34
- end
35
-
36
- def initialize(hash)
37
- @user_agent = hash["userAgent"]
38
- @viewport = Viewport.new(hash["viewport"])
39
- @device_scale_factor = hash["deviceScaleFactor"]
40
- @is_mobile = hash["isMobile"]
41
- @has_touch = hash["hasTouch"]
42
- end
43
-
44
- attr_reader :user_agent, :viewport, :device_scale_factor
45
-
46
- def mobile?
47
- @is_mobile
48
- end
49
-
50
- def has_touch?
51
- @has_touch
52
- end
53
- end
54
-
55
27
  def devices
56
28
  @devices ||= @initializer['deviceDescriptors'].map do |item|
57
- [item['name'], DeviceDescriptor.new(item['descriptor'])]
29
+ [item['name'], parse_device_descriptor(item['descriptor'])]
58
30
  end.to_h
59
31
  end
32
+
33
+ private def parse_device_descriptor(descriptor)
34
+ # This return value can be passed into Browser#new_context as it is.
35
+ # ex:
36
+ # ```
37
+ # iPhone = playwright.devices['iPhone 6']
38
+ # context = browser.new_context(**iPhone)
39
+ # page = context.new_page
40
+ #
41
+ # ```
42
+ {
43
+ userAgent: descriptor['userAgent'],
44
+ viewport: {
45
+ width: descriptor['viewport']['width'],
46
+ height: descriptor['viewport']['height'],
47
+ },
48
+ deviceScaleFactor: descriptor['deviceScaleFactor'],
49
+ isMobile: descriptor['isMobile'],
50
+ hasTouch: descriptor['hasTouch'],
51
+ }
52
+ end
60
53
  end
61
54
  end
@@ -0,0 +1,15 @@
1
+ require 'base64'
2
+
3
+ module Playwright
4
+ define_channel_owner :Stream do
5
+ def save_as(path)
6
+ File.open(path, 'wb') do |f|
7
+ loop do
8
+ binary = @channel.send_message_to_server('read')
9
+ break if !binary || binary.length == 0
10
+ f.write(Base64.strict_decode64(binary))
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -12,6 +12,12 @@ module Playwright
12
12
  @transport.on_message_received do |message|
13
13
  dispatch(message)
14
14
  end
15
+ @transport.on_driver_crashed do
16
+ @callbacks.each_value do |callback|
17
+ callback.reject(::Playwright::DriverCrashedError.new)
18
+ end
19
+ raise ::Playwright::DriverCrashedError.new
20
+ end
15
21
 
16
22
  @objects = {} # Hash[ guid => ChannelOwner ]
17
23
  @waiting_for_object = {} # Hash[ guid => Promise<ChannelOwner> ]
@@ -19,26 +25,22 @@ module Playwright
19
25
  @root_object = RootChannelOwner.new(self)
20
26
  end
21
27
 
22
- def run
23
- @transport.run
28
+ def async_run
29
+ @transport.async_run
24
30
  end
25
31
 
26
32
  def stop
27
33
  @transport.stop
28
34
  end
29
35
 
30
- def async_wait_for_object_with_known_name(guid)
36
+ def wait_for_object_with_known_name(guid)
31
37
  if @objects[guid]
32
38
  return @objects[guid]
33
39
  end
34
40
 
35
41
  callback = Concurrent::Promises.resolvable_future
36
42
  @waiting_for_object[guid] = callback
37
- callback
38
- end
39
-
40
- def wait_for_object_with_known_name(guid)
41
- async_wait_for_object_with_known_name.value!
43
+ callback.value!
42
44
  end
43
45
 
44
46
  def async_send_message_to_server(guid, method, params)
@@ -195,32 +197,9 @@ module Playwright
195
197
  end
196
198
  initializer = replace_guids_with_channels(initializer)
197
199
 
198
- class_name = case type
199
- when 'Browser'
200
- case initializer['name']
201
- when 'chromium'
202
- 'ChromiumBrowser'
203
- when 'webkit'
204
- 'WebKitBrowser'
205
- when 'firefox'
206
- 'FirefoxBrowser'
207
- else
208
- 'Browser'
209
- end
210
- when 'BrowserContext'
211
- browser_name = initializer['browserName']
212
- if browser_name == 'chromium'
213
- 'ChromiumBrowserContext'
214
- else
215
- 'BrowserContext'
216
- end
217
- else
218
- type
219
- end
220
-
221
200
  result =
222
201
  begin
223
- ChannelOwners.const_get(class_name).new(
202
+ ChannelOwners.const_get(type).new(
224
203
  parent,
225
204
  type,
226
205
  guid,
@@ -0,0 +1,27 @@
1
+ module Playwright
2
+ class Download
3
+ def initialize(url:, suggested_filename:, artifact:)
4
+ @url = url
5
+ @suggested_filename = suggested_filename
6
+ @artifact = artifact
7
+ end
8
+
9
+ attr_reader :url, :suggested_filename
10
+
11
+ def delete
12
+ @artifact.delete
13
+ end
14
+
15
+ def failure
16
+ @artifact.failure
17
+ end
18
+
19
+ def path
20
+ @artifact.path_after_finished
21
+ end
22
+
23
+ def save_as(path)
24
+ @artifact.save_as(path)
25
+ end
26
+ end
27
+ end
@@ -27,6 +27,12 @@ module Playwright
27
27
  end
28
28
  end
29
29
 
30
+ class DriverCrashedError < StandardError
31
+ def initialize
32
+ super("[BUG] Playwright driver is crashed!")
33
+ end
34
+ end
35
+
30
36
  class TimeoutError < Error
31
37
  def initialize(message:, stack: [])
32
38
  super(name: 'TimeoutError', message: message, stack: stack)
@@ -24,8 +24,10 @@ end
24
24
  },
25
25
 
26
26
  BrowserContext: {
27
+ BackgroundPage: 'backgroundpage',
27
28
  Close: 'close',
28
29
  Page: 'page',
30
+ ServiceWorker: 'serviceworker',
29
31
  },
30
32
 
31
33
  BrowserServer: {
@@ -67,11 +69,6 @@ end
67
69
  Close: 'close',
68
70
  },
69
71
 
70
- ChromiumBrowserContext: {
71
- BackgroundPage: 'backgroundpage',
72
- ServiceWorker: 'serviceworker',
73
- },
74
-
75
72
  ElectronApplication: {
76
73
  Close: 'close',
77
74
  Window: 'window',