playwright-ruby-client 0.6.6 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|