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.
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