playwright-ruby-client 1.57.1 → 1.58.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/browser_type.md +1 -2
  3. data/documentation/docs/api/locator.md +1 -1
  4. data/documentation/docs/api/locator_assertions.md +1 -1
  5. data/documentation/docs/api/route.md +4 -1
  6. data/documentation/docs/article/getting_started.md +1 -1
  7. data/documentation/docs/article/guides/inspector.md +1 -1
  8. data/documentation/docs/article/guides/launch_browser.md +5 -5
  9. data/documentation/docs/article/guides/rails_integration.md +1 -1
  10. data/documentation/docs/article/guides/recording_video.md +2 -2
  11. data/documentation/docs/article/guides/semi_automation.md +1 -1
  12. data/documentation/docs/include/api_coverage.md +1 -0
  13. data/lib/playwright/channel_owners/binding_call.rb +37 -3
  14. data/lib/playwright/channel_owners/browser_type.rb +2 -1
  15. data/lib/playwright/channel_owners/page.rb +4 -1
  16. data/lib/playwright/channel_owners/web_socket.rb +14 -0
  17. data/lib/playwright/connection.rb +25 -20
  18. data/lib/playwright/javascript/value_serializer.rb +2 -1
  19. data/lib/playwright/locator_assertions_impl.rb +1 -1
  20. data/lib/playwright/page_assertions_impl.rb +1 -1
  21. data/lib/playwright/version.rb +2 -2
  22. data/lib/playwright/waiter.rb +24 -6
  23. data/lib/playwright_api/android.rb +4 -4
  24. data/lib/playwright_api/android_device.rb +8 -8
  25. data/lib/playwright_api/api_request_context.rb +4 -4
  26. data/lib/playwright_api/browser.rb +4 -4
  27. data/lib/playwright_api/browser_context.rb +14 -14
  28. data/lib/playwright_api/browser_type.rb +8 -9
  29. data/lib/playwright_api/cdp_session.rb +4 -4
  30. data/lib/playwright_api/dialog.rb +4 -4
  31. data/lib/playwright_api/element_handle.rb +4 -4
  32. data/lib/playwright_api/frame.rb +4 -4
  33. data/lib/playwright_api/js_handle.rb +4 -4
  34. data/lib/playwright_api/locator.rb +5 -5
  35. data/lib/playwright_api/locator_assertions.rb +1 -1
  36. data/lib/playwright_api/page.rb +9 -9
  37. data/lib/playwright_api/playwright.rb +4 -4
  38. data/lib/playwright_api/request.rb +16 -4
  39. data/lib/playwright_api/response.rb +8 -8
  40. data/lib/playwright_api/route.rb +8 -5
  41. data/lib/playwright_api/tracing.rb +4 -4
  42. data/lib/playwright_api/web_socket.rb +4 -4
  43. data/lib/playwright_api/worker.rb +8 -8
  44. data/sig/playwright.rbs +4 -4
  45. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 197bc18ed9dd0a6e9f6423e6aa92223e5be7437a51fecb4ce47c3c9527554f20
4
- data.tar.gz: 58f407a7c57dc14d18f3dbfce83f17a22a5fa18d8b2765550cbd1ffd5c68c387
3
+ metadata.gz: '08aec35a2cb057d2af0f12a380e0a56393671043a9f4f1cf2af5315009fe1172'
4
+ data.tar.gz: cc722558227d085896e30d6819c391404770d319da4a6bb10b5ef72ed2317212
5
5
  SHA512:
6
- metadata.gz: 1d8f83fdef9367e6d4e56d2d9f97e73993066f8be1cd40b9051bc90fa4586062a4253efa8db034c41ecfdc6196f30c90ad6dad7478974acef8a2d3c039b8d939
7
- data.tar.gz: 59bd13cb57ffcacae9b57b532b0b4193da3893801227924e2dd761f097b4ff9b367093e295f005d582165eaa5e12342a7e5b75511eb39dbba876365bb5c97b8b
6
+ metadata.gz: 3de09e030621846244bc6c59d26a7d18b00ad716f315fa4ea0ebffe93a2095a1b33401480a20617e26a28987bf11703f37a85a6be8dfb37dc6d11c2a9a5a574c
7
+ data.tar.gz: 29527ac83824caf1c2f4ce51e691fad3af9d3213f7d7d6df3877e25118981a3c9c5a62b44878434fb6cc61667d3c2088d4c36fef58b59c0a135e975cd634dd4d
@@ -25,6 +25,7 @@ end
25
25
  def connect_over_cdp(
26
26
  endpointURL,
27
27
  headers: nil,
28
+ isLocal: nil,
28
29
  slowMo: nil,
29
30
  timeout: nil,
30
31
  &block)
@@ -63,7 +64,6 @@ def launch(
63
64
  args: nil,
64
65
  channel: nil,
65
66
  chromiumSandbox: nil,
66
- devtools: nil,
67
67
  downloadsPath: nil,
68
68
  env: nil,
69
69
  executablePath: nil,
@@ -126,7 +126,6 @@ def launch_persistent_context(
126
126
  colorScheme: nil,
127
127
  contrast: nil,
128
128
  deviceScaleFactor: nil,
129
- devtools: nil,
130
129
  downloadsPath: nil,
131
130
  env: nil,
132
131
  executablePath: nil,
@@ -343,7 +343,7 @@ def description
343
343
  ```
344
344
 
345
345
 
346
- Returns locator description previously set with [Locator#describe](./locator#describe). Returns `null` if no custom description has been set. Prefer `Locator.toString()` for a human-readable representation, as it uses the description when available.
346
+ Returns locator description previously set with [Locator#describe](./locator#describe). Returns `null` if no custom description has been set.
347
347
 
348
348
  **Usage**
349
349
 
@@ -524,7 +524,7 @@ Let's see how we can use the assertion:
524
524
 
525
525
  ```ruby
526
526
  # ✓ Contains the right items in the right order
527
- expect(page.locator("ul > li")).to contain_text(["Text 1", "Text 3", "Text 4"])
527
+ expect(page.locator("ul > li")).to contain_text(["Text 1", "Text 3"])
528
528
 
529
529
  # ✖ Wrong order
530
530
  expect(page.locator("ul > li")).to contain_text(["Text 3", "Text 2"])
@@ -49,7 +49,10 @@ The `headers` option applies to both the routed request and any redirects it ini
49
49
 
50
50
  [Route#continue](./route#continue) will immediately send the request to the network, other matching handlers won't be invoked. Use [Route#fallback](./route#fallback) If you want next matching handler in the chain to be invoked.
51
51
 
52
- **NOTE**: The `Cookie` header cannot be overridden using this method. If a value is provided, it will be ignored, and the cookie will be loaded from the browser's cookie store. To set custom cookies, use [BrowserContext#add_cookies](./browser_context#add_cookies).
52
+ **NOTE**: Some request headers are **forbidden** and cannot be overridden (for example, `Cookie`, `Host`, `Content-Length` and others, see [this MDN page](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header) for full list).
53
+ If an override is provided for a forbidden header, it will be ignored and the original request header will be used.
54
+
55
+ To set custom cookies, use [BrowserContext#add_cookies](./browser_context#add_cookies).
53
56
 
54
57
  ## fallback
55
58
 
@@ -19,7 +19,7 @@ $ npx playwright install
19
19
 
20
20
  and then set `playwright_cli_executable_path: "npx playwright"` into `Playwright.create`.
21
21
 
22
- Other methods of installation is also available. See the detail in [Download Playwright driver](./guides/download_playwright_driver)
22
+ Other methods of installation are also available. See the detail in [Download Playwright driver](./guides/download_playwright_driver)
23
23
 
24
24
  ## Enjoy with examples
25
25
 
@@ -4,7 +4,7 @@ sidebar_position: 30
4
4
 
5
5
  # Playwright inspector
6
6
 
7
- Playwright provides an useful inspector.
7
+ Playwright provides a useful inspector.
8
8
  https://playwright.dev/docs/inspector/
9
9
 
10
10
  ## Overview
@@ -8,7 +8,7 @@ sidebar_position: 2
8
8
 
9
9
  ## Create Playwright session
10
10
 
11
- In oder to launch browser, it is required to create Playwright session.
11
+ In order to launch browser, it is required to create Playwright session.
12
12
 
13
13
  In previous examples,
14
14
 
@@ -18,7 +18,7 @@ Playwright.create(playwright_cli_executable_path: 'npx playwright') do |playwrig
18
18
  end
19
19
  ```
20
20
 
21
- this is the exact procedure for creating Playwright session. Choose either of method for creating the session.
21
+ this is the exact procedure for creating Playwright session. Choose either method for creating the session.
22
22
 
23
23
  ### Define scoped Playwright session with block
24
24
 
@@ -28,13 +28,13 @@ Playwright.create(playwright_cli_executable_path: 'npx playwright') do |playwrig
28
28
  end
29
29
  ```
30
30
 
31
- As is described repetedly, this is the recommended way for creating Playwright session. Even when any exception happens, Playwright session is safely ended on leaving the block.
31
+ As described repeatedly, this is the recommended way for creating a Playwright session. Even when any exception happens, Playwright session is safely ended on leaving the block.
32
32
 
33
33
  Internally `playwright run-driver` session is alive during the block.
34
34
 
35
35
  ### Define start/end of the Playwright session separately without block.
36
36
 
37
- Sometimes we have to define separated start/end definition. `playwright-ruby-client` also allows it.
37
+ Sometimes we have to define separate start/end definitions. `playwright-ruby-client` also allows it.
38
38
 
39
39
  ```rb
40
40
  class SomeClass
@@ -103,7 +103,7 @@ Also we can use `Browser#new_page` to create a new window and new tab at once.
103
103
 
104
104
  ```rb
105
105
  Playwright.create(playwright_cli_executable_path: 'npx playwright') do |playwright|
106
- playwright.chromium.launch(headless: false) do |browser| # Chromium task icon appers in.
106
+ playwright.chromium.launch(headless: false) do |browser| # Chromium task icon appears.
107
107
  context = browser.new_context # Prepare new window.
108
108
  page = context.new_page # Open new window and new tab here. (about:blank)
109
109
  page.goto('https://example.com') # Navigate to a site.
@@ -217,7 +217,7 @@ before do |example|
217
217
  end
218
218
  ```
219
219
 
220
- ![sceenrecord](https://user-images.githubusercontent.com/11763113/121126629-71b5f600-c863-11eb-8f88-7924ab669946.gif)
220
+ ![screenrecord](https://user-images.githubusercontent.com/11763113/121126629-71b5f600-c863-11eb-8f88-7924ab669946.gif)
221
221
 
222
222
  For more details, refer [Recording video](./recording_video.md#using-screen-recording-from-capybara-driver)
223
223
 
@@ -33,7 +33,7 @@ playwright.chromium.launch do |browser|
33
33
 
34
34
  Playwright puts videos on the directory specified at `record_video_dir`.
35
35
 
36
- The previous example uses [Dir#mktmpdir](https://docs.ruby-lang.org/ja/latest/method/Dir/s/mktmpdir.html) for storing videos into a temprary directory. Also we simply specify a relative or absolute path like `./my_videos/` or `/path/to/videos`.
36
+ The previous example uses [Dir#mktmpdir](https://docs.ruby-lang.org/ja/latest/method/Dir/s/mktmpdir.html) for storing videos into a temporary directory. Also we simply specify a relative or absolute path like `./my_videos/` or `/path/to/videos`.
37
37
 
38
38
  ## Getting video path and recorded video
39
39
 
@@ -42,7 +42,7 @@ This is really confusing for beginners, but in Playwright
42
42
  * We can get the video path **only when page is alive (before calling BrowserContext#close or Page#close)**
43
43
  * We can acquire the completely saved video **only after calling BrowserContext#close**
44
44
 
45
- So in most case, we have to store the video path in advance, and handle the saved video after BrowserContext is closed, as is shown the previous example code.
45
+ So in most cases, we have to store the video path in advance, and handle the saved video after BrowserContext is closed, as shown in the previous example code.
46
46
 
47
47
  ### Using `video#save_as(path)`
48
48
 
@@ -5,7 +5,7 @@ sidebar_position: 20
5
5
  # Semi-automation
6
6
 
7
7
  Playwright Browser context is isolated and not persisted by default. But we can also use persistent browser context using [BrowserType#launch_persistent_context](/docs/api/browser_type#launch_persistent_context).
8
- This allow us to intermediate into automation, for example
8
+ This allows us to intervene in automation, for example
9
9
 
10
10
  * Authenticate with OAuth2 manually before automation
11
11
  * Testing a page after some chrome extensions are installed manually
@@ -17,6 +17,7 @@
17
17
  * redirected_to
18
18
  * resource_type
19
19
  * response
20
+ * ~~service_worker~~
20
21
  * sizes
21
22
  * timing
22
23
  * url
@@ -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
@@ -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
@@ -98,7 +98,7 @@ module Playwright
98
98
 
99
99
  private def on_route(route)
100
100
  route.send(:update_context, self)
101
-
101
+ return if @close_was_called
102
102
  # It is not desired to use PlaywrightApi.wrap directly.
103
103
  # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
104
104
  # Just a workaround...
@@ -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
@@ -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
@@ -11,9 +11,8 @@ module Playwright
11
11
  dispatch(message)
12
12
  end
13
13
  @transport.on_driver_crashed do
14
- @callbacks.each_value do |callback|
15
- callback.reject(::Playwright::DriverCrashedError.new)
16
- end
14
+ callbacks = @callbacks_mutex.synchronize { @callbacks.values }
15
+ callbacks.each { |callback| callback.reject(::Playwright::DriverCrashedError.new) }
17
16
  raise ::Playwright::DriverCrashedError.new
18
17
  end
19
18
  @transport.on_driver_closed do
@@ -21,8 +20,10 @@ module Playwright
21
20
  end
22
21
 
23
22
  @objects = {} # Hash[ guid => ChannelOwner ]
23
+ @objects_mutex = Mutex.new
24
24
  @waiting_for_object = {} # Hash[ guid => Promise<ChannelOwner> ]
25
25
  @callbacks = {} # Hash [ guid => Promise<ChannelOwner> ]
26
+ @callbacks_mutex = Mutex.new
26
27
  @root_object = RootChannelOwner.new(self)
27
28
  @remote = false
28
29
  @tracing_count = 0
@@ -50,10 +51,10 @@ module Playwright
50
51
 
51
52
  def cleanup(cause: nil)
52
53
  @closed_error = TargetClosedError.new(message: cause)
53
- @callbacks.each_value do |callback|
54
- callback.reject(@closed_error)
54
+ callbacks = @callbacks_mutex.synchronize do
55
+ @callbacks.values.tap { @callbacks.clear }
55
56
  end
56
- @callbacks.clear
57
+ callbacks.each { |callback| callback.reject(@closed_error) }
57
58
  end
58
59
 
59
60
  def initialize_playwright
@@ -80,7 +81,7 @@ module Playwright
80
81
  with_generated_id do |id|
81
82
  # register callback promise object first.
82
83
  # @see https://github.com/YusukeIwaki/puppeteer-ruby/pull/34
83
- @callbacks[id] = callback
84
+ @callbacks_mutex.synchronize { @callbacks[id] = callback }
84
85
 
85
86
  _metadata = {}
86
87
  frames = []
@@ -107,12 +108,12 @@ module Playwright
107
108
  begin
108
109
  @transport.send_message(message)
109
110
  rescue => err
110
- @callbacks.delete(id)
111
+ @callbacks_mutex.synchronize { @callbacks.delete(id) }
111
112
  callback.reject(err)
112
113
  raise unless err.is_a?(Transport::AlreadyDisconnectedError)
113
114
  end
114
115
 
115
- if @tracing_count > 0 && !frames.empty? && guid != 'localUtils'
116
+ if @tracing_count > 0 && !frames.empty? && guid != 'localUtils' && !remote?
116
117
  @local_utils.add_stack_to_tracing_no_reply(id, frames)
117
118
  end
118
119
  end
@@ -132,21 +133,24 @@ module Playwright
132
133
  # end
133
134
  # ````
134
135
  def with_generated_id(&block)
135
- @last_id ||= 0
136
- block.call(@last_id += 1)
136
+ id = @callbacks_mutex.synchronize do
137
+ @last_id ||= 0
138
+ @last_id += 1
139
+ end
140
+ block.call(id)
137
141
  end
138
142
 
139
143
  # @param guid [String]
140
144
  # @param parent [Playwright::ChannelOwner]
141
145
  # @note This method should be used internally. Accessed via .send method from Playwright::ChannelOwner, so keep private!
142
146
  def update_object_from_channel_owner(guid, parent)
143
- @objects[guid] = parent
147
+ @objects_mutex.synchronize { @objects[guid] = parent }
144
148
  end
145
149
 
146
150
  # @param guid [String]
147
151
  # @note This method should be used internally. Accessed via .send method from Playwright::ChannelOwner, so keep private!
148
152
  def delete_object_from_channel_owner(guid)
149
- @objects.delete(guid)
153
+ @objects_mutex.synchronize { @objects.delete(guid) }
150
154
  end
151
155
 
152
156
  def dispatch(msg)
@@ -154,7 +158,7 @@ module Playwright
154
158
 
155
159
  id = msg['id']
156
160
  if id
157
- callback = @callbacks.delete(id)
161
+ callback = @callbacks_mutex.synchronize { @callbacks.delete(id) }
158
162
 
159
163
  unless callback
160
164
  raise "Cannot find command to respond: #{id}"
@@ -190,13 +194,13 @@ module Playwright
190
194
  return
191
195
  end
192
196
 
193
- object = @objects[guid]
197
+ object = @objects_mutex.synchronize { @objects[guid] }
194
198
  unless object
195
199
  raise "Cannot find object to \"#{method}\": #{guid}"
196
200
  end
197
201
 
198
202
  if method == "__adopt__"
199
- child = @objects[params["guid"]]
203
+ child = @objects_mutex.synchronize { @objects[params["guid"]] }
200
204
  unless child
201
205
  raise "Unknown new child: #{params['guid']}"
202
206
  end
@@ -243,8 +247,9 @@ module Playwright
243
247
 
244
248
  if payload.is_a?(Hash)
245
249
  guid = payload['guid']
246
- if guid && @objects[guid]
247
- return @objects[guid].channel
250
+ if guid
251
+ object = @objects_mutex.synchronize { @objects[guid] }
252
+ return object.channel if object
248
253
  end
249
254
 
250
255
  return payload.map { |k, v| [k, replace_guids_with_channels(v)] }.to_h
@@ -255,7 +260,7 @@ module Playwright
255
260
 
256
261
  # @return [Playwright::ChannelOwner|nil]
257
262
  def create_remote_object(parent_guid:, type:, guid:, initializer:)
258
- parent = @objects[parent_guid]
263
+ parent = @objects_mutex.synchronize { @objects[parent_guid] }
259
264
  unless parent
260
265
  raise "Cannot find parent object #{parent_guid} to create #{guid}"
261
266
  end
@@ -273,7 +278,7 @@ module Playwright
273
278
  raise "Missing type #{type}"
274
279
  end
275
280
 
276
- callback = @waiting_for_object.delete(guid)
281
+ callback = @objects_mutex.synchronize { @waiting_for_object.delete(guid) }
277
282
  callback&.fulfill(result)
278
283
 
279
284
  result
@@ -1,5 +1,6 @@
1
1
  require_relative './visitor_info'
2
2
  require_relative './regex'
3
+ require 'uri'
3
4
 
4
5
  module Playwright
5
6
  module JavaScript
@@ -60,7 +61,7 @@ module Playwright
60
61
  when Time
61
62
  require 'time'
62
63
  { d: value.utc.iso8601 }
63
- when URI
64
+ when ::URI
64
65
  { u: value.to_s }
65
66
  when Regexp
66
67
  regex_value = Regex.new(value)
@@ -21,7 +21,7 @@ module Playwright
21
21
  private def expect_impl(expression, expect_options, expected, message, title)
22
22
  expect_options[:timeout] ||= @default_expect_timeout
23
23
  expect_options[:isNot] = @is_not
24
- message.gsub!("expected to", "not expected to") if @is_not
24
+ message = message.gsub("expected to", "not expected to") if @is_not
25
25
  expect_options.delete(:useInnerText) if expect_options.key?(:useInnerText) && expect_options[:useInnerText].nil?
26
26
 
27
27
  result = @locator.expect(expression, expect_options, title)
@@ -22,7 +22,7 @@ module Playwright
22
22
  private def expect_impl(expression, expect_options, expected, message, title)
23
23
  expect_options[:timeout] ||= @default_expect_timeout
24
24
  expect_options[:isNot] = @is_not
25
- message.gsub!("expected to", "not expected to") if @is_not
25
+ message = message.gsub("expected to", "not expected to") if @is_not
26
26
  expect_options.delete(:useInnerText) if expect_options.key?(:useInnerText) && expect_options[:useInnerText].nil?
27
27
 
28
28
  result = @frame.expect(nil, expression, expect_options, title)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.57.1'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.57.0'
4
+ VERSION = '1.58.0'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.58.0'
6
6
  end
@@ -10,6 +10,7 @@ module Playwright
10
10
  @event = wait_name
11
11
  @channel = channel_owner.channel
12
12
  @registered_listeners = Set.new
13
+ @listeners_mutex = Mutex.new
13
14
  @logs = []
14
15
  wait_for_event_info_before
15
16
  end
@@ -50,8 +51,7 @@ module Playwright
50
51
  end
51
52
  end
52
53
  }
53
- emitter.on(event, listener)
54
- @registered_listeners << [emitter, event, listener]
54
+ register_listener(emitter, event, listener)
55
55
 
56
56
  self
57
57
  end
@@ -69,16 +69,22 @@ module Playwright
69
69
  end
70
70
 
71
71
  private def cleanup
72
- @registered_listeners.each do |emitter, event, listener|
72
+ listeners = @listeners_mutex.synchronize do
73
+ @registered_listeners.to_a.tap { @registered_listeners.clear }
74
+ end
75
+ listeners.each do |emitter, event, listener|
73
76
  emitter.off(event, listener)
74
77
  end
75
- @registered_listeners.clear
76
78
  end
77
79
 
78
80
  def force_fulfill(result)
79
81
  fulfill(result)
80
82
  end
81
83
 
84
+ def force_reject(error)
85
+ reject(error)
86
+ end
87
+
82
88
  private def fulfill(result)
83
89
  cleanup
84
90
  return if @result.resolved?
@@ -107,12 +113,24 @@ module Playwright
107
113
  reject(err)
108
114
  end
109
115
  }
110
- emitter.on(event, listener)
111
- @registered_listeners << [emitter, event, listener]
116
+ register_listener(emitter, event, listener)
112
117
 
113
118
  self
114
119
  end
115
120
 
121
+ private def register_listener(emitter, event, listener)
122
+ emitter.on(event, listener)
123
+ remove_later = false
124
+ @listeners_mutex.synchronize do
125
+ if @result.resolved?
126
+ remove_later = true
127
+ else
128
+ @registered_listeners << [emitter, event, listener]
129
+ end
130
+ end
131
+ emitter.off(event, listener) if remove_later
132
+ end
133
+
116
134
  attr_reader :result
117
135
 
118
136
  def log(message)
@@ -45,8 +45,8 @@ module Playwright
45
45
 
46
46
  # -- inherited from EventEmitter --
47
47
  # @nodoc
48
- def off(event, callback)
49
- event_emitter_proxy.off(event, callback)
48
+ def once(event, callback)
49
+ event_emitter_proxy.once(event, callback)
50
50
  end
51
51
 
52
52
  # -- inherited from EventEmitter --
@@ -57,8 +57,8 @@ module Playwright
57
57
 
58
58
  # -- inherited from EventEmitter --
59
59
  # @nodoc
60
- def once(event, callback)
61
- event_emitter_proxy.once(event, callback)
60
+ def off(event, callback)
61
+ event_emitter_proxy.off(event, callback)
62
62
  end
63
63
 
64
64
  private def event_emitter_proxy
@@ -195,19 +195,19 @@ module Playwright
195
195
  end
196
196
 
197
197
  # @nodoc
198
- def tap_on(selector, duration: nil, timeout: nil)
199
- wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
198
+ def should_close_connection_on_close!
199
+ wrap_impl(@impl.should_close_connection_on_close!)
200
200
  end
201
201
 
202
202
  # @nodoc
203
- def should_close_connection_on_close!
204
- wrap_impl(@impl.should_close_connection_on_close!)
203
+ def tap_on(selector, duration: nil, timeout: nil)
204
+ wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
205
205
  end
206
206
 
207
207
  # -- inherited from EventEmitter --
208
208
  # @nodoc
209
- def off(event, callback)
210
- event_emitter_proxy.off(event, callback)
209
+ def once(event, callback)
210
+ event_emitter_proxy.once(event, callback)
211
211
  end
212
212
 
213
213
  # -- inherited from EventEmitter --
@@ -218,8 +218,8 @@ module Playwright
218
218
 
219
219
  # -- inherited from EventEmitter --
220
220
  # @nodoc
221
- def once(event, callback)
222
- event_emitter_proxy.once(event, callback)
221
+ def off(event, callback)
222
+ event_emitter_proxy.off(event, callback)
223
223
  end
224
224
 
225
225
  private def event_emitter_proxy
@@ -288,8 +288,8 @@ module Playwright
288
288
 
289
289
  # -- inherited from EventEmitter --
290
290
  # @nodoc
291
- def off(event, callback)
292
- event_emitter_proxy.off(event, callback)
291
+ def once(event, callback)
292
+ event_emitter_proxy.once(event, callback)
293
293
  end
294
294
 
295
295
  # -- inherited from EventEmitter --
@@ -300,8 +300,8 @@ module Playwright
300
300
 
301
301
  # -- inherited from EventEmitter --
302
302
  # @nodoc
303
- def once(event, callback)
304
- event_emitter_proxy.once(event, callback)
303
+ def off(event, callback)
304
+ event_emitter_proxy.off(event, callback)
305
305
  end
306
306
 
307
307
  private def event_emitter_proxy
@@ -205,8 +205,8 @@ module Playwright
205
205
 
206
206
  # -- inherited from EventEmitter --
207
207
  # @nodoc
208
- def off(event, callback)
209
- event_emitter_proxy.off(event, callback)
208
+ def once(event, callback)
209
+ event_emitter_proxy.once(event, callback)
210
210
  end
211
211
 
212
212
  # -- inherited from EventEmitter --
@@ -217,8 +217,8 @@ module Playwright
217
217
 
218
218
  # -- inherited from EventEmitter --
219
219
  # @nodoc
220
- def once(event, callback)
221
- event_emitter_proxy.once(event, callback)
220
+ def off(event, callback)
221
+ event_emitter_proxy.off(event, callback)
222
222
  end
223
223
 
224
224
  private def event_emitter_proxy