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
@@ -64,6 +64,22 @@ def request
|
|
64
64
|
|
65
65
|
Returns the matching [Request](./request) object.
|
66
66
|
|
67
|
+
## security_details
|
68
|
+
|
69
|
+
```
|
70
|
+
def security_details
|
71
|
+
```
|
72
|
+
|
73
|
+
Returns SSL and other security information.
|
74
|
+
|
75
|
+
## server_addr
|
76
|
+
|
77
|
+
```
|
78
|
+
def server_addr
|
79
|
+
```
|
80
|
+
|
81
|
+
Returns the IP address and port of the server.
|
82
|
+
|
67
83
|
## status
|
68
84
|
|
69
85
|
```
|
@@ -5,3 +5,40 @@ sidebar_position: 10
|
|
5
5
|
# WebSocket
|
6
6
|
|
7
7
|
The [WebSocket](./web_socket) class represents websocket connections in the page.
|
8
|
+
|
9
|
+
## closed?
|
10
|
+
|
11
|
+
```
|
12
|
+
def closed?
|
13
|
+
```
|
14
|
+
|
15
|
+
Indicates that the web socket has been closed.
|
16
|
+
|
17
|
+
## url
|
18
|
+
|
19
|
+
```
|
20
|
+
def url
|
21
|
+
```
|
22
|
+
|
23
|
+
Contains the URL of the WebSocket.
|
24
|
+
|
25
|
+
## expect_event
|
26
|
+
|
27
|
+
```
|
28
|
+
def expect_event(event, predicate: nil, timeout: nil, &block)
|
29
|
+
```
|
30
|
+
|
31
|
+
Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy
|
32
|
+
value. Will throw an error if the webSocket is closed before the event is fired. Returns the event data value.
|
33
|
+
|
34
|
+
## wait_for_event
|
35
|
+
|
36
|
+
```
|
37
|
+
def wait_for_event(event, predicate: nil, timeout: nil, &block)
|
38
|
+
```
|
39
|
+
|
40
|
+
> NOTE: In most cases, you should use [WebSocket#wait_for_event](./web_socket#wait_for_event).
|
41
|
+
|
42
|
+
Waits for given `event` to fire. If predicate is provided, it passes event's value into the `predicate` function and
|
43
|
+
waits for `predicate(event)` to return a truthy value. Will throw an error if the socket is closed before the `event` is
|
44
|
+
fired.
|
@@ -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).
|
@@ -0,0 +1,91 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 6
|
3
|
+
---
|
4
|
+
|
5
|
+
# Playwright on Alpine Linux
|
6
|
+
|
7
|
+
**NOTE: This feature is EXPERIMENTAL.**
|
8
|
+
|
9
|
+
Playwright actually requires a permission for shell command execution, and many run-time dependencies for each browser.
|
10
|
+
|
11
|
+

|
12
|
+
|
13
|
+
This all-in-one architecture is reasonable for browser automation in our own computers.
|
14
|
+
|
15
|
+
However we may have trouble with bringing Playwright into:
|
16
|
+
|
17
|
+
* Docker
|
18
|
+
* Alpine Linux
|
19
|
+
* Serverless computing
|
20
|
+
* AWS Lambda
|
21
|
+
* Google Cloud Functions
|
22
|
+
* PaaS
|
23
|
+
* Heroku
|
24
|
+
* Google App Engine
|
25
|
+
|
26
|
+
This article introduces a way to separate environments into client (for executing Playwright script) and server (for working with browsers). The main use-case assumes Docker (using Alpine Linux), however the way can be applied also into other use-cases.
|
27
|
+
|
28
|
+
## Overview
|
29
|
+
|
30
|
+
Playwrignt Ruby client is running on Alpine Linux. It just sends/receives JSON messages of Playwright-protocol via WebSocket.
|
31
|
+
|
32
|
+
Playwright server is running on a container of [official Docker image](https://hub.docker.com/_/microsoft-playwright). It just operates browsers in response to the JSON messages from WebSocket.
|
33
|
+
|
34
|
+

|
35
|
+
|
36
|
+
## Playwright client
|
37
|
+
|
38
|
+
Many example uses `Playwright#create`, which internally uses Pipe (stdin/stdout) transport for Playwright-protocol messaging. Instead, **just use `Playwright#connect_to_playwright_server(endpoint)`** for WebSocket transport.
|
39
|
+
|
40
|
+
```ruby {3}
|
41
|
+
require 'playwright'
|
42
|
+
|
43
|
+
Playwright.connect_to_playwright_server('wss://example.com:8888/ws') do |playwright|
|
44
|
+
playwright.chromium.launch do |browser|
|
45
|
+
page = browser.new_page
|
46
|
+
page.goto('https://github.com/microsoft/playwright')
|
47
|
+
page.screenshot(path: 'github-microsoft-playwright.png')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
`wss://example.com:8888/ws` is an example of endpoint URL of the Playwright server. In local development environment, it is typically `"ws://127.0.0.1:#{port}/ws"`.
|
53
|
+
|
54
|
+
## Playwright server
|
55
|
+
|
56
|
+
With the [official Docker image](https://hub.docker.com/_/microsoft-playwright) or in the local development environment with Node.js, just execute `npx playwright install && npx playwright run-server $PORT`. (`$PORT` is a port number of the server)
|
57
|
+
|
58
|
+
If custom Docker image is preferred, build it as follows:
|
59
|
+
|
60
|
+
```Dockerfile
|
61
|
+
FROM mcr.microsoft.com/playwright:focal
|
62
|
+
|
63
|
+
WORKDIR /root
|
64
|
+
RUN npm install playwright@1.12.3 && ./node_modules/.bin/playwright install
|
65
|
+
|
66
|
+
ENV PORT 8888
|
67
|
+
CMD ["./node_modules/.bin/playwright", "run-server", "$PORT"]
|
68
|
+
```
|
69
|
+
|
70
|
+
## Debugging for connection
|
71
|
+
|
72
|
+
The client and server are really quiet. This chapter shows how to check if the communication on the WebSocket works well or not.
|
73
|
+
|
74
|
+
### Show JSON message on client
|
75
|
+
|
76
|
+
Just set an environment variable `DEBUG=1`.
|
77
|
+
|
78
|
+
```
|
79
|
+
DEBUG=1 bundle exec ruby some-automation-with-playwright.rb
|
80
|
+
```
|
81
|
+
|
82
|
+
|
83
|
+
### Enable verbose logging on server
|
84
|
+
|
85
|
+
Just set an environment variable `DEBUG=pw:*` or `DEBUG=pw:server`
|
86
|
+
|
87
|
+
```
|
88
|
+
DEBUG=pw:* npx playwright run-server 8888
|
89
|
+
```
|
90
|
+
|
91
|
+
See [the official documentation](https://playwright.dev/docs/debug/#verbose-api-logs) for details.
|
@@ -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,71 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 5
|
3
|
+
---
|
4
|
+
|
5
|
+
# Semi-automation
|
6
|
+
|
7
|
+
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).
|
8
|
+
This allow us to intermediate into automation, for example
|
9
|
+
|
10
|
+
* Authenticate with OAuth2 manually before automation
|
11
|
+
* Testing a page after some chrome extensions are installed manually
|
12
|
+
|
13
|
+
Keep in mind repeatedly that persistent browser context is NOT RECOMMENDED for most cases because it would bring many side effects.
|
14
|
+
|
15
|
+
## Pause automation for manual operation
|
16
|
+
|
17
|
+
`Page#pause` is not implemented yet, however we can use `binding.pry` (with `pry-byebug` installed) instead.
|
18
|
+
|
19
|
+
```ruby {4}
|
20
|
+
playwright.chromium.launch_persistent_context('./data/', headless: false) do |context|
|
21
|
+
page = context.new_page
|
22
|
+
page.goto('https://example.com/')
|
23
|
+
binding.pry
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
When script is executed, it is paused as below.
|
28
|
+
|
29
|
+
```
|
30
|
+
3:
|
31
|
+
4: playwright.chromium.launch_persistent_context('./data/', headless: false) do |context|
|
32
|
+
5: page = context.new_page
|
33
|
+
6: page.goto('https://example.com/')
|
34
|
+
=> 7: binding.pry
|
35
|
+
8: end
|
36
|
+
|
37
|
+
[1] pry(main)>
|
38
|
+
```
|
39
|
+
|
40
|
+
We can inspect using `page`, `context` and also we can operate something manually during the pause.
|
41
|
+
|
42
|
+
See https://github.com/deivid-rodriguez/pry-byebug for more detailed debugging options.
|
43
|
+
|
44
|
+
## Working with Chrome extensions
|
45
|
+
|
46
|
+
**Playwright disables the Chrome extension feature by default.**
|
47
|
+
We have to enable it for installing Chrome extension, by passing these 3 parameters on launch.
|
48
|
+
|
49
|
+
* `acceptDownloads: true`
|
50
|
+
* `headless: false`
|
51
|
+
* `ignoreDefaultArgs: ['--disable-extensions']`
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'playwright'
|
55
|
+
require 'pry'
|
56
|
+
|
57
|
+
Playwright.create(playwright_cli_executable_path: './node_modules/.bin/playwright') do |playwright|
|
58
|
+
launch_params = {
|
59
|
+
acceptDownloads: true,
|
60
|
+
channel: 'chrome',
|
61
|
+
headless: false,
|
62
|
+
ignoreDefaultArgs: ['--disable-extensions'],
|
63
|
+
}
|
64
|
+
|
65
|
+
playwright.chromium.launch_persistent_context('./data/', **launch_params) do |context|
|
66
|
+
page = context.new_page
|
67
|
+
page.goto('https://example.com/')
|
68
|
+
binding.pry
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
@@ -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
|
@@ -40,10 +42,10 @@
|
|
40
42
|
|
41
43
|
## WebSocket
|
42
44
|
|
43
|
-
*
|
44
|
-
*
|
45
|
-
*
|
46
|
-
*
|
45
|
+
* closed?
|
46
|
+
* url
|
47
|
+
* expect_event
|
48
|
+
* wait_for_event
|
47
49
|
|
48
50
|
## Keyboard
|
49
51
|
|
@@ -91,6 +93,7 @@
|
|
91
93
|
* hover
|
92
94
|
* inner_html
|
93
95
|
* inner_text
|
96
|
+
* input_value
|
94
97
|
* checked?
|
95
98
|
* disabled?
|
96
99
|
* editable?
|
@@ -134,6 +137,7 @@
|
|
134
137
|
* content
|
135
138
|
* dblclick
|
136
139
|
* dispatch_event
|
140
|
+
* drag_and_drop
|
137
141
|
* eval_on_selector
|
138
142
|
* eval_on_selector_all
|
139
143
|
* evaluate
|
@@ -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?
|
@@ -213,6 +218,7 @@
|
|
213
218
|
* context
|
214
219
|
* dblclick
|
215
220
|
* dispatch_event
|
221
|
+
* drag_and_drop
|
216
222
|
* emulate_media
|
217
223
|
* eval_on_selector
|
218
224
|
* eval_on_selector_all
|
@@ -231,6 +237,7 @@
|
|
231
237
|
* hover
|
232
238
|
* inner_html
|
233
239
|
* inner_text
|
240
|
+
* input_value
|
234
241
|
* checked?
|
235
242
|
* closed?
|
236
243
|
* disabled?
|
@@ -273,12 +280,12 @@
|
|
273
280
|
* expect_navigation
|
274
281
|
* expect_popup
|
275
282
|
* expect_request
|
276
|
-
*
|
283
|
+
* expect_request_finished
|
277
284
|
* expect_response
|
278
285
|
* wait_for_selector
|
279
286
|
* ~~wait_for_timeout~~
|
280
287
|
* wait_for_url
|
281
|
-
*
|
288
|
+
* expect_websocket
|
282
289
|
* ~~expect_worker~~
|
283
290
|
* ~~workers~~
|
284
291
|
* ~~wait_for_event~~
|
@@ -300,7 +307,7 @@
|
|
300
307
|
* expose_binding
|
301
308
|
* expose_function
|
302
309
|
* grant_permissions
|
303
|
-
*
|
310
|
+
* new_cdp_session
|
304
311
|
* new_page
|
305
312
|
* pages
|
306
313
|
* route
|
@@ -317,17 +324,17 @@
|
|
317
324
|
* ~~wait_for_event~~
|
318
325
|
* tracing
|
319
326
|
|
320
|
-
##
|
327
|
+
## CDPSession
|
321
328
|
|
322
|
-
*
|
323
|
-
*
|
329
|
+
* detach
|
330
|
+
* send_message
|
324
331
|
|
325
332
|
## Browser
|
326
333
|
|
327
334
|
* close
|
328
335
|
* contexts
|
329
336
|
* connected?
|
330
|
-
*
|
337
|
+
* new_browser_cdp_session
|
331
338
|
* new_context
|
332
339
|
* new_page
|
333
340
|
* start_tracing
|
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,
|
@@ -8,7 +8,7 @@ module Playwright
|
|
8
8
|
|
9
9
|
private def after_initialize
|
10
10
|
@pages = Set.new
|
11
|
-
@routes =
|
11
|
+
@routes = []
|
12
12
|
@bindings = {}
|
13
13
|
@timeout_settings = TimeoutSettings.new
|
14
14
|
|
@@ -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)
|
@@ -205,8 +210,8 @@ module Playwright
|
|
205
210
|
end
|
206
211
|
|
207
212
|
def route(url, handler)
|
208
|
-
entry = RouteHandlerEntry.new(url, handler)
|
209
|
-
@routes
|
213
|
+
entry = RouteHandlerEntry.new(url, base_url, handler)
|
214
|
+
@routes.unshift(entry)
|
210
215
|
if @routes.count >= 1
|
211
216
|
@channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
|
212
217
|
end
|
@@ -270,5 +275,9 @@ module Playwright
|
|
270
275
|
private def has_record_video_option?
|
271
276
|
@options.key?(:recordVideo)
|
272
277
|
end
|
278
|
+
|
279
|
+
private def base_url
|
280
|
+
@options[:baseURL]
|
281
|
+
end
|
273
282
|
end
|
274
283
|
end
|