playwright-ruby-client 0.6.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -0
  3. data/documentation/docs/api/browser.md +10 -0
  4. data/documentation/docs/api/browser_context.md +10 -0
  5. data/documentation/docs/api/browser_type.md +53 -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/frame.md +15 -1
  9. data/documentation/docs/api/page.md +33 -1
  10. data/documentation/docs/api/response.md +16 -0
  11. data/documentation/docs/api/route.md +20 -21
  12. data/documentation/docs/api/web_socket.md +38 -1
  13. data/documentation/docs/article/guides/launch_browser.md +2 -0
  14. data/documentation/docs/article/guides/rails_integration.md +2 -2
  15. data/documentation/docs/article/guides/semi_automation.md +67 -0
  16. data/documentation/docs/include/api_coverage.md +19 -13
  17. data/lib/playwright.rb +36 -3
  18. data/lib/playwright/channel_owners/browser.rb +5 -0
  19. data/lib/playwright/channel_owners/browser_context.rb +5 -0
  20. data/lib/playwright/channel_owners/browser_type.rb +18 -0
  21. data/lib/playwright/channel_owners/cdp_session.rb +19 -0
  22. data/lib/playwright/channel_owners/element_handle.rb +11 -4
  23. data/lib/playwright/channel_owners/frame.rb +14 -2
  24. data/lib/playwright/channel_owners/page.rb +21 -2
  25. data/lib/playwright/channel_owners/response.rb +9 -1
  26. data/lib/playwright/channel_owners/web_socket.rb +87 -0
  27. data/lib/playwright/connection.rb +2 -4
  28. data/lib/playwright/transport.rb +0 -1
  29. data/lib/playwright/version.rb +2 -2
  30. data/lib/playwright/web_socket_client.rb +164 -0
  31. data/lib/playwright/web_socket_transport.rb +104 -0
  32. data/lib/playwright_api/android.rb +6 -6
  33. data/lib/playwright_api/android_device.rb +8 -8
  34. data/lib/playwright_api/browser.rb +7 -7
  35. data/lib/playwright_api/browser_context.rb +7 -7
  36. data/lib/playwright_api/browser_type.rb +9 -8
  37. data/lib/playwright_api/cdp_session.rb +30 -8
  38. data/lib/playwright_api/console_message.rb +6 -6
  39. data/lib/playwright_api/dialog.rb +6 -6
  40. data/lib/playwright_api/element_handle.rb +17 -11
  41. data/lib/playwright_api/frame.rb +20 -9
  42. data/lib/playwright_api/js_handle.rb +6 -6
  43. data/lib/playwright_api/page.rb +28 -17
  44. data/lib/playwright_api/playwright.rb +6 -6
  45. data/lib/playwright_api/request.rb +6 -6
  46. data/lib/playwright_api/response.rb +15 -10
  47. data/lib/playwright_api/route.rb +11 -6
  48. data/lib/playwright_api/selectors.rb +6 -6
  49. data/lib/playwright_api/web_socket.rb +28 -6
  50. data/lib/playwright_api/worker.rb +6 -6
  51. data/playwright.gemspec +2 -1
  52. metadata +37 -18
@@ -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).
@@ -41,7 +41,7 @@ Capybara.default_max_wait_time = 15
41
41
 
42
42
  ### (Optional) Update default driver
43
43
 
44
- By default, Capybara driver is set to `:rack_test`, which works only with non-JS contents. If your Rails application has many JavaScript contents, it is recommended to change the default driver to `:playwrite`.
44
+ By default, Capybara driver is set to `:rack_test`, which works only with non-JS contents. If your Rails application has many JavaScript contents, it is recommended to change the default driver to `:playwright`.
45
45
 
46
46
  ```rb
47
47
  Capybara.default_driver = :playwright
@@ -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, since [Playwright doesn't implement the feature yet](https://github.com/microsoft/playwright/pull/6207).
205
+ * `Capybara::Node::Element#drag_to` does not accept `html5` parameter. HTML5 drag and drop is not fully supported in Playwright.
@@ -0,0 +1,67 @@
1
+ # Semi-automation
2
+
3
+ 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).
4
+ This allow us to intermediate into automation, for example
5
+
6
+ * Authenticate with OAuth2 manually before automation
7
+ * Testing a page after some chrome extensions are installed manually
8
+
9
+ Keep in mind repeatedly that persistent browser context is NOT RECOMMENDED for most cases because it would bring many side effects.
10
+
11
+ ## Pause automation for manual operation
12
+
13
+ `Page#pause` is not implemented yet, however we can use `binding.pry` (with `pry-byebug` installed) instead.
14
+
15
+ ```ruby {4}
16
+ playwright.chromium.launch_persistent_context('./data/', headless: false) do |context|
17
+ page = context.new_page
18
+ page.goto('https://example.com/')
19
+ binding.pry
20
+ end
21
+ ```
22
+
23
+ When script is executed, it is paused as below.
24
+
25
+ ```
26
+ 3:
27
+ 4: playwright.chromium.launch_persistent_context('./data/', headless: false) do |context|
28
+ 5: page = context.new_page
29
+ 6: page.goto('https://example.com/')
30
+ => 7: binding.pry
31
+ 8: end
32
+
33
+ [1] pry(main)>
34
+ ```
35
+
36
+ We can inspect using `page`, `context` and also we can operate something manually during the pause.
37
+
38
+ See https://github.com/deivid-rodriguez/pry-byebug for more detailed debugging options.
39
+
40
+ ## Working with Chrome extensions
41
+
42
+ **Playwright disables the Chrome extension feature by default.**
43
+ We have to enable it for installing Chrome extension, by passing these 3 parameters on launch.
44
+
45
+ * `acceptDownloads: true`
46
+ * `headless: false`
47
+ * `ignoreDefaultArgs: ['--disable-extensions']`
48
+
49
+ ```ruby
50
+ require 'playwright'
51
+ require 'pry'
52
+
53
+ Playwright.create(playwright_cli_executable_path: './node_modules/.bin/playwright') do |playwright|
54
+ launch_params = {
55
+ acceptDownloads: true,
56
+ channel: 'chrome',
57
+ headless: false,
58
+ ignoreDefaultArgs: ['--disable-extensions'],
59
+ }
60
+
61
+ playwright.chromium.launch_persistent_context('./data/', **launch_params) do |context|
62
+ page = context.new_page
63
+ page.goto('https://example.com/')
64
+ binding.pry
65
+ end
66
+ end
67
+ ```
@@ -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
@@ -36,14 +38,15 @@
36
38
  * abort
37
39
  * continue
38
40
  * fulfill
41
+ * ~~intercept~~
39
42
  * request
40
43
 
41
- ## ~~WebSocket~~
44
+ ## WebSocket
42
45
 
43
- * ~~closed?~~
44
- * ~~url~~
45
- * ~~expect_event~~
46
- * ~~wait_for_event~~
46
+ * closed?
47
+ * url
48
+ * expect_event
49
+ * wait_for_event
47
50
 
48
51
  ## Keyboard
49
52
 
@@ -91,6 +94,7 @@
91
94
  * hover
92
95
  * inner_html
93
96
  * inner_text
97
+ * input_value
94
98
  * checked?
95
99
  * disabled?
96
100
  * editable?
@@ -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?
@@ -231,6 +236,7 @@
231
236
  * hover
232
237
  * inner_html
233
238
  * inner_text
239
+ * input_value
234
240
  * checked?
235
241
  * closed?
236
242
  * disabled?
@@ -273,12 +279,12 @@
273
279
  * expect_navigation
274
280
  * expect_popup
275
281
  * expect_request
276
- * ~~expect_request_finished~~
282
+ * expect_request_finished
277
283
  * expect_response
278
284
  * wait_for_selector
279
285
  * ~~wait_for_timeout~~
280
286
  * wait_for_url
281
- * ~~expect_websocket~~
287
+ * expect_websocket
282
288
  * ~~expect_worker~~
283
289
  * ~~workers~~
284
290
  * ~~wait_for_event~~
@@ -300,7 +306,7 @@
300
306
  * expose_binding
301
307
  * expose_function
302
308
  * grant_permissions
303
- * ~~new_cdp_session~~
309
+ * new_cdp_session
304
310
  * new_page
305
311
  * pages
306
312
  * route
@@ -317,17 +323,17 @@
317
323
  * ~~wait_for_event~~
318
324
  * tracing
319
325
 
320
- ## ~~CDPSession~~
326
+ ## CDPSession
321
327
 
322
- * ~~detach~~
323
- * ~~send_message~~
328
+ * detach
329
+ * send_message
324
330
 
325
331
  ## Browser
326
332
 
327
333
  * close
328
334
  * contexts
329
335
  * connected?
330
- * ~~new_browser_cdp_session~~
336
+ * new_browser_cdp_session
331
337
  * new_context
332
338
  * new_page
333
339
  * start_tracing
@@ -340,7 +346,7 @@
340
346
  * connect_over_cdp
341
347
  * executable_path
342
348
  * launch
343
- * ~~launch_persistent_context~~
349
+ * launch_persistent_context
344
350
  * name
345
351
 
346
352
  ## Playwright
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
@@ -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,
@@ -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)
@@ -1,5 +1,7 @@
1
1
  module Playwright
2
2
  define_channel_owner :BrowserType do
3
+ include Utils::PrepareBrowserContextOptions
4
+
3
5
  def name
4
6
  @initializer['name']
5
7
  end
@@ -20,6 +22,22 @@ module Playwright
20
22
  end
21
23
  end
22
24
 
25
+ def launch_persistent_context(userDataDir, **options, &block)
26
+ params = options.dup
27
+ prepare_browser_context_options(params)
28
+ params['userDataDir'] = userDataDir
29
+
30
+ resp = @channel.send_message_to_server('launchPersistentContext', params.compact)
31
+ context = ChannelOwners::Browser.from(resp)
32
+ return context unless block
33
+
34
+ begin
35
+ block.call(context)
36
+ ensure
37
+ context.close
38
+ end
39
+ end
40
+
23
41
  def connect_over_cdp(endpointURL, headers: nil, slowMo: nil, timeout: nil, &block)
24
42
  raise 'Connecting over CDP is only supported in Chromium.' unless name == 'chromium'
25
43
 
@@ -0,0 +1,19 @@
1
+ module Playwright
2
+ define_channel_owner :CDPSession do
3
+ private def after_initialize
4
+ @channel.on('event', method(:on_event))
5
+ end
6
+
7
+ private def on_event(params)
8
+ emit(params['method'], params['params'])
9
+ end
10
+
11
+ def send_message(method, params: {})
12
+ @channel.send_message_to_server('send', method: method, params: params)
13
+ end
14
+
15
+ def detach
16
+ @channel.send_message_to_server('detach')
17
+ end
18
+ end
19
+ end
@@ -146,6 +146,7 @@ module Playwright
146
146
  index: nil,
147
147
  value: nil,
148
148
  label: nil,
149
+ force: nil,
149
150
  noWaitAfter: nil,
150
151
  timeout: nil)
151
152
  base_params = SelectOptionValues.new(
@@ -154,7 +155,7 @@ module Playwright
154
155
  value: value,
155
156
  label: label,
156
157
  ).as_params
157
- params = base_params.merge({ noWaitAfter: noWaitAfter, timeout: timeout }.compact)
158
+ params = base_params.merge({ force: force, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
158
159
  @channel.send_message_to_server('selectOption', params)
159
160
  end
160
161
 
@@ -179,9 +180,10 @@ module Playwright
179
180
  nil
180
181
  end
181
182
 
182
- def fill(value, noWaitAfter: nil, timeout: nil)
183
+ def fill(value, force: nil, noWaitAfter: nil, timeout: nil)
183
184
  params = {
184
185
  value: value,
186
+ force: force,
185
187
  noWaitAfter: noWaitAfter,
186
188
  timeout: timeout,
187
189
  }.compact
@@ -190,13 +192,18 @@ module Playwright
190
192
  nil
191
193
  end
192
194
 
193
- def select_text(timeout: nil)
194
- params = { timeout: timeout }.compact
195
+ def select_text(force: nil, timeout: nil)
196
+ params = { force: force, timeout: timeout }.compact
195
197
  @channel.send_message_to_server('selectText', params)
196
198
 
197
199
  nil
198
200
  end
199
201
 
202
+ def input_value(timeout: nil)
203
+ params = { timeout: timeout }.compact
204
+ @channel.send_message_to_server('inputValue', params)
205
+ end
206
+
200
207
  def set_input_files(files, noWaitAfter: nil, timeout: nil)
201
208
  file_payloads = InputFiles.new(files).as_params
202
209
  params = { files: file_payloads, noWaitAfter: noWaitAfter, timeout: timeout }.compact
@@ -342,10 +342,16 @@ module Playwright
342
342
  nil
343
343
  end
344
344
 
345
- def fill(selector, value, noWaitAfter: nil, timeout: nil)
345
+ def fill(
346
+ selector,
347
+ value,
348
+ force: nil,
349
+ noWaitAfter: nil,
350
+ timeout: nil)
346
351
  params = {
347
352
  selector: selector,
348
353
  value: value,
354
+ force: force,
349
355
  noWaitAfter: noWaitAfter,
350
356
  timeout: timeout,
351
357
  }.compact
@@ -410,6 +416,7 @@ module Playwright
410
416
  index: nil,
411
417
  value: nil,
412
418
  label: nil,
419
+ force: nil,
413
420
  noWaitAfter: nil,
414
421
  timeout: nil)
415
422
  base_params = SelectOptionValues.new(
@@ -418,10 +425,15 @@ module Playwright
418
425
  value: value,
419
426
  label: label,
420
427
  ).as_params
421
- params = base_params.merge({ selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
428
+ params = base_params.merge({ selector: selector, force: force, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
422
429
  @channel.send_message_to_server('selectOption', params)
423
430
  end
424
431
 
432
+ def input_value(selector, timeout: nil)
433
+ params = { selector: selector, timeout: timeout }.compact
434
+ @channel.send_message_to_server('inputValue', params)
435
+ end
436
+
425
437
  def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
426
438
  file_payloads = InputFiles.new(files).as_params
427
439
  params = { files: file_payloads, selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
@@ -505,8 +505,13 @@ module Playwright
505
505
  )
506
506
  end
507
507
 
508
- def fill(selector, value, noWaitAfter: nil, timeout: nil)
509
- @main_frame.fill(selector, value, noWaitAfter: noWaitAfter, timeout: timeout)
508
+ def fill(
509
+ selector,
510
+ value,
511
+ force: nil,
512
+ noWaitAfter: nil,
513
+ timeout: nil)
514
+ @main_frame.fill(selector, value, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
510
515
  end
511
516
 
512
517
  def focus(selector, timeout: nil)
@@ -552,6 +557,7 @@ module Playwright
552
557
  index: nil,
553
558
  value: nil,
554
559
  label: nil,
560
+ force: nil,
555
561
  noWaitAfter: nil,
556
562
  timeout: nil)
557
563
  @main_frame.select_option(
@@ -560,11 +566,16 @@ module Playwright
560
566
  index: index,
561
567
  value: value,
562
568
  label: label,
569
+ force: force,
563
570
  noWaitAfter: noWaitAfter,
564
571
  timeout: timeout,
565
572
  )
566
573
  end
567
574
 
575
+ def input_value(selector, timeout: nil)
576
+ @main_frame.input_value(selector, timeout: timeout)
577
+ end
578
+
568
579
  def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
569
580
  @main_frame.set_input_files(selector, files, noWaitAfter: noWaitAfter, timeout: timeout)
570
581
  end
@@ -759,6 +770,10 @@ module Playwright
759
770
  expect_event(Events::Page::Request, predicate: predicate, timeout: timeout, &block)
760
771
  end
761
772
 
773
+ def expect_request_finished(predicate: nil, timeout: nil, &block)
774
+ expect_event(Events::Page::RequestFinished, predicate: predicate, timeout: timeout, &block)
775
+ end
776
+
762
777
  def expect_response(urlOrPredicate, timeout: nil, &block)
763
778
  predicate =
764
779
  case urlOrPredicate
@@ -774,6 +789,10 @@ module Playwright
774
789
  expect_event(Events::Page::Response, predicate: predicate, timeout: timeout, &block)
775
790
  end
776
791
 
792
+ def expect_websocket(predicate: nil, timeout: nil, &block)
793
+ expect_event(Events::Page::WebSocket, predicate: predicate, timeout: timeout, &block)
794
+ end
795
+
777
796
  # called from Frame with send(:timeout_settings)
778
797
  private def timeout_settings
779
798
  @timeout_settings