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
@@ -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
@@ -71,7 +71,7 @@ module Playwright
71
71
 
72
72
  predicate =
73
73
  if url
74
- matcher = UrlMatcher.new(url)
74
+ matcher = UrlMatcher.new(url, base_url: @page.context.send(:base_url))
75
75
  ->(event) { event['error'] || matcher.match?(event['url']) }
76
76
  else
77
77
  ->(_) { true }
@@ -99,7 +99,7 @@ module Playwright
99
99
  end
100
100
 
101
101
  def wait_for_url(url, timeout: nil, waitUntil: nil)
102
- matcher = UrlMatcher.new(url)
102
+ matcher = UrlMatcher.new(url, base_url: @page.context.send(:base_url))
103
103
  if matcher.match?(@url)
104
104
  wait_for_load_state(state: waitUntil, timeout: timeout)
105
105
  else
@@ -293,6 +293,26 @@ module Playwright
293
293
  nil
294
294
  end
295
295
 
296
+ def drag_and_drop(
297
+ source,
298
+ target,
299
+ force: nil,
300
+ noWaitAfter: nil,
301
+ timeout: nil,
302
+ trial: nil)
303
+ params = {
304
+ source: source,
305
+ target: target,
306
+ force: force,
307
+ noWaitAfter: noWaitAfter,
308
+ timeout: timeout,
309
+ trial: trial,
310
+ }.compact
311
+ @channel.send_message_to_server('dragAndDrop', params)
312
+
313
+ nil
314
+ end
315
+
296
316
  def dblclick(
297
317
  selector,
298
318
  button: nil,
@@ -342,10 +362,16 @@ module Playwright
342
362
  nil
343
363
  end
344
364
 
345
- def fill(selector, value, noWaitAfter: nil, timeout: nil)
365
+ def fill(
366
+ selector,
367
+ value,
368
+ force: nil,
369
+ noWaitAfter: nil,
370
+ timeout: nil)
346
371
  params = {
347
372
  selector: selector,
348
373
  value: value,
374
+ force: force,
349
375
  noWaitAfter: noWaitAfter,
350
376
  timeout: timeout,
351
377
  }.compact
@@ -410,6 +436,7 @@ module Playwright
410
436
  index: nil,
411
437
  value: nil,
412
438
  label: nil,
439
+ force: nil,
413
440
  noWaitAfter: nil,
414
441
  timeout: nil)
415
442
  base_params = SelectOptionValues.new(
@@ -418,10 +445,15 @@ module Playwright
418
445
  value: value,
419
446
  label: label,
420
447
  ).as_params
421
- params = base_params.merge({ selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
448
+ params = base_params.merge({ selector: selector, force: force, noWaitAfter: noWaitAfter, timeout: timeout }.compact)
422
449
  @channel.send_message_to_server('selectOption', params)
423
450
  end
424
451
 
452
+ def input_value(selector, timeout: nil)
453
+ params = { selector: selector, timeout: timeout }.compact
454
+ @channel.send_message_to_server('inputValue', params)
455
+ end
456
+
425
457
  def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
426
458
  file_payloads = InputFiles.new(files).as_params
427
459
  params = { files: file_payloads, selector: selector, noWaitAfter: noWaitAfter, timeout: timeout }.compact
@@ -22,7 +22,7 @@ module Playwright
22
22
  end
23
23
  @closed = false
24
24
  @bindings = {}
25
- @routes = Set.new
25
+ @routes = []
26
26
 
27
27
  @main_frame = ChannelOwners::Frame.from(@initializer['mainFrame'])
28
28
  @main_frame.send(:update_page_from_page, self)
@@ -184,15 +184,8 @@ module Playwright
184
184
  if name
185
185
  @frames.find { |f| f.name == name }
186
186
  elsif url
187
- # ref: https://github.com/microsoft/playwright-python/blob/c4320c27cb080b385a5e45be46baa3cb7a9409ff/playwright/_impl/_helper.py#L104
188
- case url
189
- when String
190
- @frames.find { |f| f.url == url }
191
- when Regexp
192
- @frames.find { |f| url.match?(f.url) }
193
- else
194
- raise NotImplementedError.new('Page#frame with url is not completely implemented yet')
195
- end
187
+ matcher = UrlMatcher.new(url, base_url: @browser_context.send(:base_url))
188
+ @frames.find { |f| matcher.match?(f.url) }
196
189
  else
197
190
  raise ArgumentError.new('Either name or url matcher should be specified')
198
191
  end
@@ -377,8 +370,8 @@ module Playwright
377
370
  end
378
371
 
379
372
  def route(url, handler)
380
- entry = RouteHandlerEntry.new(url, handler)
381
- @routes << entry
373
+ entry = RouteHandlerEntry.new(url, @browser_context.send(:base_url), handler)
374
+ @routes.unshift(entry)
382
375
  if @routes.count >= 1
383
376
  @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
384
377
  end
@@ -463,6 +456,23 @@ module Playwright
463
456
  )
464
457
  end
465
458
 
459
+ def drag_and_drop(
460
+ source,
461
+ target,
462
+ force: nil,
463
+ noWaitAfter: nil,
464
+ timeout: nil,
465
+ trial: nil)
466
+
467
+ @main_frame.drag_and_drop(
468
+ source,
469
+ target,
470
+ force: force,
471
+ noWaitAfter: noWaitAfter,
472
+ timeout: timeout,
473
+ trial: trial)
474
+ end
475
+
466
476
  def dblclick(
467
477
  selector,
468
478
  button: nil,
@@ -505,8 +515,13 @@ module Playwright
505
515
  )
506
516
  end
507
517
 
508
- def fill(selector, value, noWaitAfter: nil, timeout: nil)
509
- @main_frame.fill(selector, value, noWaitAfter: noWaitAfter, timeout: timeout)
518
+ def fill(
519
+ selector,
520
+ value,
521
+ force: nil,
522
+ noWaitAfter: nil,
523
+ timeout: nil)
524
+ @main_frame.fill(selector, value, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
510
525
  end
511
526
 
512
527
  def focus(selector, timeout: nil)
@@ -552,6 +567,7 @@ module Playwright
552
567
  index: nil,
553
568
  value: nil,
554
569
  label: nil,
570
+ force: nil,
555
571
  noWaitAfter: nil,
556
572
  timeout: nil)
557
573
  @main_frame.select_option(
@@ -560,11 +576,16 @@ module Playwright
560
576
  index: index,
561
577
  value: value,
562
578
  label: label,
579
+ force: force,
563
580
  noWaitAfter: noWaitAfter,
564
581
  timeout: timeout,
565
582
  )
566
583
  end
567
584
 
585
+ def input_value(selector, timeout: nil)
586
+ @main_frame.input_value(selector, timeout: timeout)
587
+ end
588
+
568
589
  def set_input_files(selector, files, noWaitAfter: nil, timeout: nil)
569
590
  @main_frame.set_input_files(selector, files, noWaitAfter: noWaitAfter, timeout: timeout)
570
591
  end
@@ -748,7 +769,7 @@ module Playwright
748
769
  predicate =
749
770
  case urlOrPredicate
750
771
  when String, Regexp
751
- url_matcher = UrlMatcher.new(urlOrPredicate)
772
+ url_matcher = UrlMatcher.new(urlOrPredicate, base_url: @browser_context.send(:base_url))
752
773
  -> (req){ url_matcher.match?(req.url) }
753
774
  when Proc
754
775
  urlOrPredicate
@@ -759,11 +780,15 @@ module Playwright
759
780
  expect_event(Events::Page::Request, predicate: predicate, timeout: timeout, &block)
760
781
  end
761
782
 
783
+ def expect_request_finished(predicate: nil, timeout: nil, &block)
784
+ expect_event(Events::Page::RequestFinished, predicate: predicate, timeout: timeout, &block)
785
+ end
786
+
762
787
  def expect_response(urlOrPredicate, timeout: nil, &block)
763
788
  predicate =
764
789
  case urlOrPredicate
765
790
  when String, Regexp
766
- url_matcher = UrlMatcher.new(urlOrPredicate)
791
+ url_matcher = UrlMatcher.new(urlOrPredicate, base_url: @browser_context.send(:base_url))
767
792
  -> (req){ url_matcher.match?(req.url) }
768
793
  when Proc
769
794
  urlOrPredicate
@@ -774,6 +799,10 @@ module Playwright
774
799
  expect_event(Events::Page::Response, predicate: predicate, timeout: timeout, &block)
775
800
  end
776
801
 
802
+ def expect_websocket(predicate: nil, timeout: nil, &block)
803
+ expect_event(Events::Page::WebSocket, predicate: predicate, timeout: timeout, &block)
804
+ end
805
+
777
806
  # called from Frame with send(:timeout_settings)
778
807
  private def timeout_settings
779
808
  @timeout_settings
@@ -4,7 +4,7 @@ require 'json'
4
4
  module Playwright
5
5
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
6
6
  define_channel_owner :Response do
7
- def after_initialize
7
+ private def after_initialize
8
8
  @request = ChannelOwners::Request.from(@initializer['request'])
9
9
  timing = @initializer['timing']
10
10
  @request.send(:update_timings,
@@ -44,6 +44,14 @@ module Playwright
44
44
  end.to_h
45
45
  end
46
46
 
47
+ def server_addr
48
+ @channel.send_message_to_server('serverAddr')
49
+ end
50
+
51
+ def security_details
52
+ @channel.send_message_to_server('securityDetails')
53
+ end
54
+
47
55
  def finished
48
56
  @channel.send_message_to_server('finished')
49
57
  end
@@ -1,4 +1,87 @@
1
+ require 'base64'
2
+
1
3
  module Playwright
2
4
  define_channel_owner :WebSocket do
5
+ private def after_initialize
6
+ @closed = false
7
+
8
+ @channel.on('frameSent', -> (params) {
9
+ on_frame_sent(params['opcode'], params['data'])
10
+ })
11
+ @channel.on('frameReceived', -> (params) {
12
+ on_frame_received(params['opcode'], params['data'])
13
+ })
14
+ @channel.on('socketError', -> (params) {
15
+ emit(Events::WebSocket::Error, params['error'])
16
+ })
17
+ @channel.on('close', -> (_) { on_close })
18
+ end
19
+
20
+ def url
21
+ @initializer['url']
22
+ end
23
+
24
+ class SocketClosedError < StandardError
25
+ def initialize
26
+ super('Socket closed')
27
+ end
28
+ end
29
+
30
+ class SocketError < StandardError
31
+ def initialize
32
+ super('Socket error')
33
+ end
34
+ end
35
+
36
+ class PageClosedError < StandardError
37
+ def initialize
38
+ super('Page closed')
39
+ end
40
+ end
41
+
42
+ def expect_event(event, predicate: nil, timeout: nil, &block)
43
+ wait_helper = WaitHelper.new
44
+ wait_helper.reject_on_timeout(timeout || @parent.send(:timeout_settings).timeout, "Timeout while waiting for event \"#{event}\"")
45
+
46
+ unless event == Events::WebSocket::Close
47
+ wait_helper.reject_on_event(self, Events::WebSocket::Close, SocketClosedError.new)
48
+ end
49
+
50
+ unless event == Events::WebSocket::Error
51
+ wait_helper.reject_on_event(self, Events::WebSocket::Error, SocketError.new)
52
+ end
53
+
54
+ wait_helper.reject_on_event(@parent, 'close', PageClosedError.new)
55
+ wait_helper.wait_for_event(self, event, predicate: predicate)
56
+ block&.call
57
+
58
+ wait_helper.promise.value!
59
+ end
60
+ alias_method :wait_for_event, :expect_event
61
+
62
+ private def on_frame_sent(opcode, data)
63
+ if opcode == 2
64
+ emit(Events::WebSocket::FrameSent, Base64.strict_decode64(data))
65
+ else
66
+ emit(Events::WebSocket::FrameSent, data)
67
+ end
68
+ end
69
+
70
+ private def on_frame_received(opcode, data)
71
+ if opcode == 2
72
+ emit(Events::WebSocket::FrameReceived, Base64.strict_decode64(data))
73
+ else
74
+ emit(Events::WebSocket::FrameReceived, data)
75
+ end
76
+ end
77
+
78
+ def closed?
79
+ @closed
80
+ end
81
+
82
+ private def on_close
83
+ @closed = true
84
+ emit(Events::WebSocket::Close)
85
+ end
3
86
  end
4
87
  end
@@ -5,10 +5,8 @@ module Playwright
5
5
  # https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_connection.py
6
6
  # https://github.com/microsoft/playwright-java/blob/master/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
7
7
  class Connection
8
- def initialize(playwright_cli_executable_path:)
9
- @transport = Transport.new(
10
- playwright_cli_executable_path: playwright_cli_executable_path
11
- )
8
+ def initialize(transport)
9
+ @transport = transport
12
10
  @transport.on_message_received do |message|
13
11
  dispatch(message)
14
12
  end
@@ -24,5 +24,9 @@ module Playwright
24
24
  def save_as(path)
25
25
  @artifact.save_as(path)
26
26
  end
27
+
28
+ def cancel
29
+ @artifact.cancel
30
+ end
27
31
  end
28
32
  end
@@ -1,10 +1,11 @@
1
1
  module Playwright
2
2
  class RouteHandlerEntry
3
3
  # @param url [String]
4
+ # @param base_url [String|nil]
4
5
  # @param handler [Proc]
5
- def initialize(url, handler)
6
+ def initialize(url, base_url, handler)
6
7
  @url_value = url
7
- @url_matcher = UrlMatcher.new(url)
8
+ @url_matcher = UrlMatcher.new(url, base_url: base_url)
8
9
  @handler = handler
9
10
  end
10
11
 
@@ -8,7 +8,6 @@ module Playwright
8
8
  # ref: https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_transport.py
9
9
  class Transport
10
10
  # @param playwright_cli_executable_path [String] path to playwright-cli.
11
- # @param debug [Boolean]
12
11
  def initialize(playwright_cli_executable_path:)
13
12
  @driver_executable_path = playwright_cli_executable_path
14
13
  @debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'