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
@@ -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'