playwright-ruby-client 1.58.1 → 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 (66) 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 +2 -0
  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 +16 -3
  9. data/documentation/docs/api/page.md +68 -5
  10. data/documentation/docs/api/request.md +13 -0
  11. data/documentation/docs/api/response.md +9 -0
  12. data/documentation/docs/api/tracing.md +1 -0
  13. data/documentation/docs/article/guides/rails_integration_with_null_driver.md +46 -0
  14. data/documentation/docs/include/api_coverage.md +15 -0
  15. data/lib/playwright/channel_owners/browser_context.rb +24 -4
  16. data/lib/playwright/channel_owners/debugger.rb +4 -0
  17. data/lib/playwright/channel_owners/dialog.rb +3 -1
  18. data/lib/playwright/channel_owners/disposable.rb +9 -0
  19. data/lib/playwright/channel_owners/overlay.rb +4 -0
  20. data/lib/playwright/channel_owners/page.rb +54 -31
  21. data/lib/playwright/channel_owners/request.rb +8 -0
  22. data/lib/playwright/channel_owners/response.rb +5 -0
  23. data/lib/playwright/channel_owners/tracing.rb +2 -1
  24. data/lib/playwright/console_message_impl.rb +4 -0
  25. data/lib/playwright/disposable.rb +11 -0
  26. data/lib/playwright/har_router.rb +16 -1
  27. data/lib/playwright/locator_impl.rb +19 -3
  28. data/lib/playwright/screencast.rb +91 -0
  29. data/lib/playwright/utils.rb +19 -0
  30. data/lib/playwright/version.rb +2 -2
  31. data/lib/playwright/video.rb +6 -4
  32. data/lib/playwright.rb +2 -0
  33. data/lib/playwright_api/android.rb +7 -7
  34. data/lib/playwright_api/android_device.rb +6 -6
  35. data/lib/playwright_api/api_request_context.rb +6 -6
  36. data/lib/playwright_api/browser.rb +18 -6
  37. data/lib/playwright_api/browser_context.rb +42 -16
  38. data/lib/playwright_api/browser_type.rb +11 -9
  39. data/lib/playwright_api/cdp_session.rb +6 -6
  40. data/lib/playwright_api/console_message.rb +6 -0
  41. data/lib/playwright_api/dialog.rb +6 -6
  42. data/lib/playwright_api/element_handle.rb +6 -6
  43. data/lib/playwright_api/frame.rb +8 -8
  44. data/lib/playwright_api/frame_locator.rb +2 -2
  45. data/lib/playwright_api/js_handle.rb +6 -6
  46. data/lib/playwright_api/locator.rb +14 -4
  47. data/lib/playwright_api/page.rb +68 -19
  48. data/lib/playwright_api/playwright.rb +6 -6
  49. data/lib/playwright_api/request.rb +16 -6
  50. data/lib/playwright_api/response.rb +12 -6
  51. data/lib/playwright_api/route.rb +6 -6
  52. data/lib/playwright_api/tracing.rb +8 -7
  53. data/lib/playwright_api/web_socket.rb +6 -6
  54. data/lib/playwright_api/worker.rb +8 -8
  55. data/playwright.gemspec +7 -1
  56. data/sig/playwright.rbs +28 -15
  57. metadata +7 -11
  58. data/AGENTS.md +0 -4
  59. data/CLAUDE/api_generation.md +0 -28
  60. data/CLAUDE/ci_expectations.md +0 -23
  61. data/CLAUDE/gem_release_flow.md +0 -39
  62. data/CLAUDE/past_upgrade_pr_patterns.md +0 -42
  63. data/CLAUDE/playwright_upgrade_workflow.md +0 -35
  64. data/CLAUDE/rspec_debugging.md +0 -30
  65. data/CLAUDE/unimplemented_examples.md +0 -18
  66. data/CLAUDE.md +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22108b8b1e5e9488c99db9d754715fd700d29fac599ee605449701c93be11055
4
- data.tar.gz: d9ce245e505e17085630c58c75721823f0c1019324066d79b43f707f711b9310
3
+ metadata.gz: 959a33f7494fd33763cd54ddcf1e1c17fa0d9d37c89643aef2198f0dd452bbc9
4
+ data.tar.gz: ce343d14b0541fdf0ba0892b2e79456eea0ea5340b9f651f77b50c4e9a835cf6
5
5
  SHA512:
6
- metadata.gz: 10534107850650b883dac4b7b817bfe641ba91ae7414f828a0fbcf8941dacbf6015cbcb9b68a8de828eb2559c4feefab5a5cc539e1c07935ced1a668773c98c1
7
- data.tar.gz: 8661b9a7991ae2e81fe5a43ac71f41fa7e405bdb1c9a04cf984bf02052f47615f86dfaac8a8deecf0a36f41ebd960bc3499493956087b9ba063315843ebf2bc7
6
+ metadata.gz: bab8717bee2ff89d4735682f4e4b68b545a9812b01143fe082028aded03dcac856e63db430de66ac87cbcbfd397c16ddd1f5a774d6c33e951272e8c701b0149c
7
+ data.tar.gz: bba6fa2ebc246f538ccd074630c59f57c206d2942e299514c3d42b4717ae859943e42e4e2c4bc9b8a84e3680b2559f94ec1d7a0c34e7c56c7740468483ebf93e
data/README.md CHANGED
@@ -174,16 +174,16 @@ npx playwright install && npx playwright run-server --port 8080 --path /ws
174
174
  and we can connect to the server with the code like this:
175
175
 
176
176
  ```ruby
177
- Playwright.connect_to_playwright_server('ws://127.0.0.1:8080/ws?browser=chromium') do |playwright|
178
- playwright.chromium.launch do |browser|
179
- page = browser.new_page
180
- page.goto('https://github.com/YusukeIwaki')
181
- page.screenshot(path: './YusukeIwaki.png')
182
- end
177
+ require 'playwright'
178
+
179
+ Playwright.connect_to_browser_server('wss://example.com:8888/ws') do |browser|
180
+ page = browser.new_page
181
+ page.goto('https://github.com/YusukeIwaki')
182
+ page.screenshot(path: './YusukeIwaki.png')
183
183
  end
184
184
  ```
185
185
 
186
- When `Playwright.connect_to_playwright_server` is used, playwright_cli_executable_path is not required.
186
+ When `Playwright.connect_to_browser_server` is used, playwright_cli_executable_path is not required.
187
187
 
188
188
  For more detailed instraction, refer this article: https://playwright-ruby-client.vercel.app/docs/article/guides/playwright_on_alpine_linux
189
189
 
@@ -238,6 +238,15 @@ def grant_permissions(permissions, origin: nil)
238
238
  Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
239
239
  specified.
240
240
 
241
+ ## closed?
242
+
243
+ ```
244
+ def closed?
245
+ ```
246
+
247
+
248
+ Indicates that the browser context is in the process of closing or has already been closed.
249
+
241
250
  ## new_cdp_session
242
251
 
243
252
  ```
@@ -431,6 +440,23 @@ def storage_state(indexedDB: nil, path: nil)
431
440
 
432
441
  Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
433
442
 
443
+ ## set_storage_state
444
+
445
+ ```
446
+ def set_storage_state(storageState)
447
+ ```
448
+ alias: `storage_state=`
449
+
450
+
451
+ Clears the existing cookies, local storage and IndexedDB entries for all origins and sets the new storage state.
452
+
453
+ **Usage**
454
+
455
+ ```ruby
456
+ # Load storage state from a file and apply it to the context.
457
+ context.set_storage_state("state.json")
458
+ ```
459
+
434
460
  ## unroute_all
435
461
 
436
462
  ```
@@ -62,6 +62,7 @@ A path where Playwright expects to find a bundled browser executable.
62
62
  ```
63
63
  def launch(
64
64
  args: nil,
65
+ artifactsDir: nil,
65
66
  channel: nil,
66
67
  chromiumSandbox: nil,
67
68
  downloadsPath: nil,
@@ -118,6 +119,7 @@ def launch_persistent_context(
118
119
  userDataDir,
119
120
  acceptDownloads: nil,
120
121
  args: nil,
122
+ artifactsDir: nil,
121
123
  baseURL: nil,
122
124
  bypassCSP: nil,
123
125
  channel: nil,
@@ -67,6 +67,15 @@ def text
67
67
 
68
68
  The text of the console message.
69
69
 
70
+ ## timestamp
71
+
72
+ ```
73
+ def timestamp
74
+ ```
75
+
76
+
77
+ The timestamp of the console message in milliseconds since the Unix epoch.
78
+
70
79
  ## type
71
80
 
72
81
  ```
@@ -512,7 +512,7 @@ Consider the following DOM structure.
512
512
  <button>Submit</button>
513
513
  ```
514
514
 
515
- You can locate each element by it's implicit role:
515
+ You can locate each element by its implicit role:
516
516
 
517
517
  ```ruby
518
518
  page.get_by_role("heading", name: "Sign up").visible? # => true
@@ -544,7 +544,7 @@ Consider the following DOM structure.
544
544
  <button data-testid="directions">Itinéraire</button>
545
545
  ```
546
546
 
547
- You can locate the element by it's test id:
547
+ You can locate the element by its test id:
548
548
 
549
549
  ```ruby
550
550
  page.get_by_test_id("directions").click
@@ -157,7 +157,7 @@ Consider the following DOM structure.
157
157
  <button>Submit</button>
158
158
  ```
159
159
 
160
- You can locate each element by it's implicit role:
160
+ You can locate each element by its implicit role:
161
161
 
162
162
  ```ruby
163
163
  page.get_by_role("heading", name: "Sign up").visible? # => true
@@ -189,7 +189,7 @@ Consider the following DOM structure.
189
189
  <button data-testid="directions">Itinéraire</button>
190
190
  ```
191
191
 
192
- You can locate the element by it's test id:
192
+ You can locate the element by its test id:
193
193
 
194
194
  ```ruby
195
195
  page.get_by_test_id("directions").click
@@ -87,7 +87,7 @@ button = page.get_by_role("button").and(page.get_by_title("Subscribe"))
87
87
  ## aria_snapshot
88
88
 
89
89
  ```
90
- def aria_snapshot(timeout: nil)
90
+ def aria_snapshot(depth: nil, mode: nil, timeout: nil)
91
91
  ```
92
92
 
93
93
 
@@ -127,6 +127,9 @@ Below is the HTML markup and the respective ARIA snapshot:
127
127
  - link "About"
128
128
  ```
129
129
 
130
+ An AI-optimized snapshot, controlled by `mode`, is different from a default snapshot:
131
+ 1. Includes element references `[ref=e2]`. 2. Does not wait for an element matching the locator, and throws when no elements match. 3. Includes snapshots of `<iframe>`s inside the target.
132
+
130
133
  ## blur
131
134
 
132
135
  ```
@@ -744,7 +747,7 @@ Consider the following DOM structure.
744
747
  <button>Submit</button>
745
748
  ```
746
749
 
747
- You can locate each element by it's implicit role:
750
+ You can locate each element by its implicit role:
748
751
 
749
752
  ```ruby
750
753
  page.get_by_role("heading", name: "Sign up").visible? # => true
@@ -776,7 +779,7 @@ Consider the following DOM structure.
776
779
  <button data-testid="directions">Itinéraire</button>
777
780
  ```
778
781
 
779
- You can locate the element by it's test id:
782
+ You can locate the element by its test id:
780
783
 
781
784
  ```ruby
782
785
  page.get_by_test_id("directions").click
@@ -1082,6 +1085,16 @@ The method finds an element matching the specified selector in the locator's sub
1082
1085
 
1083
1086
  [Learn more about locators](https://playwright.dev/python/docs/locators).
1084
1087
 
1088
+ ## normalize
1089
+
1090
+ ```
1091
+ def normalize
1092
+ ```
1093
+
1094
+
1095
+ Returns a new locator that uses best practices for referencing the matched element, prioritizing test ids,
1096
+ aria roles, and other user-facing attributes over CSS selectors. This is useful for converting implementation-detail selectors into more resilient, human-readable locators.
1097
+
1085
1098
  ## nth
1086
1099
 
1087
1100
  ```
@@ -94,6 +94,16 @@ def bring_to_front
94
94
 
95
95
  Brings page to front (activates tab).
96
96
 
97
+ ## cancel_pick_locator
98
+
99
+ ```
100
+ def cancel_pick_locator
101
+ ```
102
+
103
+
104
+ Cancels an ongoing [Page#pick_locator](./page#pick_locator) call by deactivating pick locator mode.
105
+ If no pick locator mode is active, this method is a no-op.
106
+
97
107
  ## check
98
108
 
99
109
  ```
@@ -706,7 +716,7 @@ Consider the following DOM structure.
706
716
  <button>Submit</button>
707
717
  ```
708
718
 
709
- You can locate each element by it's implicit role:
719
+ You can locate each element by its implicit role:
710
720
 
711
721
  ```ruby
712
722
  page.get_by_role("heading", name: "Sign up").visible? # => true
@@ -738,7 +748,7 @@ Consider the following DOM structure.
738
748
  <button data-testid="directions">Itinéraire</button>
739
749
  ```
740
750
 
741
- You can locate the element by it's test id:
751
+ You can locate the element by its test id:
742
752
 
743
753
  ```ruby
744
754
  page.get_by_test_id("directions").click
@@ -995,10 +1005,28 @@ def visible?(selector, strict: nil, timeout: nil)
995
1005
 
996
1006
  Returns whether the element is [visible](https://playwright.dev/python/docs/actionability#visible). `selector` that does not match any elements is considered not visible.
997
1007
 
1008
+ ## clear_console_messages
1009
+
1010
+ ```
1011
+ def clear_console_messages
1012
+ ```
1013
+
1014
+
1015
+ Clears all stored console messages from this page. Subsequent calls to [Page#console_messages](./page#console_messages) will only return messages logged after the clear.
1016
+
1017
+ ## clear_page_errors
1018
+
1019
+ ```
1020
+ def clear_page_errors
1021
+ ```
1022
+
1023
+
1024
+ Clears all stored page errors from this page. Subsequent calls to [Page#page_errors](./page#page_errors) will only return errors thrown after the clear.
1025
+
998
1026
  ## console_messages
999
1027
 
1000
1028
  ```
1001
- def console_messages
1029
+ def console_messages(filter: nil)
1002
1030
  ```
1003
1031
 
1004
1032
 
@@ -1007,7 +1035,7 @@ Returns up to (currently) 200 last console messages from this page. See [`event:
1007
1035
  ## page_errors
1008
1036
 
1009
1037
  ```
1010
- def page_errors
1038
+ def page_errors(filter: nil)
1011
1039
  ```
1012
1040
 
1013
1041
 
@@ -1132,6 +1160,23 @@ The `format` options are:
1132
1160
  **NOTE**: `headerTemplate` and `footerTemplate` markup have the following limitations: > 1. Script tags inside
1133
1161
  templates are not evaluated. > 2. Page styles are not visible inside templates.
1134
1162
 
1163
+ ## pick_locator
1164
+
1165
+ ```
1166
+ def pick_locator
1167
+ ```
1168
+
1169
+
1170
+ Enters pick locator mode where hovering over page elements highlights them and shows the corresponding locator.
1171
+ Once the user clicks an element, the mode is deactivated and the [Locator](./locator) for the picked element is returned.
1172
+
1173
+ **Usage**
1174
+
1175
+ ```ruby
1176
+ locator = page.pick_locator
1177
+ puts locator
1178
+ ```
1179
+
1135
1180
  ## press
1136
1181
 
1137
1182
  ```
@@ -1269,6 +1314,8 @@ end
1269
1314
  page.route("/api/**", method(:handle_route))
1270
1315
  ```
1271
1316
 
1317
+ If a request matches multiple registered routes, the most recently registered route takes precedence.
1318
+
1272
1319
  Page routes take precedence over browser context routes (set up with [BrowserContext#route](./browser_context#route)) when request
1273
1320
  matches both handlers.
1274
1321
 
@@ -1471,6 +1518,15 @@ page.viewport_size = { width: 640, height: 480 }
1471
1518
  page.goto("https://example.com")
1472
1519
  ```
1473
1520
 
1521
+ ## aria_snapshot
1522
+
1523
+ ```
1524
+ def aria_snapshot(depth: nil, mode: nil, timeout: nil)
1525
+ ```
1526
+
1527
+
1528
+ Captures the aria snapshot of the page. Read more about [aria snapshots](https://playwright.dev/python/docs/aria-snapshots).
1529
+
1474
1530
  ## tap_point
1475
1531
 
1476
1532
  ```
@@ -1600,7 +1656,7 @@ def video
1600
1656
  ```
1601
1657
 
1602
1658
 
1603
- Video object associated with this page.
1659
+ Video object associated with this page. Can be used to access the video file when using the `recordVideo` context option.
1604
1660
 
1605
1661
  ## viewport_size
1606
1662
 
@@ -1943,4 +1999,11 @@ Playwright has ability to mock clock and passage of time.
1943
1999
  API testing helper associated with this page. This method returns the same instance as
1944
2000
  [BrowserContext#request](./browser_context#request) on the page's context. See [BrowserContext#request](./browser_context#request) for more details.
1945
2001
 
2002
+ ## screencast
2003
+
2004
+
2005
+ `Screencast` object associated with this page.
2006
+
2007
+ **Usage**
2008
+
1946
2009
  ## touchscreen
@@ -216,6 +216,19 @@ def response
216
216
 
217
217
  Returns the matching [Response](./response) object, or `null` if the response was not received due to error.
218
218
 
219
+ ## existing_response
220
+
221
+ ```
222
+ def existing_response
223
+ ```
224
+
225
+
226
+ Returns the [Response](./response) object if the response has already been received, `null` otherwise.
227
+
228
+ Unlike [Request#response](./request#response), this method does not wait for the response to arrive. It returns
229
+ immediately with the response object if the response has been received, or `null` if the response
230
+ has not been received yet.
231
+
219
232
  ## sizes
220
233
 
221
234
  ```
@@ -92,6 +92,15 @@ def header_values(name)
92
92
 
93
93
  Returns all values of the headers matching the name, for example `set-cookie`. The name is case-insensitive.
94
94
 
95
+ ## http_version
96
+
97
+ ```
98
+ def http_version
99
+ ```
100
+
101
+
102
+ Returns the http version used by the response.
103
+
95
104
  ## json
96
105
 
97
106
  ```
@@ -26,6 +26,7 @@ end
26
26
 
27
27
  ```
28
28
  def start(
29
+ live: nil,
29
30
  name: nil,
30
31
  screenshots: nil,
31
32
  snapshots: nil,
@@ -85,6 +85,52 @@ describe 'example', driver: :null do
85
85
  end
86
86
  ```
87
87
 
88
+ ## Share one browser across all tests
89
+
90
+ Launching a new browser for every test is slow. A better approach is to launch the browser once for the entire suite and give each test its own `BrowserContext` for isolation (fresh cookies, localStorage, and session state).
91
+
92
+ The challenge is that `Playwright.create` and `playwright.chromium.launch` are block-scoped APIs — the browser shuts down when the block exits. RSpec has `before(:suite)` and `after(:suite)` but no `around(:suite)`. A `Fiber` bridges the gap: `start!` resumes the fiber until it yields the browser back, and `stop!` resumes it again so both blocks exit cleanly.
93
+
94
+ ```rb
95
+ module PlaywrightBrowser
96
+ class << self
97
+ attr_reader :browser
98
+
99
+ def start!
100
+ @fiber = Fiber.new do
101
+ Playwright.create(playwright_cli_executable_path: './node_modules/.bin/playwright') do |playwright|
102
+ playwright.chromium.launch(headless: false) do |browser|
103
+ Fiber.yield(browser)
104
+ end
105
+ end
106
+ end
107
+ @browser = @fiber.resume
108
+ end
109
+
110
+ def stop!
111
+ @fiber.resume
112
+ end
113
+ end
114
+ end
115
+
116
+ RSpec.configure do |config|
117
+ config.before(:suite) { PlaywrightBrowser.start! }
118
+ config.after(:suite) { PlaywrightBrowser.stop! }
119
+
120
+ config.around(driver: :null) do |example|
121
+ Capybara.current_driver = :null
122
+ base_url = Capybara.current_session.server.base_url
123
+
124
+ PlaywrightBrowser.browser.new_context(baseURL: base_url) do |browser_context|
125
+ @playwright_page = browser_context.new_page
126
+ example.run
127
+ end
128
+ end
129
+ end
130
+ ```
131
+
132
+ Each test gets a fresh `BrowserContext` (equivalent to a new incognito window), so cookies and storage never leak between tests. The block form of `new_context` ensures the context is always closed — even if the test raises an exception.
133
+
88
134
  ## Minitest Usage
89
135
 
90
136
  We can do something similar with the default Rails setup using Minitest. Here's the same example written with Minitest:
@@ -17,6 +17,7 @@
17
17
  * redirected_to
18
18
  * resource_type
19
19
  * response
20
+ * existing_response
20
21
  * ~~service_worker~~
21
22
  * sizes
22
23
  * timing
@@ -33,6 +34,7 @@
33
34
  * headers_array
34
35
  * header_value
35
36
  * header_values
37
+ * http_version
36
38
  * json
37
39
  * ok
38
40
  * request
@@ -228,6 +230,7 @@
228
230
  * location
229
231
  * page
230
232
  * text
233
+ * timestamp
231
234
  * type
232
235
  * worker
233
236
 
@@ -257,6 +260,7 @@
257
260
  * add_script_tag
258
261
  * add_style_tag
259
262
  * bring_to_front
263
+ * cancel_pick_locator
260
264
  * check
261
265
  * click
262
266
  * close
@@ -300,6 +304,8 @@
300
304
  * enabled?
301
305
  * hidden?
302
306
  * visible?
307
+ * clear_console_messages
308
+ * clear_page_errors
303
309
  * console_messages
304
310
  * page_errors
305
311
  * locator
@@ -307,6 +313,7 @@
307
313
  * opener
308
314
  * pause
309
315
  * pdf
316
+ * pick_locator
310
317
  * press
311
318
  * query_selector
312
319
  * query_selector_all
@@ -326,6 +333,7 @@
326
333
  * set_extra_http_headers
327
334
  * set_input_files
328
335
  * set_viewport_size
336
+ * aria_snapshot
329
337
  * tap_point
330
338
  * text_content
331
339
  * title
@@ -358,6 +366,7 @@
358
366
  * keyboard
359
367
  * mouse
360
368
  * request
369
+ * screencast
361
370
  * touchscreen
362
371
 
363
372
  ## BrowserContext
@@ -373,6 +382,7 @@
373
382
  * expose_binding
374
383
  * expose_function
375
384
  * grant_permissions
385
+ * closed?
376
386
  * new_cdp_session
377
387
  * new_page
378
388
  * pages
@@ -386,6 +396,7 @@
386
396
  * set_geolocation
387
397
  * set_offline
388
398
  * storage_state
399
+ * set_storage_state
389
400
  * unroute_all
390
401
  * unroute
391
402
  * expect_console_message
@@ -393,6 +404,7 @@
393
404
  * expect_page
394
405
  * ~~wait_for_event~~
395
406
  * clock
407
+ * ~~debugger~~
396
408
  * request
397
409
  * tracing
398
410
 
@@ -410,8 +422,10 @@
410
422
  * new_browser_cdp_session
411
423
  * new_context
412
424
  * new_page
425
+ * ~~bind~~
413
426
  * start_tracing
414
427
  * stop_tracing
428
+ * ~~unbind~~
415
429
  * version
416
430
 
417
431
  ## BrowserType
@@ -492,6 +506,7 @@
492
506
  * visible?
493
507
  * last
494
508
  * locator
509
+ * normalize
495
510
  * nth
496
511
  * or
497
512
  * page
@@ -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
@@ -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