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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -0
  3. data/documentation/docs/api/browser.md +18 -2
  4. data/documentation/docs/api/browser_context.md +10 -0
  5. data/documentation/docs/api/browser_type.md +1 -0
  6. data/documentation/docs/api/cdp_session.md +41 -1
  7. data/documentation/docs/api/element_handle.md +11 -2
  8. data/documentation/docs/api/experimental/android_device.md +1 -0
  9. data/documentation/docs/api/frame.md +29 -1
  10. data/documentation/docs/api/keyboard.md +11 -20
  11. data/documentation/docs/api/page.md +48 -2
  12. data/documentation/docs/api/response.md +16 -0
  13. data/documentation/docs/api/web_socket.md +37 -0
  14. data/documentation/docs/article/guides/launch_browser.md +2 -0
  15. data/documentation/docs/article/guides/playwright_on_alpine_linux.md +91 -0
  16. data/documentation/docs/article/guides/rails_integration.md +1 -1
  17. data/documentation/docs/article/guides/semi_automation.md +71 -0
  18. data/documentation/docs/include/api_coverage.md +18 -11
  19. data/lib/playwright.rb +36 -3
  20. data/lib/playwright/channel_owners/artifact.rb +4 -0
  21. data/lib/playwright/channel_owners/browser.rb +5 -0
  22. data/lib/playwright/channel_owners/browser_context.rb +12 -3
  23. data/lib/playwright/channel_owners/cdp_session.rb +19 -0
  24. data/lib/playwright/channel_owners/element_handle.rb +11 -4
  25. data/lib/playwright/channel_owners/frame.rb +36 -4
  26. data/lib/playwright/channel_owners/page.rb +45 -16
  27. data/lib/playwright/channel_owners/response.rb +9 -1
  28. data/lib/playwright/channel_owners/web_socket.rb +83 -0
  29. data/lib/playwright/connection.rb +2 -4
  30. data/lib/playwright/download.rb +4 -0
  31. data/lib/playwright/route_handler_entry.rb +3 -2
  32. data/lib/playwright/transport.rb +0 -1
  33. data/lib/playwright/url_matcher.rb +12 -2
  34. data/lib/playwright/version.rb +2 -2
  35. data/lib/playwright/web_socket_client.rb +164 -0
  36. data/lib/playwright/web_socket_transport.rb +104 -0
  37. data/lib/playwright_api/android.rb +6 -6
  38. data/lib/playwright_api/android_device.rb +10 -9
  39. data/lib/playwright_api/browser.rb +17 -11
  40. data/lib/playwright_api/browser_context.rb +7 -7
  41. data/lib/playwright_api/browser_type.rb +8 -7
  42. data/lib/playwright_api/cdp_session.rb +30 -8
  43. data/lib/playwright_api/console_message.rb +6 -6
  44. data/lib/playwright_api/dialog.rb +6 -6
  45. data/lib/playwright_api/element_handle.rb +17 -11
  46. data/lib/playwright_api/frame.rb +30 -9
  47. data/lib/playwright_api/js_handle.rb +6 -6
  48. data/lib/playwright_api/page.rb +39 -18
  49. data/lib/playwright_api/playwright.rb +6 -6
  50. data/lib/playwright_api/request.rb +6 -6
  51. data/lib/playwright_api/response.rb +15 -10
  52. data/lib/playwright_api/route.rb +6 -6
  53. data/lib/playwright_api/selectors.rb +6 -6
  54. data/lib/playwright_api/web_socket.rb +12 -12
  55. data/lib/playwright_api/worker.rb +6 -6
  56. data/playwright.gemspec +2 -1
  57. 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
- * ~~closed?~~
44
- * ~~url~~
45
- * ~~expect_event~~
46
- * ~~wait_for_event~~
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
- * ~~expect_request_finished~~
283
+ * expect_request_finished
277
284
  * expect_response
278
285
  * wait_for_selector
279
286
  * ~~wait_for_timeout~~
280
287
  * wait_for_url
281
- * ~~expect_websocket~~
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
- * ~~new_cdp_session~~
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
- ## ~~CDPSession~~
327
+ ## CDPSession
321
328
 
322
- * ~~detach~~
323
- * ~~send_message~~
329
+ * detach
330
+ * send_message
324
331
 
325
332
  ## Browser
326
333
 
327
334
  * close
328
335
  * contexts
329
336
  * connected?
330
- * ~~new_browser_cdp_session~~
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
- connection = Connection.new(playwright_cli_executable_path: playwright_cli_executable_path)
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
- module_function def instance
86
- @playwright_instance
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
@@ -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
@@ -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 = Set.new
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 << entry
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