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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/accessibility.md +52 -1
  3. data/documentation/docs/api/browser.md +8 -2
  4. data/documentation/docs/api/browser_context.md +28 -0
  5. data/documentation/docs/api/browser_type.md +1 -0
  6. data/documentation/docs/api/download.md +97 -0
  7. data/documentation/docs/api/element_handle.md +28 -3
  8. data/documentation/docs/api/experimental/android_device.md +1 -0
  9. data/documentation/docs/api/frame.md +78 -18
  10. data/documentation/docs/api/keyboard.md +11 -20
  11. data/documentation/docs/api/locator.md +650 -0
  12. data/documentation/docs/api/page.md +124 -20
  13. data/documentation/docs/api/touchscreen.md +8 -0
  14. data/documentation/docs/api/worker.md +37 -0
  15. data/documentation/docs/article/guides/inspector.md +31 -0
  16. data/documentation/docs/article/guides/playwright_on_alpine_linux.md +91 -0
  17. data/documentation/docs/article/guides/semi_automation.md +5 -1
  18. data/documentation/docs/include/api_coverage.md +72 -15
  19. data/lib/playwright.rb +0 -1
  20. data/lib/playwright/accessibility_impl.rb +50 -0
  21. data/lib/playwright/channel_owners/artifact.rb +4 -0
  22. data/lib/playwright/channel_owners/browser_context.rb +77 -3
  23. data/lib/playwright/channel_owners/frame.rb +101 -35
  24. data/lib/playwright/channel_owners/page.rb +157 -56
  25. data/lib/playwright/channel_owners/worker.rb +23 -0
  26. data/lib/playwright/{download.rb → download_impl.rb} +5 -1
  27. data/lib/playwright/javascript/expression.rb +5 -4
  28. data/lib/playwright/locator_impl.rb +314 -0
  29. data/lib/playwright/route_handler_entry.rb +3 -2
  30. data/lib/playwright/timeout_settings.rb +4 -4
  31. data/lib/playwright/touchscreen_impl.rb +7 -0
  32. data/lib/playwright/tracing_impl.rb +9 -8
  33. data/lib/playwright/url_matcher.rb +12 -2
  34. data/lib/playwright/version.rb +2 -2
  35. data/lib/playwright_api/accessibility.rb +1 -1
  36. data/lib/playwright_api/android.rb +6 -6
  37. data/lib/playwright_api/android_device.rb +8 -7
  38. data/lib/playwright_api/browser.rb +16 -10
  39. data/lib/playwright_api/browser_context.rb +16 -11
  40. data/lib/playwright_api/browser_type.rb +8 -7
  41. data/lib/playwright_api/cdp_session.rb +6 -6
  42. data/lib/playwright_api/console_message.rb +6 -6
  43. data/lib/playwright_api/dialog.rb +6 -6
  44. data/lib/playwright_api/download.rb +70 -0
  45. data/lib/playwright_api/element_handle.rb +34 -20
  46. data/lib/playwright_api/frame.rb +94 -52
  47. data/lib/playwright_api/js_handle.rb +6 -6
  48. data/lib/playwright_api/locator.rb +509 -0
  49. data/lib/playwright_api/page.rb +101 -57
  50. data/lib/playwright_api/playwright.rb +6 -6
  51. data/lib/playwright_api/request.rb +6 -6
  52. data/lib/playwright_api/response.rb +6 -6
  53. data/lib/playwright_api/route.rb +6 -11
  54. data/lib/playwright_api/selectors.rb +6 -6
  55. data/lib/playwright_api/touchscreen.rb +1 -1
  56. data/lib/playwright_api/web_socket.rb +6 -6
  57. data/lib/playwright_api/worker.rb +16 -6
  58. 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
+ ![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.
@@ -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
- `Page#pause` is not implemented yet, however we can use `binding.pry` (with `pry-byebug` installed) instead.
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
- * ~~tap_point~~
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
- ## ~~Accessibility~~
119
+ ## Accessibility
121
120
 
122
- * ~~snapshot~~
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
- * ~~wait_for_timeout~~
181
+ * wait_for_timeout
181
182
  * wait_for_url
182
183
 
183
184
  ## Worker
184
185
 
185
- * ~~evaluate~~
186
- * ~~evaluate_handle~~
187
- * ~~url~~
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
- * ~~pause~~
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
- * ~~wait_for_timeout~~
299
+ * wait_for_timeout
286
300
  * wait_for_url
287
301
  * expect_websocket
288
- * ~~expect_worker~~
289
- * ~~workers~~
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
- * ~~background_pages~~
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
- * ~~service_workers~~
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
- * ~~storage_state~~
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
@@ -26,5 +26,9 @@ module Playwright
26
26
  def delete
27
27
  @channel.send_message_to_server('delete')
28
28
  end
29
+
30
+ def cancel
31
+ @channel.send_message_to_server('cancel')
32
+ end
29
33
  end
30
34
  end
@@ -8,9 +8,11 @@ module Playwright
8
8
 
9
9
  private def after_initialize
10
10
  @pages = Set.new
11
- @routes = Set.new
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 << entry
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