playwright-ruby-client 1.59.1 → 1.61.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/api_request.md +25 -1
  3. data/documentation/docs/api/api_request_context.md +13 -11
  4. data/documentation/docs/api/api_response.md +18 -0
  5. data/documentation/docs/api/browser.md +18 -0
  6. data/documentation/docs/api/browser_context.md +7 -1
  7. data/documentation/docs/api/browser_type.md +19 -0
  8. data/documentation/docs/api/credentials.md +132 -0
  9. data/documentation/docs/api/frame.md +5 -4
  10. data/documentation/docs/api/frame_locator.md +1 -0
  11. data/documentation/docs/api/locator.md +37 -2
  12. data/documentation/docs/api/locator_assertions.md +1 -1
  13. data/documentation/docs/api/page.md +24 -4
  14. data/documentation/docs/api/page_assertions.md +28 -0
  15. data/documentation/docs/api/playwright.md +5 -0
  16. data/documentation/docs/api/touchscreen.md +1 -1
  17. data/documentation/docs/api/tracing.md +29 -0
  18. data/documentation/docs/api/web_storage.md +65 -0
  19. data/documentation/docs/include/api_coverage.md +30 -56
  20. data/lib/playwright/api_request_impl.rb +70 -0
  21. data/lib/playwright/api_response_impl.rb +8 -0
  22. data/lib/playwright/channel_owners/api_request_context.rb +5 -1
  23. data/lib/playwright/channel_owners/binding_call.rb +3 -9
  24. data/lib/playwright/channel_owners/browser.rb +17 -0
  25. data/lib/playwright/channel_owners/browser_context.rb +12 -55
  26. data/lib/playwright/channel_owners/browser_type.rb +35 -1
  27. data/lib/playwright/channel_owners/frame.rb +118 -13
  28. data/lib/playwright/channel_owners/json_pipe.rb +4 -0
  29. data/lib/playwright/channel_owners/local_utils.rb +11 -2
  30. data/lib/playwright/channel_owners/page.rb +28 -13
  31. data/lib/playwright/channel_owners/playwright.rb +4 -0
  32. data/lib/playwright/channel_owners/tracing.rb +105 -4
  33. data/lib/playwright/connection.rb +12 -2
  34. data/lib/playwright/console_message_impl.rb +10 -1
  35. data/lib/playwright/credentials_impl.rb +35 -0
  36. data/lib/playwright/errors.rb +7 -3
  37. data/lib/playwright/events.rb +7 -0
  38. data/lib/playwright/json_pipe_transport.rb +40 -0
  39. data/lib/playwright/locator_assertions_impl.rb +21 -4
  40. data/lib/playwright/locator_impl.rb +17 -3
  41. data/lib/playwright/locator_utils.rb +11 -1
  42. data/lib/playwright/page_assertions_impl.rb +33 -3
  43. data/lib/playwright/screencast.rb +8 -2
  44. data/lib/playwright/url_matcher.rb +35 -0
  45. data/lib/playwright/version.rb +2 -2
  46. data/lib/playwright/waiter.rb +9 -41
  47. data/lib/playwright/web_storage_impl.rb +34 -0
  48. data/lib/playwright.rb +1 -0
  49. data/lib/playwright_api/api_request.rb +1 -1
  50. data/lib/playwright_api/api_request_context.rb +21 -17
  51. data/lib/playwright_api/api_response.rb +12 -0
  52. data/lib/playwright_api/browser.rb +8 -8
  53. data/lib/playwright_api/browser_context.rb +20 -13
  54. data/lib/playwright_api/browser_type.rb +12 -9
  55. data/lib/playwright_api/cdp_session.rb +6 -6
  56. data/lib/playwright_api/credentials.rb +120 -0
  57. data/lib/playwright_api/dialog.rb +6 -6
  58. data/lib/playwright_api/element_handle.rb +6 -6
  59. data/lib/playwright_api/frame.rb +25 -14
  60. data/lib/playwright_api/frame_locator.rb +2 -1
  61. data/lib/playwright_api/js_handle.rb +6 -6
  62. data/lib/playwright_api/locator.rb +39 -10
  63. data/lib/playwright_api/locator_assertions.rb +2 -2
  64. data/lib/playwright_api/page.rb +44 -25
  65. data/lib/playwright_api/page_assertions.rb +22 -0
  66. data/lib/playwright_api/playwright.rb +7 -7
  67. data/lib/playwright_api/request.rb +8 -8
  68. data/lib/playwright_api/response.rb +6 -6
  69. data/lib/playwright_api/route.rb +6 -6
  70. data/lib/playwright_api/touchscreen.rb +1 -1
  71. data/lib/playwright_api/tracing.rb +27 -4
  72. data/lib/playwright_api/web_socket.rb +6 -6
  73. data/lib/playwright_api/web_storage.rb +48 -0
  74. data/lib/playwright_api/worker.rb +8 -8
  75. data/sig/playwright.rbs +51 -39
  76. metadata +11 -12
  77. data/documentation/docs/api/experimental/android.md +0 -42
  78. data/documentation/docs/api/experimental/android_device.md +0 -109
  79. data/documentation/docs/api/experimental/android_input.md +0 -43
  80. data/documentation/docs/api/experimental/android_socket.md +0 -7
  81. data/documentation/docs/api/experimental/android_web_view.md +0 -7
  82. data/lib/playwright_api/android.rb +0 -68
  83. data/lib/playwright_api/android_device.rb +0 -229
  84. data/lib/playwright_api/android_input.rb +0 -34
  85. data/lib/playwright_api/android_socket.rb +0 -18
  86. data/lib/playwright_api/android_web_view.rb +0 -24
@@ -293,6 +293,7 @@
293
293
  * go_forward
294
294
  * ~~request_gc~~
295
295
  * goto
296
+ * hide_highlight
296
297
  * hover
297
298
  * inner_html
298
299
  * inner_text
@@ -364,6 +365,8 @@
364
365
  * ~~wait_for_event~~
365
366
  * clock
366
367
  * keyboard
368
+ * local_storage
369
+ * session_storage
367
370
  * mouse
368
371
  * request
369
372
  * screencast
@@ -404,6 +407,7 @@
404
407
  * expect_page
405
408
  * ~~wait_for_event~~
406
409
  * clock
410
+ * credentials
407
411
  * ~~debugger~~
408
412
  * request
409
413
  * tracing
@@ -422,15 +426,15 @@
422
426
  * new_browser_cdp_session
423
427
  * new_context
424
428
  * new_page
425
- * ~~bind~~
429
+ * bind
426
430
  * start_tracing
427
431
  * stop_tracing
428
- * ~~unbind~~
432
+ * unbind
429
433
  * version
430
434
 
431
435
  ## BrowserType
432
436
 
433
- * ~~connect~~
437
+ * connect
434
438
  * connect_over_cdp
435
439
  * executable_path
436
440
  * launch
@@ -443,7 +447,7 @@
443
447
  * chromium
444
448
  * devices
445
449
  * firefox
446
- * ~~request~~
450
+ * request
447
451
  * selectors
448
452
  * webkit
449
453
 
@@ -451,10 +455,12 @@
451
455
 
452
456
  * start
453
457
  * start_chunk
458
+ * start_har
454
459
  * group
455
460
  * group_end
456
461
  * stop
457
462
  * stop_chunk
463
+ * stop_har
458
464
 
459
465
  ## Locator
460
466
 
@@ -474,6 +480,7 @@
474
480
  * description
475
481
  * dispatch_event
476
482
  * drag_to
483
+ * drop
477
484
  * element_handle
478
485
  * element_handles
479
486
  * content_frame
@@ -493,6 +500,7 @@
493
500
  * get_by_test_id
494
501
  * get_by_text
495
502
  * get_by_title
503
+ * hide_highlight
496
504
  * highlight
497
505
  * hover
498
506
  * inner_html
@@ -548,6 +556,8 @@
548
556
  * headers_array
549
557
  * json
550
558
  * ok
559
+ * security_details
560
+ * server_addr
551
561
  * status
552
562
  * status_text
553
563
  * text
@@ -564,10 +574,11 @@
564
574
  * post
565
575
  * put
566
576
  * ~~storage_state~~
577
+ * tracing
567
578
 
568
- ## ~~APIRequest~~
579
+ ## APIRequest
569
580
 
570
- * ~~new_context~~
581
+ * new_context
571
582
 
572
583
  ## LocatorAssertions
573
584
 
@@ -628,59 +639,22 @@
628
639
 
629
640
  * not_to_have_title
630
641
  * not_to_have_url
642
+ * to_match_aria_snapshot
643
+ * not_to_match_aria_snapshot
631
644
  * to_have_title
632
645
  * to_have_url
633
646
 
634
- ## Android
647
+ ## WebStorage
635
648
 
636
- * ~~connect~~
637
- * devices
638
- * set_default_timeout
639
-
640
- ## AndroidDevice
641
-
642
- * close
643
- * ~~drag~~
644
- * ~~fill~~
645
- * ~~fling~~
646
- * info
647
- * ~~install_apk~~
648
- * launch_browser
649
- * ~~long_tap~~
650
- * model
651
- * ~~open~~
652
- * ~~pinch_close~~
653
- * ~~pinch_open~~
654
- * ~~press~~
655
- * ~~push~~
656
- * screenshot
657
- * ~~scroll~~
658
- * serial
659
- * ~~set_default_timeout~~
660
- * shell
661
- * ~~swipe~~
662
- * ~~tap_point~~
663
- * ~~wait~~
664
- * ~~wait_for_event~~
665
- * ~~web_view~~
666
- * ~~web_views~~
667
- * input
668
-
669
- ## AndroidInput
670
-
671
- * drag
672
- * press
673
- * ~~swipe~~
674
- * tap_point
675
- * type
676
-
677
- ## ~~AndroidSocket~~
678
-
679
- * ~~close~~
680
- * ~~write~~
649
+ * items
650
+ * get_item
651
+ * set_item
652
+ * remove_item
653
+ * clear
681
654
 
682
- ## ~~AndroidWebView~~
655
+ ## Credentials
683
656
 
684
- * ~~page~~
685
- * ~~pid~~
686
- * ~~pkg~~
657
+ * install
658
+ * create
659
+ * delete
660
+ * get
@@ -0,0 +1,70 @@
1
+ require 'base64'
2
+ require 'json'
3
+
4
+ module Playwright
5
+ define_api_implementation :APIRequestImpl do
6
+ def initialize(playwright)
7
+ @playwright = playwright
8
+ end
9
+
10
+ def new_context(
11
+ baseURL: nil,
12
+ clientCertificates: nil,
13
+ extraHTTPHeaders: nil,
14
+ failOnStatusCode: nil,
15
+ httpCredentials: nil,
16
+ ignoreHTTPSErrors: nil,
17
+ maxRedirects: nil,
18
+ proxy: nil,
19
+ storageState: nil,
20
+ timeout: nil,
21
+ userAgent: nil)
22
+ params = {
23
+ baseURL: baseURL,
24
+ clientCertificates: prepare_client_certificates(clientCertificates),
25
+ extraHTTPHeaders: extraHTTPHeaders ? HttpHeaders.new(extraHTTPHeaders).as_serialized : nil,
26
+ failOnStatusCode: failOnStatusCode,
27
+ httpCredentials: httpCredentials,
28
+ ignoreHTTPSErrors: ignoreHTTPSErrors,
29
+ maxRedirects: maxRedirects,
30
+ proxy: proxy,
31
+ storageState: prepare_storage_state(storageState),
32
+ timeout: timeout,
33
+ userAgent: userAgent,
34
+ }.compact
35
+
36
+ result = @playwright.channel.send_message_to_server_result('newRequest', params)
37
+ context = ChannelOwners::APIRequestContext.from(result['request'])
38
+ context.send(:_update_timeout_settings, TimeoutSettings.new.tap { |settings| settings.default_timeout = timeout if timeout })
39
+ context
40
+ end
41
+
42
+ private def prepare_storage_state(storage_state)
43
+ storage_state.is_a?(String) ? JSON.parse(File.read(storage_state)) : storage_state
44
+ end
45
+
46
+ private def prepare_client_certificates(client_certificates)
47
+ return unless client_certificates.is_a?(Array)
48
+
49
+ client_certificates.filter_map do |item|
50
+ out_record = {
51
+ origin: item[:origin],
52
+ passphrase: item[:passphrase],
53
+ }
54
+
55
+ { pfxPath: 'pfx', certPath: 'cert', keyPath: 'key' }.each do |key, out_key|
56
+ if (filepath = item[key])
57
+ out_record[out_key] = Base64.encode64(File.read(filepath)) rescue ''
58
+ elsif (value = item[out_key.to_sym])
59
+ out_record[out_key] = value
60
+ end
61
+ end
62
+
63
+ out_record.compact!
64
+ next nil if out_record.empty?
65
+
66
+ out_record
67
+ end
68
+ end
69
+ end
70
+ end
@@ -39,6 +39,14 @@ module Playwright
39
39
  @headers.headers_array
40
40
  end
41
41
 
42
+ def security_details
43
+ @initializer['securityDetails']
44
+ end
45
+
46
+ def server_addr
47
+ @initializer['serverAddr']
48
+ end
49
+
42
50
  class AlreadyDisposedError < StandardError
43
51
  def initialize
44
52
  super('Response has been disposed')
@@ -8,13 +8,17 @@ module Playwright
8
8
  @timeout_settings = TimeoutSettings.new
9
9
  end
10
10
 
11
+ attr_reader :tracing
12
+
11
13
  private def _update_timeout_settings(timeout_settings)
12
14
  @timeout_settings = timeout_settings
13
15
  end
14
16
 
15
17
  def dispose(reason: nil)
16
18
  @close_reason = reason
17
- @channel.send_message_to_server('dispose')
19
+ @tracing.send(:export_all_hars)
20
+ @channel.send_message_to_server('dispose', { reason: reason }.compact)
21
+ @tracing.send(:reset_stack_counter)
18
22
  end
19
23
 
20
24
  def delete(url, **options)
@@ -53,15 +53,9 @@ module Playwright
53
53
  page: PlaywrightApi.wrap(frame.page),
54
54
  frame: PlaywrightApi.wrap(frame),
55
55
  }
56
- args =
57
- if @initializer['handle']
58
- handle = ChannelOwners::ElementHandle.from(@initializer['handle'])
59
- [handle]
60
- else
61
- @initializer['args'].map do |arg|
62
- JavaScript::ValueParser.new(arg).parse
63
- end
64
- end
56
+ args = @initializer['args'].map do |arg|
57
+ JavaScript::ValueParser.new(arg).parse
58
+ end
65
59
 
66
60
  begin
67
61
  result = PlaywrightApi.unwrap(callback.call(source, *args))
@@ -87,6 +87,22 @@ module Playwright
87
87
  @initializer['version']
88
88
  end
89
89
 
90
+ def bind(title, host: nil, metadata: nil, port: nil, workspaceDir: nil)
91
+ params = {
92
+ title: title,
93
+ host: host,
94
+ metadata: metadata,
95
+ port: port,
96
+ workspaceDir: workspaceDir,
97
+ }.compact
98
+ @channel.send_message_to_server_result('startServer', params)
99
+ end
100
+
101
+ def unbind
102
+ @channel.send_message_to_server('stopServer')
103
+ nil
104
+ end
105
+
90
106
  def new_browser_cdp_session
91
107
  resp = @channel.send_message_to_server('newBrowserCDPSession')
92
108
  ChannelOwners::CDPSession.from(resp)
@@ -135,6 +151,7 @@ module Playwright
135
151
  if @browser_type
136
152
  setup_browser_context(context)
137
153
  end
154
+ emit(Events::Browser::Context, context)
138
155
  end
139
156
 
140
157
  private def setup_browser_context(context)
@@ -5,7 +5,7 @@ module Playwright
5
5
 
6
6
  attr_accessor :browser
7
7
  attr_writer :owner_page, :options
8
- attr_reader :clock, :tracing, :request
8
+ attr_reader :clock, :tracing, :request, :credentials
9
9
 
10
10
  private def after_initialize
11
11
  @options = @initializer['options']
@@ -20,7 +20,7 @@ module Playwright
20
20
  @request = ChannelOwners::APIRequestContext.from(@initializer['requestContext'])
21
21
  @request.send(:_update_timeout_settings, @timeout_settings)
22
22
  @clock = ClockImpl.new(self)
23
- @har_recorders = {}
23
+ @credentials = CredentialsImpl.new(self)
24
24
 
25
25
  @channel.on('bindingCall', ->(params) { on_binding(ChannelOwners::BindingCall.from(params['binding'])) })
26
26
  @channel.once('close', ->(_) { on_close })
@@ -34,8 +34,9 @@ module Playwright
34
34
  })
35
35
  @channel.on('pageError', ->(params) {
36
36
  on_page_error(
37
- Error.parse(params['error']['error']),
37
+ Error.parse(params.dig('error', 'error') || params['error']),
38
38
  ChannelOwners::Page.from_nullable(params['page']),
39
+ params['location'],
39
40
  )
40
41
  })
41
42
  @channel.on('dialog', ->(params) {
@@ -82,7 +83,7 @@ module Playwright
82
83
 
83
84
  default_policy = record_har_path.end_with?('.zip') ? 'attach' : 'embed'
84
85
  content_policy = record_har_content || (record_har_omit_content ? 'omit' : default_policy)
85
- record_into_har(record_har_path, nil,
86
+ @tracing.send(:record_into_har, record_har_path, nil,
86
87
  url: record_har_url_filter,
87
88
  update_content: content_policy,
88
89
  update_mode: record_har_mode || 'full',
@@ -186,8 +187,8 @@ module Playwright
186
187
  end
187
188
  end
188
189
 
189
- private def on_page_error(error, page)
190
- emit(Events::BrowserContext::WebError, WebError.new(error, page))
190
+ private def on_page_error(error, page, location)
191
+ emit(Events::BrowserContext::WebError, WebError.new(error, page, location))
191
192
  if page
192
193
  page.emit(Events::Page::PageError, error)
193
194
  end
@@ -337,19 +338,15 @@ module Playwright
337
338
  ChannelOwners::Disposable.from(result['disposable'])
338
339
  end
339
340
 
340
- def expose_binding(name, callback, handle: nil)
341
+ def expose_binding(name, callback)
341
342
  if @pages.any? { |page| page.send(:has_bindings?, name) }
342
343
  raise ArgumentError.new("Function \"#{name}\" has been already registered in one of the pages")
343
344
  end
344
345
  if @bindings.key?(name)
345
346
  raise ArgumentError.new("Function \"#{name}\" has been already registered")
346
347
  end
347
- params = {
348
- name: name,
349
- needsHandle: handle,
350
- }.compact
351
348
  @bindings[name] = callback
352
- result = @channel.send_message_to_server_result('exposeBinding', params)
349
+ result = @channel.send_message_to_server_result('exposeBinding', name: name)
353
350
  ChannelOwners::Disposable.from(result['disposable'])
354
351
  end
355
352
 
@@ -376,32 +373,9 @@ module Playwright
376
373
  update_interception_patterns
377
374
  end
378
375
 
379
- private def record_into_har(har, page, url:, update_content:, update_mode:)
380
- options = {
381
- zip: har.end_with?('.zip'),
382
- content: update_content || 'attach',
383
- }
384
-
385
- if url.is_a?(Regexp)
386
- regex = ::Playwright::JavaScript::Regex.new(url)
387
- options[:urlRegexSource] = regex.source
388
- options[:urlRegexFlags] = regex.flag
389
- elsif url.is_a?(String)
390
- options[:urlGlob] = url
391
- end
392
-
393
- params = { options: options }
394
- if page
395
- params[:page] = page.channel
396
- end
397
-
398
- har_id = @channel.send_message_to_server('harStart', params)
399
- @har_recorders[har_id] = { path: har, content: update_content || 'attach' }
400
- end
401
-
402
376
  def route_from_har(har, notFound: nil, update: nil, updateContent: nil, updateMode: nil, url: nil)
403
377
  if update
404
- record_into_har(har, nil, url: url, update_content: updateContent, update_mode: updateMode)
378
+ @tracing.send(:record_into_har, har, nil, url: url, update_content: updateContent, update_mode: updateMode)
405
379
  return
406
380
  end
407
381
 
@@ -443,6 +417,7 @@ module Playwright
443
417
  @browser.send(:remove_context, self)
444
418
  @browser.browser_type.send(:playwright_selectors_browser_contexts).delete(self)
445
419
  end
420
+ @tracing.send(:reset_stack_counter)
446
421
  emit(Events::BrowserContext::Close)
447
422
  @closed_promise.fulfill(true)
448
423
  end
@@ -452,30 +427,12 @@ module Playwright
452
427
  @close_was_called = true
453
428
  @close_reason = reason
454
429
  @request.dispose(reason: reason)
455
- inner_close
430
+ @tracing.send(:export_all_hars)
456
431
  @channel.send_message_to_server('close', { reason: reason }.compact)
457
432
  @closed_promise.value!
458
433
  nil
459
434
  end
460
435
 
461
- private def inner_close
462
- @har_recorders.each do |har_id, params|
463
- har = ChannelOwners::Artifact.from(@channel.send_message_to_server('harExport', harId: har_id))
464
- # Server side will compress artifact if content is attach or if file is .zip.
465
- compressed = params[:content] == "attach" || params[:path].end_with?('.zip')
466
- need_comppressed = params[:path].end_with?('.zip')
467
- if compressed && !need_comppressed
468
- tmp_path = "#{params[:path]}.tmp"
469
- har.save_as(tmp_path)
470
- @connection.local_utils.har_unzip(tmp_path, params[:path])
471
- else
472
- har.save_as(params[:path])
473
- end
474
-
475
- har.delete
476
- end
477
- end
478
-
479
436
  # REMARK: enable_debug_console is playwright-ruby-client specific method.
480
437
  def enable_debug_console!
481
438
  # Ruby is not supported in Playwright officially,
@@ -43,6 +43,7 @@ module Playwright
43
43
  browser = ChannelOwners::Browser.from(result['browser'])
44
44
  browser.send(:connect_to_browser_type, self, params[:tracesDir])
45
45
  context = ChannelOwners::BrowserContext.from(result['context'])
46
+ context.send(:update_options, context_options: params, browser_options: params)
46
47
  context.send(:initialize_har_from_options,
47
48
  record_har_content: params[:record_har_content],
48
49
  record_har_mode: params[:record_har_mode],
@@ -60,13 +61,46 @@ module Playwright
60
61
  end
61
62
  end
62
63
 
63
- def connect_over_cdp(endpointURL, headers: nil, isLocal: nil, slowMo: nil, timeout: nil, &block)
64
+ def connect(endpoint, exposeNetwork: nil, headers: nil, slowMo: nil, timeout: nil, &block)
65
+ params = {
66
+ endpoint: endpoint,
67
+ headers: { 'x-playwright-browser' => name }.merge(headers || {}),
68
+ exposeNetwork: exposeNetwork,
69
+ slowMo: slowMo,
70
+ timeout: @timeout_settings.timeout(timeout),
71
+ }.compact
72
+
73
+ transport = JsonPipeTransport.new(@connection.local_utils, params)
74
+ connection = Connection.new(transport)
75
+ connection.mark_as_remote
76
+ connection.async_run
77
+
78
+ playwright = connection.initialize_playwright
79
+ browser = playwright.send(:pre_launched_browser)
80
+ browser.send(:should_close_connection_on_close!)
81
+ browser.send(:connect_to_browser_type, self, nil)
82
+
83
+ return browser unless block
84
+
85
+ begin
86
+ block.call(browser)
87
+ ensure
88
+ browser.close
89
+ end
90
+ rescue
91
+ connection&.stop
92
+ raise
93
+ end
94
+
95
+ def connect_over_cdp(endpointURL, artifactsDir: nil, headers: nil, isLocal: nil, noDefaults: nil, slowMo: nil, timeout: nil, &block)
64
96
  raise 'Connecting over CDP is only supported in Chromium.' unless name == 'chromium'
65
97
 
66
98
  params = {
67
99
  endpointURL: endpointURL,
100
+ artifactsDir: artifactsDir,
68
101
  headers: headers,
69
102
  isLocal: isLocal,
103
+ noDefaults: noDefaults,
70
104
  slowMo: slowMo,
71
105
  timeout: @timeout_settings.timeout(timeout),
72
106
  }.compact
@@ -1,3 +1,4 @@
1
+ require 'base64'
1
2
  require_relative '../locator_utils'
2
3
 
3
4
  module Playwright
@@ -47,6 +48,7 @@ module Playwright
47
48
  unless @parent_frame
48
49
  if add == 'load'
49
50
  @page&.emit(Events::Page::Load, @page)
51
+ @page&.context&.emit(Events::BrowserContext::PageLoad, @page)
50
52
  elsif add == 'domcontentloaded'
51
53
  @page&.emit(Events::Page::DOMContentLoaded, @page)
52
54
  end
@@ -65,6 +67,7 @@ module Playwright
65
67
 
66
68
  unless event['error']
67
69
  @page&.emit(Events::Page::FrameNavigated, self)
70
+ @page&.context&.emit(Events::BrowserContext::FrameNavigated, self)
68
71
  end
69
72
  end
70
73
 
@@ -388,6 +391,23 @@ module Playwright
388
391
  nil
389
392
  end
390
393
 
394
+ def drop(
395
+ selector,
396
+ payload,
397
+ position: nil,
398
+ strict: nil,
399
+ timeout: nil)
400
+ params = {
401
+ selector: selector,
402
+ position: position,
403
+ strict: strict,
404
+ timeout: _timeout(timeout),
405
+ }.compact.merge(drop_payload_params(payload))
406
+ @channel.send_message_to_server('drop', params)
407
+
408
+ nil
409
+ end
410
+
391
411
  def dblclick(
392
412
  selector,
393
413
  button: nil,
@@ -690,8 +710,12 @@ module Playwright
690
710
  @channel.send_message_to_server('title')
691
711
  end
692
712
 
693
- def highlight(selector)
694
- @channel.send_message_to_server('highlight', selector: selector)
713
+ def highlight(selector, style: nil)
714
+ @channel.send_message_to_server('highlight', { selector: selector, style: style }.compact)
715
+ end
716
+
717
+ def hide_highlight(selector)
718
+ @channel.send_message_to_server('hideHighlight', selector: selector)
695
719
  end
696
720
 
697
721
  def expect(selector, expression, options, title)
@@ -701,21 +725,102 @@ module Playwright
701
725
  .serialize
702
726
  end
703
727
 
704
- result = @channel.send_message_to_server_result(
705
- title, # title
706
- "expect", # method
707
- { # params
708
- selector: selector,
709
- expression: expression,
710
- **options,
728
+ is_not = options[:isNot] || false
729
+
730
+ # Since Playwright 1.61, the `expect` command resolves on a successful
731
+ # match and rejects with FrameExpectErrorDetails on a mismatch/timeout.
732
+ begin
733
+ @channel.send_message_to_server_result(
734
+ title, # title
735
+ "expect", # method
736
+ { # params
737
+ selector: selector,
738
+ expression: expression,
739
+ **options,
740
+ }.compact
741
+ )
742
+ { 'matches' => !is_not }
743
+ rescue ::Playwright::Error => err
744
+ details = err.details || {}
745
+ received = details['received']
746
+ if received.is_a?(Hash)
747
+ received = received.dup
748
+ if received.key?('value')
749
+ received['value'] = JavaScript::ValueParser.new(received['value']).parse
750
+ end
751
+ end
752
+
753
+ error_message =
754
+ if details['customErrorMessage']
755
+ "Error: #{details['customErrorMessage']}"
756
+ end
757
+
758
+ {
759
+ 'matches' => is_not,
760
+ 'received' => received,
761
+ 'log' => err.raw_log,
762
+ 'timedOut' => details['timedOut'],
763
+ 'errorMessage' => error_message,
711
764
  }.compact
712
- )
765
+ end
766
+ end
713
767
 
714
- if result.key?('received')
715
- result['received'] = JavaScript::ValueParser.new(result['received']).parse
768
+ private def drop_payload_params(payload)
769
+ unless payload.is_a?(Hash)
770
+ raise ArgumentError.new('payload must be a Hash')
716
771
  end
772
+ unless payload.key?(:files) || payload.key?('files') || payload.key?(:data) || payload.key?('data')
773
+ raise ArgumentError.new('At least one of "files" or "data" must be specified')
774
+ end
775
+
776
+ params = {}
777
+ files = payload[:files] || payload['files']
778
+ params[:payloads] = drop_file_payloads(files) if files
779
+
780
+ data = payload[:data] || payload['data']
781
+ if data
782
+ params[:data] = data.map do |mime_type, value|
783
+ { mimeType: mime_type, value: value }
784
+ end
785
+ end
786
+
787
+ params
788
+ end
789
+
790
+ private def drop_file_payloads(files)
791
+ items =
792
+ if files.is_a?(Array)
793
+ files
794
+ else
795
+ [files]
796
+ end
717
797
 
718
- result
798
+ if items.any? { |item| item.is_a?(String) || item.is_a?(File) }
799
+ unless items.all? { |item| item.is_a?(String) || item.is_a?(File) }
800
+ raise ArgumentError.new('File paths cannot be mixed with buffers')
801
+ end
802
+ return items.map do |item|
803
+ path = item.is_a?(File) ? item.path : item
804
+ raise ArgumentError.new('Dropping a directory is not supported - pass individual files.') if File.directory?(path)
805
+
806
+ {
807
+ name: File.basename(path),
808
+ buffer: Base64.strict_encode64(File.binread(path)),
809
+ }
810
+ end
811
+ end
812
+
813
+ items.map do |item|
814
+ unless item.is_a?(Hash)
815
+ raise ArgumentError.new('files must be file paths or file payload hashes')
816
+ end
817
+ buffer = item[:buffer] || item['buffer']
818
+ {
819
+ name: item[:name] || item['name'],
820
+ mimeType: item[:mimeType] || item['mimeType'],
821
+ buffer: Base64.strict_encode64(buffer),
822
+ }.compact
823
+ end
719
824
  end
720
825
 
721
826
  # @param page [Page]