playwright-ruby-client 1.39.0 → 1.40.0

Sign up to get free protection for your applications and to get access to all the features.
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