playwright-ruby-client 1.57.0 → 1.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -7
  3. data/documentation/docs/api/browser_context.md +26 -0
  4. data/documentation/docs/api/browser_type.md +3 -2
  5. data/documentation/docs/api/console_message.md +9 -0
  6. data/documentation/docs/api/frame.md +2 -2
  7. data/documentation/docs/api/frame_locator.md +2 -2
  8. data/documentation/docs/api/locator.md +17 -4
  9. data/documentation/docs/api/locator_assertions.md +1 -1
  10. data/documentation/docs/api/page.md +68 -5
  11. data/documentation/docs/api/request.md +13 -0
  12. data/documentation/docs/api/response.md +9 -0
  13. data/documentation/docs/api/route.md +4 -1
  14. data/documentation/docs/api/tracing.md +1 -0
  15. data/documentation/docs/article/getting_started.md +1 -1
  16. data/documentation/docs/article/guides/inspector.md +1 -1
  17. data/documentation/docs/article/guides/launch_browser.md +5 -5
  18. data/documentation/docs/article/guides/rails_integration.md +3 -3
  19. data/documentation/docs/article/guides/rails_integration_with_null_driver.md +46 -0
  20. data/documentation/docs/article/guides/recording_video.md +2 -2
  21. data/documentation/docs/article/guides/semi_automation.md +1 -1
  22. data/documentation/docs/include/api_coverage.md +16 -0
  23. data/lib/playwright/channel_owners/api_request_context.rb +24 -2
  24. data/lib/playwright/channel_owners/binding_call.rb +37 -3
  25. data/lib/playwright/channel_owners/browser_context.rb +24 -4
  26. data/lib/playwright/channel_owners/browser_type.rb +2 -1
  27. data/lib/playwright/channel_owners/debugger.rb +4 -0
  28. data/lib/playwright/channel_owners/dialog.rb +3 -1
  29. data/lib/playwright/channel_owners/disposable.rb +9 -0
  30. data/lib/playwright/channel_owners/overlay.rb +4 -0
  31. data/lib/playwright/channel_owners/page.rb +58 -32
  32. data/lib/playwright/channel_owners/request.rb +8 -0
  33. data/lib/playwright/channel_owners/response.rb +5 -0
  34. data/lib/playwright/channel_owners/stream.rb +4 -0
  35. data/lib/playwright/channel_owners/tracing.rb +17 -7
  36. data/lib/playwright/channel_owners/web_socket.rb +14 -0
  37. data/lib/playwright/connection.rb +25 -20
  38. data/lib/playwright/console_message_impl.rb +4 -0
  39. data/lib/playwright/disposable.rb +11 -0
  40. data/lib/playwright/har_router.rb +16 -1
  41. data/lib/playwright/javascript/value_serializer.rb +2 -1
  42. data/lib/playwright/locator_assertions_impl.rb +1 -1
  43. data/lib/playwright/locator_impl.rb +19 -3
  44. data/lib/playwright/page_assertions_impl.rb +1 -1
  45. data/lib/playwright/screencast.rb +91 -0
  46. data/lib/playwright/utils.rb +19 -0
  47. data/lib/playwright/version.rb +2 -2
  48. data/lib/playwright/video.rb +6 -4
  49. data/lib/playwright/waiter.rb +24 -6
  50. data/lib/playwright.rb +2 -0
  51. data/lib/playwright_api/android.rb +7 -7
  52. data/lib/playwright_api/android_device.rb +8 -8
  53. data/lib/playwright_api/api_request_context.rb +6 -6
  54. data/lib/playwright_api/browser.rb +18 -6
  55. data/lib/playwright_api/browser_context.rb +32 -6
  56. data/lib/playwright_api/browser_type.rb +13 -12
  57. data/lib/playwright_api/cdp_session.rb +6 -6
  58. data/lib/playwright_api/console_message.rb +6 -0
  59. data/lib/playwright_api/dialog.rb +6 -6
  60. data/lib/playwright_api/element_handle.rb +6 -6
  61. data/lib/playwright_api/frame.rb +8 -8
  62. data/lib/playwright_api/frame_locator.rb +2 -2
  63. data/lib/playwright_api/js_handle.rb +6 -6
  64. data/lib/playwright_api/locator.rb +19 -9
  65. data/lib/playwright_api/locator_assertions.rb +1 -1
  66. data/lib/playwright_api/page.rb +72 -23
  67. data/lib/playwright_api/playwright.rb +6 -6
  68. data/lib/playwright_api/request.rb +28 -6
  69. data/lib/playwright_api/response.rb +12 -6
  70. data/lib/playwright_api/route.rb +10 -7
  71. data/lib/playwright_api/tracing.rb +8 -7
  72. data/lib/playwright_api/web_socket.rb +6 -6
  73. data/lib/playwright_api/worker.rb +6 -6
  74. data/playwright.gemspec +9 -1
  75. data/sig/playwright.rbs +30 -17
  76. metadata +35 -2
@@ -17,6 +17,8 @@
17
17
  * redirected_to
18
18
  * resource_type
19
19
  * response
20
+ * existing_response
21
+ * ~~service_worker~~
20
22
  * sizes
21
23
  * timing
22
24
  * url
@@ -32,6 +34,7 @@
32
34
  * headers_array
33
35
  * header_value
34
36
  * header_values
37
+ * http_version
35
38
  * json
36
39
  * ok
37
40
  * request
@@ -227,6 +230,7 @@
227
230
  * location
228
231
  * page
229
232
  * text
233
+ * timestamp
230
234
  * type
231
235
  * worker
232
236
 
@@ -256,6 +260,7 @@
256
260
  * add_script_tag
257
261
  * add_style_tag
258
262
  * bring_to_front
263
+ * cancel_pick_locator
259
264
  * check
260
265
  * click
261
266
  * close
@@ -299,6 +304,8 @@
299
304
  * enabled?
300
305
  * hidden?
301
306
  * visible?
307
+ * clear_console_messages
308
+ * clear_page_errors
302
309
  * console_messages
303
310
  * page_errors
304
311
  * locator
@@ -306,6 +313,7 @@
306
313
  * opener
307
314
  * pause
308
315
  * pdf
316
+ * pick_locator
309
317
  * press
310
318
  * query_selector
311
319
  * query_selector_all
@@ -325,6 +333,7 @@
325
333
  * set_extra_http_headers
326
334
  * set_input_files
327
335
  * set_viewport_size
336
+ * aria_snapshot
328
337
  * tap_point
329
338
  * text_content
330
339
  * title
@@ -357,6 +366,7 @@
357
366
  * keyboard
358
367
  * mouse
359
368
  * request
369
+ * screencast
360
370
  * touchscreen
361
371
 
362
372
  ## BrowserContext
@@ -372,6 +382,7 @@
372
382
  * expose_binding
373
383
  * expose_function
374
384
  * grant_permissions
385
+ * closed?
375
386
  * new_cdp_session
376
387
  * new_page
377
388
  * pages
@@ -385,6 +396,7 @@
385
396
  * set_geolocation
386
397
  * set_offline
387
398
  * storage_state
399
+ * set_storage_state
388
400
  * unroute_all
389
401
  * unroute
390
402
  * expect_console_message
@@ -392,6 +404,7 @@
392
404
  * expect_page
393
405
  * ~~wait_for_event~~
394
406
  * clock
407
+ * ~~debugger~~
395
408
  * request
396
409
  * tracing
397
410
 
@@ -409,8 +422,10 @@
409
422
  * new_browser_cdp_session
410
423
  * new_context
411
424
  * new_page
425
+ * ~~bind~~
412
426
  * start_tracing
413
427
  * stop_tracing
428
+ * ~~unbind~~
414
429
  * version
415
430
 
416
431
  ## BrowserType
@@ -491,6 +506,7 @@
491
506
  * visible?
492
507
  * last
493
508
  * locator
509
+ * normalize
494
510
  * nth
495
511
  * or
496
512
  * page
@@ -1,5 +1,5 @@
1
1
  require 'base64'
2
- require 'cgi'
2
+ require 'cgi/escape'
3
3
 
4
4
  module Playwright
5
5
  define_channel_owner :APIRequestContext do
@@ -197,7 +197,7 @@ module Playwright
197
197
  end
198
198
 
199
199
  private def query_string_to_array(query_string)
200
- params = CGI.parse(query_string)
200
+ params = cgi_parse(query_string)
201
201
 
202
202
  params.map do |key, values|
203
203
  values.map do |value|
@@ -206,6 +206,28 @@ module Playwright
206
206
  end.flatten
207
207
  end
208
208
 
209
+ # https://bugs.ruby-lang.org/issues/21258
210
+ # CGI.parse is defined in 'cgi' library.
211
+ # But it produces an error in Ruby 2.4 environment: undefined method `delete_prefix' for "CONTENT_LENGTH":String
212
+ # So we implement our own version of CGI.parse here.
213
+ private def cgi_parse(query)
214
+ # https://github.com/ruby/cgi/blob/master/lib/cgi/core.rb#L396
215
+ params = {}
216
+
217
+ query.split(/[&;]/).each do |pairs|
218
+ key, value = pairs.split('=',2).map do |v|
219
+ CGI.unescape(v)
220
+ end
221
+
222
+ next unless key
223
+ params[key] ||= []
224
+ next unless value
225
+ params[key] << value
226
+ end
227
+
228
+ params
229
+ end
230
+
209
231
  private def object_to_array(hash)
210
232
  hash&.map do |key, value|
211
233
  { name: key, value: value.to_s }
@@ -1,11 +1,45 @@
1
1
  module Playwright
2
2
  define_channel_owner :BindingCall do
3
+ class << self
4
+ def call_queue
5
+ @call_queue ||= Queue.new
6
+ end
7
+
8
+ def worker_mutex
9
+ @worker_mutex ||= Mutex.new
10
+ end
11
+
12
+ def ensure_worker
13
+ worker_mutex.synchronize do
14
+ return if @worker&.alive?
15
+
16
+ @worker = Thread.new do
17
+ loop do
18
+ job = call_queue.pop
19
+ begin
20
+ job.call
21
+ rescue => err
22
+ $stderr.write("BindingCall worker error: #{err.class}: #{err.message}\n")
23
+ err.backtrace&.each { |line| $stderr.write("#{line}\n") }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
3
31
  def name
4
32
  @initializer['name']
5
33
  end
6
34
 
7
35
  def call_async(callback)
8
- Thread.new(callback) { call(callback) }
36
+ # Binding callbacks can be fired concurrently from multiple threads.
37
+ # Enqueue and execute them on a single worker thread so we:
38
+ # - preserve the delivery order of binding calls
39
+ # - avoid spawning a thread per call (bursty timers create many callbacks)
40
+ # - keep the protocol dispatch thread unblocked
41
+ self.class.ensure_worker
42
+ self.class.call_queue << -> { call(callback) }
9
43
  end
10
44
 
11
45
  # @param callback [Proc]
@@ -31,9 +65,9 @@ module Playwright
31
65
 
32
66
  begin
33
67
  result = PlaywrightApi.unwrap(callback.call(source, *args))
34
- @channel.send_message_to_server('resolve', result: JavaScript::ValueSerializer.new(result).serialize)
68
+ @channel.async_send_message_to_server('resolve', result: JavaScript::ValueSerializer.new(result).serialize)
35
69
  rescue => err
36
- @channel.send_message_to_server('reject', error: { error: { message: err.message, name: 'Error' }})
70
+ @channel.async_send_message_to_server('reject', error: { error: { message: err.message, name: 'Error' }})
37
71
  end
38
72
  end
39
73
  end
@@ -333,8 +333,8 @@ module Playwright
333
333
  raise ArgumentError.new('Either path or script parameter must be specified')
334
334
  end
335
335
 
336
- @channel.send_message_to_server('addInitScript', source: source)
337
- nil
336
+ result = @channel.send_message_to_server_result('addInitScript', source: source)
337
+ ChannelOwners::Disposable.from(result['disposable'])
338
338
  end
339
339
 
340
340
  def expose_binding(name, callback, handle: nil)
@@ -349,17 +349,19 @@ module Playwright
349
349
  needsHandle: handle,
350
350
  }.compact
351
351
  @bindings[name] = callback
352
- @channel.send_message_to_server('exposeBinding', params)
352
+ result = @channel.send_message_to_server_result('exposeBinding', params)
353
+ ChannelOwners::Disposable.from(result['disposable'])
353
354
  end
354
355
 
355
356
  def expose_function(name, callback)
356
- expose_binding(name, ->(_source, *args) { callback.call(*args) }, )
357
+ expose_binding(name, ->(_source, *args) { callback.call(*args) })
357
358
  end
358
359
 
359
360
  def route(url, handler, times: nil)
360
361
  entry = RouteHandler.new(url, base_url, handler, times)
361
362
  @routes.unshift(entry)
362
363
  update_interception_patterns
364
+ DisposableStub.new { unroute(url, handler: handler) }
363
365
  end
364
366
 
365
367
  def unroute_all(behavior: nil)
@@ -547,6 +549,24 @@ module Playwright
547
549
  @timeout_settings
548
550
  end
549
551
 
552
+ def set_storage_state(storageState)
553
+ payload = case storageState
554
+ when String
555
+ begin
556
+ JSON.parse(File.read(storageState))
557
+ rescue => e
558
+ raise ::Playwright::Error.new(message: "Failed to read storage state from '#{storageState}': #{e.message}")
559
+ end
560
+ else
561
+ storageState
562
+ end
563
+ @channel.send_message_to_server('setStorageState', storageState: payload)
564
+ end
565
+
566
+ def closed?
567
+ @close_was_called
568
+ end
569
+
550
570
  private def has_record_video_option?
551
571
  @options.key?(:recordVideo)
552
572
  end
@@ -60,12 +60,13 @@ module Playwright
60
60
  end
61
61
  end
62
62
 
63
- def connect_over_cdp(endpointURL, headers: nil, slowMo: nil, timeout: nil, &block)
63
+ def connect_over_cdp(endpointURL, headers: nil, isLocal: nil, slowMo: nil, timeout: nil, &block)
64
64
  raise 'Connecting over CDP is only supported in Chromium.' unless name == 'chromium'
65
65
 
66
66
  params = {
67
67
  endpointURL: endpointURL,
68
68
  headers: headers,
69
+ isLocal: isLocal,
69
70
  slowMo: slowMo,
70
71
  timeout: @timeout_settings.timeout(timeout),
71
72
  }.compact
@@ -0,0 +1,4 @@
1
+ module Playwright
2
+ define_channel_owner :Debugger do
3
+ end
4
+ end
@@ -26,7 +26,9 @@ module Playwright
26
26
  end
27
27
 
28
28
  def dismiss
29
- @channel.async_send_message_to_server('dismiss')
29
+ @channel.send_message_to_server('dismiss')
30
+ rescue TargetClosedError
31
+ # Swallow target-closed errors for beforeunload dialogs.
30
32
  end
31
33
  end
32
34
  end
@@ -0,0 +1,9 @@
1
+ module Playwright
2
+ define_channel_owner :Disposable do
3
+ def dispose
4
+ @channel.send_message_to_server('dispose')
5
+ rescue TargetClosedError
6
+ nil
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module Playwright
2
+ define_channel_owner :Overlay do
3
+ end
4
+ end
@@ -56,7 +56,9 @@ module Playwright
56
56
  emit(Events::Page::PageError, Error.parse(params['error']['error']))
57
57
  })
58
58
  @channel.on('route', ->(params) { on_route(ChannelOwners::Route.from(params['route'])) })
59
- @channel.on('video', method(:on_video))
59
+ if @initializer['video']
60
+ @video_artifact = ChannelOwners::Artifact.from(@initializer['video'])
61
+ end
60
62
  @channel.on('viewportSizeChanged', method(:on_viewport_size_changed))
61
63
  @channel.on('webSocket', ->(params) {
62
64
  emit(Events::Page::WebSocket, ChannelOwners::WebSocket.from(params['webSocket']))
@@ -98,7 +100,7 @@ module Playwright
98
100
 
99
101
  private def on_route(route)
100
102
  route.send(:update_context, self)
101
-
103
+ return if @close_was_called
102
104
  # It is not desired to use PlaywrightApi.wrap directly.
103
105
  # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
104
106
  # Just a workaround...
@@ -171,11 +173,6 @@ module Playwright
171
173
  emit(Events::Page::Download, download)
172
174
  end
173
175
 
174
- private def on_video(params)
175
- artifact = ChannelOwners::Artifact.from(params['artifact'])
176
- video.send(:set_artifact, artifact)
177
- end
178
-
179
176
  private def on_viewport_size_changed(params)
180
177
  @viewport_size = {
181
178
  width: params['viewportSize']['width'],
@@ -307,8 +304,9 @@ module Playwright
307
304
  end
308
305
 
309
306
  def expose_function(name, callback)
310
- @channel.send_message_to_server('exposeBinding', name: name)
307
+ result = @channel.send_message_to_server_result('exposeBinding', name: name)
311
308
  @bindings[name] = ->(_source, *args) { callback.call(*args) }
309
+ ChannelOwners::Disposable.from(result['disposable'])
312
310
  end
313
311
 
314
312
  def expose_binding(name, callback, handle: nil)
@@ -316,8 +314,9 @@ module Playwright
316
314
  name: name,
317
315
  needsHandle: handle,
318
316
  }.compact
319
- @channel.send_message_to_server('exposeBinding', params)
317
+ result = @channel.send_message_to_server_result('exposeBinding', params)
320
318
  @bindings[name] = callback
319
+ ChannelOwners::Disposable.from(result['disposable'])
321
320
  end
322
321
 
323
322
  def set_extra_http_headers(headers)
@@ -412,14 +411,15 @@ module Playwright
412
411
  raise ArgumentError.new('Either path or script parameter must be specified')
413
412
  end
414
413
 
415
- @channel.send_message_to_server('addInitScript', source: source)
416
- nil
414
+ result = @channel.send_message_to_server_result('addInitScript', source: source)
415
+ ChannelOwners::Disposable.from(result['disposable'])
417
416
  end
418
417
 
419
418
  def route(url, handler, times: nil)
420
419
  entry = RouteHandler.new(url, @browser_context.send(:base_url), handler, times)
421
420
  @routes.unshift(entry)
422
421
  update_interception_patterns
422
+ DisposableStub.new { unroute(url, handler: handler) }
423
423
  end
424
424
 
425
425
  def unroute_all(behavior: nil)
@@ -508,6 +508,9 @@ module Playwright
508
508
 
509
509
  def close(runBeforeUnload: nil, reason: nil)
510
510
  @close_reason = reason
511
+ unless runBeforeUnload
512
+ @close_was_called = true
513
+ end
511
514
  if @owned_context
512
515
  @owned_context.close
513
516
  else
@@ -643,20 +646,49 @@ module Playwright
643
646
  timeout: timeout)
644
647
  end
645
648
 
646
- def console_messages
647
- messages = @channel.send_message_to_server('consoleMessages')
649
+ def console_messages(filter: nil)
650
+ params = {}
651
+ params[:filter] = filter if filter
652
+ messages = @channel.send_message_to_server('consoleMessages', params)
648
653
  messages.map do |message|
649
654
  ConsoleMessageImpl.new(message, self, nil)
650
655
  end
651
656
  end
652
657
 
653
- def page_errors
654
- errors = @channel.send_message_to_server('pageErrors')
658
+ def page_errors(filter: nil)
659
+ params = {}
660
+ params[:filter] = filter if filter
661
+ errors = @channel.send_message_to_server('pageErrors', params)
655
662
  errors.map do |error|
656
663
  Error.parse(error['error'])
657
664
  end
658
665
  end
659
666
 
667
+ def clear_console_messages
668
+ @channel.send_message_to_server('clearConsoleMessages')
669
+ end
670
+
671
+ def clear_page_errors
672
+ @channel.send_message_to_server('clearPageErrors')
673
+ end
674
+
675
+ def cancel_pick_locator
676
+ @channel.send_message_to_server('cancelPickLocator')
677
+ end
678
+
679
+ def aria_snapshot(depth: nil, mode: nil, timeout: nil, _track: nil)
680
+ params = { selector: 'body' }
681
+ params[:timeout] = @timeout_settings.timeout(timeout)
682
+ params[:depth] = depth if depth
683
+ params[:mode] = mode if mode
684
+ if _track
685
+ params.delete(:selector)
686
+ params[:track] = _track
687
+ end
688
+ result = @main_frame.channel.send_message_to_server_result('ariaSnapshot', params)
689
+ result['snapshot']
690
+ end
691
+
660
692
  def locator(
661
693
  selector,
662
694
  has: nil,
@@ -901,28 +933,22 @@ module Playwright
901
933
  decoded_binary
902
934
  end
903
935
 
936
+ def screencast
937
+ @screencast ||= Screencast.new(self)
938
+ end
939
+
904
940
  def video
905
941
  return nil unless @browser_context.send(:has_record_video_option?)
906
- @video ||= Video.new(self)
942
+ @video ||= Video.new(self, artifact: @video_artifact)
907
943
  end
908
944
 
909
- def snapshot_for_ai(timeout: nil, mode: nil, track: nil)
910
- option_mode = mode || 'full'
911
- unless ['full', 'incremental'].include?(option_mode)
912
- raise ArgumentError.new("mode must be either 'full' or 'incremental'")
913
- end
945
+ def pick_locator
946
+ result = @channel.send_message_to_server_result('pickLocator', {})
947
+ @main_frame.locator(result['selector'])
948
+ end
914
949
 
915
- options = {
916
- timeout: @timeout_settings.timeout(timeout),
917
- mode: option_mode,
918
- }
919
- options[:track] = track if track
920
- result = @channel.send_message_to_server_result('snapshotForAI', options)
921
- if option_mode == 'full'
922
- result['full']
923
- elsif option_mode == 'incremental'
924
- result['incremental']
925
- end
950
+ def snapshot_for_ai(timeout: nil, depth: nil, _track: nil)
951
+ aria_snapshot(mode: 'ai', timeout: timeout, depth: depth, _track: _track)
926
952
  end
927
953
 
928
954
  def start_js_coverage(resetOnNavigation: nil, reportAnonymousScripts: nil)
@@ -81,6 +81,10 @@ module Playwright
81
81
  end
82
82
  end
83
83
 
84
+ def existing_response
85
+ @response
86
+ end
87
+
84
88
  def response
85
89
  resp = @channel.send_message_to_server('response')
86
90
  ChannelOwners::Response.from_nullable(resp)
@@ -162,6 +166,10 @@ module Playwright
162
166
  res.send(:sizes)
163
167
  end
164
168
 
169
+ private def update_response(response)
170
+ @response = response
171
+ end
172
+
165
173
  private def update_redirected_to(request)
166
174
  @redirected_to = request
167
175
  end
@@ -7,6 +7,7 @@ module Playwright
7
7
  private def after_initialize
8
8
  @provisional_headers = RawHeaders.new(@initializer['headers'])
9
9
  @request = ChannelOwners::Request.from(@initializer['request'])
10
+ @request.send(:update_response, self)
10
11
  timing = @initializer['timing']
11
12
  @request.send(:update_timings,
12
13
  start_time: timing["startTime"],
@@ -74,6 +75,10 @@ module Playwright
74
75
  actual_headers.get_all(name)
75
76
  end
76
77
 
78
+ def http_version
79
+ @channel.send_message_to_server('httpVersion')
80
+ end
81
+
77
82
  def server_addr
78
83
  @channel.send_message_to_server('serverAddr')
79
84
  end
@@ -1,8 +1,12 @@
1
1
  require 'base64'
2
+ require 'fileutils'
2
3
 
3
4
  module Playwright
4
5
  define_channel_owner :Stream do
5
6
  def save_as(path)
7
+ parent_dir = File.dirname(path)
8
+ FileUtils.mkdir_p(parent_dir) unless parent_dir == '.'
9
+
6
10
  File.open(path, 'wb') do |f|
7
11
  read_with_block do |chunk|
8
12
  f.write(chunk)
@@ -1,11 +1,12 @@
1
1
  module Playwright
2
2
  define_channel_owner :Tracing do
3
- def start(name: nil, title: nil, screenshots: nil, snapshots: nil, sources: nil)
3
+ def start(name: nil, title: nil, screenshots: nil, snapshots: nil, sources: nil, live: nil)
4
4
  params = {
5
5
  name: name,
6
6
  screenshots: screenshots,
7
7
  snapshots: snapshots,
8
8
  sources: sources,
9
+ live: live,
9
10
  }.compact
10
11
  @include_sources = params[:sources] || false
11
12
  @channel.send_message_to_server('tracingStart', params)
@@ -23,7 +24,8 @@ module Playwright
23
24
  @is_tracing = true
24
25
  @connection.set_in_tracing(true)
25
26
  end
26
- @stacks_id = @connection.local_utils.tracing_started(@traces_dir, trace_name)
27
+ local_utils = @connection.local_utils
28
+ @stacks_id = local_utils&.tracing_started(@traces_dir, trace_name)
27
29
  end
28
30
 
29
31
  def stop_chunk(path: nil)
@@ -40,20 +42,26 @@ module Playwright
40
42
  @is_tracing = false
41
43
  @connection.set_in_tracing(false)
42
44
  end
45
+ local_utils = @connection.local_utils
43
46
 
44
47
  unless file_path
45
48
  # Not interested in any artifacts
46
49
  @channel.send_message_to_server('tracingStopChunk', mode: 'discard')
47
50
  if @stacks_id
48
- @connection.local_utils.trace_discarded(@stacks_id)
51
+ local_utils.trace_discarded(@stacks_id) if local_utils
49
52
  end
50
53
 
51
54
  return
52
55
  end
53
56
 
54
- unless @connection.remote?
57
+ is_local = !@connection.remote?
58
+ if is_local
59
+ unless local_utils
60
+ raise 'Cannot save trace because localUtils is unavailable.'
61
+ end
62
+
55
63
  result = @channel.send_message_to_server_result('tracingStopChunk', mode: 'entries')
56
- @connection.local_utils.zip(
64
+ local_utils.zip(
57
65
  zipFile: file_path,
58
66
  entries: result['entries'],
59
67
  stacksId: @stacks_id,
@@ -69,7 +77,7 @@ module Playwright
69
77
  # The artifact may be missing if the browser closed while stopping tracing.
70
78
  unless result['artifact']
71
79
  if @stacks_id
72
- @connection.local_utils.trace_discarded(@stacks_id)
80
+ local_utils.trace_discarded(@stacks_id) if local_utils
73
81
  end
74
82
 
75
83
  return
@@ -80,7 +88,9 @@ module Playwright
80
88
  artifact.save_as(file_path)
81
89
  artifact.delete
82
90
 
83
- @connection.local_utils.zip(
91
+ return unless local_utils
92
+
93
+ local_utils.zip(
84
94
  zipFile: file_path,
85
95
  entries: [],
86
96
  stacksId: @stacks_id,
@@ -48,7 +48,21 @@ module Playwright
48
48
 
49
49
  waiter.reject_on_event(@parent, 'close', -> { @parent.send(:close_error_with_reason) })
50
50
  waiter.wait_for_event(self, event, predicate: predicate)
51
+ if @closed
52
+ if event == Events::WebSocket::Close
53
+ waiter.force_fulfill(nil)
54
+ else
55
+ waiter.force_reject(SocketClosedError.new)
56
+ end
57
+ end
51
58
  block&.call
59
+ if @closed
60
+ if event == Events::WebSocket::Close
61
+ waiter.force_fulfill(nil)
62
+ else
63
+ waiter.force_reject(SocketClosedError.new)
64
+ end
65
+ end
52
66
 
53
67
  waiter.result.value!
54
68
  end