playwright-ruby-client 0.8.0 → 1.14.beta2
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/documentation/docs/api/accessibility.md +52 -1
- data/documentation/docs/api/browser.md +8 -2
- data/documentation/docs/api/browser_context.md +28 -0
- data/documentation/docs/api/browser_type.md +1 -0
- data/documentation/docs/api/download.md +97 -0
- data/documentation/docs/api/element_handle.md +28 -3
- data/documentation/docs/api/experimental/android_device.md +1 -0
- data/documentation/docs/api/frame.md +78 -18
- data/documentation/docs/api/keyboard.md +11 -20
- data/documentation/docs/api/locator.md +650 -0
- data/documentation/docs/api/page.md +124 -20
- data/documentation/docs/api/touchscreen.md +8 -0
- data/documentation/docs/api/worker.md +37 -0
- data/documentation/docs/article/guides/inspector.md +31 -0
- data/documentation/docs/article/guides/playwright_on_alpine_linux.md +91 -0
- data/documentation/docs/article/guides/semi_automation.md +5 -1
- data/documentation/docs/include/api_coverage.md +72 -15
- data/lib/playwright.rb +0 -1
- data/lib/playwright/accessibility_impl.rb +50 -0
- data/lib/playwright/channel_owners/artifact.rb +4 -0
- data/lib/playwright/channel_owners/browser_context.rb +77 -3
- data/lib/playwright/channel_owners/frame.rb +101 -35
- data/lib/playwright/channel_owners/page.rb +157 -56
- data/lib/playwright/channel_owners/worker.rb +23 -0
- data/lib/playwright/{download.rb → download_impl.rb} +5 -1
- data/lib/playwright/javascript/expression.rb +5 -4
- data/lib/playwright/locator_impl.rb +314 -0
- data/lib/playwright/route_handler_entry.rb +3 -2
- data/lib/playwright/timeout_settings.rb +4 -4
- data/lib/playwright/touchscreen_impl.rb +7 -0
- data/lib/playwright/tracing_impl.rb +9 -8
- data/lib/playwright/url_matcher.rb +12 -2
- data/lib/playwright/version.rb +2 -2
- data/lib/playwright_api/accessibility.rb +1 -1
- data/lib/playwright_api/android.rb +6 -6
- data/lib/playwright_api/android_device.rb +8 -7
- data/lib/playwright_api/browser.rb +16 -10
- data/lib/playwright_api/browser_context.rb +16 -11
- data/lib/playwright_api/browser_type.rb +8 -7
- data/lib/playwright_api/cdp_session.rb +6 -6
- data/lib/playwright_api/console_message.rb +6 -6
- data/lib/playwright_api/dialog.rb +6 -6
- data/lib/playwright_api/download.rb +70 -0
- data/lib/playwright_api/element_handle.rb +34 -20
- data/lib/playwright_api/frame.rb +94 -52
- data/lib/playwright_api/js_handle.rb +6 -6
- data/lib/playwright_api/locator.rb +509 -0
- data/lib/playwright_api/page.rb +101 -57
- data/lib/playwright_api/playwright.rb +6 -6
- data/lib/playwright_api/request.rb +6 -6
- data/lib/playwright_api/response.rb +6 -6
- data/lib/playwright_api/route.rb +6 -11
- data/lib/playwright_api/selectors.rb +6 -6
- data/lib/playwright_api/touchscreen.rb +1 -1
- data/lib/playwright_api/web_socket.rb +6 -6
- data/lib/playwright_api/worker.rb +16 -6
- metadata +14 -6
@@ -0,0 +1,91 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 7
|
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.
|
@@ -1,3 +1,7 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 5
|
3
|
+
---
|
4
|
+
|
1
5
|
# Semi-automation
|
2
6
|
|
3
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).
|
@@ -10,7 +14,7 @@ Keep in mind repeatedly that persistent browser context is NOT RECOMMENDED for m
|
|
10
14
|
|
11
15
|
## Pause automation for manual operation
|
12
16
|
|
13
|
-
|
17
|
+
We can simply use `binding.pry` (with `pry-byebug` installed).
|
14
18
|
|
15
19
|
```ruby {4}
|
16
20
|
playwright.chromium.launch_persistent_context('./data/', headless: false) do |context|
|
@@ -38,7 +38,6 @@
|
|
38
38
|
* abort
|
39
39
|
* continue
|
40
40
|
* fulfill
|
41
|
-
* ~~intercept~~
|
42
41
|
* request
|
43
42
|
|
44
43
|
## WebSocket
|
@@ -66,7 +65,7 @@
|
|
66
65
|
|
67
66
|
## Touchscreen
|
68
67
|
|
69
|
-
*
|
68
|
+
* tap_point
|
70
69
|
|
71
70
|
## JSHandle
|
72
71
|
|
@@ -117,9 +116,9 @@
|
|
117
116
|
* wait_for_element_state
|
118
117
|
* wait_for_selector
|
119
118
|
|
120
|
-
##
|
119
|
+
## Accessibility
|
121
120
|
|
122
|
-
*
|
121
|
+
* snapshot
|
123
122
|
|
124
123
|
## FileChooser
|
125
124
|
|
@@ -138,6 +137,7 @@
|
|
138
137
|
* content
|
139
138
|
* dblclick
|
140
139
|
* dispatch_event
|
140
|
+
* drag_and_drop
|
141
141
|
* eval_on_selector
|
142
142
|
* eval_on_selector_all
|
143
143
|
* evaluate
|
@@ -158,6 +158,7 @@
|
|
158
158
|
* enabled?
|
159
159
|
* hidden?
|
160
160
|
* visible?
|
161
|
+
* locator
|
161
162
|
* name
|
162
163
|
* page
|
163
164
|
* parent_frame
|
@@ -177,14 +178,14 @@
|
|
177
178
|
* wait_for_load_state
|
178
179
|
* expect_navigation
|
179
180
|
* wait_for_selector
|
180
|
-
*
|
181
|
+
* wait_for_timeout
|
181
182
|
* wait_for_url
|
182
183
|
|
183
184
|
## Worker
|
184
185
|
|
185
|
-
*
|
186
|
-
*
|
187
|
-
*
|
186
|
+
* evaluate
|
187
|
+
* evaluate_handle
|
188
|
+
* url
|
188
189
|
|
189
190
|
## Selectors
|
190
191
|
|
@@ -205,6 +206,17 @@
|
|
205
206
|
* message
|
206
207
|
* type
|
207
208
|
|
209
|
+
## Download
|
210
|
+
|
211
|
+
* cancel
|
212
|
+
* delete
|
213
|
+
* failure
|
214
|
+
* page
|
215
|
+
* path
|
216
|
+
* save_as
|
217
|
+
* suggested_filename
|
218
|
+
* url
|
219
|
+
|
208
220
|
## Page
|
209
221
|
|
210
222
|
* add_init_script
|
@@ -218,6 +230,7 @@
|
|
218
230
|
* context
|
219
231
|
* dblclick
|
220
232
|
* dispatch_event
|
233
|
+
* drag_and_drop
|
221
234
|
* emulate_media
|
222
235
|
* eval_on_selector
|
223
236
|
* eval_on_selector_all
|
@@ -244,9 +257,10 @@
|
|
244
257
|
* enabled?
|
245
258
|
* hidden?
|
246
259
|
* visible?
|
260
|
+
* locator
|
247
261
|
* main_frame
|
248
262
|
* opener
|
249
|
-
*
|
263
|
+
* pause
|
250
264
|
* pdf
|
251
265
|
* press
|
252
266
|
* query_selector
|
@@ -282,11 +296,11 @@
|
|
282
296
|
* expect_request_finished
|
283
297
|
* expect_response
|
284
298
|
* wait_for_selector
|
285
|
-
*
|
299
|
+
* wait_for_timeout
|
286
300
|
* wait_for_url
|
287
301
|
* expect_websocket
|
288
|
-
*
|
289
|
-
*
|
302
|
+
* expect_worker
|
303
|
+
* workers
|
290
304
|
* ~~wait_for_event~~
|
291
305
|
* accessibility
|
292
306
|
* keyboard
|
@@ -297,7 +311,7 @@
|
|
297
311
|
|
298
312
|
* add_cookies
|
299
313
|
* add_init_script
|
300
|
-
*
|
314
|
+
* background_pages
|
301
315
|
* browser
|
302
316
|
* clear_cookies
|
303
317
|
* clear_permissions
|
@@ -310,13 +324,13 @@
|
|
310
324
|
* new_page
|
311
325
|
* pages
|
312
326
|
* route
|
313
|
-
*
|
327
|
+
* service_workers
|
314
328
|
* set_default_navigation_timeout
|
315
329
|
* set_default_timeout
|
316
330
|
* set_extra_http_headers
|
317
331
|
* set_geolocation
|
318
332
|
* set_offline
|
319
|
-
*
|
333
|
+
* storage_state
|
320
334
|
* unroute
|
321
335
|
* expect_event
|
322
336
|
* expect_page
|
@@ -363,6 +377,49 @@
|
|
363
377
|
* start
|
364
378
|
* stop
|
365
379
|
|
380
|
+
## Locator
|
381
|
+
|
382
|
+
* all_inner_texts
|
383
|
+
* all_text_contents
|
384
|
+
* bounding_box
|
385
|
+
* check
|
386
|
+
* click
|
387
|
+
* count
|
388
|
+
* dblclick
|
389
|
+
* dispatch_event
|
390
|
+
* element_handle
|
391
|
+
* element_handles
|
392
|
+
* evaluate
|
393
|
+
* evaluate_all
|
394
|
+
* evaluate_handle
|
395
|
+
* fill
|
396
|
+
* first
|
397
|
+
* focus
|
398
|
+
* get_attribute
|
399
|
+
* hover
|
400
|
+
* inner_html
|
401
|
+
* inner_text
|
402
|
+
* input_value
|
403
|
+
* checked?
|
404
|
+
* disabled?
|
405
|
+
* editable?
|
406
|
+
* enabled?
|
407
|
+
* hidden?
|
408
|
+
* visible?
|
409
|
+
* last
|
410
|
+
* locator
|
411
|
+
* nth
|
412
|
+
* press
|
413
|
+
* screenshot
|
414
|
+
* scroll_into_view_if_needed
|
415
|
+
* select_option
|
416
|
+
* select_text
|
417
|
+
* set_input_files
|
418
|
+
* tap_point
|
419
|
+
* text_content
|
420
|
+
* type
|
421
|
+
* uncheck
|
422
|
+
|
366
423
|
## Android
|
367
424
|
|
368
425
|
* devices
|
data/lib/playwright.rb
CHANGED
@@ -17,7 +17,6 @@ require 'playwright/utils'
|
|
17
17
|
require 'playwright/api_implementation'
|
18
18
|
require 'playwright/channel'
|
19
19
|
require 'playwright/channel_owner'
|
20
|
-
require 'playwright/download'
|
21
20
|
require 'playwright/http_headers'
|
22
21
|
require 'playwright/input_files'
|
23
22
|
require 'playwright/connection'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Playwright
|
2
|
+
define_api_implementation :AccessibilityImpl do
|
3
|
+
def initialize(channel)
|
4
|
+
@channel = channel
|
5
|
+
end
|
6
|
+
|
7
|
+
def snapshot(interestingOnly: nil, root: nil)
|
8
|
+
params = {
|
9
|
+
interestingOnly: interestingOnly,
|
10
|
+
root: root&.channel,
|
11
|
+
}.compact
|
12
|
+
result = @channel.send_message_to_server('accessibilitySnapshot', params)
|
13
|
+
format_ax_node_from_protocol(result) if result
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
# original JS implementation create a new Hash from ax_node,
|
18
|
+
# but this implementation directly modify ax_node and don't return hash.
|
19
|
+
private def format_ax_node_from_protocol(ax_node)
|
20
|
+
value = ax_node.delete('valueNumber') || ax_node.delete('valueString')
|
21
|
+
ax_node['value'] = value unless value.nil?
|
22
|
+
|
23
|
+
checked =
|
24
|
+
case ax_node['checked']
|
25
|
+
when 'checked'
|
26
|
+
true
|
27
|
+
when 'unchecked'
|
28
|
+
false
|
29
|
+
else
|
30
|
+
ax_node['checked']
|
31
|
+
end
|
32
|
+
ax_node['checked'] = checked unless checked.nil?
|
33
|
+
|
34
|
+
pressed =
|
35
|
+
case ax_node['pressed']
|
36
|
+
when 'pressed'
|
37
|
+
true
|
38
|
+
when 'released'
|
39
|
+
false
|
40
|
+
else
|
41
|
+
ax_node['pressed']
|
42
|
+
end
|
43
|
+
ax_node['pressed'] = pressed unless pressed.nil?
|
44
|
+
|
45
|
+
ax_node['children']&.each do |child|
|
46
|
+
format_ax_node_from_protocol(child)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -8,9 +8,11 @@ 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
|
+
@service_workers = Set.new
|
15
|
+
@background_pages = Set.new
|
14
16
|
|
15
17
|
@tracing = TracingImpl.new(@channel, self)
|
16
18
|
@channel.on('bindingCall', ->(params) { on_binding(ChannelOwners::BindingCall.from(params['binding'])) })
|
@@ -19,6 +21,12 @@ module Playwright
|
|
19
21
|
@channel.on('route', ->(params) {
|
20
22
|
on_route(ChannelOwners::Route.from(params['route']), ChannelOwners::Request.from(params['request']))
|
21
23
|
})
|
24
|
+
@channel.on('backgroundPage', ->(params) {
|
25
|
+
on_background_page(ChannelOwners::Page.from(params['page']))
|
26
|
+
})
|
27
|
+
@channel.on('serviceWorker', ->(params) {
|
28
|
+
on_service_worker(ChannelOwners::Worker.from(params['worker']))
|
29
|
+
})
|
22
30
|
@channel.on('request', ->(params) {
|
23
31
|
on_request(
|
24
32
|
ChannelOwners::Request.from(params['request']),
|
@@ -56,6 +64,11 @@ module Playwright
|
|
56
64
|
page.send(:emit_popup_event_from_browser_context)
|
57
65
|
end
|
58
66
|
|
67
|
+
private def on_background_page(page)
|
68
|
+
@background_pages << page
|
69
|
+
emit(Events::BrowserContext::BackgroundPage, page)
|
70
|
+
end
|
71
|
+
|
59
72
|
private def on_route(route, request)
|
60
73
|
# It is not desired to use PlaywrightApi.wrap directly.
|
61
74
|
# However it is a little difficult to define wrapper for `handler` parameter in generate_api.
|
@@ -98,6 +111,20 @@ module Playwright
|
|
98
111
|
page&.emit(Events::Page::Response, response)
|
99
112
|
end
|
100
113
|
|
114
|
+
private def on_service_worker(worker)
|
115
|
+
worker.context = self
|
116
|
+
@service_workers << worker
|
117
|
+
emit(Events::BrowserContext::ServiceWorker, worker)
|
118
|
+
end
|
119
|
+
|
120
|
+
def background_pages
|
121
|
+
@background_pages.to_a
|
122
|
+
end
|
123
|
+
|
124
|
+
def service_workers
|
125
|
+
@service_workers.to_a
|
126
|
+
end
|
127
|
+
|
101
128
|
def new_cdp_session(page)
|
102
129
|
resp = @channel.send_message_to_server('newCDPSession', page: page.channel)
|
103
130
|
ChannelOwners::CDPSession.from(resp)
|
@@ -210,8 +237,8 @@ module Playwright
|
|
210
237
|
end
|
211
238
|
|
212
239
|
def route(url, handler)
|
213
|
-
entry = RouteHandlerEntry.new(url, handler)
|
214
|
-
@routes
|
240
|
+
entry = RouteHandlerEntry.new(url, base_url, handler)
|
241
|
+
@routes.unshift(entry)
|
215
242
|
if @routes.count >= 1
|
216
243
|
@channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
|
217
244
|
end
|
@@ -250,10 +277,45 @@ module Playwright
|
|
250
277
|
raise unless safe_close_error?(err)
|
251
278
|
end
|
252
279
|
|
280
|
+
# REMARK: enable_debug_console is playwright-ruby-client specific method.
|
281
|
+
def enable_debug_console!
|
282
|
+
# Ruby is not supported in Playwright officially,
|
283
|
+
# and causes error:
|
284
|
+
#
|
285
|
+
# Error:
|
286
|
+
# ===============================
|
287
|
+
# Unsupported language: 'ruby'
|
288
|
+
# ===============================
|
289
|
+
#
|
290
|
+
# So, launch inspector as Python app.
|
291
|
+
# NOTE: This should be used only for Page#pause at this moment.
|
292
|
+
@channel.send_message_to_server('recorderSupplementEnable', language: :python)
|
293
|
+
@debug_console_enabled = true
|
294
|
+
end
|
295
|
+
|
296
|
+
class DebugConsoleNotEnabledError < StandardError
|
297
|
+
def initialize
|
298
|
+
super('Debug console should be enabled in advance, by calling `browser_context.enable_debug_console!`')
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
253
302
|
def pause
|
303
|
+
unless @debug_console_enabled
|
304
|
+
raise DebugConsoleNotEnabledError.new
|
305
|
+
end
|
254
306
|
@channel.send_message_to_server('pause')
|
255
307
|
end
|
256
308
|
|
309
|
+
def storage_state(path: nil)
|
310
|
+
@channel.send_message_to_server_result('storageState', {}).tap do |result|
|
311
|
+
if path
|
312
|
+
File.open(path, 'w') do |f|
|
313
|
+
f.write(JSON.dump(result))
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
257
319
|
def expect_page(predicate: nil, timeout: nil)
|
258
320
|
params = {
|
259
321
|
predicate: predicate,
|
@@ -267,6 +329,14 @@ module Playwright
|
|
267
329
|
@pages.delete(page)
|
268
330
|
end
|
269
331
|
|
332
|
+
private def remove_background_page(page)
|
333
|
+
@background_pages.delete(page)
|
334
|
+
end
|
335
|
+
|
336
|
+
private def remove_service_worker(worker)
|
337
|
+
@service_workers.delete(worker)
|
338
|
+
end
|
339
|
+
|
270
340
|
# called from Page with send(:_timeout_settings), so keep private.
|
271
341
|
private def _timeout_settings
|
272
342
|
@timeout_settings
|
@@ -275,5 +345,9 @@ module Playwright
|
|
275
345
|
private def has_record_video_option?
|
276
346
|
@options.key?(:recordVideo)
|
277
347
|
end
|
348
|
+
|
349
|
+
private def base_url
|
350
|
+
@options[:baseURL]
|
351
|
+
end
|
278
352
|
end
|
279
353
|
end
|