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
@@ -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
|
+
![all-in-one](https://user-images.githubusercontent.com/11763113/124934388-9c9c9100-e03f-11eb-8f13-324afac3be2a.png)
|
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
|
+
![overview](https://user-images.githubusercontent.com/11763113/124934448-ad4d0700-e03f-11eb-942e-b9f3282bb703.png)
|
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
|