playwright-ruby-client 0.6.6 → 0.8.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/README.md +26 -0
- data/documentation/docs/api/browser.md +18 -2
- data/documentation/docs/api/browser_context.md +10 -0
- data/documentation/docs/api/browser_type.md +1 -0
- data/documentation/docs/api/cdp_session.md +41 -1
- data/documentation/docs/api/element_handle.md +11 -2
- data/documentation/docs/api/experimental/android_device.md +1 -0
- data/documentation/docs/api/frame.md +29 -1
- data/documentation/docs/api/keyboard.md +11 -20
- data/documentation/docs/api/page.md +48 -2
- data/documentation/docs/api/response.md +16 -0
- data/documentation/docs/api/web_socket.md +37 -0
- data/documentation/docs/article/guides/launch_browser.md +2 -0
- data/documentation/docs/article/guides/playwright_on_alpine_linux.md +91 -0
- data/documentation/docs/article/guides/rails_integration.md +1 -1
- data/documentation/docs/article/guides/semi_automation.md +71 -0
- data/documentation/docs/include/api_coverage.md +18 -11
- data/lib/playwright.rb +36 -3
- data/lib/playwright/channel_owners/artifact.rb +4 -0
- data/lib/playwright/channel_owners/browser.rb +5 -0
- data/lib/playwright/channel_owners/browser_context.rb +12 -3
- data/lib/playwright/channel_owners/cdp_session.rb +19 -0
- data/lib/playwright/channel_owners/element_handle.rb +11 -4
- data/lib/playwright/channel_owners/frame.rb +36 -4
- data/lib/playwright/channel_owners/page.rb +45 -16
- data/lib/playwright/channel_owners/response.rb +9 -1
- data/lib/playwright/channel_owners/web_socket.rb +83 -0
- data/lib/playwright/connection.rb +2 -4
- data/lib/playwright/download.rb +4 -0
- data/lib/playwright/route_handler_entry.rb +3 -2
- data/lib/playwright/transport.rb +0 -1
- data/lib/playwright/url_matcher.rb +12 -2
- data/lib/playwright/version.rb +2 -2
- data/lib/playwright/web_socket_client.rb +164 -0
- data/lib/playwright/web_socket_transport.rb +104 -0
- data/lib/playwright_api/android.rb +6 -6
- data/lib/playwright_api/android_device.rb +10 -9
- data/lib/playwright_api/browser.rb +17 -11
- data/lib/playwright_api/browser_context.rb +7 -7
- data/lib/playwright_api/browser_type.rb +8 -7
- data/lib/playwright_api/cdp_session.rb +30 -8
- data/lib/playwright_api/console_message.rb +6 -6
- data/lib/playwright_api/dialog.rb +6 -6
- data/lib/playwright_api/element_handle.rb +17 -11
- data/lib/playwright_api/frame.rb +30 -9
- data/lib/playwright_api/js_handle.rb +6 -6
- data/lib/playwright_api/page.rb +39 -18
- data/lib/playwright_api/playwright.rb +6 -6
- data/lib/playwright_api/request.rb +6 -6
- data/lib/playwright_api/response.rb +15 -10
- data/lib/playwright_api/route.rb +6 -6
- data/lib/playwright_api/selectors.rb +6 -6
- data/lib/playwright_api/web_socket.rb +12 -12
- data/lib/playwright_api/worker.rb +6 -6
- data/playwright.gemspec +2 -1
- metadata +37 -18
@@ -0,0 +1,19 @@
|
|
1
|
+
module Playwright
|
2
|
+
define_channel_owner :CDPSession do
|
3
|
+
private def after_initialize
|
4
|
+
@channel.on('event', method(:on_event))
|
5
|
+
end
|
6
|
+
|
7
|
+
private def on_event(params)
|
8
|
+
emit(params['method'], params['params'])
|
9
|
+
end
|
10
|
+
|
11
|
+
def send_message(method, params: {})
|
12
|
+
@channel.send_message_to_server('send', method: method, params: params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def detach
|
16
|
+
@channel.send_message_to_server('detach')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -146,6 +146,7 @@ module Playwright
|
|
146
146
|
index: nil,
|
147
147
|
value: nil,
|
148
148
|
label: nil,
|
149
|
+
force: nil,
|
149
150
|
noWaitAfter: nil,
|
150
151
|
timeout: nil)
|
151
152
|
base_params = SelectOptionValues.new(
|
@@ -154,7 +155,7 @@ module Playwright
|
|
154
155
|
value: value,
|
155
156
|
label: label,
|
156
157
|
).as_params
|
157
|
-
params = base_params.merge({ noWaitAfter: noWaitAfter, timeout: timeout }.compact)
|
158
|
+
params = base_params.merge({ force: force, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
|
158
159
|
@channel.send_message_to_server('selectOption', params)
|
159
160
|
end
|
160
161
|
|
@@ -179,9 +180,10 @@ module Playwright
|
|
179
180
|
nil
|
180
181
|
end
|
181
182
|
|
182
|
-
def fill(value, noWaitAfter: nil, timeout: nil)
|
183
|
+
def fill(value, force: nil, noWaitAfter: nil, timeout: nil)
|
183
184
|
params = {
|
184
185
|
value: value,
|
186
|
+
force: force,
|
185
187
|
noWaitAfter: noWaitAfter,
|
186
188
|
timeout: timeout,
|
187
189
|
}.compact
|
@@ -190,13 +192,18 @@ module Playwright
|
|
190
192
|
nil
|
191
193
|
end
|
192
194
|
|
193
|
-
def select_text(timeout: nil)
|
194
|
-
params = { timeout: timeout }.compact
|
195
|
+
def select_text(force: nil, timeout: nil)
|
196
|
+
params = { force: force, timeout: timeout }.compact
|
195
197
|
@channel.send_message_to_server('selectText', params)
|
196
198
|
|
197
199
|
nil
|
198
200
|
end
|
199
201
|
|
202
|
+
def input_value(timeout: nil)
|
203
|
+
params = { timeout: timeout }.compact
|
204
|
+
@channel.send_message_to_server('inputValue', params)
|
205
|
+
end
|
206
|
+
|
200
207
|
def set_input_files(files, noWaitAfter: nil, timeout: nil)
|
201
208
|
file_payloads = InputFiles.new(files).as_params
|
202
209
|
params = { files: file_payloads, noWaitAfter: noWaitAfter, timeout: timeout }.compact
|
@@ -71,7 +71,7 @@ module Playwright
|
|
71
71
|
|
72
72
|
predicate =
|
73
73
|
if url
|
74
|
-
matcher = UrlMatcher.new(url)
|
74
|
+
matcher = UrlMatcher.new(url, base_url: @page.context.send(:base_url))
|
75
75
|
->(event) { event['error'] || matcher.match?(event['url']) }
|
76
76
|
else
|
77
77
|
->(_) { true }
|
@@ -99,7 +99,7 @@ module Playwright
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def wait_for_url(url, timeout: nil, waitUntil: nil)
|
102
|
-
matcher = UrlMatcher.new(url)
|
102
|
+
matcher = UrlMatcher.new(url, base_url: @page.context.send(:base_url))
|
103
103
|
if matcher.match?(@url)
|
104
104
|
wait_for_load_state(state: waitUntil, timeout: timeout)
|
105
105
|
else
|
@@ -293,6 +293,26 @@ module Playwright
|
|
293
293
|
nil
|
294
294
|
end
|
295
295
|
|
296
|
+
def drag_and_drop(
|
297
|
+
source,
|
298
|
+
target,
|
299
|
+
force: nil,
|
300
|
+
noWaitAfter: nil,
|
301
|
+
timeout: nil,
|
302
|
+
trial: nil)
|
303
|
+
params = {
|
304
|
+
source: source,
|
305
|
+
target: target,
|
306
|
+
force: force,
|
307
|
+
noWaitAfter: noWaitAfter,
|
308
|
+
timeout: timeout,
|
309
|
+
trial: trial,
|
310
|
+
}.compact
|
311
|
+
@channel.send_message_to_server('dragAndDrop', params)
|
312
|
+
|
313
|
+
nil
|
314
|
+
end
|
315
|
+
|
296
316
|
def dblclick(
|
297
317
|
selector,
|
298
318
|
button: nil,
|
@@ -342,10 +362,16 @@ module Playwright
|
|
342
362
|
nil
|
343
363
|
end
|
344
364
|
|
345
|
-
def fill(
|
365
|
+
def fill(
|
366
|
+
selector,
|
367
|
+
value,
|
368
|
+
force: nil,
|
369
|
+
noWaitAfter: nil,
|
370
|
+
timeout: nil)
|
346
371
|
params = {
|
347
372
|
selector: selector,
|
348
373
|
value: value,
|
374
|
+
force: force,
|
349
375
|
noWaitAfter: noWaitAfter,
|
350
376
|
timeout: timeout,
|
351
377
|
}.compact
|
@@ -410,6 +436,7 @@ module Playwright
|
|
410
436
|
index: nil,
|
411
437
|
value: nil,
|
412
438
|
label: nil,
|
439
|
+
force: nil,
|
413
440
|
noWaitAfter: nil,
|
414
441
|
timeout: nil)
|
415
442
|
base_params = SelectOptionValues.new(
|
@@ -418,10 +445,15 @@ module Playwright
|
|
418
445
|
value: value,
|
419
446
|
label: label,
|
420
447
|
).as_params
|
421
|
-
params = base_params.merge({ selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
|
448
|
+
params = base_params.merge({ selector: selector, force: force, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
|
422
449
|
@channel.send_message_to_server('selectOption', params)
|
423
450
|
end
|
424
451
|
|
452
|
+
def input_value(selector, timeout: nil)
|
453
|
+
params = { selector: selector, timeout: timeout }.compact
|
454
|
+
@channel.send_message_to_server('inputValue', params)
|
455
|
+
end
|
456
|
+
|
425
457
|
def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
|
426
458
|
file_payloads = InputFiles.new(files).as_params
|
427
459
|
params = { files: file_payloads, selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
|
@@ -22,7 +22,7 @@ module Playwright
|
|
22
22
|
end
|
23
23
|
@closed = false
|
24
24
|
@bindings = {}
|
25
|
-
@routes =
|
25
|
+
@routes = []
|
26
26
|
|
27
27
|
@main_frame = ChannelOwners::Frame.from(@initializer['mainFrame'])
|
28
28
|
@main_frame.send(:update_page_from_page, self)
|
@@ -184,15 +184,8 @@ module Playwright
|
|
184
184
|
if name
|
185
185
|
@frames.find { |f| f.name == name }
|
186
186
|
elsif url
|
187
|
-
|
188
|
-
|
189
|
-
when String
|
190
|
-
@frames.find { |f| f.url == url }
|
191
|
-
when Regexp
|
192
|
-
@frames.find { |f| url.match?(f.url) }
|
193
|
-
else
|
194
|
-
raise NotImplementedError.new('Page#frame with url is not completely implemented yet')
|
195
|
-
end
|
187
|
+
matcher = UrlMatcher.new(url, base_url: @browser_context.send(:base_url))
|
188
|
+
@frames.find { |f| matcher.match?(f.url) }
|
196
189
|
else
|
197
190
|
raise ArgumentError.new('Either name or url matcher should be specified')
|
198
191
|
end
|
@@ -377,8 +370,8 @@ module Playwright
|
|
377
370
|
end
|
378
371
|
|
379
372
|
def route(url, handler)
|
380
|
-
entry = RouteHandlerEntry.new(url, handler)
|
381
|
-
@routes
|
373
|
+
entry = RouteHandlerEntry.new(url, @browser_context.send(:base_url), handler)
|
374
|
+
@routes.unshift(entry)
|
382
375
|
if @routes.count >= 1
|
383
376
|
@channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
|
384
377
|
end
|
@@ -463,6 +456,23 @@ module Playwright
|
|
463
456
|
)
|
464
457
|
end
|
465
458
|
|
459
|
+
def drag_and_drop(
|
460
|
+
source,
|
461
|
+
target,
|
462
|
+
force: nil,
|
463
|
+
noWaitAfter: nil,
|
464
|
+
timeout: nil,
|
465
|
+
trial: nil)
|
466
|
+
|
467
|
+
@main_frame.drag_and_drop(
|
468
|
+
source,
|
469
|
+
target,
|
470
|
+
force: force,
|
471
|
+
noWaitAfter: noWaitAfter,
|
472
|
+
timeout: timeout,
|
473
|
+
trial: trial)
|
474
|
+
end
|
475
|
+
|
466
476
|
def dblclick(
|
467
477
|
selector,
|
468
478
|
button: nil,
|
@@ -505,8 +515,13 @@ module Playwright
|
|
505
515
|
)
|
506
516
|
end
|
507
517
|
|
508
|
-
def fill(
|
509
|
-
|
518
|
+
def fill(
|
519
|
+
selector,
|
520
|
+
value,
|
521
|
+
force: nil,
|
522
|
+
noWaitAfter: nil,
|
523
|
+
timeout: nil)
|
524
|
+
@main_frame.fill(selector, value, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
|
510
525
|
end
|
511
526
|
|
512
527
|
def focus(selector, timeout: nil)
|
@@ -552,6 +567,7 @@ module Playwright
|
|
552
567
|
index: nil,
|
553
568
|
value: nil,
|
554
569
|
label: nil,
|
570
|
+
force: nil,
|
555
571
|
noWaitAfter: nil,
|
556
572
|
timeout: nil)
|
557
573
|
@main_frame.select_option(
|
@@ -560,11 +576,16 @@ module Playwright
|
|
560
576
|
index: index,
|
561
577
|
value: value,
|
562
578
|
label: label,
|
579
|
+
force: force,
|
563
580
|
noWaitAfter: noWaitAfter,
|
564
581
|
timeout: timeout,
|
565
582
|
)
|
566
583
|
end
|
567
584
|
|
585
|
+
def input_value(selector, timeout: nil)
|
586
|
+
@main_frame.input_value(selector, timeout: timeout)
|
587
|
+
end
|
588
|
+
|
568
589
|
def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
|
569
590
|
@main_frame.set_input_files(selector, files, noWaitAfter: noWaitAfter, timeout: timeout)
|
570
591
|
end
|
@@ -748,7 +769,7 @@ module Playwright
|
|
748
769
|
predicate =
|
749
770
|
case urlOrPredicate
|
750
771
|
when String, Regexp
|
751
|
-
url_matcher = UrlMatcher.new(urlOrPredicate)
|
772
|
+
url_matcher = UrlMatcher.new(urlOrPredicate, base_url: @browser_context.send(:base_url))
|
752
773
|
-> (req){ url_matcher.match?(req.url) }
|
753
774
|
when Proc
|
754
775
|
urlOrPredicate
|
@@ -759,11 +780,15 @@ module Playwright
|
|
759
780
|
expect_event(Events::Page::Request, predicate: predicate, timeout: timeout, &block)
|
760
781
|
end
|
761
782
|
|
783
|
+
def expect_request_finished(predicate: nil, timeout: nil, &block)
|
784
|
+
expect_event(Events::Page::RequestFinished, predicate: predicate, timeout: timeout, &block)
|
785
|
+
end
|
786
|
+
|
762
787
|
def expect_response(urlOrPredicate, timeout: nil, &block)
|
763
788
|
predicate =
|
764
789
|
case urlOrPredicate
|
765
790
|
when String, Regexp
|
766
|
-
url_matcher = UrlMatcher.new(urlOrPredicate)
|
791
|
+
url_matcher = UrlMatcher.new(urlOrPredicate, base_url: @browser_context.send(:base_url))
|
767
792
|
-> (req){ url_matcher.match?(req.url) }
|
768
793
|
when Proc
|
769
794
|
urlOrPredicate
|
@@ -774,6 +799,10 @@ module Playwright
|
|
774
799
|
expect_event(Events::Page::Response, predicate: predicate, timeout: timeout, &block)
|
775
800
|
end
|
776
801
|
|
802
|
+
def expect_websocket(predicate: nil, timeout: nil, &block)
|
803
|
+
expect_event(Events::Page::WebSocket, predicate: predicate, timeout: timeout, &block)
|
804
|
+
end
|
805
|
+
|
777
806
|
# called from Frame with send(:timeout_settings)
|
778
807
|
private def timeout_settings
|
779
808
|
@timeout_settings
|
@@ -4,7 +4,7 @@ require 'json'
|
|
4
4
|
module Playwright
|
5
5
|
# @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
|
6
6
|
define_channel_owner :Response do
|
7
|
-
def after_initialize
|
7
|
+
private def after_initialize
|
8
8
|
@request = ChannelOwners::Request.from(@initializer['request'])
|
9
9
|
timing = @initializer['timing']
|
10
10
|
@request.send(:update_timings,
|
@@ -44,6 +44,14 @@ module Playwright
|
|
44
44
|
end.to_h
|
45
45
|
end
|
46
46
|
|
47
|
+
def server_addr
|
48
|
+
@channel.send_message_to_server('serverAddr')
|
49
|
+
end
|
50
|
+
|
51
|
+
def security_details
|
52
|
+
@channel.send_message_to_server('securityDetails')
|
53
|
+
end
|
54
|
+
|
47
55
|
def finished
|
48
56
|
@channel.send_message_to_server('finished')
|
49
57
|
end
|
@@ -1,4 +1,87 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
1
3
|
module Playwright
|
2
4
|
define_channel_owner :WebSocket do
|
5
|
+
private def after_initialize
|
6
|
+
@closed = false
|
7
|
+
|
8
|
+
@channel.on('frameSent', -> (params) {
|
9
|
+
on_frame_sent(params['opcode'], params['data'])
|
10
|
+
})
|
11
|
+
@channel.on('frameReceived', -> (params) {
|
12
|
+
on_frame_received(params['opcode'], params['data'])
|
13
|
+
})
|
14
|
+
@channel.on('socketError', -> (params) {
|
15
|
+
emit(Events::WebSocket::Error, params['error'])
|
16
|
+
})
|
17
|
+
@channel.on('close', -> (_) { on_close })
|
18
|
+
end
|
19
|
+
|
20
|
+
def url
|
21
|
+
@initializer['url']
|
22
|
+
end
|
23
|
+
|
24
|
+
class SocketClosedError < StandardError
|
25
|
+
def initialize
|
26
|
+
super('Socket closed')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class SocketError < StandardError
|
31
|
+
def initialize
|
32
|
+
super('Socket error')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class PageClosedError < StandardError
|
37
|
+
def initialize
|
38
|
+
super('Page closed')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def expect_event(event, predicate: nil, timeout: nil, &block)
|
43
|
+
wait_helper = WaitHelper.new
|
44
|
+
wait_helper.reject_on_timeout(timeout || @parent.send(:timeout_settings).timeout, "Timeout while waiting for event \"#{event}\"")
|
45
|
+
|
46
|
+
unless event == Events::WebSocket::Close
|
47
|
+
wait_helper.reject_on_event(self, Events::WebSocket::Close, SocketClosedError.new)
|
48
|
+
end
|
49
|
+
|
50
|
+
unless event == Events::WebSocket::Error
|
51
|
+
wait_helper.reject_on_event(self, Events::WebSocket::Error, SocketError.new)
|
52
|
+
end
|
53
|
+
|
54
|
+
wait_helper.reject_on_event(@parent, 'close', PageClosedError.new)
|
55
|
+
wait_helper.wait_for_event(self, event, predicate: predicate)
|
56
|
+
block&.call
|
57
|
+
|
58
|
+
wait_helper.promise.value!
|
59
|
+
end
|
60
|
+
alias_method :wait_for_event, :expect_event
|
61
|
+
|
62
|
+
private def on_frame_sent(opcode, data)
|
63
|
+
if opcode == 2
|
64
|
+
emit(Events::WebSocket::FrameSent, Base64.strict_decode64(data))
|
65
|
+
else
|
66
|
+
emit(Events::WebSocket::FrameSent, data)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private def on_frame_received(opcode, data)
|
71
|
+
if opcode == 2
|
72
|
+
emit(Events::WebSocket::FrameReceived, Base64.strict_decode64(data))
|
73
|
+
else
|
74
|
+
emit(Events::WebSocket::FrameReceived, data)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def closed?
|
79
|
+
@closed
|
80
|
+
end
|
81
|
+
|
82
|
+
private def on_close
|
83
|
+
@closed = true
|
84
|
+
emit(Events::WebSocket::Close)
|
85
|
+
end
|
3
86
|
end
|
4
87
|
end
|
@@ -5,10 +5,8 @@ module Playwright
|
|
5
5
|
# https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_connection.py
|
6
6
|
# https://github.com/microsoft/playwright-java/blob/master/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
|
7
7
|
class Connection
|
8
|
-
def initialize(
|
9
|
-
@transport =
|
10
|
-
playwright_cli_executable_path: playwright_cli_executable_path
|
11
|
-
)
|
8
|
+
def initialize(transport)
|
9
|
+
@transport = transport
|
12
10
|
@transport.on_message_received do |message|
|
13
11
|
dispatch(message)
|
14
12
|
end
|
data/lib/playwright/download.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Playwright
|
2
2
|
class RouteHandlerEntry
|
3
3
|
# @param url [String]
|
4
|
+
# @param base_url [String|nil]
|
4
5
|
# @param handler [Proc]
|
5
|
-
def initialize(url, handler)
|
6
|
+
def initialize(url, base_url, handler)
|
6
7
|
@url_value = url
|
7
|
-
@url_matcher = UrlMatcher.new(url)
|
8
|
+
@url_matcher = UrlMatcher.new(url, base_url: base_url)
|
8
9
|
@handler = handler
|
9
10
|
end
|
10
11
|
|
data/lib/playwright/transport.rb
CHANGED
@@ -8,7 +8,6 @@ module Playwright
|
|
8
8
|
# ref: https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_transport.py
|
9
9
|
class Transport
|
10
10
|
# @param playwright_cli_executable_path [String] path to playwright-cli.
|
11
|
-
# @param debug [Boolean]
|
12
11
|
def initialize(playwright_cli_executable_path:)
|
13
12
|
@driver_executable_path = playwright_cli_executable_path
|
14
13
|
@debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
|