playwright-ruby-client 0.0.8 → 1.58.1.alpha1

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 (209) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +4 -0
  3. data/CLAUDE/api_generation.md +28 -0
  4. data/CLAUDE/ci_expectations.md +23 -0
  5. data/CLAUDE/gem_release_flow.md +39 -0
  6. data/CLAUDE/past_upgrade_pr_patterns.md +42 -0
  7. data/CLAUDE/playwright_upgrade_workflow.md +35 -0
  8. data/CLAUDE/rspec_debugging.md +30 -0
  9. data/CLAUDE/unimplemented_examples.md +18 -0
  10. data/CLAUDE.md +32 -0
  11. data/CONTRIBUTING.md +5 -0
  12. data/README.md +60 -16
  13. data/documentation/README.md +33 -0
  14. data/documentation/babel.config.js +3 -0
  15. data/documentation/docs/api/api_request.md +7 -0
  16. data/documentation/docs/api/api_request_context.md +298 -0
  17. data/documentation/docs/api/api_response.md +114 -0
  18. data/documentation/docs/api/browser.md +237 -0
  19. data/documentation/docs/api/browser_context.md +503 -0
  20. data/documentation/docs/api/browser_type.md +184 -0
  21. data/documentation/docs/api/cdp_session.md +44 -0
  22. data/documentation/docs/api/clock.md +154 -0
  23. data/documentation/docs/api/console_message.md +85 -0
  24. data/documentation/docs/api/dialog.md +84 -0
  25. data/documentation/docs/api/download.md +111 -0
  26. data/documentation/docs/api/element_handle.md +694 -0
  27. data/documentation/docs/api/experimental/_category_.yml +3 -0
  28. data/documentation/docs/api/experimental/android.md +42 -0
  29. data/documentation/docs/api/experimental/android_device.md +109 -0
  30. data/documentation/docs/api/experimental/android_input.md +43 -0
  31. data/documentation/docs/api/experimental/android_socket.md +7 -0
  32. data/documentation/docs/api/experimental/android_web_view.md +7 -0
  33. data/documentation/docs/api/file_chooser.md +53 -0
  34. data/documentation/docs/api/frame.md +1218 -0
  35. data/documentation/docs/api/frame_locator.md +348 -0
  36. data/documentation/docs/api/js_handle.md +121 -0
  37. data/documentation/docs/api/keyboard.md +170 -0
  38. data/documentation/docs/api/locator.md +1495 -0
  39. data/documentation/docs/api/locator_assertions.md +827 -0
  40. data/documentation/docs/api/mouse.md +86 -0
  41. data/documentation/docs/api/page.md +1946 -0
  42. data/documentation/docs/api/page_assertions.md +65 -0
  43. data/documentation/docs/api/playwright.md +66 -0
  44. data/documentation/docs/api/request.md +255 -0
  45. data/documentation/docs/api/response.md +176 -0
  46. data/documentation/docs/api/route.md +205 -0
  47. data/documentation/docs/api/selectors.md +63 -0
  48. data/documentation/docs/api/touchscreen.md +22 -0
  49. data/documentation/docs/api/tracing.md +129 -0
  50. data/documentation/docs/api/web_socket.md +51 -0
  51. data/documentation/docs/api/worker.md +83 -0
  52. data/documentation/docs/article/api_coverage.mdx +11 -0
  53. data/documentation/docs/article/getting_started.md +161 -0
  54. data/documentation/docs/article/guides/_category_.yml +3 -0
  55. data/documentation/docs/article/guides/download_playwright_driver.md +55 -0
  56. data/documentation/docs/article/guides/inspector.md +31 -0
  57. data/documentation/docs/article/guides/launch_browser.md +121 -0
  58. data/documentation/docs/article/guides/playwright_on_alpine_linux.md +112 -0
  59. data/documentation/docs/article/guides/rails_integration.md +278 -0
  60. data/documentation/docs/article/guides/rails_integration_with_null_driver.md +145 -0
  61. data/documentation/docs/article/guides/recording_video.md +79 -0
  62. data/documentation/docs/article/guides/rspec_integration.md +59 -0
  63. data/documentation/docs/article/guides/semi_automation.md +71 -0
  64. data/documentation/docs/article/guides/use_storage_state.md +78 -0
  65. data/documentation/docs/include/api_coverage.md +671 -0
  66. data/documentation/docusaurus.config.js +114 -0
  67. data/documentation/package.json +39 -0
  68. data/documentation/sidebars.js +15 -0
  69. data/documentation/src/components/HomepageFeatures.js +61 -0
  70. data/documentation/src/components/HomepageFeatures.module.css +13 -0
  71. data/documentation/src/css/custom.css +44 -0
  72. data/documentation/src/pages/index.js +49 -0
  73. data/documentation/src/pages/index.module.css +41 -0
  74. data/documentation/src/pages/markdown-page.md +7 -0
  75. data/documentation/static/.nojekyll +0 -0
  76. data/documentation/static/img/playwright-logo.svg +9 -0
  77. data/documentation/static/img/playwright-ruby-client.png +0 -0
  78. data/documentation/static/img/undraw_dropdown_menu.svg +1 -0
  79. data/documentation/static/img/undraw_web_development.svg +1 -0
  80. data/documentation/static/img/undraw_windows.svg +1 -0
  81. data/documentation/yarn.lock +9005 -0
  82. data/lib/playwright/{input_types/android_input.rb → android_input_impl.rb} +5 -1
  83. data/lib/playwright/api_implementation.rb +18 -0
  84. data/lib/playwright/api_response_impl.rb +77 -0
  85. data/lib/playwright/channel.rb +62 -1
  86. data/lib/playwright/channel_owner.rb +70 -7
  87. data/lib/playwright/channel_owners/android.rb +16 -3
  88. data/lib/playwright/channel_owners/android_device.rb +22 -66
  89. data/lib/playwright/channel_owners/api_request_context.rb +247 -0
  90. data/lib/playwright/channel_owners/artifact.rb +40 -0
  91. data/lib/playwright/channel_owners/binding_call.rb +70 -0
  92. data/lib/playwright/channel_owners/browser.rb +114 -22
  93. data/lib/playwright/channel_owners/browser_context.rb +589 -15
  94. data/lib/playwright/channel_owners/browser_type.rb +90 -1
  95. data/lib/playwright/channel_owners/cdp_session.rb +19 -0
  96. data/lib/playwright/channel_owners/dialog.rb +32 -0
  97. data/lib/playwright/channel_owners/element_handle.rb +107 -43
  98. data/lib/playwright/channel_owners/fetch_request.rb +8 -0
  99. data/lib/playwright/channel_owners/frame.rb +334 -104
  100. data/lib/playwright/channel_owners/js_handle.rb +9 -13
  101. data/lib/playwright/channel_owners/local_utils.rb +82 -0
  102. data/lib/playwright/channel_owners/page.rb +778 -95
  103. data/lib/playwright/channel_owners/playwright.rb +25 -30
  104. data/lib/playwright/channel_owners/request.rb +120 -18
  105. data/lib/playwright/channel_owners/response.rb +113 -0
  106. data/lib/playwright/channel_owners/route.rb +181 -0
  107. data/lib/playwright/channel_owners/stream.rb +30 -0
  108. data/lib/playwright/channel_owners/tracing.rb +117 -0
  109. data/lib/playwright/channel_owners/web_socket.rb +96 -0
  110. data/lib/playwright/channel_owners/worker.rb +46 -0
  111. data/lib/playwright/channel_owners/writable_stream.rb +14 -0
  112. data/lib/playwright/clock_impl.rb +67 -0
  113. data/lib/playwright/connection.rb +111 -63
  114. data/lib/playwright/console_message_impl.rb +29 -0
  115. data/lib/playwright/download_impl.rb +32 -0
  116. data/lib/playwright/errors.rb +42 -5
  117. data/lib/playwright/event_emitter.rb +17 -3
  118. data/lib/playwright/event_emitter_proxy.rb +49 -0
  119. data/lib/playwright/events.rb +10 -5
  120. data/lib/playwright/file_chooser_impl.rb +24 -0
  121. data/lib/playwright/frame_locator_impl.rb +66 -0
  122. data/lib/playwright/har_router.rb +89 -0
  123. data/lib/playwright/http_headers.rb +14 -0
  124. data/lib/playwright/input_files.rb +102 -15
  125. data/lib/playwright/javascript/expression.rb +7 -11
  126. data/lib/playwright/javascript/regex.rb +23 -0
  127. data/lib/playwright/javascript/source_url.rb +16 -0
  128. data/lib/playwright/javascript/value_parser.rb +108 -19
  129. data/lib/playwright/javascript/value_serializer.rb +47 -8
  130. data/lib/playwright/javascript/visitor_info.rb +26 -0
  131. data/lib/playwright/javascript.rb +2 -10
  132. data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +6 -2
  133. data/lib/playwright/locator_assertions_impl.rb +571 -0
  134. data/lib/playwright/locator_impl.rb +544 -0
  135. data/lib/playwright/locator_utils.rb +136 -0
  136. data/lib/playwright/mouse_impl.rb +57 -0
  137. data/lib/playwright/page_assertions_impl.rb +154 -0
  138. data/lib/playwright/playwright_api.rb +102 -30
  139. data/lib/playwright/raw_headers.rb +61 -0
  140. data/lib/playwright/route_handler.rb +78 -0
  141. data/lib/playwright/select_option_values.rb +34 -13
  142. data/lib/playwright/selectors_impl.rb +45 -0
  143. data/lib/playwright/test.rb +102 -0
  144. data/lib/playwright/timeout_settings.rb +9 -4
  145. data/lib/playwright/touchscreen_impl.rb +14 -0
  146. data/lib/playwright/transport.rb +61 -10
  147. data/lib/playwright/url_matcher.rb +24 -2
  148. data/lib/playwright/utils.rb +48 -13
  149. data/lib/playwright/version.rb +2 -1
  150. data/lib/playwright/video.rb +54 -0
  151. data/lib/playwright/waiter.rb +166 -0
  152. data/lib/playwright/web_socket_client.rb +167 -0
  153. data/lib/playwright/web_socket_transport.rb +116 -0
  154. data/lib/playwright.rb +188 -11
  155. data/lib/playwright_api/android.rb +46 -11
  156. data/lib/playwright_api/android_device.rb +182 -31
  157. data/lib/playwright_api/android_input.rb +22 -13
  158. data/lib/playwright_api/android_socket.rb +18 -0
  159. data/lib/playwright_api/android_web_view.rb +24 -0
  160. data/lib/playwright_api/api_request.rb +26 -0
  161. data/lib/playwright_api/api_request_context.rb +311 -0
  162. data/lib/playwright_api/api_response.rb +92 -0
  163. data/lib/playwright_api/browser.rb +116 -103
  164. data/lib/playwright_api/browser_context.rb +290 -389
  165. data/lib/playwright_api/browser_type.rb +96 -118
  166. data/lib/playwright_api/cdp_session.rb +36 -39
  167. data/lib/playwright_api/clock.rb +121 -0
  168. data/lib/playwright_api/console_message.rb +35 -19
  169. data/lib/playwright_api/dialog.rb +53 -50
  170. data/lib/playwright_api/download.rb +49 -43
  171. data/lib/playwright_api/element_handle.rb +354 -402
  172. data/lib/playwright_api/file_chooser.rb +15 -18
  173. data/lib/playwright_api/frame.rb +703 -603
  174. data/lib/playwright_api/frame_locator.rb +285 -0
  175. data/lib/playwright_api/js_handle.rb +50 -76
  176. data/lib/playwright_api/keyboard.rb +67 -146
  177. data/lib/playwright_api/locator.rb +1304 -0
  178. data/lib/playwright_api/locator_assertions.rb +704 -0
  179. data/lib/playwright_api/mouse.rb +23 -29
  180. data/lib/playwright_api/page.rb +1196 -1176
  181. data/lib/playwright_api/page_assertions.rb +60 -0
  182. data/lib/playwright_api/playwright.rb +54 -122
  183. data/lib/playwright_api/request.rb +112 -74
  184. data/lib/playwright_api/response.rb +92 -20
  185. data/lib/playwright_api/route.rb +152 -62
  186. data/lib/playwright_api/selectors.rb +47 -61
  187. data/lib/playwright_api/touchscreen.rb +8 -2
  188. data/lib/playwright_api/tracing.rb +128 -0
  189. data/lib/playwright_api/web_socket.rb +43 -5
  190. data/lib/playwright_api/worker.rb +74 -34
  191. data/playwright.gemspec +14 -9
  192. data/sig/playwright.rbs +658 -0
  193. metadata +216 -50
  194. data/docs/api_coverage.md +0 -354
  195. data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
  196. data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
  197. data/lib/playwright/channel_owners/console_message.rb +0 -21
  198. data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
  199. data/lib/playwright/channel_owners/selectors.rb +0 -4
  200. data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
  201. data/lib/playwright/input_type.rb +0 -19
  202. data/lib/playwright/input_types/mouse.rb +0 -4
  203. data/lib/playwright/input_types/touchscreen.rb +0 -4
  204. data/lib/playwright/javascript/function.rb +0 -67
  205. data/lib/playwright/wait_helper.rb +0 -73
  206. data/lib/playwright_api/accessibility.rb +0 -93
  207. data/lib/playwright_api/binding_call.rb +0 -23
  208. data/lib/playwright_api/chromium_browser_context.rb +0 -57
  209. data/lib/playwright_api/video.rb +0 -24
@@ -1,20 +1,234 @@
1
1
  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
- include Utils::Errors::SafeCloseError
5
- attr_writer :browser, :owner_page, :options
4
+ include Utils::PrepareBrowserContextOptions
6
5
 
7
- def after_initialize
6
+ attr_accessor :browser
7
+ attr_writer :owner_page, :options
8
+ attr_reader :clock, :tracing, :request
9
+
10
+ private def after_initialize
11
+ @options = @initializer['options']
8
12
  @pages = Set.new
13
+ @routes = []
14
+ @bindings = {}
15
+ @timeout_settings = TimeoutSettings.new
16
+ @service_workers = Set.new
17
+ @owner_page = nil
18
+
19
+ @tracing = ChannelOwners::Tracing.from(@initializer['tracing'])
20
+ @request = ChannelOwners::APIRequestContext.from(@initializer['requestContext'])
21
+ @request.send(:_update_timeout_settings, @timeout_settings)
22
+ @clock = ClockImpl.new(self)
23
+ @har_recorders = {}
9
24
 
25
+ @channel.on('bindingCall', ->(params) { on_binding(ChannelOwners::BindingCall.from(params['binding'])) })
10
26
  @channel.once('close', ->(_) { on_close })
11
- @channel.on('page', ->(hash) { on_page(ChannelOwners::Page.from(hash['page']) )})
27
+ @channel.on('page', ->(params) { on_page(ChannelOwners::Page.from(params['page']) )})
28
+ @channel.on('route', ->(params) { on_route(ChannelOwners::Route.from(params['route'])) })
29
+ @channel.on('serviceWorker', ->(params) {
30
+ on_service_worker(ChannelOwners::Worker.from(params['worker']))
31
+ })
32
+ @channel.on('console', ->(params) {
33
+ on_console_message(ConsoleMessageImpl.new(params, ChannelOwners::Page.from_nullable(params['page']), ChannelOwners::Worker.from_nullable(params['worker'])))
34
+ })
35
+ @channel.on('pageError', ->(params) {
36
+ on_page_error(
37
+ Error.parse(params['error']['error']),
38
+ ChannelOwners::Page.from_nullable(params['page']),
39
+ )
40
+ })
41
+ @channel.on('dialog', ->(params) {
42
+ on_dialog(ChannelOwners::Dialog.from(params['dialog']))
43
+ })
44
+ @channel.on('request', ->(params) {
45
+ on_request(
46
+ ChannelOwners::Request.from(params['request']),
47
+ ChannelOwners::Page.from_nullable(params['page']),
48
+ )
49
+ })
50
+ @channel.on('requestFailed', ->(params) {
51
+ on_request_failed(
52
+ ChannelOwners::Request.from(params['request']),
53
+ params['responseEndTiming'],
54
+ params['failureText'],
55
+ ChannelOwners::Page.from_nullable(params['page']),
56
+ )
57
+ })
58
+ @channel.on('requestFinished', method(:on_request_finished))
59
+ @channel.on('response', ->(params) {
60
+ on_response(
61
+ ChannelOwners::Response.from(params['response']),
62
+ ChannelOwners::Page.from_nullable(params['page']),
63
+ )
64
+ })
65
+
66
+ @closed_promise = Concurrent::Promises.resolvable_future
67
+ @close_reason = nil
68
+ set_event_to_subscription_mapping({
69
+ Events::BrowserContext::Console => 'console',
70
+ Events::BrowserContext::Dialog => 'dialog',
71
+ Events::BrowserContext::Request => "request",
72
+ Events::BrowserContext::Response => "response",
73
+ Events::BrowserContext::RequestFinished => "requestFinished",
74
+ Events::BrowserContext::RequestFailed => "requestFailed",
75
+ })
76
+
77
+ @close_was_called = false
78
+ end
79
+
80
+ private def initialize_har_from_options(record_har_path:, record_har_content:, record_har_omit_content:, record_har_url_filter:, record_har_mode:)
81
+ return unless record_har_path
82
+
83
+ default_policy = record_har_path.end_with?('.zip') ? 'attach' : 'embed'
84
+ content_policy = record_har_content || (record_har_omit_content ? 'omit' : default_policy)
85
+ record_into_har(record_har_path, nil,
86
+ url: record_har_url_filter,
87
+ update_content: content_policy,
88
+ update_mode: record_har_mode || 'full',
89
+ )
90
+ end
91
+
92
+ private def update_options(context_options:, browser_options:)
93
+ @options = context_options
94
+ @tracing.send(:update_traces_dir, browser_options[:tracesDir])
12
95
  end
13
96
 
14
97
  private def on_page(page)
15
- page.send(:update_browser_context, self)
16
98
  @pages << page
17
99
  emit(Events::BrowserContext::Page, page)
100
+ page.send(:emit_popup_event_from_browser_context)
101
+ end
102
+
103
+ private def on_route(route)
104
+ route.send(:update_context, self)
105
+
106
+ # It is not desired to use PlaywrightApi.wrap directly.
107
+ # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
108
+ # Just a workaround...
109
+ Concurrent::Promises.future(PlaywrightApi.wrap(route)) do |wrapped_route|
110
+ handled = @routes.any? do |handler_entry|
111
+ next false unless handler_entry.match?(route.request.url)
112
+
113
+ promise = Concurrent::Promises.resolvable_future
114
+ route.send(:set_handling_future, promise)
115
+
116
+ promise_handled = Concurrent::Promises.zip(
117
+ promise,
118
+ handler_entry.async_handle(wrapped_route)
119
+ ).value!.first
120
+
121
+ promise_handled
122
+ end
123
+
124
+ @routes.reject!(&:expired?)
125
+ if @routes.count == 0
126
+ async_update_interception_patterns
127
+ end
128
+
129
+ unless handled
130
+ route.send(:async_continue_route, internal: true).rescue do |err|
131
+ puts err, err.backtrace
132
+ end
133
+ end
134
+ end.rescue do |err|
135
+ puts err, err.backtrace
136
+ end
137
+ end
138
+
139
+ private def on_binding(binding_call)
140
+ func = @bindings[binding_call.name]
141
+ if func
142
+ binding_call.call_async(func)
143
+ end
144
+ end
145
+
146
+ private def on_request_failed(request, response_end_timing, failure_text, page)
147
+ request.send(:update_failure_text, failure_text)
148
+ request.send(:update_response_end_timing, response_end_timing)
149
+ emit(Events::BrowserContext::RequestFailed, request)
150
+ page&.emit(Events::Page::RequestFailed, request)
151
+ end
152
+
153
+ private def on_request_finished(params)
154
+ request = ChannelOwners::Request.from(params['request'])
155
+ response = ChannelOwners::Response.from_nullable(params['response'])
156
+ page = ChannelOwners::Page.from_nullable(params['page'])
157
+
158
+ request.send(:update_response_end_timing, params['responseEndTiming'])
159
+ emit(Events::BrowserContext::RequestFinished, request)
160
+ page&.emit(Events::Page::RequestFinished, request)
161
+ response&.send(:mark_as_finished)
162
+ end
163
+
164
+ private def on_console_message(message)
165
+ if (worker = message.worker)
166
+ worker.emit(Events::Worker::Console, message)
167
+ end
168
+ if (page = message.page)
169
+ page.emit(Events::Page::Console, message)
170
+ end
171
+ emit(Events::BrowserContext::Console, message)
172
+ end
173
+
174
+ private def on_dialog(dialog)
175
+ consumed_by_context = emit(Events::BrowserContext::Dialog, dialog)
176
+ if (page = dialog.page)
177
+ consumed_by_page = page.emit(Events::Page::Dialog, dialog)
178
+ end
179
+
180
+ if !consumed_by_context && !consumed_by_page
181
+ if dialog.type == 'beforeunload'
182
+ dialog.accept_async
183
+ else
184
+ dialog.dismiss
185
+ end
186
+ end
187
+ end
188
+
189
+ private def on_page_error(error, page)
190
+ emit(Events::BrowserContext::WebError, WebError.new(error, page))
191
+ if page
192
+ page.emit(Events::Page::PageError, error)
193
+ end
194
+ end
195
+
196
+ private def on_request(request, page)
197
+ emit(Events::BrowserContext::Request, request)
198
+ page&.emit(Events::Page::Request, request)
199
+ end
200
+
201
+ private def on_response(response, page)
202
+ emit(Events::BrowserContext::Response, response)
203
+ page&.emit(Events::Page::Response, response)
204
+ end
205
+
206
+ private def on_service_worker(worker)
207
+ worker.context = self
208
+ @service_workers << worker
209
+ emit(Events::BrowserContext::ServiceWorker, worker)
210
+ end
211
+
212
+ def background_pages
213
+ puts '[WARNING] BrowserContext#background_pages is deprecated. Returns an empty list.'
214
+ []
215
+ end
216
+
217
+ def service_workers
218
+ @service_workers.to_a
219
+ end
220
+
221
+ def new_cdp_session(page)
222
+ resp = @channel.send_message_to_server('newCDPSession', page: page.channel)
223
+ ChannelOwners::CDPSession.from(resp)
224
+ end
225
+
226
+ def set_default_navigation_timeout(timeout)
227
+ @timeout_settings.default_navigation_timeout = timeout
228
+ end
229
+
230
+ def set_default_timeout(timeout)
231
+ @timeout_settings.default_timeout = timeout
18
232
  end
19
233
 
20
234
  def pages
@@ -22,25 +236,301 @@ module Playwright
22
236
  end
23
237
 
24
238
  # @returns [Playwright::Page]
25
- def new_page
239
+ def new_page(&block)
26
240
  raise 'Please use browser.new_context' if @owner_page
27
241
  resp = @channel.send_message_to_server('newPage')
28
- ChannelOwners::Page.from(resp)
242
+ page = ChannelOwners::Page.from(resp)
243
+ return page unless block
244
+
245
+ begin
246
+ block.call(page)
247
+ ensure
248
+ page.close
249
+ end
250
+ end
251
+
252
+ def cookies(urls: nil)
253
+ target_urls =
254
+ if urls.nil?
255
+ []
256
+ elsif urls.is_a?(Enumerable)
257
+ urls
258
+ else
259
+ [urls]
260
+ end
261
+ @channel.send_message_to_server('cookies', urls: target_urls)
262
+ end
263
+
264
+ def add_cookies(cookies)
265
+ @channel.send_message_to_server('addCookies', cookies: cookies)
266
+ end
267
+
268
+ def clear_cookies(domain: nil, name: nil, path: nil)
269
+ params = {}
270
+
271
+ case name
272
+ when String
273
+ params[:name] = name
274
+ when Regexp
275
+ regex = JavaScript::Regex.new(name)
276
+ params[:nameRegexSource] = regex.source
277
+ params[:nameRegexFlags] = regex.flag
278
+ end
279
+
280
+ case domain
281
+ when String
282
+ params[:domain] = domain
283
+ when Regexp
284
+ regex = JavaScript::Regex.new(domain)
285
+ params[:domainRegexSource] = regex.source
286
+ params[:domainRegexFlags] = regex.flag
287
+ end
288
+
289
+ case path
290
+ when String
291
+ params[:path] = path
292
+ when Regexp
293
+ regex = JavaScript::Regex.new(path)
294
+ params[:pathRegexSource] = regex.source
295
+ params[:pathRegexFlags] = regex.flag
296
+ end
297
+
298
+ @channel.send_message_to_server('clearCookies', params)
299
+ end
300
+
301
+ def grant_permissions(permissions, origin: nil)
302
+ params = {
303
+ permissions: permissions,
304
+ origin: origin,
305
+ }.compact
306
+ @channel.send_message_to_server('grantPermissions', params)
307
+ end
308
+
309
+ def clear_permissions
310
+ @channel.send_message_to_server('clearPermissions')
311
+ end
312
+
313
+ def set_geolocation(geolocation)
314
+ @channel.send_message_to_server('setGeolocation', geolocation: geolocation)
315
+ end
316
+
317
+ def set_extra_http_headers(headers)
318
+ @channel.send_message_to_server('setExtraHTTPHeaders',
319
+ headers: HttpHeaders.new(headers).as_serialized)
320
+ end
321
+
322
+ def set_offline(offline)
323
+ @channel.send_message_to_server('setOffline', offline: offline)
324
+ end
325
+
326
+ def add_init_script(path: nil, script: nil)
327
+ source =
328
+ if path
329
+ JavaScript::SourceUrl.new(File.read(path), path).to_s
330
+ elsif script
331
+ script
332
+ else
333
+ raise ArgumentError.new('Either path or script parameter must be specified')
334
+ end
335
+
336
+ @channel.send_message_to_server('addInitScript', source: source)
337
+ nil
338
+ end
339
+
340
+ def expose_binding(name, callback, handle: nil)
341
+ if @pages.any? { |page| page.send(:has_bindings?, name) }
342
+ raise ArgumentError.new("Function \"#{name}\" has been already registered in one of the pages")
343
+ end
344
+ if @bindings.key?(name)
345
+ raise ArgumentError.new("Function \"#{name}\" has been already registered")
346
+ end
347
+ params = {
348
+ name: name,
349
+ needsHandle: handle,
350
+ }.compact
351
+ @bindings[name] = callback
352
+ @channel.send_message_to_server('exposeBinding', params)
353
+ end
354
+
355
+ def expose_function(name, callback)
356
+ expose_binding(name, ->(_source, *args) { callback.call(*args) }, )
357
+ end
358
+
359
+ def route(url, handler, times: nil)
360
+ entry = RouteHandler.new(url, base_url, handler, times)
361
+ @routes.unshift(entry)
362
+ update_interception_patterns
363
+ end
364
+
365
+ def unroute_all(behavior: nil)
366
+ @routes.clear
367
+ update_interception_patterns
368
+ end
369
+
370
+ def unroute(url, handler: nil)
371
+ @routes.reject! do |handler_entry|
372
+ handler_entry.same_value?(url: url, handler: handler)
373
+ end
374
+ update_interception_patterns
375
+ end
376
+
377
+ private def record_into_har(har, page, url:, update_content:, update_mode:)
378
+ options = {
379
+ zip: har.end_with?('.zip'),
380
+ content: update_content || 'attach',
381
+ }
382
+
383
+ if url.is_a?(Regexp)
384
+ regex = ::Playwright::JavaScript::Regex.new(url)
385
+ options[:urlRegexSource] = regex.source
386
+ options[:urlRegexFlags] = regex.flag
387
+ elsif url.is_a?(String)
388
+ options[:urlGlob] = url
389
+ end
390
+
391
+ params = { options: options }
392
+ if page
393
+ params[:page] = page.channel
394
+ end
395
+
396
+ har_id = @channel.send_message_to_server('harStart', params)
397
+ @har_recorders[har_id] = { path: har, content: update_content || 'attach' }
398
+ end
399
+
400
+ def route_from_har(har, notFound: nil, update: nil, updateContent: nil, updateMode: nil, url: nil)
401
+ if update
402
+ record_into_har(har, nil, url: url, update_content: updateContent, update_mode: updateMode)
403
+ return
404
+ end
405
+
406
+ router = HarRouter.create(
407
+ @connection.local_utils,
408
+ har.to_s,
409
+ notFound || "abort",
410
+ url_match: url,
411
+ )
412
+ router.add_context_route(self)
413
+ end
414
+
415
+ private def async_update_interception_patterns
416
+ patterns = RouteHandler.prepare_interception_patterns(@routes)
417
+ @channel.async_send_message_to_server('setNetworkInterceptionPatterns', patterns: patterns)
418
+ end
419
+
420
+ private def update_interception_patterns
421
+ patterns = RouteHandler.prepare_interception_patterns(@routes)
422
+ @channel.send_message_to_server('setNetworkInterceptionPatterns', patterns: patterns)
423
+ end
424
+
425
+ def expect_event(event, predicate: nil, timeout: nil, &block)
426
+ waiter = Waiter.new(self, wait_name: "browser_context.expect_event(#{event})")
427
+ timeout_value = timeout || @timeout_settings.timeout
428
+ waiter.reject_on_timeout(timeout_value, "Timeout #{timeout}ms exceeded while waiting for event \"#{event}\"")
429
+ unless event == Events::BrowserContext::Close
430
+ waiter.reject_on_event(self, Events::BrowserContext::Close, TargetClosedError.new)
431
+ end
432
+ waiter.wait_for_event(self, event, predicate: predicate)
433
+
434
+ block&.call
435
+
436
+ waiter.result.value!
29
437
  end
30
438
 
31
439
  private def on_close
32
- @closed_or_closing = true
33
- @browser&.send(:remove_context, self)
440
+ if @browser
441
+ @browser.send(:remove_context, self)
442
+ @browser.browser_type.send(:playwright_selectors_browser_contexts).delete(self)
443
+ end
34
444
  emit(Events::BrowserContext::Close)
445
+ @closed_promise.fulfill(true)
35
446
  end
36
447
 
37
- def close
38
- return if @closed_or_closing
39
- @closed_or_closing = true
40
- @channel.send_message_to_server('close')
448
+ def close(reason: nil)
449
+ return if @close_was_called
450
+ @close_was_called = true
451
+ @close_reason = reason
452
+ @request.dispose(reason: reason)
453
+ inner_close
454
+ @channel.send_message_to_server('close', { reason: reason }.compact)
455
+ @closed_promise.value!
41
456
  nil
42
- rescue => err
43
- raise unless safe_close_error?(err)
457
+ end
458
+
459
+ private def inner_close
460
+ @har_recorders.each do |har_id, params|
461
+ har = ChannelOwners::Artifact.from(@channel.send_message_to_server('harExport', harId: har_id))
462
+ # Server side will compress artifact if content is attach or if file is .zip.
463
+ compressed = params[:content] == "attach" || params[:path].end_with?('.zip')
464
+ need_comppressed = params[:path].end_with?('.zip')
465
+ if compressed && !need_comppressed
466
+ tmp_path = "#{params[:path]}.tmp"
467
+ har.save_as(tmp_path)
468
+ @connection.local_utils.har_unzip(tmp_path, params[:path])
469
+ else
470
+ har.save_as(params[:path])
471
+ end
472
+
473
+ har.delete
474
+ end
475
+ end
476
+
477
+ # REMARK: enable_debug_console is playwright-ruby-client specific method.
478
+ def enable_debug_console!
479
+ # Ruby is not supported in Playwright officially,
480
+ # and causes error:
481
+ #
482
+ # Error:
483
+ # ===============================
484
+ # Unsupported language: 'ruby'
485
+ # ===============================
486
+ #
487
+ # So, launch inspector as Python app.
488
+ # NOTE: This should be used only for Page#pause at this moment.
489
+ @channel.send_message_to_server('enableRecorder', language: :python)
490
+ @debug_console_enabled = true
491
+ end
492
+
493
+ class DebugConsoleNotEnabledError < StandardError
494
+ def initialize
495
+ super('Debug console should be enabled in advance, by calling `browser_context.enable_debug_console!`')
496
+ end
497
+ end
498
+
499
+ def pause
500
+ unless @debug_console_enabled
501
+ raise DebugConsoleNotEnabledError.new
502
+ end
503
+ @channel.send_message_to_server('pause')
504
+ end
505
+
506
+ def storage_state(path: nil, indexedDB: nil)
507
+ @channel.send_message_to_server_result('storageState', { indexedDB: indexedDB }.compact).tap do |result|
508
+ if path
509
+ File.open(path, 'w') do |f|
510
+ f.write(JSON.dump(result))
511
+ end
512
+ end
513
+ end
514
+ end
515
+
516
+ private def effective_close_reason
517
+ @close_reason || @browser&.send(:close_reason)
518
+ end
519
+
520
+ def expect_console_message(predicate: nil, timeout: nil, &block)
521
+ params = {
522
+ predicate: predicate,
523
+ timeout: timeout,
524
+ }.compact
525
+ expect_event(Events::BrowserContext::Console, params, &block)
526
+ end
527
+
528
+ def expect_page(predicate: nil, timeout: nil, &block)
529
+ params = {
530
+ predicate: predicate,
531
+ timeout: timeout,
532
+ }.compact
533
+ expect_event(Events::BrowserContext::Page, params, &block)
44
534
  end
45
535
 
46
536
  # called from Page#on_close with send(:remove_page, page), so keep private
@@ -48,9 +538,93 @@ module Playwright
48
538
  @pages.delete(page)
49
539
  end
50
540
 
541
+ private def remove_service_worker(worker)
542
+ @service_workers.delete(worker)
543
+ end
544
+
51
545
  # called from Page with send(:_timeout_settings), so keep private.
52
546
  private def _timeout_settings
53
547
  @timeout_settings
54
548
  end
549
+
550
+ private def has_record_video_option?
551
+ @options.key?(:recordVideo)
552
+ end
553
+
554
+ private def base_url
555
+ @options[:baseURL]
556
+ end
557
+
558
+ # called from InputFiles
559
+ # @param filepath [string]
560
+ # @return [WritableStream, Array<WritableStream>]
561
+ private def create_temp_files(local_directory, files)
562
+ if local_directory
563
+ params = {
564
+ rootDirName: File.basename(local_directory),
565
+ items: files.map do |filepath|
566
+ {
567
+ name: Pathname.new(filepath).relative_path_from(Pathname.new(local_directory)).to_s,
568
+ lastModifiedMs: File.mtime(filepath).to_i * 1000,
569
+ }
570
+ end
571
+ }
572
+ else
573
+ params = {
574
+ items: files.map do |filepath|
575
+ {
576
+ name: File.basename(filepath),
577
+ lastModifiedMs: File.mtime(filepath).to_i * 1000,
578
+ }
579
+ end
580
+ }
581
+ end
582
+
583
+ result = @channel.send_message_to_server_result('createTempFiles', params)
584
+
585
+ [
586
+ ChannelOwners::WritableStream.from_nullable(result['rootDir']),
587
+ result['writableStreams'].map do |s|
588
+ ChannelOwners::WritableStream.from(s)
589
+ end,
590
+ ]
591
+ end
592
+
593
+ private def clock_fast_forward(ticks_params)
594
+ @channel.send_message_to_server('clockFastForward', ticks_params)
595
+ end
596
+
597
+ private def clock_install(time_params)
598
+ @channel.send_message_to_server('clockInstall', time_params)
599
+ end
600
+
601
+ private def clock_pause_at(time_params)
602
+ @channel.send_message_to_server('clockPauseAt', time_params)
603
+ end
604
+
605
+ private def clock_resume
606
+ @channel.send_message_to_server('clockResume')
607
+ end
608
+
609
+ private def clock_run_for(ticks_params)
610
+ @channel.send_message_to_server('clockRunFor', ticks_params)
611
+ end
612
+
613
+ private def clock_set_fixed_time(time_params)
614
+ @channel.send_message_to_server('clockSetFixedTime', time_params)
615
+ end
616
+
617
+ private def clock_set_system_time(time_params)
618
+ @channel.send_message_to_server('clockSetSystemTime', time_params)
619
+ end
620
+
621
+ private def register_selector_engine(selector_engine)
622
+ @channel.send_message_to_server('registerSelectorEngine', { selectorEngine: selector_engine })
623
+ end
624
+
625
+ private def set_test_id_attribute_name(test_id_attribute_name)
626
+ @channel.send_message_to_server('setTestIdAttributeName', { testIdAttributeName: test_id_attribute_name })
627
+ ::Playwright::LocatorUtils.instance_variable_set(:@test_id_attribute_name, test_id_attribute_name)
628
+ end
55
629
  end
56
630
  end