playwright-ruby-client 1.38.0 → 1.38.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -527,6 +527,49 @@
527
527
 
528
528
  * ~~new_context~~
529
529
 
530
+ ## LocatorAssertions
531
+
532
+ * not_to_be_attached
533
+ * not_to_be_checked
534
+ * not_to_be_disabled
535
+ * not_to_be_editable
536
+ * not_to_be_empty
537
+ * not_to_be_enabled
538
+ * not_to_be_focused
539
+ * not_to_be_hidden
540
+ * not_to_be_in_viewport
541
+ * not_to_be_visible
542
+ * not_to_contain_text
543
+ * not_to_have_attribute
544
+ * not_to_have_class
545
+ * not_to_have_count
546
+ * not_to_have_css
547
+ * not_to_have_id
548
+ * not_to_have_js_property
549
+ * not_to_have_text
550
+ * not_to_have_value
551
+ * not_to_have_values
552
+ * to_be_attached
553
+ * to_be_checked
554
+ * to_be_disabled
555
+ * to_be_editable
556
+ * to_be_empty
557
+ * to_be_enabled
558
+ * to_be_focused
559
+ * to_be_hidden
560
+ * to_be_in_viewport
561
+ * to_be_visible
562
+ * to_contain_text
563
+ * to_have_attribute
564
+ * to_have_class
565
+ * to_have_count
566
+ * to_have_css
567
+ * to_have_id
568
+ * to_have_js_property
569
+ * to_have_text
570
+ * to_have_value
571
+ * to_have_values
572
+
530
573
  ## Android
531
574
 
532
575
  * ~~connect~~
@@ -46,7 +46,7 @@ module Playwright
46
46
 
47
47
  private def with_logging(&block)
48
48
  locations = caller_locations
49
- first_api_call_location_idx = locations.index { |loc| loc.absolute_path.include?('playwright_api') }
49
+ first_api_call_location_idx = locations.index { |loc| loc.absolute_path&.include?('playwright_api') }
50
50
  unless first_api_call_location_idx
51
51
  return block.call(nil)
52
52
  end
@@ -49,4 +49,6 @@ module Playwright
49
49
 
50
50
  attr_reader :error, :page
51
51
  end
52
+
53
+ class AssertionError < StandardError; end
52
54
  end
@@ -24,7 +24,7 @@ module Playwright
24
24
  @handles << value.channel
25
25
  { h: index }
26
26
  when nil
27
- { v: 'undefined' }
27
+ { v: 'null' }
28
28
  when Float::NAN
29
29
  { v: 'NaN'}
30
30
  when Float::INFINITY
@@ -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
+ true,
113
+ true,
114
+ 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
+ true,
131
+ true,
132
+ 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, timeout: nil)
150
+ expected_text = to_expected_text_values([value])
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
+ true,
282
+ true,
283
+ 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
+ true,
299
+ true,
300
+ 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.define_method(root_method_name) do |*args, **kwargs|
64
+ Matchers::PlaywrightMatcher.new(method_name, *args, **kwargs)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.38.0'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.38.0'
4
+ VERSION = '1.38.1'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.38.1'
6
6
  end
@@ -1149,6 +1149,11 @@ module Playwright
1149
1149
  wrap_impl(@impl.wait_for(state: unwrap_impl(state), timeout: unwrap_impl(timeout)))
1150
1150
  end
1151
1151
 
1152
+ # @nodoc
1153
+ def expect(expression, options)
1154
+ wrap_impl(@impl.expect(unwrap_impl(expression), unwrap_impl(options)))
1155
+ end
1156
+
1152
1157
  # @nodoc
1153
1158
  def to_s
1154
1159
  wrap_impl(@impl.to_s)