playwright-ruby-client 0.6.4 → 0.8.0
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 +10 -0
- data/documentation/docs/api/browser_context.md +10 -0
- data/documentation/docs/api/browser_type.md +53 -0
- data/documentation/docs/api/cdp_session.md +41 -1
- data/documentation/docs/api/element_handle.md +11 -2
- data/documentation/docs/api/frame.md +15 -1
- data/documentation/docs/api/page.md +33 -1
- data/documentation/docs/api/response.md +16 -0
- data/documentation/docs/api/route.md +20 -21
- data/documentation/docs/api/web_socket.md +38 -1
- data/documentation/docs/article/guides/launch_browser.md +2 -0
- data/documentation/docs/article/guides/rails_integration.md +2 -2
- data/documentation/docs/article/guides/semi_automation.md +67 -0
- data/documentation/docs/include/api_coverage.md +19 -13
- data/lib/playwright.rb +36 -3
- data/lib/playwright/channel_owners/browser.rb +5 -0
- data/lib/playwright/channel_owners/browser_context.rb +5 -0
- data/lib/playwright/channel_owners/browser_type.rb +18 -0
- 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 +14 -2
- data/lib/playwright/channel_owners/page.rb +21 -2
- data/lib/playwright/channel_owners/response.rb +9 -1
- data/lib/playwright/channel_owners/web_socket.rb +87 -0
- data/lib/playwright/connection.rb +2 -4
- data/lib/playwright/transport.rb +0 -1
- 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 +8 -8
- data/lib/playwright_api/browser.rb +7 -7
- data/lib/playwright_api/browser_context.rb +7 -7
- data/lib/playwright_api/browser_type.rb +9 -8
- 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 +20 -9
- data/lib/playwright_api/js_handle.rb +6 -6
- data/lib/playwright_api/page.rb +28 -17
- 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 +11 -6
- data/lib/playwright_api/selectors.rb +6 -6
- data/lib/playwright_api/web_socket.rb +28 -6
- data/lib/playwright_api/worker.rb +6 -6
- data/playwright.gemspec +2 -1
- metadata +37 -18
@@ -117,3 +117,5 @@ Playwright.create(playwright_cli_executable_path: 'npx playwright') do |playwrig
|
|
117
117
|
end
|
118
118
|
end
|
119
119
|
```
|
120
|
+
|
121
|
+
Note that each browser context is isolated and not persisted by default. When persistent browser context is needed, we can use [BrowserType#launch_persistent_context](/docs/api/browser_type#launch_persistent_context).
|
@@ -41,7 +41,7 @@ Capybara.default_max_wait_time = 15
|
|
41
41
|
|
42
42
|
### (Optional) Update default driver
|
43
43
|
|
44
|
-
By default, Capybara driver is set to `:rack_test`, which works only with non-JS contents. If your Rails application has many JavaScript contents, it is recommended to change the default driver to `:
|
44
|
+
By default, Capybara driver is set to `:rack_test`, which works only with non-JS contents. If your Rails application has many JavaScript contents, it is recommended to change the default driver to `:playwright`.
|
45
45
|
|
46
46
|
```rb
|
47
47
|
Capybara.default_driver = :playwright
|
@@ -202,4 +202,4 @@ end
|
|
202
202
|
|
203
203
|
* Playwright doesn't allow clicking invisible DOM elements or moving elements. `click` sometimes doesn't work as Selenium does. See the detail in https://playwright.dev/docs/actionability/
|
204
204
|
* `current_window.maximize` and `current_window.fullscreen` work only on headful (non-headless) mode, as selenium driver does.
|
205
|
-
* `Capybara::Node::Element#drag_to` does not accept `html5` parameter
|
205
|
+
* `Capybara::Node::Element#drag_to` does not accept `html5` parameter. HTML5 drag and drop is not fully supported in Playwright.
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Semi-automation
|
2
|
+
|
3
|
+
Playwright Browser context is isolated and not persisted by default. But we can also use persistent browser context using [BrowserType#launch_persistent_context](/docs/api/browser_type#launch_persistent_context).
|
4
|
+
This allow us to intermediate into automation, for example
|
5
|
+
|
6
|
+
* Authenticate with OAuth2 manually before automation
|
7
|
+
* Testing a page after some chrome extensions are installed manually
|
8
|
+
|
9
|
+
Keep in mind repeatedly that persistent browser context is NOT RECOMMENDED for most cases because it would bring many side effects.
|
10
|
+
|
11
|
+
## Pause automation for manual operation
|
12
|
+
|
13
|
+
`Page#pause` is not implemented yet, however we can use `binding.pry` (with `pry-byebug` installed) instead.
|
14
|
+
|
15
|
+
```ruby {4}
|
16
|
+
playwright.chromium.launch_persistent_context('./data/', headless: false) do |context|
|
17
|
+
page = context.new_page
|
18
|
+
page.goto('https://example.com/')
|
19
|
+
binding.pry
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
When script is executed, it is paused as below.
|
24
|
+
|
25
|
+
```
|
26
|
+
3:
|
27
|
+
4: playwright.chromium.launch_persistent_context('./data/', headless: false) do |context|
|
28
|
+
5: page = context.new_page
|
29
|
+
6: page.goto('https://example.com/')
|
30
|
+
=> 7: binding.pry
|
31
|
+
8: end
|
32
|
+
|
33
|
+
[1] pry(main)>
|
34
|
+
```
|
35
|
+
|
36
|
+
We can inspect using `page`, `context` and also we can operate something manually during the pause.
|
37
|
+
|
38
|
+
See https://github.com/deivid-rodriguez/pry-byebug for more detailed debugging options.
|
39
|
+
|
40
|
+
## Working with Chrome extensions
|
41
|
+
|
42
|
+
**Playwright disables the Chrome extension feature by default.**
|
43
|
+
We have to enable it for installing Chrome extension, by passing these 3 parameters on launch.
|
44
|
+
|
45
|
+
* `acceptDownloads: true`
|
46
|
+
* `headless: false`
|
47
|
+
* `ignoreDefaultArgs: ['--disable-extensions']`
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'playwright'
|
51
|
+
require 'pry'
|
52
|
+
|
53
|
+
Playwright.create(playwright_cli_executable_path: './node_modules/.bin/playwright') do |playwright|
|
54
|
+
launch_params = {
|
55
|
+
acceptDownloads: true,
|
56
|
+
channel: 'chrome',
|
57
|
+
headless: false,
|
58
|
+
ignoreDefaultArgs: ['--disable-extensions'],
|
59
|
+
}
|
60
|
+
|
61
|
+
playwright.chromium.launch_persistent_context('./data/', **launch_params) do |context|
|
62
|
+
page = context.new_page
|
63
|
+
page.goto('https://example.com/')
|
64
|
+
binding.pry
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
@@ -26,6 +26,8 @@
|
|
26
26
|
* json
|
27
27
|
* ok
|
28
28
|
* request
|
29
|
+
* security_details
|
30
|
+
* server_addr
|
29
31
|
* status
|
30
32
|
* status_text
|
31
33
|
* text
|
@@ -36,14 +38,15 @@
|
|
36
38
|
* abort
|
37
39
|
* continue
|
38
40
|
* fulfill
|
41
|
+
* ~~intercept~~
|
39
42
|
* request
|
40
43
|
|
41
|
-
##
|
44
|
+
## WebSocket
|
42
45
|
|
43
|
-
*
|
44
|
-
*
|
45
|
-
*
|
46
|
-
*
|
46
|
+
* closed?
|
47
|
+
* url
|
48
|
+
* expect_event
|
49
|
+
* wait_for_event
|
47
50
|
|
48
51
|
## Keyboard
|
49
52
|
|
@@ -91,6 +94,7 @@
|
|
91
94
|
* hover
|
92
95
|
* inner_html
|
93
96
|
* inner_text
|
97
|
+
* input_value
|
94
98
|
* checked?
|
95
99
|
* disabled?
|
96
100
|
* editable?
|
@@ -146,6 +150,7 @@
|
|
146
150
|
* hover
|
147
151
|
* inner_html
|
148
152
|
* inner_text
|
153
|
+
* input_value
|
149
154
|
* checked?
|
150
155
|
* detached?
|
151
156
|
* disabled?
|
@@ -231,6 +236,7 @@
|
|
231
236
|
* hover
|
232
237
|
* inner_html
|
233
238
|
* inner_text
|
239
|
+
* input_value
|
234
240
|
* checked?
|
235
241
|
* closed?
|
236
242
|
* disabled?
|
@@ -273,12 +279,12 @@
|
|
273
279
|
* expect_navigation
|
274
280
|
* expect_popup
|
275
281
|
* expect_request
|
276
|
-
*
|
282
|
+
* expect_request_finished
|
277
283
|
* expect_response
|
278
284
|
* wait_for_selector
|
279
285
|
* ~~wait_for_timeout~~
|
280
286
|
* wait_for_url
|
281
|
-
*
|
287
|
+
* expect_websocket
|
282
288
|
* ~~expect_worker~~
|
283
289
|
* ~~workers~~
|
284
290
|
* ~~wait_for_event~~
|
@@ -300,7 +306,7 @@
|
|
300
306
|
* expose_binding
|
301
307
|
* expose_function
|
302
308
|
* grant_permissions
|
303
|
-
*
|
309
|
+
* new_cdp_session
|
304
310
|
* new_page
|
305
311
|
* pages
|
306
312
|
* route
|
@@ -317,17 +323,17 @@
|
|
317
323
|
* ~~wait_for_event~~
|
318
324
|
* tracing
|
319
325
|
|
320
|
-
##
|
326
|
+
## CDPSession
|
321
327
|
|
322
|
-
*
|
323
|
-
*
|
328
|
+
* detach
|
329
|
+
* send_message
|
324
330
|
|
325
331
|
## Browser
|
326
332
|
|
327
333
|
* close
|
328
334
|
* contexts
|
329
335
|
* connected?
|
330
|
-
*
|
336
|
+
* new_browser_cdp_session
|
331
337
|
* new_context
|
332
338
|
* new_page
|
333
339
|
* start_tracing
|
@@ -340,7 +346,7 @@
|
|
340
346
|
* connect_over_cdp
|
341
347
|
* executable_path
|
342
348
|
* launch
|
343
|
-
*
|
349
|
+
* launch_persistent_context
|
344
350
|
* name
|
345
351
|
|
346
352
|
## Playwright
|
data/lib/playwright.rb
CHANGED
@@ -59,7 +59,8 @@ module Playwright
|
|
59
59
|
# and we *must* call execution.stop on the end.
|
60
60
|
# The instance of playwright is available by calling execution.playwright
|
61
61
|
module_function def create(playwright_cli_executable_path:, &block)
|
62
|
-
|
62
|
+
transport = Transport.new(playwright_cli_executable_path: playwright_cli_executable_path)
|
63
|
+
connection = Connection.new(transport)
|
63
64
|
connection.async_run
|
64
65
|
|
65
66
|
execution =
|
@@ -82,7 +83,39 @@ module Playwright
|
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
85
|
-
|
86
|
-
|
86
|
+
# Connects to Playwright server, launched by `npx playwright run-server` via WebSocket transport.
|
87
|
+
#
|
88
|
+
# Playwright.connect_to_playwright_server(...) do |playwright|
|
89
|
+
# browser = playwright.chromium.launch
|
90
|
+
# ...
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# @experimental
|
94
|
+
module_function def connect_to_playwright_server(ws_endpoint, &block)
|
95
|
+
require 'playwright/web_socket_client'
|
96
|
+
require 'playwright/web_socket_transport'
|
97
|
+
|
98
|
+
transport = WebSocketTransport.new(ws_endpoint: ws_endpoint)
|
99
|
+
connection = Connection.new(transport)
|
100
|
+
connection.async_run
|
101
|
+
|
102
|
+
execution =
|
103
|
+
begin
|
104
|
+
playwright = connection.wait_for_object_with_known_name('Playwright')
|
105
|
+
Execution.new(connection, PlaywrightApi.wrap(playwright))
|
106
|
+
rescue
|
107
|
+
connection.stop
|
108
|
+
raise
|
109
|
+
end
|
110
|
+
|
111
|
+
if block
|
112
|
+
begin
|
113
|
+
block.call(execution.playwright)
|
114
|
+
ensure
|
115
|
+
execution.stop
|
116
|
+
end
|
117
|
+
else
|
118
|
+
execution
|
119
|
+
end
|
87
120
|
end
|
88
121
|
end
|
@@ -67,6 +67,11 @@ module Playwright
|
|
67
67
|
@initializer['version']
|
68
68
|
end
|
69
69
|
|
70
|
+
def new_browser_cdp_session
|
71
|
+
resp = @channel.send_message_to_server('newBrowserCDPSession')
|
72
|
+
ChannelOwners::CDPSession.from(resp)
|
73
|
+
end
|
74
|
+
|
70
75
|
def start_tracing(page: nil, categories: nil, path: nil, screenshots: nil)
|
71
76
|
params = {
|
72
77
|
page: page&.channel,
|
@@ -98,6 +98,11 @@ module Playwright
|
|
98
98
|
page&.emit(Events::Page::Response, response)
|
99
99
|
end
|
100
100
|
|
101
|
+
def new_cdp_session(page)
|
102
|
+
resp = @channel.send_message_to_server('newCDPSession', page: page.channel)
|
103
|
+
ChannelOwners::CDPSession.from(resp)
|
104
|
+
end
|
105
|
+
|
101
106
|
def set_default_navigation_timeout(timeout)
|
102
107
|
@timeout_settings.default_navigation_timeout = timeout
|
103
108
|
@channel.send_message_to_server('setDefaultNavigationTimeoutNoReply', timeout: timeout)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Playwright
|
2
2
|
define_channel_owner :BrowserType do
|
3
|
+
include Utils::PrepareBrowserContextOptions
|
4
|
+
|
3
5
|
def name
|
4
6
|
@initializer['name']
|
5
7
|
end
|
@@ -20,6 +22,22 @@ module Playwright
|
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
25
|
+
def launch_persistent_context(userDataDir, **options, &block)
|
26
|
+
params = options.dup
|
27
|
+
prepare_browser_context_options(params)
|
28
|
+
params['userDataDir'] = userDataDir
|
29
|
+
|
30
|
+
resp = @channel.send_message_to_server('launchPersistentContext', params.compact)
|
31
|
+
context = ChannelOwners::Browser.from(resp)
|
32
|
+
return context unless block
|
33
|
+
|
34
|
+
begin
|
35
|
+
block.call(context)
|
36
|
+
ensure
|
37
|
+
context.close
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
23
41
|
def connect_over_cdp(endpointURL, headers: nil, slowMo: nil, timeout: nil, &block)
|
24
42
|
raise 'Connecting over CDP is only supported in Chromium.' unless name == 'chromium'
|
25
43
|
|
@@ -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
|
@@ -342,10 +342,16 @@ module Playwright
|
|
342
342
|
nil
|
343
343
|
end
|
344
344
|
|
345
|
-
def fill(
|
345
|
+
def fill(
|
346
|
+
selector,
|
347
|
+
value,
|
348
|
+
force: nil,
|
349
|
+
noWaitAfter: nil,
|
350
|
+
timeout: nil)
|
346
351
|
params = {
|
347
352
|
selector: selector,
|
348
353
|
value: value,
|
354
|
+
force: force,
|
349
355
|
noWaitAfter: noWaitAfter,
|
350
356
|
timeout: timeout,
|
351
357
|
}.compact
|
@@ -410,6 +416,7 @@ module Playwright
|
|
410
416
|
index: nil,
|
411
417
|
value: nil,
|
412
418
|
label: nil,
|
419
|
+
force: nil,
|
413
420
|
noWaitAfter: nil,
|
414
421
|
timeout: nil)
|
415
422
|
base_params = SelectOptionValues.new(
|
@@ -418,10 +425,15 @@ module Playwright
|
|
418
425
|
value: value,
|
419
426
|
label: label,
|
420
427
|
).as_params
|
421
|
-
params = base_params.merge({ selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
|
428
|
+
params = base_params.merge({ selector: selector, force: force, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
|
422
429
|
@channel.send_message_to_server('selectOption', params)
|
423
430
|
end
|
424
431
|
|
432
|
+
def input_value(selector, timeout: nil)
|
433
|
+
params = { selector: selector, timeout: timeout }.compact
|
434
|
+
@channel.send_message_to_server('inputValue', params)
|
435
|
+
end
|
436
|
+
|
425
437
|
def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
|
426
438
|
file_payloads = InputFiles.new(files).as_params
|
427
439
|
params = { files: file_payloads, selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
|
@@ -505,8 +505,13 @@ module Playwright
|
|
505
505
|
)
|
506
506
|
end
|
507
507
|
|
508
|
-
def fill(
|
509
|
-
|
508
|
+
def fill(
|
509
|
+
selector,
|
510
|
+
value,
|
511
|
+
force: nil,
|
512
|
+
noWaitAfter: nil,
|
513
|
+
timeout: nil)
|
514
|
+
@main_frame.fill(selector, value, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
|
510
515
|
end
|
511
516
|
|
512
517
|
def focus(selector, timeout: nil)
|
@@ -552,6 +557,7 @@ module Playwright
|
|
552
557
|
index: nil,
|
553
558
|
value: nil,
|
554
559
|
label: nil,
|
560
|
+
force: nil,
|
555
561
|
noWaitAfter: nil,
|
556
562
|
timeout: nil)
|
557
563
|
@main_frame.select_option(
|
@@ -560,11 +566,16 @@ module Playwright
|
|
560
566
|
index: index,
|
561
567
|
value: value,
|
562
568
|
label: label,
|
569
|
+
force: force,
|
563
570
|
noWaitAfter: noWaitAfter,
|
564
571
|
timeout: timeout,
|
565
572
|
)
|
566
573
|
end
|
567
574
|
|
575
|
+
def input_value(selector, timeout: nil)
|
576
|
+
@main_frame.input_value(selector, timeout: timeout)
|
577
|
+
end
|
578
|
+
|
568
579
|
def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
|
569
580
|
@main_frame.set_input_files(selector, files, noWaitAfter: noWaitAfter, timeout: timeout)
|
570
581
|
end
|
@@ -759,6 +770,10 @@ module Playwright
|
|
759
770
|
expect_event(Events::Page::Request, predicate: predicate, timeout: timeout, &block)
|
760
771
|
end
|
761
772
|
|
773
|
+
def expect_request_finished(predicate: nil, timeout: nil, &block)
|
774
|
+
expect_event(Events::Page::RequestFinished, predicate: predicate, timeout: timeout, &block)
|
775
|
+
end
|
776
|
+
|
762
777
|
def expect_response(urlOrPredicate, timeout: nil, &block)
|
763
778
|
predicate =
|
764
779
|
case urlOrPredicate
|
@@ -774,6 +789,10 @@ module Playwright
|
|
774
789
|
expect_event(Events::Page::Response, predicate: predicate, timeout: timeout, &block)
|
775
790
|
end
|
776
791
|
|
792
|
+
def expect_websocket(predicate: nil, timeout: nil, &block)
|
793
|
+
expect_event(Events::Page::WebSocket, predicate: predicate, timeout: timeout, &block)
|
794
|
+
end
|
795
|
+
|
777
796
|
# called from Frame with send(:timeout_settings)
|
778
797
|
private def timeout_settings
|
779
798
|
@timeout_settings
|