playwright-ruby-client 0.2.1 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -5
  3. data/docs/api_coverage.md +116 -74
  4. data/lib/playwright.rb +48 -9
  5. data/lib/playwright/channel.rb +12 -2
  6. data/lib/playwright/channel_owners/artifact.rb +30 -0
  7. data/lib/playwright/channel_owners/binding_call.rb +3 -0
  8. data/lib/playwright/channel_owners/browser.rb +21 -0
  9. data/lib/playwright/channel_owners/browser_context.rb +154 -3
  10. data/lib/playwright/channel_owners/browser_type.rb +28 -0
  11. data/lib/playwright/channel_owners/dialog.rb +28 -0
  12. data/lib/playwright/channel_owners/element_handle.rb +7 -7
  13. data/lib/playwright/channel_owners/frame.rb +26 -5
  14. data/lib/playwright/channel_owners/js_handle.rb +2 -2
  15. data/lib/playwright/channel_owners/page.rb +125 -25
  16. data/lib/playwright/channel_owners/playwright.rb +24 -27
  17. data/lib/playwright/channel_owners/request.rb +26 -2
  18. data/lib/playwright/channel_owners/response.rb +60 -0
  19. data/lib/playwright/channel_owners/route.rb +78 -0
  20. data/lib/playwright/channel_owners/selectors.rb +19 -1
  21. data/lib/playwright/channel_owners/stream.rb +15 -0
  22. data/lib/playwright/connection.rb +11 -32
  23. data/lib/playwright/download.rb +27 -0
  24. data/lib/playwright/errors.rb +6 -0
  25. data/lib/playwright/events.rb +2 -5
  26. data/lib/playwright/keyboard_impl.rb +1 -1
  27. data/lib/playwright/mouse_impl.rb +41 -0
  28. data/lib/playwright/playwright_api.rb +3 -1
  29. data/lib/playwright/route_handler_entry.rb +28 -0
  30. data/lib/playwright/select_option_values.rb +31 -22
  31. data/lib/playwright/transport.rb +29 -7
  32. data/lib/playwright/url_matcher.rb +1 -1
  33. data/lib/playwright/utils.rb +9 -0
  34. data/lib/playwright/version.rb +1 -1
  35. data/lib/playwright/video.rb +51 -0
  36. data/lib/playwright/wait_helper.rb +2 -2
  37. data/lib/playwright_api/accessibility.rb +39 -1
  38. data/lib/playwright_api/android.rb +74 -2
  39. data/lib/playwright_api/android_device.rb +141 -23
  40. data/lib/playwright_api/android_input.rb +17 -13
  41. data/lib/playwright_api/android_socket.rb +16 -0
  42. data/lib/playwright_api/android_web_view.rb +21 -0
  43. data/lib/playwright_api/browser.rb +77 -2
  44. data/lib/playwright_api/browser_context.rb +178 -25
  45. data/lib/playwright_api/browser_type.rb +40 -9
  46. data/lib/playwright_api/dialog.rb +54 -7
  47. data/lib/playwright_api/element_handle.rb +105 -31
  48. data/lib/playwright_api/file_chooser.rb +6 -1
  49. data/lib/playwright_api/frame.rb +229 -36
  50. data/lib/playwright_api/js_handle.rb +23 -0
  51. data/lib/playwright_api/keyboard.rb +48 -1
  52. data/lib/playwright_api/mouse.rb +26 -5
  53. data/lib/playwright_api/page.rb +491 -81
  54. data/lib/playwright_api/playwright.rb +21 -4
  55. data/lib/playwright_api/request.rb +30 -2
  56. data/lib/playwright_api/response.rb +21 -11
  57. data/lib/playwright_api/route.rb +51 -5
  58. data/lib/playwright_api/selectors.rb +27 -1
  59. data/lib/playwright_api/touchscreen.rb +1 -1
  60. data/lib/playwright_api/worker.rb +25 -1
  61. data/playwright.gemspec +4 -2
  62. metadata +42 -14
  63. data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
  64. data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
  65. data/lib/playwright/channel_owners/download.rb +0 -27
  66. data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
  67. data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
  68. data/lib/playwright_api/binding_call.rb +0 -27
  69. data/lib/playwright_api/chromium_browser_context.rb +0 -59
  70. data/lib/playwright_api/download.rb +0 -95
  71. data/lib/playwright_api/video.rb +0 -24
@@ -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)
@@ -14,12 +14,21 @@ module Playwright
14
14
  @mouse = MouseImpl.new(@channel)
15
15
  @touchscreen = TouchscreenImpl.new(@channel)
16
16
 
17
- @viewport_size = @initializer['viewportSize']
17
+ if @initializer['viewportSize']
18
+ @viewport_size = {
19
+ width: @initializer['viewportSize']['width'],
20
+ height: @initializer['viewportSize']['height'],
21
+ }
22
+ end
18
23
  @closed = false
24
+ @bindings = {}
25
+ @routes = Set.new
26
+
19
27
  @main_frame = ChannelOwners::Frame.from(@initializer['mainFrame'])
20
28
  @main_frame.send(:update_page_from_page, self)
21
29
  @frames = Set.new
22
30
  @frames << @main_frame
31
+ @opener = ChannelOwners::Page.from_nullable(@initializer['opener'])
23
32
 
24
33
  @channel.once('close', ->(_) { on_close })
25
34
  @channel.on('console', ->(params) {
@@ -29,9 +38,7 @@ module Playwright
29
38
  @channel.on('crash', ->(_) { emit(Events::Page::Crash) })
30
39
  @channel.on('dialog', method(:on_dialog))
31
40
  @channel.on('domcontentloaded', ->(_) { emit(Events::Page::DOMContentLoaded) })
32
- @channel.on('download', ->(params) {
33
- emit(Events::Page::Download, ChannelOwners::Download.from(params['download']))
34
- })
41
+ @channel.on('download', method(:on_download))
35
42
  @channel.on('fileChooser', ->(params) {
36
43
  chooser = FileChooserImpl.new(
37
44
  page: self,
@@ -49,9 +56,6 @@ module Playwright
49
56
  @channel.on('pageError', ->(params) {
50
57
  emit(Events::Page::PageError, Error.parse(params['error']['error']))
51
58
  })
52
- @channel.on('popup', ->(params) {
53
- emit(Events::Page::Popup, ChannelOwners::Page.from(params['page']))
54
- })
55
59
  @channel.on('request', ->(params) {
56
60
  emit(Events::Page::Request, ChannelOwners::Request.from(params['request']))
57
61
  })
@@ -74,9 +78,7 @@ module Playwright
74
78
  @channel.on('route', ->(params) {
75
79
  on_route(ChannelOwners::Route.from(params['route']), ChannelOwners::Request.from(params['request']))
76
80
  })
77
- @channel.on('video', ->(params) {
78
- video.send(:update_relative_path, params['relativePath'])
79
- })
81
+ @channel.on('video', method(:on_video))
80
82
  @channel.on('webSocket', ->(params) {
81
83
  emit(Events::Page::WebSocket, ChannelOwners::WebSocket.from(params['webSocket']))
82
84
  })
@@ -96,12 +98,12 @@ module Playwright
96
98
  private def on_request_failed(request, response_end_timing, failure_text)
97
99
  request.send(:update_failure_text, failure_text)
98
100
  request.send(:update_response_end_timing, response_end_timing)
99
- emit(Events::Page::RequestFailed)
101
+ emit(Events::Page::RequestFailed, request)
100
102
  end
101
103
 
102
104
  private def on_request_finished(request, response_end_timing)
103
105
  request.send(:update_response_end_timing, response_end_timing)
104
- emit(Events::Page::RequestFinished)
106
+ emit(Events::Page::RequestFinished, request)
105
107
  end
106
108
 
107
109
  private def on_frame_attached(frame)
@@ -117,8 +119,15 @@ module Playwright
117
119
  end
118
120
 
119
121
  private def on_route(route, request)
120
- # @routes.each ...
121
- @browser_context.send(:on_route, route, request)
122
+ # It is not desired to use PlaywrightApi.wrap directly.
123
+ # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
124
+ # Just a workaround...
125
+ wrapped_route = PlaywrightApi.wrap(route)
126
+ wrapped_request = PlaywrightApi.wrap(request)
127
+
128
+ if @routes.none? { |handler_entry| handler_entry.handle(wrapped_route, wrapped_request) }
129
+ @browser_context.send(:on_route, route, request)
130
+ end
122
131
  end
123
132
 
124
133
  private def on_close
@@ -130,14 +139,28 @@ module Playwright
130
139
  private def on_dialog(params)
131
140
  dialog = ChannelOwners::Dialog.from(params['dialog'])
132
141
  unless emit(Events::Page::Dialog, dialog)
133
- dialog.dismiss # FIXME: this should be asynchronous
142
+ dialog.dismiss
134
143
  end
135
144
  end
136
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
+
137
160
  # @override
138
161
  def on(event, callback)
139
162
  if event == Events::Page::FileChooser && listener_count(event) == 0
140
- @channel.send_no_reply('setFileChooserInterceptedNoReply', intercepted: true)
163
+ @channel.async_send_message_to_server('setFileChooserInterceptedNoReply', intercepted: true)
141
164
  end
142
165
  super
143
166
  end
@@ -145,7 +168,7 @@ module Playwright
145
168
  # @override
146
169
  def once(event, callback)
147
170
  if event == Events::Page::FileChooser && listener_count(event) == 0
148
- @channel.send_no_reply('setFileChooserInterceptedNoReply', intercepted: true)
171
+ @channel.async_send_message_to_server('setFileChooserInterceptedNoReply', intercepted: true)
149
172
  end
150
173
  super
151
174
  end
@@ -154,7 +177,7 @@ module Playwright
154
177
  def off(event, callback)
155
178
  super
156
179
  if event == Events::Page::FileChooser && listener_count(event) == 0
157
- @channel.send_no_reply('setFileChooserInterceptedNoReply', intercepted: false)
180
+ @channel.async_send_message_to_server('setFileChooserInterceptedNoReply', intercepted: false)
158
181
  end
159
182
  end
160
183
 
@@ -163,8 +186,17 @@ module Playwright
163
186
  end
164
187
 
165
188
  def opener
166
- resp = @channel.send_message_to_server('opener')
167
- 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
168
200
  end
169
201
 
170
202
  def frame(name: nil, url: nil)
@@ -311,6 +343,10 @@ module Playwright
311
343
  @main_frame.wait_for_load_state(state: state, timeout: timeout)
312
344
  end
313
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
+
314
350
  def go_back(timeout: nil, waitUntil: nil)
315
351
  params = { timeout: timeout, waitUntil: waitUntil }.compact
316
352
  resp = @channel.send_message_to_server('goBack', params)
@@ -347,7 +383,7 @@ module Playwright
347
383
  def add_init_script(path: nil, script: nil)
348
384
  source =
349
385
  if path
350
- File.read(path, 'r')
386
+ File.read(path)
351
387
  elsif script
352
388
  script
353
389
  else
@@ -358,6 +394,23 @@ module Playwright
358
394
  nil
359
395
  end
360
396
 
397
+ def route(url, handler)
398
+ entry = RouteHandlerEntry.new(url, handler)
399
+ @routes << entry
400
+ if @routes.count >= 1
401
+ @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
402
+ end
403
+ end
404
+
405
+ def unroute(url, handler: nil)
406
+ @routes.reject! do |handler_entry|
407
+ handler_entry.same_value?(url: url, handler: handler)
408
+ end
409
+ if @routes.count == 0
410
+ @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: false)
411
+ end
412
+ end
413
+
361
414
  def screenshot(
362
415
  path: nil,
363
416
  type: nil,
@@ -546,12 +599,24 @@ module Playwright
546
599
  @main_frame.press(selector, key, delay: delay, noWaitAfter: noWaitAfter, timeout: timeout)
547
600
  end
548
601
 
549
- def check(selector, force: nil, noWaitAfter: nil, timeout: nil)
550
- @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)
551
610
  end
552
611
 
553
- def uncheck(selector, force: nil, noWaitAfter: nil, timeout: nil)
554
- @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)
555
620
  end
556
621
 
557
622
  def wait_for_function(pageFunction, arg: nil, polling: nil, timeout: nil)
@@ -597,6 +662,36 @@ module Playwright
597
662
  decoded_binary
598
663
  end
599
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
+
600
695
  class CrashedError < StandardError
601
696
  def initialize
602
697
  super('Page crashed')
@@ -698,5 +793,10 @@ module Playwright
698
793
  private def timeout_settings
699
794
  @timeout_settings
700
795
  end
796
+
797
+ # called from BrowserContext#expose_binding
798
+ private def has_bindings?(name)
799
+ @bindings.key?(name)
800
+ end
701
801
  end
702
802
  end
@@ -20,38 +20,35 @@ module Playwright
20
20
  @electron ||= ::Playwright::ChannelOwners::Electron.from(@initializer['electron'])
21
21
  end
22
22
 
23
- class DeviceDescriptor
24
- class Viewport
25
- def initialize(hash)
26
- @width = hash['width']
27
- @heirhgt = hash['height']
28
- end
29
- attr_reader :width, :height
30
- end
31
-
32
- def initialize(hash)
33
- @user_agent = hash["userAgent"]
34
- @viewport = Viewport.new(hash["viewport"])
35
- @device_scale_factor = hash["deviceScaleFactor"]
36
- @is_mobile = hash["isMobile"]
37
- @has_touch = hash["hasTouch"]
38
- end
39
-
40
- attr_reader :user_agent, :viewport, :device_scale_factor
41
-
42
- def mobile?
43
- @is_mobile
44
- end
45
-
46
- def has_touch?
47
- @has_touch
48
- end
23
+ def selectors
24
+ @selectors ||= ::Playwright::ChannelOwners::Selectors.from(@initializer['selectors'])
49
25
  end
50
26
 
51
27
  def devices
52
28
  @devices ||= @initializer['deviceDescriptors'].map do |item|
53
- [item['name'], DeviceDescriptor.new(item['descriptor'])]
29
+ [item['name'], parse_device_descriptor(item['descriptor'])]
54
30
  end.to_h
55
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
56
53
  end
57
54
  end
@@ -29,7 +29,7 @@ module Playwright
29
29
  end
30
30
 
31
31
  def method
32
- @initialize['method']
32
+ @initializer['method']
33
33
  end
34
34
 
35
35
  def post_data
@@ -69,7 +69,7 @@ module Playwright
69
69
  end
70
70
 
71
71
  def frame
72
- @initializer['frame']
72
+ ChannelOwners::Frame.from(@initializer['frame'])
73
73
  end
74
74
 
75
75
  def navigation_request?
@@ -90,6 +90,30 @@ module Playwright
90
90
  @failure_text = failure_text
91
91
  end
92
92
 
93
+ private def update_timings(
94
+ start_time:,
95
+ domain_lookup_start:,
96
+ domain_lookup_end:,
97
+ connect_start:,
98
+ secure_connection_start:,
99
+ connect_end:,
100
+ request_start:,
101
+ response_start:)
102
+
103
+ @timing["startTime"] = start_time
104
+ @timing["domainLookupStart"] = domain_lookup_start
105
+ @timing["domainLookupEnd"] = domain_lookup_end
106
+ @timing["connectStart"] = connect_start
107
+ @timing["secureConnectionStart"] = secure_connection_start
108
+ @timing["connectEnd"] = connect_end
109
+ @timing["requestStart"] = request_start
110
+ @timing["responseStart"] = response_start
111
+ end
112
+
113
+ private def update_headers(headers)
114
+ @headers = parse_headers(headers)
115
+ end
116
+
93
117
  private def update_response_end_timing(response_end_timing)
94
118
  @timing[:responseEnd] = response_end_timing
95
119
  end
@@ -1,5 +1,65 @@
1
+ require 'base64'
2
+ require 'json'
3
+
1
4
  module Playwright
2
5
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
3
6
  define_channel_owner :Response do
7
+ def after_initialize
8
+ @request = ChannelOwners::Request.from(@initializer['request'])
9
+ timing = @initializer['timing']
10
+ @request.send(:update_timings,
11
+ start_time: timing["startTime"],
12
+ domain_lookup_start: timing["domainLookupStart"],
13
+ domain_lookup_end: timing["domainLookupEnd"],
14
+ connect_start: timing["connectStart"],
15
+ secure_connection_start: timing["secureConnectionStart"],
16
+ connect_end: timing["connectEnd"],
17
+ request_start: timing["requestStart"],
18
+ response_start: timing["responseStart"],
19
+ )
20
+ @request.send(:update_headers, @initializer['requestHeaders'])
21
+ end
22
+ attr_reader :request
23
+
24
+ def url
25
+ @initializer['url']
26
+ end
27
+
28
+ def ok
29
+ status == 0 || (200...300).include?(status)
30
+ end
31
+ alias_method :ok?, :ok
32
+
33
+ def status
34
+ @initializer['status']
35
+ end
36
+
37
+ def status_text
38
+ @initializer['statusText']
39
+ end
40
+
41
+ def headers
42
+ @initializer['headers'].map do |header|
43
+ [header['name'].downcase, header['value']]
44
+ end.to_h
45
+ end
46
+
47
+ def finished
48
+ @channel.send_message_to_server('finished')
49
+ end
50
+
51
+ def body
52
+ binary = @channel.send_message_to_server("body")
53
+ Base64.strict_decode64(binary)
54
+ end
55
+ alias_method :text, :body
56
+
57
+ def json
58
+ JSON.parse(text)
59
+ end
60
+
61
+ def frame
62
+ @request.frame
63
+ end
4
64
  end
5
65
  end