playwright-ruby-client 0.6.4 → 0.8.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/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
|