playwright-ruby-client 1.39.0 → 1.39.1
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 +4 -4
- data/documentation/docs/api/browser.md +9 -13
- data/documentation/docs/api/browser_context.md +32 -51
- data/documentation/docs/api/browser_type.md +7 -12
- data/documentation/docs/api/dialog.md +15 -18
- data/documentation/docs/api/download.md +9 -10
- data/documentation/docs/api/element_handle.md +5 -6
- data/documentation/docs/api/frame.md +24 -54
- data/documentation/docs/api/locator.md +20 -22
- data/documentation/docs/api/locator_assertions.md +642 -0
- data/documentation/docs/api/page.md +48 -101
- data/documentation/docs/api/playwright.md +20 -23
- data/documentation/docs/api/selectors.md +29 -34
- data/documentation/docs/include/api_coverage.md +43 -0
- data/lib/playwright/channel.rb +1 -1
- data/lib/playwright/errors.rb +2 -0
- data/lib/playwright/javascript/value_serializer.rb +1 -1
- data/lib/playwright/locator_assertions_impl.rb +417 -0
- data/lib/playwright/locator_impl.rb +24 -5
- data/lib/playwright/test.rb +68 -0
- data/lib/playwright/version.rb +1 -1
- data/lib/playwright_api/locator.rb +5 -0
- data/lib/playwright_api/locator_assertions.rb +551 -0
- data/sig/playwright.rbs +43 -0
- metadata +6 -2
@@ -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
|
-
|
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
|
data/lib/playwright/version.rb
CHANGED
@@ -1175,6 +1175,11 @@ module Playwright
|
|
1175
1175
|
wrap_impl(@impl.wait_for(state: unwrap_impl(state), timeout: unwrap_impl(timeout)))
|
1176
1176
|
end
|
1177
1177
|
|
1178
|
+
# @nodoc
|
1179
|
+
def expect(expression, options)
|
1180
|
+
wrap_impl(@impl.expect(unwrap_impl(expression), unwrap_impl(options)))
|
1181
|
+
end
|
1182
|
+
|
1178
1183
|
# @nodoc
|
1179
1184
|
def to_s
|
1180
1185
|
wrap_impl(@impl.to_s)
|