playwright-ruby-client 1.39.0 → 1.40.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/api_request_context.md +1 -2
  3. data/documentation/docs/api/browser.md +10 -14
  4. data/documentation/docs/api/browser_context.md +32 -51
  5. data/documentation/docs/api/browser_type.md +8 -12
  6. data/documentation/docs/api/dialog.md +15 -18
  7. data/documentation/docs/api/download.md +10 -12
  8. data/documentation/docs/api/element_handle.md +9 -7
  9. data/documentation/docs/api/frame.md +28 -55
  10. data/documentation/docs/api/locator.md +24 -23
  11. data/documentation/docs/api/locator_assertions.md +652 -0
  12. data/documentation/docs/api/page.md +53 -103
  13. data/documentation/docs/api/playwright.md +20 -23
  14. data/documentation/docs/api/selectors.md +29 -34
  15. data/documentation/docs/article/guides/rails_integration.md +80 -51
  16. data/documentation/docs/article/guides/rspec_integration.md +59 -0
  17. data/documentation/docs/include/api_coverage.md +43 -0
  18. data/documentation/docusaurus.config.js +1 -1
  19. data/documentation/package.json +7 -7
  20. data/documentation/yarn.lock +4641 -5023
  21. data/lib/playwright/api_response_impl.rb +2 -2
  22. data/lib/playwright/channel.rb +1 -1
  23. data/lib/playwright/channel_owners/api_request_context.rb +12 -3
  24. data/lib/playwright/channel_owners/browser.rb +11 -7
  25. data/lib/playwright/channel_owners/browser_context.rb +35 -15
  26. data/lib/playwright/channel_owners/frame.rb +38 -14
  27. data/lib/playwright/channel_owners/page.rb +29 -16
  28. data/lib/playwright/channel_owners/web_socket.rb +8 -13
  29. data/lib/playwright/connection.rb +18 -2
  30. data/lib/playwright/errors.rb +22 -2
  31. data/lib/playwright/input_files.rb +4 -4
  32. data/lib/playwright/javascript/value_serializer.rb +1 -1
  33. data/lib/playwright/locator_assertions_impl.rb +417 -0
  34. data/lib/playwright/locator_impl.rb +24 -5
  35. data/lib/playwright/test.rb +68 -0
  36. data/lib/playwright/utils.rb +3 -10
  37. data/lib/playwright/version.rb +2 -2
  38. data/lib/playwright/waiter.rb +146 -0
  39. data/lib/playwright.rb +1 -1
  40. data/lib/playwright_api/api_request_context.rb +1 -2
  41. data/lib/playwright_api/browser.rb +2 -2
  42. data/lib/playwright_api/browser_context.rb +3 -3
  43. data/lib/playwright_api/browser_type.rb +2 -1
  44. data/lib/playwright_api/download.rb +1 -2
  45. data/lib/playwright_api/element_handle.rb +4 -1
  46. data/lib/playwright_api/frame.rb +4 -1
  47. data/lib/playwright_api/locator.rb +9 -1
  48. data/lib/playwright_api/locator_assertions.rb +561 -0
  49. data/lib/playwright_api/page.rb +16 -13
  50. data/lib/playwright_api/request.rb +4 -4
  51. data/lib/playwright_api/worker.rb +4 -4
  52. data/sig/playwright.rbs +48 -5
  53. metadata +9 -4
  54. data/lib/playwright/wait_helper.rb +0 -73
@@ -0,0 +1,417 @@
1
+ module Playwright
2
+ # ref: https://github.com/microsoft/playwright-python/blob/main/playwright/_impl/_assertions.py
3
+ define_api_implementation :LocatorAssertionsImpl do
4
+ def self._define_negation(method_name)
5
+ define_method("not_#{method_name}") do |*args, **kwargs|
6
+ if kwargs.empty? # for Ruby < 2.7
7
+ _not.public_send(method_name, *args)
8
+ else
9
+ _not.public_send(method_name, *args, **kwargs)
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize(locator, timeout, is_not, message)
15
+ @locator = locator
16
+ @timeout = timeout
17
+ @is_not = is_not
18
+ @custom_message = message
19
+ end
20
+
21
+ private def expect_impl(expression, expect_options, expected, message)
22
+ expect_options[:timeout] ||= 5000
23
+ expect_options[:isNot] = @is_not
24
+ message.gsub!("expected to", "not expected to") if @is_not
25
+ expect_options.delete(:useInnerText) if expect_options.key?(:useInnerText) && expect_options[:useInnerText].nil?
26
+
27
+ result = @locator.expect(expression, expect_options)
28
+
29
+ if result["matches"] == @is_not
30
+ actual = result["received"]
31
+
32
+ log =
33
+ if result.key?("log")
34
+ log_contents = result["log"].join("\n").strip
35
+
36
+ "\nCall log:\n #{log_contents}"
37
+ else
38
+ ""
39
+ end
40
+
41
+ out_message =
42
+ if @custom_message && expected
43
+ "\nExpected value: '#{expected}'"
44
+ elsif @custom_message
45
+ @custom_message
46
+ elsif message != "" && expected
47
+ "\n#{message} '#{expected}'"
48
+ else
49
+ "\n#{message}"
50
+ end
51
+
52
+ out = "#{out_message}\nActual value #{actual} #{log}"
53
+ raise AssertionError.new(out)
54
+ else
55
+ true
56
+ end
57
+ end
58
+
59
+ private def _not # "not" is reserved in Ruby
60
+ LocatorAssertionsImpl.new(
61
+ @locator,
62
+ @timeout,
63
+ !@is_not,
64
+ @message
65
+ )
66
+ end
67
+
68
+ private def expected_regex(pattern, match_substring, normalize_white_space, ignore_case)
69
+ regex = JavaScript::Regex.new(pattern)
70
+ expected = {
71
+ regexSource: regex.source,
72
+ regexFlags: regex.flag,
73
+ matchSubstring: match_substring,
74
+ normalizeWhiteSpace: normalize_white_space,
75
+ ignoreCase: ignore_case
76
+ }
77
+ expected.delete(:ignoreCase) if ignore_case.nil?
78
+
79
+ expected
80
+ end
81
+
82
+ private def to_expected_text_values(items, match_substring: false, normalize_white_space: false, ignore_case: false)
83
+ return [] unless items.respond_to?(:each)
84
+
85
+ items.each.with_object([]) do |item, out|
86
+ out <<
87
+ if item.is_a?(String) && ignore_case
88
+ {
89
+ string: item,
90
+ matchSubstring: match_substring,
91
+ normalizeWhiteSpace: normalize_white_space,
92
+ ignoreCase: ignore_case,
93
+ }
94
+ elsif item.is_a?(String)
95
+ {
96
+ string: item,
97
+ matchSubstring: match_substring,
98
+ normalizeWhiteSpace: normalize_white_space,
99
+ }
100
+ elsif item.is_a?(Regexp)
101
+ expected_regex(item, match_substring, normalize_white_space, ignore_case)
102
+ end
103
+ end
104
+ end
105
+
106
+ def to_contain_text(expected, ignoreCase: nil, timeout: nil, useInnerText: nil)
107
+ useInnerText = false if useInnerText.nil?
108
+
109
+ if expected.respond_to?(:each)
110
+ expected_text = to_expected_text_values(
111
+ expected,
112
+ match_substring: true,
113
+ normalize_white_space: true,
114
+ ignore_case: ignoreCase,
115
+ )
116
+
117
+ expect_impl(
118
+ "to.contain.text.array",
119
+ {
120
+ expectedText: expected_text,
121
+ useInnerText: useInnerText,
122
+ timeout: timeout,
123
+ },
124
+ expected,
125
+ "Locator expected to contain text"
126
+ )
127
+ else
128
+ expected_text = to_expected_text_values(
129
+ [expected],
130
+ match_substring: true,
131
+ normalize_white_space: true,
132
+ ignore_case: ignoreCase,
133
+ )
134
+
135
+ expect_impl(
136
+ "to.have.text",
137
+ {
138
+ expectedText: expected_text,
139
+ useInnerText: useInnerText,
140
+ timeout: timeout,
141
+ },
142
+ expected,
143
+ "Locator expected to contain text"
144
+ )
145
+ end
146
+ end
147
+ _define_negation :to_contain_text
148
+
149
+ def to_have_attribute(name, value, ignoreCase: nil, timeout: nil)
150
+ expected_text = to_expected_text_values([value], ignore_case: ignoreCase)
151
+ expect_impl(
152
+ "to.have.attribute.value",
153
+ {
154
+ expressionArg: name,
155
+ expectedText: expected_text,
156
+ timeout: timeout,
157
+ },
158
+ value,
159
+ "Locator expected to have attribute"
160
+ )
161
+ end
162
+ _define_negation :to_have_attribute
163
+
164
+ def to_have_class(expected, timeout: nil)
165
+ if expected.respond_to?(:each)
166
+ expected_text = to_expected_text_values(expected)
167
+ expect_impl(
168
+ "to.have.class.array",
169
+ {
170
+ expectedText: expected_text,
171
+ timeout: timeout,
172
+ },
173
+ expected,
174
+ "Locator expected to have class"
175
+ )
176
+ else
177
+ expected_text = to_expected_text_values([expected])
178
+ expect_impl(
179
+ "to.have.class",
180
+ {
181
+ expectedText: expected_text,
182
+ timeout: timeout,
183
+ },
184
+ expected,
185
+ "Locator expected to have class"
186
+ )
187
+ end
188
+ end
189
+ _define_negation :to_have_class
190
+
191
+ def to_have_count(count, timeout: nil)
192
+ expect_impl(
193
+ "to.have.count",
194
+ {
195
+ expectedNumber: count,
196
+ timeout: timeout,
197
+ },
198
+ count,
199
+ "Locator expected to have count"
200
+ )
201
+ end
202
+ _define_negation :to_have_count
203
+
204
+ def to_have_css(name, value, timeout: nil)
205
+ expected_text = to_expected_text_values([value])
206
+ expect_impl(
207
+ "to.have.css",
208
+ {
209
+ expressionArg: name,
210
+ expectedText: expected_text,
211
+ timeout: timeout,
212
+ },
213
+ value,
214
+ "Locator expected to have CSS"
215
+ )
216
+ end
217
+ _define_negation :to_have_css
218
+
219
+ def to_have_id(id, timeout: nil)
220
+ expected_text = to_expected_text_values([id])
221
+ expect_impl(
222
+ "to.have.id",
223
+ {
224
+ expectedText: expected_text,
225
+ timeout: timeout,
226
+ },
227
+ id,
228
+ "Locator expected to have ID"
229
+ )
230
+ end
231
+ _define_negation :to_have_id
232
+
233
+ def to_have_js_property(name, value, timeout: nil)
234
+ expect_impl(
235
+ "to.have.property",
236
+ {
237
+ expressionArg: name,
238
+ expectedValue: value,
239
+ timeout: timeout,
240
+ },
241
+ value,
242
+ "Locator expected to have JS Property"
243
+ )
244
+ end
245
+ _define_negation :to_have_js_property
246
+
247
+ def to_have_value(value, timeout: nil)
248
+ expected_text = to_expected_text_values([value])
249
+
250
+ expect_impl(
251
+ "to.have.value",
252
+ {
253
+ expectedText: expected_text,
254
+ timeout: timeout,
255
+ },
256
+ value,
257
+ "Locator expected to have Value"
258
+ )
259
+ end
260
+ _define_negation :to_have_value
261
+
262
+ def to_have_values(values, timeout: nil)
263
+ expected_text = to_expected_text_values(values)
264
+
265
+ expect_impl(
266
+ "to.have.values",
267
+ {
268
+ expectedText: expected_text,
269
+ timeout: timeout,
270
+ },
271
+ values,
272
+ "Locator expected to have Values"
273
+ )
274
+ end
275
+ _define_negation :to_have_values
276
+
277
+ def to_have_text(expected, ignoreCase: nil, timeout: nil, useInnerText: nil)
278
+ if expected.respond_to?(:each)
279
+ expected_text = to_expected_text_values(
280
+ expected,
281
+ match_substring: true,
282
+ normalize_white_space: true,
283
+ ignore_case: ignoreCase,
284
+ )
285
+ expect_impl(
286
+ "to.have.text.array",
287
+ {
288
+ expectedText: expected_text,
289
+ useInnerText: useInnerText,
290
+ timeout: timeout,
291
+ },
292
+ expected,
293
+ "Locator expected to have text"
294
+ )
295
+ else
296
+ expected_text = to_expected_text_values(
297
+ [expected],
298
+ match_substring: true,
299
+ normalize_white_space: true,
300
+ ignore_case: ignoreCase,
301
+ )
302
+ expect_impl(
303
+ "to.have.text",
304
+ {
305
+ expectedText: expected_text,
306
+ useInnerText: useInnerText,
307
+ timeout: timeout,
308
+ },
309
+ expected,
310
+ "Locator expected to have text"
311
+ )
312
+ end
313
+ end
314
+ _define_negation :to_have_text
315
+
316
+ def to_be_attached(attached: nil, timeout: nil)
317
+ expect_impl(
318
+ (attached || attached.nil?) ? "to.be.attached" : "to.be.detached",
319
+ { timeout: timeout },
320
+ nil,
321
+ "Locator expected to be attached"
322
+ )
323
+ end
324
+ _define_negation :to_be_attached
325
+
326
+ def to_be_checked(checked: nil, timeout: nil)
327
+ expect_impl(
328
+ (checked || checked.nil?) ? "to.be.checked" : "to.be.unchecked",
329
+ { timeout: timeout },
330
+ nil,
331
+ "Locator expected to be checked"
332
+ )
333
+ end
334
+ _define_negation :to_be_checked
335
+
336
+ def to_be_disabled(timeout: nil)
337
+ expect_impl(
338
+ "to.be.disabled",
339
+ { timeout: timeout },
340
+ nil,
341
+ "Locator expected to be disabled"
342
+ )
343
+ end
344
+ _define_negation :to_be_disabled
345
+
346
+ def to_be_editable(editable: nil, timeout: nil)
347
+ expect_impl(
348
+ (editable || editable.nil?) ? "to.be.editable" : "to.be.readonly",
349
+ { timeout: timeout },
350
+ nil,
351
+ "Locator expected to be editable"
352
+ )
353
+ end
354
+ _define_negation :to_be_editable
355
+
356
+ def to_be_empty(timeout: nil)
357
+ expect_impl(
358
+ "to.be.empty",
359
+ { timeout: timeout },
360
+ nil,
361
+ "Locator expected to be empty"
362
+ )
363
+ end
364
+ _define_negation :to_be_empty
365
+
366
+ def to_be_enabled(enabled: nil, timeout: nil)
367
+ expect_impl(
368
+ (enabled || enabled.nil?) ? "to.be.enabled" : "to.be.disabled",
369
+ { timeout: timeout },
370
+ nil,
371
+ "Locator expected to be enabled"
372
+ )
373
+ end
374
+ _define_negation :to_be_enabled
375
+
376
+ def to_be_hidden(timeout: nil)
377
+ expect_impl(
378
+ "to.be.hidden",
379
+ { timeout: timeout },
380
+ nil,
381
+ "Locator expected to be hidden"
382
+ )
383
+ end
384
+ _define_negation :to_be_hidden
385
+
386
+ def to_be_visible(timeout: nil, visible: nil)
387
+ expect_impl(
388
+ (visible || visible.nil?) ? "to.be.visible" : "to.be.hidden",
389
+ { timeout: timeout },
390
+ nil,
391
+ "Locator expected to be visible"
392
+ )
393
+ end
394
+ _define_negation :to_be_visible
395
+
396
+ def to_be_focused(timeout: nil)
397
+ expect_impl(
398
+ "to.be.focused",
399
+ { timeout: timeout },
400
+ nil,
401
+ "Locator expected to be focused"
402
+ )
403
+ end
404
+ _define_negation :to_be_focused
405
+
406
+ def to_be_in_viewport(ratio: nil, timeout: nil)
407
+ expect_impl(
408
+ "to.be.in.viewport",
409
+ { timeout: timeout, expectedNumber: ratio },
410
+ nil,
411
+ "Locator expected to be in viewport"
412
+ )
413
+ end
414
+ _define_negation :to_be_in_viewport
415
+
416
+ end
417
+ end
@@ -304,11 +304,7 @@ module Playwright
304
304
  end
305
305
 
306
306
  def all
307
- Enumerator.new do |out|
308
- count.times do |i|
309
- out << nth(i)
310
- end
311
- end
307
+ count.times.map { |i| nth(i) }
312
308
  end
313
309
 
314
310
  def count
@@ -488,5 +484,28 @@ module Playwright
488
484
  def highlight
489
485
  @frame.highlight(@selector)
490
486
  end
487
+
488
+ def expect(expression, options)
489
+ if options.key?(:expectedValue)
490
+ options[:expectedValue] = JavaScript::ValueSerializer
491
+ .new(options[:expectedValue])
492
+ .serialize
493
+ end
494
+
495
+ result = @frame.channel.send_message_to_server_result(
496
+ "expect",
497
+ {
498
+ selector: @selector,
499
+ expression: expression,
500
+ **options,
501
+ }
502
+ )
503
+
504
+ if result.key?('received')
505
+ result['received'] = JavaScript::ValueParser.new(result['received']).parse
506
+ end
507
+
508
+ result
509
+ end
491
510
  end
492
511
  end
@@ -0,0 +1,68 @@
1
+ module Playwright
2
+ # this module is responsible for running playwright assertions and integrating
3
+ # with test frameworks.
4
+ module Test
5
+ # ref: https://github.com/microsoft/playwright-python/blob/main/playwright/sync_api/__init__.py#L90
6
+ class Expect
7
+ def initialize
8
+ @timeout_settings = TimeoutSettings.new
9
+ end
10
+
11
+ def call(actual, message = nil)
12
+ if actual.is_a?(Locator)
13
+ LocatorAssertions.new(
14
+ LocatorAssertionsImpl.new(
15
+ actual,
16
+ @timeout_settings.timeout,
17
+ false,
18
+ message,
19
+ )
20
+ )
21
+ else
22
+ raise NotImplementedError.new("Only locator assertions are currently implemented")
23
+ end
24
+ end
25
+ end
26
+
27
+ module Matchers
28
+ class PlaywrightMatcher
29
+ def initialize(expectation_method, *args, **kwargs)
30
+ @method = expectation_method
31
+ @args = args
32
+ @kwargs = kwargs
33
+ end
34
+
35
+ def matches?(actual)
36
+ Expect.new.call(actual).send(@method, *@args, **@kwargs)
37
+ true
38
+ rescue AssertionError => e
39
+ @failure_message = e.full_message
40
+ false
41
+ end
42
+
43
+ def failure_message
44
+ @failure_message
45
+ end
46
+
47
+ # we have to invert the message again here because RSpec wants to control
48
+ # its own negation
49
+ def failure_message_when_negated
50
+ @failure_message.gsub("expected to", "not expected to")
51
+ end
52
+ end
53
+ end
54
+
55
+ ALL_ASSERTIONS = LocatorAssertions.instance_methods(false)
56
+
57
+ ALL_ASSERTIONS
58
+ .map(&:to_s)
59
+ .each do |method_name|
60
+ # to_be_visible => be_visible
61
+ # not_to_be_visible => not_be_visible
62
+ root_method_name = method_name.gsub("to_", "")
63
+ Matchers.send(:define_method, root_method_name) do |*args, **kwargs|
64
+ Matchers::PlaywrightMatcher.new(method_name, *args, **kwargs)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -70,17 +70,10 @@ module Playwright
70
70
  end
71
71
 
72
72
  module Errors
73
- module SafeCloseError
73
+ module TargetClosedErrorMethods
74
74
  # @param err [Exception]
75
- private def safe_close_error?(err)
76
- return true if err.is_a?(Transport::AlreadyDisconnectedError)
77
-
78
- [
79
- 'Browser has been closed',
80
- 'Target page, context or browser has been closed',
81
- ].any? do |closed_message|
82
- err.message.end_with?(closed_message)
83
- end
75
+ private def target_closed_error?(err)
76
+ err.is_a?(TargetClosedError)
84
77
  end
85
78
  end
86
79
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.39.0'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.39.0'
4
+ VERSION = '1.40.0'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.40.1'
6
6
  end
@@ -0,0 +1,146 @@
1
+ require 'securerandom'
2
+
3
+ module Playwright
4
+ # ref: https://github.com/microsoft/playwright-python/blob/v1.40.0/playwright/_impl/_waiter.py
5
+ # ref: https://github.com/microsoft/playwright/blob/v1.40.0/packages/playwright-core/src/client/waiter.ts
6
+ class Waiter
7
+ def initialize(channel_owner, wait_name:)
8
+ @result = Concurrent::Promises.resolvable_future
9
+ @wait_id = SecureRandom.hex(16)
10
+ @event = wait_name
11
+ @channel = channel_owner.channel
12
+ @registered_listeners = Set.new
13
+ @logs = []
14
+ wait_for_event_info_before
15
+ end
16
+
17
+ private def wait_for_event_info_before
18
+ @channel.async_send_message_to_server(
19
+ "waitForEventInfo",
20
+ {
21
+ "info": {
22
+ "waitId": @wait_id,
23
+ "phase": "before",
24
+ "event": @event,
25
+ }
26
+ },
27
+ )
28
+ end
29
+
30
+ private def wait_for_event_info_after(error: nil)
31
+ @channel.async_send_message_to_server(
32
+ "waitForEventInfo",
33
+ {
34
+ "info": {
35
+ "waitId": @wait_id,
36
+ "phase": "after",
37
+ "error": error,
38
+ }.compact,
39
+ },
40
+ )
41
+ end
42
+
43
+ def reject_on_event(emitter, event, error_or_proc, predicate: nil)
44
+ listener = -> (*args) {
45
+ if !predicate || predicate.call(*args)
46
+ if error_or_proc.respond_to?(:call)
47
+ reject(error_or_proc.call)
48
+ else
49
+ reject(error_or_proc)
50
+ end
51
+ end
52
+ }
53
+ emitter.on(event, listener)
54
+ @registered_listeners << [emitter, event, listener]
55
+
56
+ self
57
+ end
58
+
59
+ def reject_on_timeout(timeout_ms, message)
60
+ return if timeout_ms <= 0
61
+
62
+ Concurrent::Promises.schedule(timeout_ms / 1000.0) do
63
+ reject(TimeoutError.new(message: message))
64
+ end.rescue do |err|
65
+ puts err, err.backtrace
66
+ end
67
+
68
+ self
69
+ end
70
+
71
+ private def cleanup
72
+ @registered_listeners.each do |emitter, event, listener|
73
+ emitter.off(event, listener)
74
+ end
75
+ @registered_listeners.clear
76
+ end
77
+
78
+ private def fulfill(result)
79
+ cleanup
80
+ unless @result.resolved?
81
+ @result.fulfill(result)
82
+ end
83
+ wait_for_event_info_after
84
+ end
85
+
86
+ private def reject(error)
87
+ cleanup
88
+ klass = error.is_a?(TimeoutError) ? TimeoutError : Error
89
+ ex = klass.new(message: "#{error.message}#{format_log_recording(@logs)}")
90
+ unless @result.resolved?
91
+ @result.reject(ex)
92
+ end
93
+ wait_for_event_info_after(error: ex)
94
+ end
95
+
96
+ # @param [Playwright::EventEmitter]
97
+ # @param
98
+ def wait_for_event(emitter, event, predicate: nil)
99
+ listener = -> (*args) {
100
+ begin
101
+ if !predicate || predicate.call(*args)
102
+ fulfill(args.first)
103
+ end
104
+ rescue => err
105
+ reject(err)
106
+ end
107
+ }
108
+ emitter.on(event, listener)
109
+ @registered_listeners << [emitter, event, listener]
110
+
111
+ self
112
+ end
113
+
114
+ attr_reader :result
115
+
116
+ def log(message)
117
+ @logs << message
118
+ begin
119
+ @channel.async_send_message_to_server(
120
+ "waitForEventInfo",
121
+ {
122
+ "info": {
123
+ "waitId": @wait_id,
124
+ "phase": "log",
125
+ "message": message,
126
+ },
127
+ },
128
+ )
129
+ rescue => err
130
+ # ignore
131
+ end
132
+ end
133
+
134
+ # @param logs [Array<String>]
135
+ private def format_log_recording(logs)
136
+ return "" if logs.empty?
137
+
138
+ header = " logs "
139
+ header_length = 60
140
+ left_length = ((header_length - header.length) / 2.0).to_i
141
+ right_length = header_length - header.length - left_length
142
+ new_line = "\n"
143
+ "#{new_line}#{'=' * left_length}#{header}#{'=' * right_length}#{new_line}#{logs.join(new_line)}#{new_line}#{'=' * header_length}"
144
+ end
145
+ end
146
+ end
data/lib/playwright.rb CHANGED
@@ -29,7 +29,7 @@ require 'playwright/transport'
29
29
  require 'playwright/url_matcher'
30
30
  require 'playwright/version'
31
31
  require 'playwright/video'
32
- require 'playwright/wait_helper'
32
+ require 'playwright/waiter'
33
33
 
34
34
  require 'playwright/playwright_api'
35
35
  # load generated files