playwright-ruby-client 1.20.2 → 1.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/api_request_context.md +15 -2
  3. data/documentation/docs/api/browser.md +16 -0
  4. data/documentation/docs/api/browser_context.md +15 -2
  5. data/documentation/docs/api/browser_type.md +5 -1
  6. data/documentation/docs/api/console_message.md +27 -1
  7. data/documentation/docs/api/element_handle.md +23 -13
  8. data/documentation/docs/api/experimental/android.md +1 -1
  9. data/documentation/docs/api/experimental/android_device.md +4 -0
  10. data/documentation/docs/api/file_chooser.md +1 -1
  11. data/documentation/docs/api/frame.md +12 -5
  12. data/documentation/docs/api/frame_locator.md +1 -1
  13. data/documentation/docs/api/locator.md +44 -13
  14. data/documentation/docs/api/page.md +32 -9
  15. data/documentation/docs/api/request.md +3 -1
  16. data/documentation/docs/api/response.md +12 -1
  17. data/documentation/docs/api/route.md +67 -0
  18. data/documentation/docs/include/api_coverage.md +6 -3
  19. data/documentation/package.json +6 -6
  20. data/documentation/yarn.lock +2931 -3220
  21. data/lib/playwright/channel.rb +1 -3
  22. data/lib/playwright/channel_owners/browser.rb +13 -0
  23. data/lib/playwright/channel_owners/browser_context.rb +89 -13
  24. data/lib/playwright/channel_owners/browser_type.rb +4 -0
  25. data/lib/playwright/channel_owners/element_handle.rb +12 -3
  26. data/lib/playwright/channel_owners/frame.rb +20 -7
  27. data/lib/playwright/channel_owners/local_utils.rb +29 -0
  28. data/lib/playwright/channel_owners/page.rb +54 -22
  29. data/lib/playwright/channel_owners/request.rb +31 -6
  30. data/lib/playwright/channel_owners/response.rb +6 -0
  31. data/lib/playwright/channel_owners/route.rb +104 -45
  32. data/lib/playwright/channel_owners/writable_stream.rb +14 -0
  33. data/lib/playwright/connection.rb +6 -1
  34. data/lib/playwright/har_router.rb +82 -0
  35. data/lib/playwright/http_headers.rb +1 -1
  36. data/lib/playwright/input_files.rb +60 -8
  37. data/lib/playwright/javascript/regex.rb +23 -0
  38. data/lib/playwright/javascript/value_parser.rb +17 -2
  39. data/lib/playwright/javascript/value_serializer.rb +16 -6
  40. data/lib/playwright/javascript/visitor_info.rb +26 -0
  41. data/lib/playwright/javascript.rb +1 -0
  42. data/lib/playwright/locator_impl.rb +18 -5
  43. data/lib/playwright/playwright_api.rb +26 -6
  44. data/lib/playwright/route_handler.rb +2 -6
  45. data/lib/playwright/transport.rb +12 -2
  46. data/lib/playwright/utils.rb +31 -6
  47. data/lib/playwright/version.rb +2 -2
  48. data/lib/playwright.rb +2 -0
  49. data/lib/playwright_api/accessibility.rb +2 -1
  50. data/lib/playwright_api/android.rb +2 -2
  51. data/lib/playwright_api/android_device.rb +5 -1
  52. data/lib/playwright_api/api_request.rb +3 -3
  53. data/lib/playwright_api/api_request_context.rb +15 -2
  54. data/lib/playwright_api/browser.rb +15 -2
  55. data/lib/playwright_api/browser_context.rb +17 -7
  56. data/lib/playwright_api/browser_type.rb +7 -3
  57. data/lib/playwright_api/console_message.rb +20 -1
  58. data/lib/playwright_api/element_handle.rb +53 -49
  59. data/lib/playwright_api/file_chooser.rb +1 -1
  60. data/lib/playwright_api/frame.rb +30 -23
  61. data/lib/playwright_api/frame_locator.rb +1 -1
  62. data/lib/playwright_api/locator.rb +58 -38
  63. data/lib/playwright_api/page.rb +52 -32
  64. data/lib/playwright_api/playwright.rb +1 -1
  65. data/lib/playwright_api/request.rb +8 -1
  66. data/lib/playwright_api/response.rb +14 -1
  67. data/lib/playwright_api/route.rb +63 -2
  68. data/lib/playwright_api/selectors.rb +1 -1
  69. data/lib/playwright_api/tracing.rb +1 -1
  70. metadata +7 -4
  71. data/lib/playwright_api/local_utils.rb +0 -9
@@ -3,13 +3,28 @@ require 'mime/types'
3
3
 
4
4
  module Playwright
5
5
  define_channel_owner :Route do
6
+ private def set_handling_future(future)
7
+ @handling_future = future
8
+ end
9
+
10
+ private def handling_with_result(done, &block)
11
+ chain = @handling_future
12
+ raise 'Route is already handled!' unless chain
13
+ block.call
14
+ @handling_future = nil
15
+ chain.fulfill(done)
16
+ end
17
+
6
18
  def request
7
19
  ChannelOwners::Request.from(@initializer['request'])
8
20
  end
9
21
 
10
22
  def abort(errorCode: nil)
11
- params = { errorCode: errorCode }.compact
12
- @channel.async_send_message_to_server('abort', params)
23
+ handling_with_result(true) do
24
+ params = { errorCode: errorCode }.compact
25
+ # TODO _race_with_page_close
26
+ @channel.async_send_message_to_server('abort', params)
27
+ end
13
28
  end
14
29
 
15
30
  def fulfill(
@@ -19,69 +34,113 @@ module Playwright
19
34
  path: nil,
20
35
  status: nil,
21
36
  response: nil)
22
- params = {
23
- contentType: contentType,
24
- status: status,
25
- }.compact
26
- option_body = body
37
+ handling_with_result(true) do
38
+ params = {
39
+ contentType: contentType,
40
+ status: status,
41
+ }.compact
42
+ option_body = body
27
43
 
28
- if response
29
- params[:status] ||= response.status
30
- params[:headers] ||= response.headers
44
+ if response
45
+ params[:status] ||= response.status
46
+ params[:headers] ||= response.headers
31
47
 
32
- if !body && !path && response.is_a?(APIResponse)
33
- if response.send(:_request).send(:same_connection?, self)
34
- params[:fetchResponseUid] = response.send(:fetch_uid)
35
- else
36
- option_body = response.body
48
+ if !body && !path && response.is_a?(APIResponse)
49
+ if response.send(:_request).send(:same_connection?, self)
50
+ params[:fetchResponseUid] = response.send(:fetch_uid)
51
+ else
52
+ option_body = response.body
53
+ end
37
54
  end
38
55
  end
39
- end
40
56
 
41
- content =
42
- if option_body
43
- option_body
57
+ content =
58
+ if option_body
59
+ option_body
60
+ elsif path
61
+ File.read(path)
62
+ else
63
+ nil
64
+ end
65
+
66
+ param_headers = headers || {}
67
+ if contentType
68
+ param_headers['content-type'] = contentType
44
69
  elsif path
45
- File.read(path)
46
- else
47
- nil
70
+ param_headers['content-type'] = mime_type_for(path)
48
71
  end
49
72
 
50
- param_headers = headers || {}
51
- if contentType
52
- param_headers['content-type'] = contentType
53
- elsif path
54
- param_headers['content-type'] = mime_type_for(path)
55
- end
56
-
57
- if content
58
- if content.is_a?(String)
59
- params[:body] = content
60
- params[:isBase64] = false
61
- else
62
- params[:body] = Base64.strict_encode64(content)
63
- params[:isBase64] = true
73
+ if content
74
+ if content.is_a?(String)
75
+ params[:body] = content
76
+ params[:isBase64] = false
77
+ else
78
+ params[:body] = Base64.strict_encode64(content)
79
+ params[:isBase64] = true
80
+ end
81
+ param_headers['content-length'] ||= content.length.to_s
64
82
  end
65
- param_headers['content-length'] ||= content.length.to_s
83
+
84
+ params[:headers] = HttpHeaders.new(param_headers).as_serialized
85
+
86
+ @channel.async_send_message_to_server('fulfill', params)
66
87
  end
88
+ end
67
89
 
68
- params[:headers] = HttpHeaders.new(param_headers).as_serialized
90
+ def fallback(headers: nil, method: nil, postData: nil, url: nil)
91
+ overrides = {
92
+ headers: headers,
93
+ method: method,
94
+ postData: postData,
95
+ url: url,
96
+ }.compact
69
97
 
70
- @channel.async_send_message_to_server('fulfill', params)
98
+ handling_with_result(false) do
99
+ request.apply_fallback_overrides(overrides)
100
+ end
71
101
  end
72
102
 
73
103
  def continue(headers: nil, method: nil, postData: nil, url: nil)
74
- overrides = { url: url, method: method }.compact
104
+ overrides = {
105
+ headers: headers,
106
+ method: method,
107
+ postData: postData,
108
+ url: url,
109
+ }.compact
75
110
 
76
- if headers
77
- overrides[:headers] = HttpHeaders.new(headers).as_serialized
111
+ handling_with_result(true) do
112
+ request.apply_fallback_overrides(overrides)
113
+ async_continue_route
78
114
  end
115
+ end
79
116
 
80
- if postData
81
- overrides[:postData] = Base64.strict_encode64(postData)
117
+ private def async_continue_route
118
+ post_data_for_wire =
119
+ if (post_data_from_overrides = request.send(:fallback_overrides)[:postData])
120
+ post_data_for_wire = Base64.strict_encode64(post_data_from_overrides)
121
+ else
122
+ nil
123
+ end
124
+
125
+ params = request.send(:fallback_overrides).dup
126
+
127
+ if params[:headers]
128
+ params[:headers] = HttpHeaders.new(params[:headers]).as_serialized
129
+ end
130
+
131
+ if post_data_for_wire
132
+ params[:postData] = post_data_for_wire
82
133
  end
83
134
 
84
- @channel.async_send_message_to_server('continue', overrides)
135
+ # TODO _race_with_page_close
136
+ @channel.async_send_message_to_server('continue', params)
137
+ end
138
+
139
+ def redirect_navigation_request(url)
140
+ handling_with_result(true) do
141
+ # TODO _race_with_page_close
142
+ @channel.send_message_to_server('redirectNavigationRequest', { url: url })
143
+ end
85
144
  end
86
145
 
87
146
  private def mime_type_for(filepath)
@@ -0,0 +1,14 @@
1
+ require 'base64'
2
+
3
+ module Playwright
4
+ define_channel_owner :WritableStream do
5
+ # @param readable [File|IO]
6
+ def write(readable, bufsize = 1048576)
7
+ while buf = readable.read(bufsize)
8
+ binary = Base64.strict_encode64(buf)
9
+ @channel.send_message_to_server('write', binary: binary)
10
+ end
11
+ @channel.send_message_to_server('close')
12
+ end
13
+ end
14
+ end
@@ -24,6 +24,8 @@ module Playwright
24
24
  @remote = false
25
25
  end
26
26
 
27
+ attr_reader :local_utils
28
+
27
29
  def mark_as_remote
28
30
  @remote = true
29
31
  end
@@ -127,12 +129,15 @@ module Playwright
127
129
  params = msg['params']
128
130
 
129
131
  if method == "__create__"
130
- create_remote_object(
132
+ remote_object = create_remote_object(
131
133
  parent_guid: guid,
132
134
  type: params["type"],
133
135
  guid: params["guid"],
134
136
  initializer: params["initializer"],
135
137
  )
138
+ if remote_object.is_a?(ChannelOwners::LocalUtils)
139
+ @local_utils = remote_object
140
+ end
136
141
  return
137
142
  end
138
143
 
@@ -0,0 +1,82 @@
1
+ module Playwright
2
+ class HarRouter
3
+ # @param local_utils [LocalUtils]
4
+ # @param file [String]
5
+ # @param not_found_action [String] 'abort' or 'fallback'
6
+ # @param url_match [String||Regexp|nil]
7
+ def self.create(local_utils, file, not_found_action, url_match: nil)
8
+ har_id = local_utils.har_open(file)
9
+
10
+ new(
11
+ local_utils: local_utils,
12
+ har_id: har_id,
13
+ not_found_action: not_found_action,
14
+ url_match: url_match,
15
+ )
16
+ end
17
+
18
+ # @param local_utils [LocalUtils]
19
+ # @param har_id [String]
20
+ # @param not_found_action [String] 'abort' or 'fallback'
21
+ # @param url_match [String||Regexp|nil]
22
+ def initialize(local_utils:, har_id:, not_found_action:, url_match: nil)
23
+ unless ['abort', 'fallback'].include?(not_found_action)
24
+ raise ArgumentError.new("not_found_action must be either 'abort' or 'fallback'. '#{not_found_action}' is specified.")
25
+ end
26
+
27
+ @local_utils = local_utils
28
+ @har_id = har_id
29
+ @not_found_action = not_found_action
30
+ @url_match = url_match || '**/*'
31
+ @debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
32
+ end
33
+
34
+ private def handle(route, request)
35
+ response = @local_utils.har_lookup(
36
+ har_id: @har_id,
37
+ url: request.url,
38
+ method: request.method,
39
+ headers: request.headers_array,
40
+ post_data: request.post_data_buffer,
41
+ is_navigation_request: request.navigation_request?,
42
+ )
43
+ case response['action']
44
+ when 'redirect'
45
+ redirect_url = response['redirectURL']
46
+ puts "pw:api HAR: #{request.url} redirected to #{redirect_url}" if @debug
47
+ route.redirect_navigation_request(redirect_url)
48
+ when 'fulfill'
49
+ route.fulfill(
50
+ status: response['status'],
51
+ headers: response['headers'].map { |header| [header['name'], header['value']] }.to_h,
52
+ body: Base64.strict_decode64(response['body']),
53
+ )
54
+ else
55
+ # Report the error, but fall through to the default handler.
56
+ if response['action'] == 'error'
57
+ puts "pw:api HAR: #{response['message']} redirected to #{redirect_url}" if @debug
58
+ end
59
+
60
+ if @not_found_action == 'abort'
61
+ route.abort
62
+ else
63
+ route.fallback
64
+ end
65
+ end
66
+ end
67
+
68
+ def add_context_route(context)
69
+ context.route(@url_match, method(:handle))
70
+ context.once(Events::BrowserContext::Close, method(:dispose))
71
+ end
72
+
73
+ def add_page_route(page)
74
+ page.route(@url_match, method(:handle))
75
+ page.once(Events::Page::Close, method(:dispose))
76
+ end
77
+
78
+ def dispose
79
+ @local_utils.async_har_close(@har_id)
80
+ end
81
+ end
82
+ end
@@ -7,7 +7,7 @@ module Playwright
7
7
 
8
8
  def as_serialized
9
9
  @headers.map do |key, value|
10
- { name: key, value: value }
10
+ { 'name' => key, 'value' => value }
11
11
  end
12
12
  end
13
13
  end
@@ -2,18 +2,64 @@ require 'base64'
2
2
 
3
3
  module Playwright
4
4
  class InputFiles
5
- def initialize(files)
6
- @params = convert(files)
5
+ def initialize(context, files)
6
+ @context = context
7
+ if files.is_a?(Enumerable)
8
+ @files = files
9
+ else
10
+ @files = [files]
11
+ end
7
12
  end
8
13
 
9
- def as_params
10
- @params
14
+ def as_method_and_params
15
+ if has_large_file?
16
+ ['setInputFilePaths', params_for_set_input_file_paths]
17
+ else
18
+ ['setInputFiles', params_for_set_input_files]
19
+ end
11
20
  end
12
21
 
13
- private def convert(files)
14
- return convert([files]) unless files.is_a?(Array)
22
+ private def has_large_file?
23
+ max_bufsize = 1024 * 1024 # 1MB
15
24
 
16
- files.map do |file|
25
+ @files.any? do |file|
26
+ case file
27
+ when String
28
+ File::Stat.new(file).size > max_bufsize
29
+ when File
30
+ file.stat.size > max_bufsize
31
+ else
32
+ raise_argument_error
33
+ end
34
+ end
35
+ end
36
+
37
+ private def params_for_set_input_file_paths
38
+ writable_streams = @files.map do |file|
39
+ case file
40
+ when String
41
+ writable_stream = @context.send(:create_temp_file, File.basename(file))
42
+
43
+ File.open(file, 'rb') do |file|
44
+ writable_stream.write(file)
45
+ end
46
+
47
+ writable_stream.channel
48
+ when File
49
+ writable_stream = @context.send(:create_temp_file, File.basename(file.path))
50
+ writable_stream.write(file)
51
+
52
+ writable_stream.channel
53
+ else
54
+ raise_argument_error
55
+ end
56
+ end
57
+
58
+ { streams: writable_streams }
59
+ end
60
+
61
+ private def params_for_set_input_files
62
+ file_payloads = @files.map do |file|
17
63
  case file
18
64
  when String
19
65
  {
@@ -26,9 +72,15 @@ module Playwright
26
72
  buffer: Base64.strict_encode64(file.read),
27
73
  }
28
74
  else
29
- raise ArgumentError.new('file must be a String or File.')
75
+ raise_argument_error
30
76
  end
31
77
  end
78
+
79
+ { files: file_payloads }
80
+ end
81
+
82
+ private def raise_argument_error
83
+ raise ArgumentError.new('file must be a String or File.')
32
84
  end
33
85
  end
34
86
  end
@@ -0,0 +1,23 @@
1
+ module Playwright
2
+ module JavaScript
3
+ class Regex
4
+ def initialize(regexp)
5
+ unless regexp.is_a?(Regexp)
6
+ raise ArgumentError("Argument must be a Regexp: #{regexp} (#{regexp.class})")
7
+ end
8
+
9
+ @source = regexp.source
10
+ @flag = flag_for(regexp)
11
+ end
12
+
13
+ attr_reader :source, :flag
14
+
15
+ private def flag_for(regexp)
16
+ flags = []
17
+ flags << 'ms' if (regexp.options & Regexp::MULTILINE) != 0
18
+ flags << 'i' if (regexp.options & Regexp::IGNORECASE) != 0
19
+ flags.join('')
20
+ end
21
+ end
22
+ end
23
+ end
@@ -5,6 +5,7 @@ module Playwright
5
5
  class ValueParser
6
6
  def initialize(hash)
7
7
  @hash = hash
8
+ @refs = {}
8
9
  end
9
10
 
10
11
  # @return [Hash]
@@ -23,6 +24,10 @@ module Playwright
23
24
  return hash[key] if hash.key?(key)
24
25
  end
25
26
 
27
+ if hash.key?('ref')
28
+ return @refs[hash['ref']]
29
+ end
30
+
26
31
  if hash.key?('v')
27
32
  return case hash['v']
28
33
  when 'undefined'
@@ -55,11 +60,21 @@ module Playwright
55
60
  end
56
61
 
57
62
  if hash.key?('a')
58
- return hash['a'].map { |value| parse_hash(value) }
63
+ result = []
64
+ if hash['id']
65
+ @refs[hash['id']] = result
66
+ end
67
+ hash['a'].each { |value| result << parse_hash(value) }
68
+ return result
59
69
  end
60
70
 
61
71
  if hash.key?('o')
62
- return hash['o'].map { |obj| [obj['k'], parse_hash(obj['v'])] }.to_h
72
+ result = {}
73
+ if hash['id']
74
+ @refs[hash['id']] = result
75
+ end
76
+ hash['o'].each { |obj| result[obj['k']] = parse_hash(obj['v']) }
77
+ return result
63
78
  end
64
79
 
65
80
  if hash.key?('h')
@@ -1,8 +1,12 @@
1
+ require_relative './visitor_info'
2
+ require_relative './regex'
3
+
1
4
  module Playwright
2
5
  module JavaScript
3
6
  class ValueSerializer
4
7
  def initialize(ruby_value)
5
8
  @value = ruby_value
9
+ @visited = VisitorInfo.new
6
10
  end
7
11
 
8
12
  # @return [Hash]
@@ -37,14 +41,20 @@ module Playwright
37
41
  require 'time'
38
42
  { d: value.utc.iso8601 }
39
43
  when Regexp
40
- flags = []
41
- flags << 'ms' if (value.options & Regexp::MULTILINE) != 0
42
- flags << 'i' if (value.options & Regexp::IGNORECASE) != 0
43
- { r: { p: value.source, f: flags.join('') } }
44
+ regex_value = Regex.new(value)
45
+ { r: { p: regex_value.source, f: regex_value.flag } }
46
+ when -> (value) { @visited.ref(value) }
47
+ { ref: @visited.ref(value) }
44
48
  when Array
45
- { a: value.map { |v| serialize_value(v) } }
49
+ id = @visited.log(value)
50
+ result = []
51
+ value.each { |v| result << serialize_value(v) }
52
+ { a: result, id: id }
46
53
  when Hash
47
- { o: value.map { |key, v| { k: key, v: serialize_value(v) } } }
54
+ id = @visited.log(value)
55
+ result = []
56
+ value.each { |key, v| result << { k: key, v: serialize_value(v) } }
57
+ { o: result, id: id }
48
58
  else
49
59
  raise ArgumentError.new("Unexpected value: #{value}")
50
60
  end
@@ -0,0 +1,26 @@
1
+ module Playwright
2
+ module JavaScript
3
+ class VisitorInfo
4
+ def initialize
5
+ @data = {}
6
+ @last_id = 0
7
+ end
8
+
9
+ # returns [Integer|nil]
10
+ def ref(object)
11
+ @data[object]
12
+ end
13
+
14
+ def log(object)
15
+ if @data[object]
16
+ raise ArgumentError.new("Already visited")
17
+ end
18
+
19
+ id = @last_id + 1
20
+ @last_id = id # FIXME: should thread-safe
21
+
22
+ @data[object] = id
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,4 @@
1
1
  require_relative './javascript/expression'
2
2
  require_relative './javascript/value_parser'
3
3
  require_relative './javascript/value_serializer'
4
+ require_relative './javascript/regex'
@@ -31,11 +31,9 @@ module Playwright
31
31
 
32
32
  case hasText
33
33
  when Regexp
34
- source = EscapeWithQuotes.new(hasText.source, '"')
35
- flags = []
36
- flags << 'ms' if (hasText.options & Regexp::MULTILINE) != 0
37
- flags << 'i' if (hasText.options & Regexp::IGNORECASE) != 0
38
- selector_scopes << ":scope:text-matches(#{source}, \"#{flags.join('')}\")"
34
+ regex = JavaScript::Regex.new(hasText)
35
+ source = EscapeWithQuotes.new(regex.source, '"')
36
+ selector_scopes << ":scope:text-matches(#{source}, \"#{regex.flag}\")"
39
37
  when String
40
38
  text = EscapeWithQuotes.new(hasText, '"')
41
39
  selector_scopes << ":scope:has-text(#{text})"
@@ -239,6 +237,16 @@ module Playwright
239
237
  @frame.query_selector_all(@selector)
240
238
  end
241
239
 
240
+ def filter(has: nil, hasText: nil)
241
+ LocatorImpl.new(
242
+ frame: @frame,
243
+ timeout_settings: @timeout_settings,
244
+ selector: @selector,
245
+ hasText: hasText,
246
+ has: has,
247
+ )
248
+ end
249
+
242
250
  def first
243
251
  LocatorImpl.new(
244
252
  frame: @frame,
@@ -314,19 +322,24 @@ module Playwright
314
322
 
315
323
  def screenshot(
316
324
  animations: nil,
325
+ caret: nil,
317
326
  mask: nil,
318
327
  omitBackground: nil,
319
328
  path: nil,
320
329
  quality: nil,
330
+ scale: nil,
321
331
  timeout: nil,
322
332
  type: nil)
333
+
323
334
  with_element(timeout: timeout) do |handle, options|
324
335
  handle.screenshot(
325
336
  animations: animations,
337
+ caret: caret,
326
338
  mask: mask,
327
339
  omitBackground: omitBackground,
328
340
  path: path,
329
341
  quality: quality,
342
+ scale: scale,
330
343
  timeout: options[:timeout],
331
344
  type: type)
332
345
  end
@@ -122,21 +122,41 @@ module Playwright
122
122
  }
123
123
  end
124
124
 
125
- private def wrap_impl(object)
125
+ private def wrap_impl(object, visited: {})
126
126
  if object.is_a?(Array)
127
- object.map { |obj| wrap_impl(obj) }
127
+ unless visited[object]
128
+ visited[object] = []
129
+ object.each { |obj| visited[object] << wrap_impl(obj) }
130
+ end
131
+ visited[object]
128
132
  elsif object.is_a?(Hash)
129
- object.map { |key, obj| [key, wrap_impl(obj)] }.to_h
133
+ unless visited[object]
134
+ visited[object] = {}
135
+ object.each do |key, obj|
136
+ visited[object][key] = wrap_impl(obj, visited: visited)
137
+ end
138
+ end
139
+ visited[object]
130
140
  else
131
141
  ::Playwright::PlaywrightApi.wrap(object)
132
142
  end
133
143
  end
134
144
 
135
- private def unwrap_impl(object)
145
+ private def unwrap_impl(object, visited: {})
136
146
  if object.is_a?(Array)
137
- object.map { |obj| unwrap_impl(obj) }
147
+ unless visited[object]
148
+ visited[object] = []
149
+ object.each { |obj| visited[object] << unwrap_impl(obj) }
150
+ end
151
+ visited[object]
138
152
  elsif object.is_a?(Hash)
139
- object.map { |key, obj| [key, unwrap_impl(obj)] }.to_h
153
+ unless visited[object]
154
+ visited[object] = {}
155
+ object.each do |key, obj|
156
+ visited[object][key] = unwrap_impl(obj, visited: visited)
157
+ end
158
+ end
159
+ visited[object]
140
160
  elsif object.is_a?(PlaywrightApi)
141
161
  ::Playwright::PlaywrightApi.unwrap(object)
142
162
  else
@@ -50,12 +50,8 @@ module Playwright
50
50
  def async_handle(route, request)
51
51
  @counter.increment
52
52
 
53
- Concurrent::Promises.future do
54
- begin
55
- @handler.call(route, request)
56
- rescue => err
57
- puts err, err.backtrace
58
- end
53
+ Concurrent::Promises.future { @handler.call(route, request) }.rescue do |err|
54
+ puts err, err.backtrace
59
55
  end
60
56
  end
61
57