playwright-ruby-client 1.57.0 → 1.57.2.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 430aa6a1bab67d9edca2cce8f72c76578402ec3aab93f296366335d23894d3dd
4
- data.tar.gz: 0efe7aba9e4c2e45f6374c957dad31630e66157124f4b1b5dc2589815d39e6fc
3
+ metadata.gz: 6d2a03c73551065e1c8bf121c3784103fb51b5dd4d9fd3b8f63cb2e86907369a
4
+ data.tar.gz: 7a66180bf3f8eae379ae8a4cbd5e1d67d8531cdfc0689719cf96524f294c5b89
5
5
  SHA512:
6
- metadata.gz: 5d19738002e6edef932c3eba936a99ffc3b54d805640a806cb3e255542d56b72be7526cf69c6752ed409c1337899f9c132494335c76b0231601c5cd7a596a677
7
- data.tar.gz: 5864d506cb7f6cfc4d0a35a65900241ca7a686f2b5e59716f16e4d4f588e832153bcff19bf71fae82bda9b91e55a7c9ee1f974e33d1541ad1a49e78a51e4b341
6
+ metadata.gz: 7ec32a794ca503b5d95c147834bbd7dd0b0e6c4b8b803df15c35cbe6f2044062cbc709eab9641a55da9aff084f549b98296ffe64d20a44e0ad2a41099e521927
7
+ data.tar.gz: 6bfce7e393d7ab498639dca2342dfe3434b87ce0bee462b7e8c42017965ff9512e1d232de2126de0b92983dc0eb9faa1c084283eac1521b2bceb4a2d8b152be3
@@ -43,9 +43,9 @@ If Playwright is running in an independent container, with docker-compose.yaml c
43
43
 
44
44
  ```
45
45
  playwright: # this is our PLAYWRIGHT_HOST value
46
- image: mcr.microsoft.com/playwright:v1.56.1-noble
46
+ image: mcr.microsoft.com/playwright:v1.57.0-noble
47
47
  command: >
48
- /bin/sh -c "npx -y playwright@1.56.1 run-server --port 3000 --host 0.0.0.0 --path /ws"
48
+ /bin/sh -c "npx -y playwright@1.57.0 run-server --port 3000 --host 0.0.0.0 --path /ws"
49
49
  init: true
50
50
  restart: unless-stopped
51
51
  ```
@@ -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
@@ -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)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.57.0'
4
+ VERSION = '1.57.2.alpha1'
5
5
  COMPATIBLE_PLAYWRIGHT_VERSION = '1.57.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)
@@ -195,13 +195,13 @@ 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 --
@@ -464,28 +464,28 @@ module Playwright
464
464
  end
465
465
 
466
466
  # @nodoc
467
- def enable_debug_console!
468
- wrap_impl(@impl.enable_debug_console!)
467
+ def owner_page=(req)
468
+ wrap_impl(@impl.owner_page=(unwrap_impl(req)))
469
469
  end
470
470
 
471
471
  # @nodoc
472
- def pause
473
- wrap_impl(@impl.pause)
472
+ def browser=(req)
473
+ wrap_impl(@impl.browser=(unwrap_impl(req)))
474
474
  end
475
475
 
476
476
  # @nodoc
477
- def owner_page=(req)
478
- wrap_impl(@impl.owner_page=(unwrap_impl(req)))
477
+ def options=(req)
478
+ wrap_impl(@impl.options=(unwrap_impl(req)))
479
479
  end
480
480
 
481
481
  # @nodoc
482
- def options=(req)
483
- wrap_impl(@impl.options=(unwrap_impl(req)))
482
+ def enable_debug_console!
483
+ wrap_impl(@impl.enable_debug_console!)
484
484
  end
485
485
 
486
486
  # @nodoc
487
- def browser=(req)
488
- wrap_impl(@impl.browser=(unwrap_impl(req)))
487
+ def pause
488
+ wrap_impl(@impl.pause)
489
489
  end
490
490
 
491
491
  # -- inherited from EventEmitter --
@@ -1287,13 +1287,13 @@ module Playwright
1287
1287
  end
1288
1288
 
1289
1289
  # @nodoc
1290
- def expect(expression, options, title)
1291
- wrap_impl(@impl.expect(unwrap_impl(expression), unwrap_impl(options), unwrap_impl(title)))
1290
+ def resolve_selector
1291
+ wrap_impl(@impl.resolve_selector)
1292
1292
  end
1293
1293
 
1294
1294
  # @nodoc
1295
- def resolve_selector
1296
- wrap_impl(@impl.resolve_selector)
1295
+ def expect(expression, options, title)
1296
+ wrap_impl(@impl.expect(unwrap_impl(expression), unwrap_impl(options), unwrap_impl(title)))
1297
1297
  end
1298
1298
 
1299
1299
  # @nodoc
@@ -1828,23 +1828,23 @@ module Playwright
1828
1828
  end
1829
1829
 
1830
1830
  # @nodoc
1831
- def start_css_coverage(resetOnNavigation: nil, reportAnonymousScripts: nil)
1832
- wrap_impl(@impl.start_css_coverage(resetOnNavigation: unwrap_impl(resetOnNavigation), reportAnonymousScripts: unwrap_impl(reportAnonymousScripts)))
1831
+ def stop_css_coverage
1832
+ wrap_impl(@impl.stop_css_coverage)
1833
1833
  end
1834
1834
 
1835
1835
  # @nodoc
1836
- def stop_css_coverage
1837
- wrap_impl(@impl.stop_css_coverage)
1836
+ def stop_js_coverage
1837
+ wrap_impl(@impl.stop_js_coverage)
1838
1838
  end
1839
1839
 
1840
1840
  # @nodoc
1841
- def owned_context=(req)
1842
- wrap_impl(@impl.owned_context=(unwrap_impl(req)))
1841
+ def start_css_coverage(resetOnNavigation: nil, reportAnonymousScripts: nil)
1842
+ wrap_impl(@impl.start_css_coverage(resetOnNavigation: unwrap_impl(resetOnNavigation), reportAnonymousScripts: unwrap_impl(reportAnonymousScripts)))
1843
1843
  end
1844
1844
 
1845
1845
  # @nodoc
1846
- def stop_js_coverage
1847
- wrap_impl(@impl.stop_js_coverage)
1846
+ def owned_context=(req)
1847
+ wrap_impl(@impl.owned_context=(unwrap_impl(req)))
1848
1848
  end
1849
1849
 
1850
1850
  # -- inherited from EventEmitter --
@@ -64,13 +64,13 @@ module Playwright
64
64
  end
65
65
 
66
66
  # @nodoc
67
- def context=(req)
68
- wrap_impl(@impl.context=(unwrap_impl(req)))
67
+ def page=(req)
68
+ wrap_impl(@impl.page=(unwrap_impl(req)))
69
69
  end
70
70
 
71
71
  # @nodoc
72
- def page=(req)
73
- wrap_impl(@impl.page=(unwrap_impl(req)))
72
+ def context=(req)
73
+ wrap_impl(@impl.context=(unwrap_impl(req)))
74
74
  end
75
75
 
76
76
  # -- inherited from EventEmitter --
data/playwright.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.require_paths = ['lib']
26
26
 
27
27
  spec.required_ruby_version = '>= 2.4'
28
+ spec.add_dependency 'base64'
28
29
  spec.add_dependency 'concurrent-ruby', '>= 1.1.6'
29
30
  spec.add_dependency 'mime-types', '>= 3.0'
30
31
  spec.add_development_dependency 'bundler'
@@ -32,6 +33,7 @@ Gem::Specification.new do |spec|
32
33
  spec.add_development_dependency 'dry-inflector'
33
34
  spec.add_development_dependency 'faye-websocket'
34
35
  spec.add_development_dependency 'pry-byebug'
36
+ spec.add_development_dependency 'ostruct'
35
37
  spec.add_development_dependency 'puma'
36
38
  spec.add_development_dependency 'rack', '< 3'
37
39
  spec.add_development_dependency 'rake'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: playwright-ruby-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.57.0
4
+ version: 1.57.2.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: concurrent-ruby
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -107,6 +121,20 @@ dependencies:
107
121
  - - ">="
108
122
  - !ruby/object:Gem::Version
109
123
  version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: ostruct
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
110
138
  - !ruby/object:Gem::Dependency
111
139
  name: puma
112
140
  requirement: !ruby/object:Gem::Requirement