playwright-ruby-client 1.62.0 → 1.64.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.
- checksums.yaml +4 -4
- data/documentation/docs/api/api_request_context.md +50 -0
- data/documentation/docs/api/browser.md +2 -0
- data/documentation/docs/api/browser_context.md +3 -3
- data/documentation/docs/api/browser_type.md +1 -0
- data/documentation/docs/api/clock.md +1 -0
- data/documentation/docs/api/credentials.md +6 -3
- data/documentation/docs/api/frame.md +18 -2
- data/documentation/docs/api/frame_locator.md +20 -0
- data/documentation/docs/api/locator.md +66 -0
- data/documentation/docs/api/page.md +44 -2
- data/documentation/docs/api/request.md +1 -1
- data/documentation/docs/api/response.md +2 -1
- data/documentation/docs/api/tracing.md +5 -3
- data/documentation/docs/api/web_mcp.md +34 -0
- data/documentation/docs/article/guides/playwright_on_alpine_linux.md +1 -1
- data/documentation/docs/include/api_coverage.md +13 -0
- data/lib/playwright/api_request_impl.rb +3 -1
- data/lib/playwright/channel.rb +2 -1
- data/lib/playwright/channel_owners/api_request_context.rb +16 -0
- data/lib/playwright/channel_owners/browser.rb +1 -0
- data/lib/playwright/channel_owners/browser_context.rb +21 -33
- data/lib/playwright/channel_owners/frame.rb +6 -5
- data/lib/playwright/channel_owners/local_utils.rb +5 -0
- data/lib/playwright/channel_owners/page.rb +12 -3
- data/lib/playwright/channel_owners/tracing.rb +45 -13
- data/lib/playwright/connection.rb +6 -2
- data/lib/playwright/credentials_impl.rb +2 -1
- data/lib/playwright/errors.rb +4 -1
- data/lib/playwright/events.rb +2 -0
- data/lib/playwright/frame_locator_impl.rb +20 -5
- data/lib/playwright/locator_impl.rb +26 -8
- data/lib/playwright/screencast.rb +4 -2
- data/lib/playwright/transport.rb +21 -5
- data/lib/playwright/utils.rb +32 -0
- data/lib/playwright/version.rb +2 -2
- data/lib/playwright/web_mcp_impl.rb +21 -0
- data/lib/playwright_api/api_request_context.rb +47 -7
- data/lib/playwright_api/browser.rb +10 -8
- data/lib/playwright_api/browser_context.rb +17 -17
- data/lib/playwright_api/browser_type.rb +8 -7
- data/lib/playwright_api/cdp_session.rb +6 -6
- data/lib/playwright_api/clock.rb +1 -0
- data/lib/playwright_api/credentials.rb +7 -4
- data/lib/playwright_api/dialog.rb +6 -6
- data/lib/playwright_api/element_handle.rb +6 -6
- data/lib/playwright_api/frame.rb +33 -16
- data/lib/playwright_api/frame_locator.rb +20 -0
- data/lib/playwright_api/js_handle.rb +6 -6
- data/lib/playwright_api/locator.rb +63 -3
- data/lib/playwright_api/page.rb +57 -17
- data/lib/playwright_api/playwright.rb +6 -6
- data/lib/playwright_api/request.rb +7 -7
- data/lib/playwright_api/response.rb +8 -7
- data/lib/playwright_api/route.rb +6 -6
- data/lib/playwright_api/tracing.rb +12 -10
- data/lib/playwright_api/web_mcp.rb +26 -0
- data/lib/playwright_api/web_socket.rb +6 -6
- data/lib/playwright_api/worker.rb +6 -6
- data/sig/playwright.rbs +26 -12
- metadata +5 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Playwright
|
|
2
2
|
# @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_browser_context.py
|
|
3
3
|
define_channel_owner :BrowserContext do
|
|
4
|
+
include Utils::ClearCookiesParameters
|
|
5
|
+
|
|
4
6
|
include Utils::PrepareBrowserContextOptions
|
|
5
7
|
|
|
6
8
|
attr_accessor :browser
|
|
@@ -42,6 +44,11 @@ module Playwright
|
|
|
42
44
|
@channel.on('dialog', ->(params) {
|
|
43
45
|
on_dialog(ChannelOwners::Dialog.from(params['dialog']))
|
|
44
46
|
})
|
|
47
|
+
@channel.on('dialogClosed', ->(params) {
|
|
48
|
+
dialog = ChannelOwners::Dialog.from(params['dialog'])
|
|
49
|
+
emit(Events::BrowserContext::DialogClosed, dialog)
|
|
50
|
+
dialog.page&.emit(Events::Page::DialogClosed, dialog)
|
|
51
|
+
})
|
|
45
52
|
@channel.on('request', ->(params) {
|
|
46
53
|
on_request(
|
|
47
54
|
ChannelOwners::Request.from(params['request']),
|
|
@@ -69,6 +76,7 @@ module Playwright
|
|
|
69
76
|
set_event_to_subscription_mapping({
|
|
70
77
|
Events::BrowserContext::Console => 'console',
|
|
71
78
|
Events::BrowserContext::Dialog => 'dialog',
|
|
79
|
+
Events::BrowserContext::DialogClosed => 'dialogClosed',
|
|
72
80
|
Events::BrowserContext::Request => "request",
|
|
73
81
|
Events::BrowserContext::Response => "response",
|
|
74
82
|
Events::BrowserContext::RequestFinished => "requestFinished",
|
|
@@ -93,6 +101,7 @@ module Playwright
|
|
|
93
101
|
private def update_options(context_options:, browser_options:)
|
|
94
102
|
@options = context_options
|
|
95
103
|
@tracing.send(:update_traces_dir, browser_options[:tracesDir])
|
|
104
|
+
@request.tracing.send(:update_traces_dir, browser_options[:tracesDir])
|
|
96
105
|
end
|
|
97
106
|
|
|
98
107
|
private def on_page(page)
|
|
@@ -267,36 +276,7 @@ module Playwright
|
|
|
267
276
|
end
|
|
268
277
|
|
|
269
278
|
def clear_cookies(domain: nil, name: nil, path: nil)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
case name
|
|
273
|
-
when String
|
|
274
|
-
params[:name] = name
|
|
275
|
-
when Regexp
|
|
276
|
-
regex = JavaScript::Regex.new(name)
|
|
277
|
-
params[:nameRegexSource] = regex.source
|
|
278
|
-
params[:nameRegexFlags] = regex.flag
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
case domain
|
|
282
|
-
when String
|
|
283
|
-
params[:domain] = domain
|
|
284
|
-
when Regexp
|
|
285
|
-
regex = JavaScript::Regex.new(domain)
|
|
286
|
-
params[:domainRegexSource] = regex.source
|
|
287
|
-
params[:domainRegexFlags] = regex.flag
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
case path
|
|
291
|
-
when String
|
|
292
|
-
params[:path] = path
|
|
293
|
-
when Regexp
|
|
294
|
-
regex = JavaScript::Regex.new(path)
|
|
295
|
-
params[:pathRegexSource] = regex.source
|
|
296
|
-
params[:pathRegexFlags] = regex.flag
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
@channel.send_message_to_server('clearCookies', params)
|
|
279
|
+
@channel.send_message_to_server('clearCookies', clear_cookies_params(domain: domain, name: name, path: path))
|
|
300
280
|
end
|
|
301
281
|
|
|
302
282
|
def grant_permissions(permissions, origin: nil)
|
|
@@ -418,6 +398,7 @@ module Playwright
|
|
|
418
398
|
@browser.browser_type.send(:playwright_selectors_browser_contexts).delete(self)
|
|
419
399
|
end
|
|
420
400
|
@tracing.send(:reset_stack_counter)
|
|
401
|
+
@request.tracing.send(:reset_stack_counter)
|
|
421
402
|
emit(Events::BrowserContext::Close)
|
|
422
403
|
@closed_promise.fulfill(true)
|
|
423
404
|
end
|
|
@@ -427,9 +408,15 @@ module Playwright
|
|
|
427
408
|
@close_was_called = true
|
|
428
409
|
@close_reason = reason
|
|
429
410
|
@request.dispose(reason: reason)
|
|
430
|
-
|
|
411
|
+
har_error = nil
|
|
412
|
+
begin
|
|
413
|
+
@tracing.send(:export_all_hars)
|
|
414
|
+
rescue => error
|
|
415
|
+
har_error = error
|
|
416
|
+
end
|
|
431
417
|
@channel.send_message_to_server('close', { reason: reason }.compact)
|
|
432
418
|
@closed_promise.value!
|
|
419
|
+
raise har_error if har_error
|
|
433
420
|
nil
|
|
434
421
|
end
|
|
435
422
|
|
|
@@ -445,7 +432,7 @@ module Playwright
|
|
|
445
432
|
#
|
|
446
433
|
# So, launch inspector as Python app.
|
|
447
434
|
# NOTE: This should be used only for Page#pause at this moment.
|
|
448
|
-
@channel.send_message_to_server('
|
|
435
|
+
@channel.send_message_to_server('showRecorder', language: :python)
|
|
449
436
|
@debug_console_enabled = true
|
|
450
437
|
end
|
|
451
438
|
|
|
@@ -462,9 +449,10 @@ module Playwright
|
|
|
462
449
|
@channel.send_message_to_server('pause')
|
|
463
450
|
end
|
|
464
451
|
|
|
465
|
-
def storage_state(path: nil, indexedDB: nil, credentials: nil)
|
|
452
|
+
def storage_state(path: nil, indexedDB: nil, opfs: nil, credentials: nil)
|
|
466
453
|
params = {
|
|
467
454
|
indexedDB: indexedDB,
|
|
455
|
+
opfs: opfs,
|
|
468
456
|
credentials: credentials,
|
|
469
457
|
}.compact
|
|
470
458
|
@channel.send_message_to_server_result('storageState', params).tap do |result|
|
|
@@ -17,6 +17,7 @@ module Playwright
|
|
|
17
17
|
@child_frames = Set.new
|
|
18
18
|
@load_states = Set.new(@initializer['loadStates'])
|
|
19
19
|
@event_emitter = Object.new.extend(EventEmitter)
|
|
20
|
+
@webmcp = WebMCPImpl.new(self)
|
|
20
21
|
|
|
21
22
|
@channel.on('loadstate', ->(params) {
|
|
22
23
|
on_load_state(add: params['add'], remove: params['remove'])
|
|
@@ -24,7 +25,7 @@ module Playwright
|
|
|
24
25
|
@channel.on('navigated', method(:on_frame_navigated))
|
|
25
26
|
end
|
|
26
27
|
|
|
27
|
-
attr_reader :page, :parent_frame
|
|
28
|
+
attr_reader :page, :parent_frame, :webmcp
|
|
28
29
|
attr_writer :detached
|
|
29
30
|
|
|
30
31
|
private def _timeout(timeout)
|
|
@@ -272,8 +273,8 @@ module Playwright
|
|
|
272
273
|
JavaScript::Expression.new(pageFunction, arg).eval_on_selector_all(@channel, selector)
|
|
273
274
|
end
|
|
274
275
|
|
|
275
|
-
def content
|
|
276
|
-
@channel.send_message_to_server('content')
|
|
276
|
+
def content(includeShadow: nil)
|
|
277
|
+
@channel.send_message_to_server('content', { includeShadow: includeShadow }.compact)
|
|
277
278
|
end
|
|
278
279
|
|
|
279
280
|
def set_content(html, timeout: nil, waitUntil: nil)
|
|
@@ -506,8 +507,8 @@ module Playwright
|
|
|
506
507
|
hasText: hasText)
|
|
507
508
|
end
|
|
508
509
|
|
|
509
|
-
def frame_locator(selector)
|
|
510
|
-
FrameLocatorImpl.new(frame: self, frame_selector: selector)
|
|
510
|
+
def frame_locator(selector = nil)
|
|
511
|
+
FrameLocatorImpl.new(frame: self, frame_selector: selector || 'internal:control=any-frame')
|
|
511
512
|
end
|
|
512
513
|
|
|
513
514
|
def focus(selector, strict: nil, timeout: nil)
|
|
@@ -62,6 +62,11 @@ module Playwright
|
|
|
62
62
|
nil
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def as_locator_description(selector)
|
|
66
|
+
# Ruby uses the Python SDK language for upstream code generation and the inspector.
|
|
67
|
+
@channel.send_message_to_server('asLocatorDescription', selector: selector, sdkLanguage: 'python')
|
|
68
|
+
end
|
|
69
|
+
|
|
65
70
|
def connect(params)
|
|
66
71
|
result = @channel.send_message_to_server_result('connect', params)
|
|
67
72
|
ChannelOwners::JsonPipe.from(result['pipe'])
|
|
@@ -72,6 +72,7 @@ module Playwright
|
|
|
72
72
|
set_event_to_subscription_mapping({
|
|
73
73
|
Events::Page::Console => "console",
|
|
74
74
|
Events::Page::Dialog => "dialog",
|
|
75
|
+
Events::Page::DialogClosed => "dialogClosed",
|
|
75
76
|
Events::Page::Request => "request",
|
|
76
77
|
Events::Page::Response => "response",
|
|
77
78
|
Events::Page::RequestFinished => "requestFinished",
|
|
@@ -337,8 +338,16 @@ module Playwright
|
|
|
337
338
|
@main_frame.url
|
|
338
339
|
end
|
|
339
340
|
|
|
340
|
-
def content
|
|
341
|
-
@main_frame.content
|
|
341
|
+
def content(includeShadow: nil)
|
|
342
|
+
@main_frame.content(includeShadow: includeShadow)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def webmcp
|
|
346
|
+
@main_frame.webmcp
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def get_by_ref(ref)
|
|
350
|
+
locator("aria-ref=#{ref}")
|
|
342
351
|
end
|
|
343
352
|
|
|
344
353
|
def set_content(html, timeout: nil, waitUntil: nil)
|
|
@@ -723,7 +732,7 @@ module Playwright
|
|
|
723
732
|
hasText: hasText)
|
|
724
733
|
end
|
|
725
734
|
|
|
726
|
-
def frame_locator(selector)
|
|
735
|
+
def frame_locator(selector = nil)
|
|
727
736
|
@main_frame.frame_locator(selector)
|
|
728
737
|
end
|
|
729
738
|
|
|
@@ -5,23 +5,26 @@ module Playwright
|
|
|
5
5
|
@har_id = nil
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def start(name: nil, title: nil, screenshots: nil, snapshots: nil, sources: nil, live: nil)
|
|
8
|
+
def start(name: nil, title: nil, screenshots: nil, snapshots: nil, ariaSnapshots: nil, screenSnapshots: nil, sources: nil, live: nil)
|
|
9
9
|
params = {
|
|
10
10
|
name: name,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
screencast: screenshots,
|
|
12
|
+
snapshotDom: snapshots,
|
|
13
|
+
snapshotAria: ariaSnapshots,
|
|
14
|
+
snapshotScreen: screenSnapshots,
|
|
14
15
|
live: live,
|
|
15
16
|
}.compact
|
|
16
|
-
@include_sources =
|
|
17
|
+
@include_sources = sources || false
|
|
17
18
|
@channel.send_message_to_server('tracingStart', params)
|
|
18
19
|
trace_name = @channel.send_message_to_server('tracingStartChunk', { title: title, name: name }.compact)
|
|
19
20
|
start_collecting_stacks(trace_name)
|
|
21
|
+
DisposableStub.new { stop }
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def start_chunk(title: nil, name: nil)
|
|
23
25
|
trace_name = @channel.send_message_to_server('tracingStartChunk', { title: title, name: name }.compact)
|
|
24
26
|
start_collecting_stacks(trace_name)
|
|
27
|
+
DisposableStub.new { stop_chunk }
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
private def start_collecting_stacks(trace_name)
|
|
@@ -38,19 +41,43 @@ module Playwright
|
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
def stop(path: nil)
|
|
41
|
-
|
|
44
|
+
save_error = nil
|
|
45
|
+
begin
|
|
46
|
+
do_stop_chunk(file_path: path)
|
|
47
|
+
rescue => error
|
|
48
|
+
save_error = error
|
|
49
|
+
end
|
|
42
50
|
@channel.send_message_to_server('tracingStop')
|
|
51
|
+
raise save_error if save_error
|
|
43
52
|
end
|
|
44
53
|
|
|
45
54
|
private def do_stop_chunk(file_path:)
|
|
46
55
|
reset_stack_counter
|
|
56
|
+
stacks_id = @stacks_id
|
|
57
|
+
@stacks_id = nil
|
|
58
|
+
begin
|
|
59
|
+
save_chunk(file_path: file_path, stacks_id: stacks_id)
|
|
60
|
+
rescue
|
|
61
|
+
# Release the session even if saving fails so subsequent traces start fresh.
|
|
62
|
+
if stacks_id
|
|
63
|
+
begin
|
|
64
|
+
@connection.local_utils&.trace_discarded(stacks_id)
|
|
65
|
+
rescue
|
|
66
|
+
# Preserve the error from saving the trace.
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
raise
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private def save_chunk(file_path:, stacks_id:)
|
|
47
74
|
local_utils = @connection.local_utils
|
|
48
75
|
|
|
49
76
|
unless file_path
|
|
50
77
|
# Not interested in any artifacts
|
|
51
78
|
@channel.send_message_to_server('tracingStopChunk', mode: 'discard')
|
|
52
|
-
if
|
|
53
|
-
local_utils.trace_discarded(
|
|
79
|
+
if stacks_id
|
|
80
|
+
local_utils.trace_discarded(stacks_id) if local_utils
|
|
54
81
|
end
|
|
55
82
|
|
|
56
83
|
return
|
|
@@ -66,7 +93,7 @@ module Playwright
|
|
|
66
93
|
local_utils.zip(
|
|
67
94
|
zipFile: file_path,
|
|
68
95
|
entries: result['entries'],
|
|
69
|
-
stacksId:
|
|
96
|
+
stacksId: stacks_id,
|
|
70
97
|
mode: 'write',
|
|
71
98
|
includeSources: @include_sources,
|
|
72
99
|
)
|
|
@@ -78,8 +105,8 @@ module Playwright
|
|
|
78
105
|
result = @channel.send_message_to_server_result('tracingStopChunk', mode: 'archive')
|
|
79
106
|
# The artifact may be missing if the browser closed while stopping tracing.
|
|
80
107
|
unless result['artifact']
|
|
81
|
-
if
|
|
82
|
-
local_utils.trace_discarded(
|
|
108
|
+
if stacks_id
|
|
109
|
+
local_utils.trace_discarded(stacks_id) if local_utils
|
|
83
110
|
end
|
|
84
111
|
|
|
85
112
|
return
|
|
@@ -87,7 +114,12 @@ module Playwright
|
|
|
87
114
|
|
|
88
115
|
# Save trace to the final local file.
|
|
89
116
|
artifact = ChannelOwners::Artifact.from(result['artifact'])
|
|
90
|
-
|
|
117
|
+
begin
|
|
118
|
+
artifact.save_as(file_path)
|
|
119
|
+
rescue
|
|
120
|
+
artifact.delete rescue nil
|
|
121
|
+
raise
|
|
122
|
+
end
|
|
91
123
|
artifact.delete
|
|
92
124
|
|
|
93
125
|
return unless local_utils
|
|
@@ -95,7 +127,7 @@ module Playwright
|
|
|
95
127
|
local_utils.zip(
|
|
96
128
|
zipFile: file_path,
|
|
97
129
|
entries: [],
|
|
98
|
-
stacksId:
|
|
130
|
+
stacksId: stacks_id,
|
|
99
131
|
mode: 'append',
|
|
100
132
|
includeSources: @include_sources,
|
|
101
133
|
)
|
|
@@ -24,6 +24,7 @@ module Playwright
|
|
|
24
24
|
@waiting_for_object = {} # Hash[ guid => Promise<ChannelOwner> ]
|
|
25
25
|
@callbacks = {} # Hash [ guid => Promise<ChannelOwner> ]
|
|
26
26
|
@callbacks_mutex = Mutex.new
|
|
27
|
+
@call_id_prefix = Array.new(4) { ('a'..'z').to_a.sample }.join
|
|
27
28
|
@root_object = RootChannelOwner.new(self)
|
|
28
29
|
@remote = false
|
|
29
30
|
@tracing_count = 0
|
|
@@ -77,7 +78,10 @@ module Playwright
|
|
|
77
78
|
if method == '__waitInfo__' && @closed_error
|
|
78
79
|
return Concurrent::Promises.fulfilled_future(nil)
|
|
79
80
|
end
|
|
80
|
-
|
|
81
|
+
# After the driver is gone, fail fast with the close error instead of
|
|
82
|
+
# returning nil (which would blow up with NoMethodError on .value!).
|
|
83
|
+
# ref: https://github.com/YusukeIwaki/playwright-ruby-client/issues/392
|
|
84
|
+
return Concurrent::Promises.rejected_future(@closed_error) if @closed_error
|
|
81
85
|
|
|
82
86
|
callback = Concurrent::Promises.resolvable_future
|
|
83
87
|
fire_and_forget = method == '__waitInfo__'
|
|
@@ -150,7 +154,7 @@ module Playwright
|
|
|
150
154
|
@last_id ||= 0
|
|
151
155
|
@last_id += 1
|
|
152
156
|
end
|
|
153
|
-
block.call(id)
|
|
157
|
+
block.call("#{@call_id_prefix}@#{id}")
|
|
154
158
|
end
|
|
155
159
|
|
|
156
160
|
# @param guid [String]
|
|
@@ -11,13 +11,14 @@ module Playwright
|
|
|
11
11
|
nil
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def create(rpId, id: nil, userHandle: nil, privateKey: nil, publicKey: nil)
|
|
14
|
+
def create(rpId, id: nil, userHandle: nil, privateKey: nil, publicKey: nil, signCount: nil)
|
|
15
15
|
params = {
|
|
16
16
|
rpId: rpId,
|
|
17
17
|
id: id,
|
|
18
18
|
userHandle: userHandle,
|
|
19
19
|
privateKey: privateKey,
|
|
20
20
|
publicKey: publicKey,
|
|
21
|
+
signCount: signCount,
|
|
21
22
|
}.compact
|
|
22
23
|
@browser_context.channel.send_message_to_server('credentialsCreate', params)
|
|
23
24
|
end
|
data/lib/playwright/errors.rb
CHANGED
|
@@ -2,7 +2,7 @@ module Playwright
|
|
|
2
2
|
class Error < StandardError
|
|
3
3
|
# ref: https://github.com/microsoft/playwright-python/blob/0b4a980fed366c4c1dee9bfcdd72662d629fdc8d/playwright/_impl/_helper.py#L155
|
|
4
4
|
def self.parse(error_payload)
|
|
5
|
-
if error_payload['name'] == 'TimeoutError'
|
|
5
|
+
error = if error_payload['name'] == 'TimeoutError'
|
|
6
6
|
TimeoutError.new(
|
|
7
7
|
message: error_payload['message'],
|
|
8
8
|
stack: error_payload['stack'],
|
|
@@ -19,6 +19,8 @@ module Playwright
|
|
|
19
19
|
stack: error_payload['stack'],
|
|
20
20
|
)
|
|
21
21
|
end
|
|
22
|
+
error.code = error_payload['code']
|
|
23
|
+
error
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
# @param name [String]
|
|
@@ -34,6 +36,7 @@ module Playwright
|
|
|
34
36
|
attr_reader :name, :message, :stack, :raw_log
|
|
35
37
|
# Error details for `expect` failures (received value, timedOut, customErrorMessage).
|
|
36
38
|
attr_accessor :details
|
|
39
|
+
attr_accessor :code
|
|
37
40
|
|
|
38
41
|
def log=(log)
|
|
39
42
|
return unless log
|
data/lib/playwright/events.rb
CHANGED
|
@@ -29,6 +29,7 @@ end
|
|
|
29
29
|
Close: 'close',
|
|
30
30
|
Console: 'console',
|
|
31
31
|
Dialog: 'dialog',
|
|
32
|
+
DialogClosed: 'dialogclosed',
|
|
32
33
|
Download: 'download',
|
|
33
34
|
FrameAttached: 'frameattached',
|
|
34
35
|
FrameDetached: 'framedetached',
|
|
@@ -53,6 +54,7 @@ end
|
|
|
53
54
|
Crash: 'crash',
|
|
54
55
|
Console: 'console',
|
|
55
56
|
Dialog: 'dialog',
|
|
57
|
+
DialogClosed: 'dialogclosed',
|
|
56
58
|
Download: 'download',
|
|
57
59
|
FileChooser: 'filechooser',
|
|
58
60
|
DOMContentLoaded: 'domcontentloaded',
|
|
@@ -13,6 +13,21 @@ module Playwright
|
|
|
13
13
|
@frame.send(:_timeout, timeout)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
private def child_selector(selector)
|
|
17
|
+
if @frame_selector == 'internal:control=any-frame'
|
|
18
|
+
"#{@frame_selector} >> #{selector}"
|
|
19
|
+
else
|
|
20
|
+
"#{@frame_selector} >> internal:control=enter-frame >> #{selector}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private def nth_selector(index)
|
|
25
|
+
if @frame_selector == 'internal:control=any-frame'
|
|
26
|
+
raise 'Selecting the nth frame is not allowed on frameLocator().'
|
|
27
|
+
end
|
|
28
|
+
"#{@frame_selector} >> nth=#{index}"
|
|
29
|
+
end
|
|
30
|
+
|
|
16
31
|
def locator(
|
|
17
32
|
selector,
|
|
18
33
|
has: nil,
|
|
@@ -21,7 +36,7 @@ module Playwright
|
|
|
21
36
|
hasText: nil)
|
|
22
37
|
LocatorImpl.new(
|
|
23
38
|
frame: @frame,
|
|
24
|
-
selector:
|
|
39
|
+
selector: child_selector(selector),
|
|
25
40
|
has: has,
|
|
26
41
|
hasNot: hasNot,
|
|
27
42
|
hasNotText: hasNotText,
|
|
@@ -38,28 +53,28 @@ module Playwright
|
|
|
38
53
|
def frame_locator(selector)
|
|
39
54
|
FrameLocatorImpl.new(
|
|
40
55
|
frame: @frame,
|
|
41
|
-
frame_selector:
|
|
56
|
+
frame_selector: child_selector(selector),
|
|
42
57
|
)
|
|
43
58
|
end
|
|
44
59
|
|
|
45
60
|
def first
|
|
46
61
|
FrameLocatorImpl.new(
|
|
47
62
|
frame: @frame,
|
|
48
|
-
frame_selector:
|
|
63
|
+
frame_selector: nth_selector(0),
|
|
49
64
|
)
|
|
50
65
|
end
|
|
51
66
|
|
|
52
67
|
def last
|
|
53
68
|
FrameLocatorImpl.new(
|
|
54
69
|
frame: @frame,
|
|
55
|
-
frame_selector:
|
|
70
|
+
frame_selector: nth_selector(-1),
|
|
56
71
|
)
|
|
57
72
|
end
|
|
58
73
|
|
|
59
74
|
def nth(index)
|
|
60
75
|
FrameLocatorImpl.new(
|
|
61
76
|
frame: @frame,
|
|
62
|
-
frame_selector:
|
|
77
|
+
frame_selector: nth_selector(index),
|
|
63
78
|
)
|
|
64
79
|
end
|
|
65
80
|
end
|
|
@@ -5,8 +5,9 @@ module Playwright
|
|
|
5
5
|
define_api_implementation :LocatorImpl do
|
|
6
6
|
include LocatorUtils
|
|
7
7
|
|
|
8
|
-
def initialize(frame:, selector:, has: nil, hasNot: nil, hasNotText: nil, hasText: nil, visible: nil)
|
|
8
|
+
def initialize(frame:, selector:, has: nil, hasNot: nil, hasNotText: nil, hasText: nil, visible: nil, locator_code: nil)
|
|
9
9
|
@frame = frame
|
|
10
|
+
@locator_code = locator_code
|
|
10
11
|
selector_scopes = [selector]
|
|
11
12
|
|
|
12
13
|
if hasText
|
|
@@ -39,11 +40,18 @@ module Playwright
|
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def to_s
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
custom_description = description
|
|
44
|
+
return custom_description if custom_description
|
|
45
|
+
return @locator_code if @locator_code
|
|
46
|
+
|
|
47
|
+
local_utils = @frame.instance_variable_get(:@connection).local_utils
|
|
48
|
+
return "Locator@#{@selector}" unless local_utils
|
|
49
|
+
|
|
50
|
+
local_utils.as_locator_description(@selector)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def within(locator)
|
|
54
|
+
locator.locator(self)
|
|
47
55
|
end
|
|
48
56
|
|
|
49
57
|
private def to_protocol
|
|
@@ -232,6 +240,11 @@ module Playwright
|
|
|
232
240
|
hasNot: nil,
|
|
233
241
|
hasNotText: nil,
|
|
234
242
|
hasText: nil)
|
|
243
|
+
if selector.is_a?(LocatorImpl)
|
|
244
|
+
raise ArgumentError, 'Locators must belong to the same frame.' unless same_frame?(selector)
|
|
245
|
+
|
|
246
|
+
selector = "internal:chain=#{selector.send(:selector_json)}"
|
|
247
|
+
end
|
|
235
248
|
LocatorImpl.new(
|
|
236
249
|
frame: @frame,
|
|
237
250
|
selector: "#{@selector} >> #{selector}",
|
|
@@ -296,6 +309,10 @@ module Playwright
|
|
|
296
309
|
)
|
|
297
310
|
end
|
|
298
311
|
|
|
312
|
+
def visible
|
|
313
|
+
filter(visible: true)
|
|
314
|
+
end
|
|
315
|
+
|
|
299
316
|
def first
|
|
300
317
|
LocatorImpl.new(
|
|
301
318
|
frame: @frame,
|
|
@@ -449,10 +466,11 @@ module Playwright
|
|
|
449
466
|
end
|
|
450
467
|
|
|
451
468
|
def normalize
|
|
452
|
-
|
|
469
|
+
result = @frame.channel.send_message_to_server_result('resolveSelector', { selector: @selector, sdkLanguage: 'python' })
|
|
453
470
|
LocatorImpl.new(
|
|
454
471
|
frame: @frame,
|
|
455
|
-
selector:
|
|
472
|
+
selector: result['resolvedSelector'],
|
|
473
|
+
locator_code: result['locatorCode'],
|
|
456
474
|
)
|
|
457
475
|
end
|
|
458
476
|
|
|
@@ -11,7 +11,7 @@ module Playwright
|
|
|
11
11
|
@page.send(:channel).on('screencastFrame', method(:handle_screencast_frame))
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def start(path: nil, size: nil, quality: nil, &on_frame)
|
|
14
|
+
def start(path: nil, size: nil, quality: nil, fps: nil, &on_frame)
|
|
15
15
|
raise 'Screencast is already started' if @started
|
|
16
16
|
|
|
17
17
|
@started = true
|
|
@@ -21,6 +21,7 @@ module Playwright
|
|
|
21
21
|
params[:record] = true if path
|
|
22
22
|
params[:size] = size if size
|
|
23
23
|
params[:quality] = quality if quality
|
|
24
|
+
params[:fps] = fps unless fps.nil?
|
|
24
25
|
params[:sendFrames] = true if on_frame
|
|
25
26
|
|
|
26
27
|
result = @page.send(:channel).send_message_to_server_result('screencastStart', params)
|
|
@@ -63,12 +64,13 @@ module Playwright
|
|
|
63
64
|
@page.send(:channel).send_message_to_server('screencastChapter', params)
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
def show_actions(duration: nil, fontSize: nil, position: nil, cursor: nil)
|
|
67
|
+
def show_actions(duration: nil, fontSize: nil, position: nil, cursor: nil, style: nil)
|
|
67
68
|
params = {}
|
|
68
69
|
params[:duration] = duration if duration
|
|
69
70
|
params[:fontSize] = fontSize if fontSize
|
|
70
71
|
params[:position] = position if position
|
|
71
72
|
params[:cursor] = cursor if cursor
|
|
73
|
+
params[:style] = style if style
|
|
72
74
|
@page.send(:channel).send_message_to_server('screencastShowActions', params)
|
|
73
75
|
DisposableStub.new { hide_actions }
|
|
74
76
|
end
|
data/lib/playwright/transport.rb
CHANGED
|
@@ -32,10 +32,15 @@ module Playwright
|
|
|
32
32
|
def send_message(message)
|
|
33
33
|
debug_send_message(message) if @debug
|
|
34
34
|
msg = JSON.dump(message)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
# Build the whole frame (4-byte little-endian length header + JSON payload) in a
|
|
36
|
+
# single buffer, and write it at once. Two separate writes can be torn by an
|
|
37
|
+
# asynchronous exception (e.g. Timeout::Error from Timeout.timeout, delivered via
|
|
38
|
+
# Thread#raise), which crashes the driver with a JSON parse error.
|
|
39
|
+
# ref: https://github.com/YusukeIwaki/playwright-ruby-client/issues/392
|
|
40
|
+
frame = [msg.bytesize].pack('V') + msg.b # unsigned 32bit, little endian, real byte size instead of chars
|
|
41
|
+
Thread.handle_interrupt(Object => :never) do
|
|
42
|
+
@mutex.synchronize { @stdin.write(frame) } # write UTF-8 in binary mode as byte stream
|
|
43
|
+
end
|
|
39
44
|
rescue Errno::EPIPE, IOError
|
|
40
45
|
raise AlreadyDisconnectedError.new('send_message failed')
|
|
41
46
|
end
|
|
@@ -85,6 +90,10 @@ module Playwright
|
|
|
85
90
|
debug_recv_message(obj) if @debug
|
|
86
91
|
@on_message&.call(obj)
|
|
87
92
|
end
|
|
93
|
+
# exited the loop on clean EOF. The driver is gone, so reject pending
|
|
94
|
+
# callbacks instead of hanging forever on promises that will never resolve.
|
|
95
|
+
# ref: https://github.com/YusukeIwaki/playwright-ruby-client/issues/392
|
|
96
|
+
@on_driver_closed&.call
|
|
88
97
|
rescue IOError
|
|
89
98
|
# disconnected by remote.
|
|
90
99
|
@on_driver_closed&.call
|
|
@@ -92,6 +101,9 @@ module Playwright
|
|
|
92
101
|
|
|
93
102
|
def handle_stderr
|
|
94
103
|
while err = @stderr.read
|
|
104
|
+
# IO#read without length on a pipe returns "" (not nil) at EOF.
|
|
105
|
+
# Bail out instead of spinning forever on empty reads.
|
|
106
|
+
break if err.empty?
|
|
95
107
|
# sometimed driver crashes with the error below.
|
|
96
108
|
# --------
|
|
97
109
|
# undefined:1
|
|
@@ -103,7 +115,11 @@ module Playwright
|
|
|
103
115
|
# at Transport.transport.onmessage (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/cli/driver.js:42:73)
|
|
104
116
|
# at Immediate.<anonymous> (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/protocol/transport.js:74:26)
|
|
105
117
|
# at processImmediate (internal/timers.js:461:21)
|
|
106
|
-
|
|
118
|
+
#
|
|
119
|
+
# Node >= 16 prints '<anonymous_script>:1' instead of 'undefined:1'.
|
|
120
|
+
# ref: https://github.com/YusukeIwaki/playwright-ruby-client/issues/392
|
|
121
|
+
if err.include?('undefined:1') || err.include?('<anonymous_script>:1') ||
|
|
122
|
+
err.include?('is not valid JSON')
|
|
107
123
|
@on_driver_crashed&.call
|
|
108
124
|
break
|
|
109
125
|
end
|
data/lib/playwright/utils.rb
CHANGED
|
@@ -18,9 +18,38 @@ module Playwright
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
module Utils
|
|
21
|
+
module ClearCookiesParameters
|
|
22
|
+
private def clear_cookies_params(domain:, name:, path:)
|
|
23
|
+
params = {}
|
|
24
|
+
{ domain: domain, name: name, path: path }.each do |key, value|
|
|
25
|
+
case value
|
|
26
|
+
when String
|
|
27
|
+
params[key] = value
|
|
28
|
+
when Regexp
|
|
29
|
+
regex = JavaScript::Regex.new(value)
|
|
30
|
+
params["#{key}RegexSource".to_sym] = regex.source
|
|
31
|
+
params["#{key}RegexFlags".to_sym] = regex.flag
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
params
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module PrepareHttpCredentials
|
|
39
|
+
private def prepare_http_credentials(credentials)
|
|
40
|
+
return nil unless credentials
|
|
41
|
+
|
|
42
|
+
list = credentials.is_a?(Array) ? credentials : [credentials]
|
|
43
|
+
list.empty? ? nil : list
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
21
47
|
module PrepareBrowserContextOptions
|
|
48
|
+
include PrepareHttpCredentials
|
|
49
|
+
|
|
22
50
|
# @see https://github.com/microsoft/playwright/blob/5a2cfdbd47ed3c3deff77bb73e5fac34241f649d/src/client/browserContext.ts#L265
|
|
23
51
|
private def prepare_browser_context_options(params)
|
|
52
|
+
params[:httpCredentials] = prepare_http_credentials(params[:httpCredentials])
|
|
24
53
|
if params[:noViewport] == 0
|
|
25
54
|
params.delete(:noViewport)
|
|
26
55
|
params[:noDefaultViewport] = true
|
|
@@ -35,6 +64,9 @@ module Playwright
|
|
|
35
64
|
if params[:record_video_size]
|
|
36
65
|
params[:recordVideo][:size] = params.delete(:record_video_size)
|
|
37
66
|
end
|
|
67
|
+
unless params[:record_video_fps].nil?
|
|
68
|
+
params[:recordVideo][:fps] = params.delete(:record_video_fps)
|
|
69
|
+
end
|
|
38
70
|
if params[:record_video_show_actions]
|
|
39
71
|
params[:recordVideo][:showActions] = params.delete(:record_video_show_actions)
|
|
40
72
|
end
|