playwright-ruby-client 0.2.0 → 0.5.4

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 +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 +6 -4
  13. data/lib/playwright/channel_owners/frame.rb +25 -2
  14. data/lib/playwright/channel_owners/js_handle.rb +2 -2
  15. data/lib/playwright/channel_owners/page.rb +141 -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/transport.rb +29 -7
  31. data/lib/playwright/url_matcher.rb +1 -1
  32. data/lib/playwright/utils.rb +9 -0
  33. data/lib/playwright/version.rb +1 -1
  34. data/lib/playwright/video.rb +51 -0
  35. data/lib/playwright/wait_helper.rb +2 -2
  36. data/lib/playwright_api/accessibility.rb +39 -1
  37. data/lib/playwright_api/android.rb +74 -2
  38. data/lib/playwright_api/android_device.rb +141 -23
  39. data/lib/playwright_api/android_input.rb +17 -13
  40. data/lib/playwright_api/android_socket.rb +16 -0
  41. data/lib/playwright_api/android_web_view.rb +21 -0
  42. data/lib/playwright_api/browser.rb +77 -2
  43. data/lib/playwright_api/browser_context.rb +182 -29
  44. data/lib/playwright_api/browser_type.rb +40 -9
  45. data/lib/playwright_api/dialog.rb +54 -7
  46. data/lib/playwright_api/element_handle.rb +105 -31
  47. data/lib/playwright_api/file_chooser.rb +6 -1
  48. data/lib/playwright_api/frame.rb +229 -36
  49. data/lib/playwright_api/js_handle.rb +23 -0
  50. data/lib/playwright_api/keyboard.rb +48 -1
  51. data/lib/playwright_api/mouse.rb +26 -5
  52. data/lib/playwright_api/page.rb +491 -81
  53. data/lib/playwright_api/playwright.rb +21 -4
  54. data/lib/playwright_api/request.rb +30 -2
  55. data/lib/playwright_api/response.rb +21 -11
  56. data/lib/playwright_api/route.rb +51 -5
  57. data/lib/playwright_api/selectors.rb +27 -1
  58. data/lib/playwright_api/touchscreen.rb +1 -1
  59. data/lib/playwright_api/worker.rb +25 -1
  60. data/playwright.gemspec +4 -2
  61. metadata +42 -14
  62. data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
  63. data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
  64. data/lib/playwright/channel_owners/download.rb +0 -27
  65. data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
  66. data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
  67. data/lib/playwright_api/binding_call.rb +0 -27
  68. data/lib/playwright_api/chromium_browser_context.rb +0 -59
  69. data/lib/playwright_api/download.rb +0 -95
  70. data/lib/playwright_api/video.rb +0 -24
@@ -0,0 +1,30 @@
1
+ module Playwright
2
+ define_channel_owner :Artifact do
3
+ private def after_initialize
4
+ @is_remote = false
5
+ @absolute_path = @initializer['absolutePath']
6
+ end
7
+
8
+ attr_reader :absolute_path
9
+
10
+ def path_after_finished
11
+ if @is_remote
12
+ raise "Path is not available when using browser_type.connect(). Use save_as() to save a local copy."
13
+ end
14
+ @channel.send_message_to_server('pathAfterFinished')
15
+ end
16
+
17
+ def save_as(path)
18
+ stream = ChannelOwners::Stream.from(@channel.send_message_to_server('saveAsStream'))
19
+ stream.save_as(path)
20
+ end
21
+
22
+ def failure
23
+ @channel.send_message_to_server('failure')
24
+ end
25
+
26
+ def delete
27
+ @channel.send_message_to_server('delete')
28
+ end
29
+ end
30
+ end
@@ -1,4 +1,7 @@
1
1
  module Playwright
2
2
  define_channel_owner :BindingCall do
3
+ def name
4
+ @initializer['name']
5
+ end
3
6
  end
4
7
  end
@@ -59,12 +59,33 @@ module Playwright
59
59
  @initializer['version']
60
60
  end
61
61
 
62
+ def start_tracing(page: nil, categories: nil, path: nil, screenshots: nil)
63
+ params = {
64
+ page: page&.channel,
65
+ categories: categories,
66
+ path: path,
67
+ screenshots: screenshots,
68
+ }.compact
69
+
70
+ @channel.send_message_to_server('startTracing', params)
71
+ end
72
+
73
+ def stop_tracing
74
+ encoded_binary = @channel.send_message_to_server("stopTracing")
75
+ return Base64.strict_decode64(encoded_binary)
76
+ end
77
+
62
78
  private def on_close(_ = {})
63
79
  @connected = false
64
80
  emit(Events::Browser::Disconnected)
65
81
  @closed_or_closing = false
66
82
  end
67
83
 
84
+ # called from BrowserType#connectOverCDP
85
+ private def add_context(context)
86
+ @contexts << context
87
+ end
88
+
68
89
  # called from BrowserContext#on_close with send(:remove_context), so keep private.
69
90
  private def remove_context(context)
70
91
  @contexts.delete(context)
@@ -2,11 +2,16 @@ module Playwright
2
2
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_browser_context.py
3
3
  define_channel_owner :BrowserContext do
4
4
  include Utils::Errors::SafeCloseError
5
- attr_writer :browser, :owner_page, :options
5
+ attr_accessor :browser
6
+ attr_writer :owner_page, :options
6
7
 
7
8
  private def after_initialize
8
9
  @pages = Set.new
10
+ @routes = Set.new
11
+ @bindings = {}
12
+ @timeout_settings = TimeoutSettings.new
9
13
 
14
+ @channel.on('bindingCall', ->(params) { on_binding(ChannelOwners::BindingCall.from(params['binding'])) })
10
15
  @channel.once('close', ->(_) { on_close })
11
16
  @channel.on('page', ->(params) { on_page(ChannelOwners::Page.from(params['page']) )})
12
17
  @channel.on('route', ->(params) {
@@ -18,11 +23,36 @@ module Playwright
18
23
  page.send(:update_browser_context, self)
19
24
  @pages << page
20
25
  emit(Events::BrowserContext::Page, page)
26
+ page.send(:emit_popup_event_from_browser_context)
21
27
  end
22
28
 
23
29
  private def on_route(route, request)
24
- # @routes.each ...
25
- route.continue_
30
+ # It is not desired to use PlaywrightApi.wrap directly.
31
+ # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
32
+ # Just a workaround...
33
+ wrapped_route = PlaywrightApi.wrap(route)
34
+ wrapped_request = PlaywrightApi.wrap(request)
35
+
36
+ if @routes.none? { |handler_entry| handler_entry.handle(wrapped_route, wrapped_request) }
37
+ route.continue
38
+ end
39
+ end
40
+
41
+ private def on_binding(binding_call)
42
+ func = @binding[binding_call.name]
43
+ if func
44
+ binding_call.call(func)
45
+ end
46
+ end
47
+
48
+ def set_default_navigation_timeout(timeout)
49
+ @timeout_settings.default_navigation_timeout = timeout
50
+ @channel.send_message_to_server('setDefaultNavigationTimeoutNoReply', timeout: timeout)
51
+ end
52
+
53
+ def set_default_timeout(timeout)
54
+ @timeout_settings.default_timeout = timeout
55
+ @channel.send_message_to_server('setDefaultTimeoutNoReply', timeout: timeout)
26
56
  end
27
57
 
28
58
  def pages
@@ -36,6 +66,111 @@ module Playwright
36
66
  ChannelOwners::Page.from(resp)
37
67
  end
38
68
 
69
+ def cookies(urls: nil)
70
+ target_urls =
71
+ if urls.nil?
72
+ []
73
+ elsif urls.is_a?(Enumerable)
74
+ urls
75
+ else
76
+ [urls]
77
+ end
78
+ @channel.send_message_to_server('cookies', urls: urls)
79
+ end
80
+
81
+ def add_cookies(cookies)
82
+ @channel.send_message_to_server('addCookies', cookies: cookies)
83
+ end
84
+
85
+ def clear_cookies
86
+ @channel.send_message_to_server('clearCookies')
87
+ end
88
+
89
+ def grant_permissions(permissions, origin: nil)
90
+ params = {
91
+ permissions: permissions,
92
+ origin: origin,
93
+ }.compact
94
+ @channel.send_message_to_server('grantPermissions', params)
95
+ end
96
+
97
+ def clear_permissions
98
+ @channel.send_message_to_server('clearPermissions')
99
+ end
100
+
101
+ def set_geolocation(geolocation)
102
+ @channel.send_message_to_server('setGeolocation', geolocation: geolocation)
103
+ end
104
+
105
+ def set_extra_http_headers(headers)
106
+ @channel.send_message_to_server('setExtraHTTPHeaders',
107
+ headers: HttpHeaders.new(headers).as_serialized)
108
+ end
109
+
110
+ def set_offline(offline)
111
+ @channel.send_message_to_server('setOffline', offline: offline)
112
+ end
113
+
114
+ def add_init_script(path: nil, script: nil)
115
+ source =
116
+ if path
117
+ File.read(path)
118
+ elsif script
119
+ script
120
+ else
121
+ raise ArgumentError.new('Either path or script parameter must be specified')
122
+ end
123
+
124
+ @channel.send_message_to_server('addInitScript', source: script)
125
+ nil
126
+ end
127
+
128
+ def expose_binding(name, callback, handle: nil)
129
+ if @pages.any? { |page| page.send(:has_bindings?, name) }
130
+ raise ArgumentError.new("Function \"#{name}\" has been already registered in one of the pages")
131
+ end
132
+ if @bindings.key?(name)
133
+ raise ArgumentError.new("Function \"#{name}\" has been already registered")
134
+ end
135
+ params = {
136
+ name: name,
137
+ needsHandle: handle,
138
+ }.compact
139
+ @bindings[name] = callback
140
+ @channel.send_message_to_server('exposeBinding', params)
141
+ end
142
+
143
+ def expose_function(name, callback)
144
+ expose_binding(name, ->(_source, *args) { callback.call(*args) }, )
145
+ end
146
+
147
+ def route(url, handler)
148
+ entry = RouteHandlerEntry.new(url, handler)
149
+ @routes << entry
150
+ if @routes.count >= 1
151
+ @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
152
+ end
153
+ end
154
+
155
+ def unroute(url, handler: nil)
156
+ @routes.reject! do |handler_entry|
157
+ handler_entry.same_value?(url: url, handler: handler)
158
+ end
159
+ if @routes.count == 0
160
+ @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: false)
161
+ end
162
+ end
163
+
164
+ def expect_event(event, predicate: nil, timeout: nil, &block)
165
+ wait_helper = WaitHelper.new
166
+ wait_helper.reject_on_timeout(timeout || @timeout_settings.timeout, "Timeout while waiting for event \"#{event}\"")
167
+ wait_helper.wait_for_event(self, event, predicate: predicate)
168
+
169
+ block&.call
170
+
171
+ wait_helper.promise.value!
172
+ end
173
+
39
174
  private def on_close
40
175
  @closed_or_closing = true
41
176
  @browser&.send(:remove_context, self)
@@ -51,6 +186,18 @@ module Playwright
51
186
  raise unless safe_close_error?(err)
52
187
  end
53
188
 
189
+ def pause
190
+ @channel.send_message_to_server('pause')
191
+ end
192
+
193
+ def expect_page(predicate: nil, timeout: nil)
194
+ params = {
195
+ predicate: predicate,
196
+ timeout: timeout,
197
+ }.compact
198
+ expect_event(Events::BrowserContext::Page, params)
199
+ end
200
+
54
201
  # called from Page#on_close with send(:remove_page, page), so keep private
55
202
  private def remove_page(page)
56
203
  @pages.delete(page)
@@ -60,5 +207,9 @@ module Playwright
60
207
  private def _timeout_settings
61
208
  @timeout_settings
62
209
  end
210
+
211
+ private def has_record_video_option?
212
+ @options.key?(:recordVideo)
213
+ end
63
214
  end
64
215
  end
@@ -22,5 +22,33 @@ module Playwright
22
22
  browser
23
23
  end
24
24
  end
25
+
26
+ def connect_over_cdp(endpointURL, slowMo: nil, timeout: nil, &block)
27
+ raise 'Connecting over CDP is only supported in Chromium.' unless name == 'chromium'
28
+
29
+ params = {
30
+ sdkLanguage: 'ruby',
31
+ endpointURL: endpointURL,
32
+ slowMo: slowMo,
33
+ timeout: timeout,
34
+ }.compact
35
+ result = @channel.send_message_to_server_result('connectOverCDP', params)
36
+ browser = ChannelOwners::Browser.from(result['browser'])
37
+
38
+ if result['defaultContext']
39
+ context = ChannelOwners::BrowserContext.from(result['defaultContext'])
40
+ browser.send(:add_context, context)
41
+ end
42
+
43
+ if block
44
+ begin
45
+ block.call(browser)
46
+ ensure
47
+ browser.close
48
+ end
49
+ else
50
+ browser
51
+ end
52
+ end
25
53
  end
26
54
  end
@@ -0,0 +1,28 @@
1
+ module Playwright
2
+ define_channel_owner :Dialog do
3
+ def type
4
+ @initializer['type']
5
+ end
6
+
7
+ def message
8
+ @initializer['message']
9
+ end
10
+
11
+ def default_value
12
+ @initializer['defaultValue']
13
+ end
14
+
15
+ def accept(promptText: nil)
16
+ accept_async(prompt_text: promptText).value!
17
+ end
18
+
19
+ def accept_async(promptText: nil)
20
+ params = { promptText: promptText }.compact
21
+ @channel.async_send_message_to_server('accept', params)
22
+ end
23
+
24
+ def dismiss
25
+ @channel.async_send_message_to_server('dismiss')
26
+ end
27
+ end
28
+ end
@@ -82,7 +82,7 @@ module Playwright
82
82
  modifiers: modifiers,
83
83
  position: position,
84
84
  timeout: timeout,
85
- }
85
+ }.compact
86
86
  @channel.send_message_to_server('hover', params)
87
87
 
88
88
  nil
@@ -179,7 +179,7 @@ module Playwright
179
179
  value: value,
180
180
  noWaitAfter: noWaitAfter,
181
181
  timeout: timeout,
182
- }
182
+ }.compact
183
183
  @channel.send_message_to_server('fill', params)
184
184
 
185
185
  nil
@@ -230,10 +230,11 @@ module Playwright
230
230
  nil
231
231
  end
232
232
 
233
- def check(force: nil, noWaitAfter: nil, timeout: nil)
233
+ def check(force: nil, noWaitAfter: nil, position: nil, timeout: nil)
234
234
  params = {
235
235
  force: force,
236
236
  noWaitAfter: noWaitAfter,
237
+ position: position,
237
238
  timeout: timeout,
238
239
  }.compact
239
240
  @channel.send_message_to_server('check', params)
@@ -241,10 +242,11 @@ module Playwright
241
242
  nil
242
243
  end
243
244
 
244
- def uncheck(force: nil, noWaitAfter: nil, timeout: nil)
245
+ def uncheck(force: nil, noWaitAfter: nil, position: nil, timeout: nil)
245
246
  params = {
246
247
  force: force,
247
248
  noWaitAfter: noWaitAfter,
249
+ position: position,
248
250
  timeout: timeout,
249
251
  }.compact
250
252
  @channel.send_message_to_server('uncheck', params)
@@ -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
@@ -464,11 +473,18 @@ module Playwright
464
473
  nil
465
474
  end
466
475
 
467
- def check(selector, force: nil, noWaitAfter: nil, timeout: nil)
476
+ def check(
477
+ selector,
478
+ force: nil,
479
+ noWaitAfter: nil,
480
+ position: nil,
481
+ timeout: nil)
482
+
468
483
  params = {
469
484
  selector: selector,
470
485
  force: force,
471
486
  noWaitAfter: noWaitAfter,
487
+ position: position,
472
488
  timeout: timeout,
473
489
  }.compact
474
490
  @channel.send_message_to_server('check', params)
@@ -476,11 +492,18 @@ module Playwright
476
492
  nil
477
493
  end
478
494
 
479
- def uncheck(selector, force: nil, noWaitAfter: nil, timeout: nil)
495
+ def uncheck(
496
+ selector,
497
+ force: nil,
498
+ noWaitAfter: nil,
499
+ position: nil,
500
+ timeout: nil)
501
+
480
502
  params = {
481
503
  selector: selector,
482
504
  force: force,
483
505
  noWaitAfter: noWaitAfter,
506
+ position: position,
484
507
  timeout: timeout,
485
508
  }.compact
486
509
  @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)
@@ -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')
@@ -634,6 +729,18 @@ module Playwright
634
729
  wait_helper.promise.value!
635
730
  end
636
731
 
732
+ def expect_console_message(predicate: nil, timeout: nil, &block)
733
+ expect_event(Events::Page::Console, predicate: predicate, timeout: timeout, &block)
734
+ end
735
+
736
+ def expect_download(predicate: nil, timeout: nil, &block)
737
+ expect_event(Events::Page::Download, predicate: predicate, timeout: timeout, &block)
738
+ end
739
+
740
+ def expect_file_chooser(predicate: nil, timeout: nil, &block)
741
+ expect_event(Events::Page::FileChooser, predicate: predicate, timeout: timeout, &block)
742
+ end
743
+
637
744
  def expect_navigation(timeout: nil, url: nil, waitUntil: nil, &block)
638
745
  @main_frame.expect_navigation(
639
746
  timeout: timeout,
@@ -642,6 +749,10 @@ module Playwright
642
749
  &block)
643
750
  end
644
751
 
752
+ def expect_popup(predicate: nil, timeout: nil, &block)
753
+ expect_event(Events::Page::Popup, predicate: predicate, timeout: timeout, &block)
754
+ end
755
+
645
756
  def expect_request(urlOrPredicate, timeout: nil)
646
757
  predicate =
647
758
  case urlOrPredicate
@@ -682,5 +793,10 @@ module Playwright
682
793
  private def timeout_settings
683
794
  @timeout_settings
684
795
  end
796
+
797
+ # called from BrowserContext#expose_binding
798
+ private def has_bindings?(name)
799
+ @bindings.key?(name)
800
+ end
685
801
  end
686
802
  end