playwright-ruby-client 1.15.beta1 → 1.15.beta2

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -14
  3. data/documentation/docs/api/browser.md +2 -0
  4. data/documentation/docs/api/browser_context.md +5 -1
  5. data/documentation/docs/api/browser_type.md +1 -0
  6. data/documentation/docs/api/element_handle.md +27 -1
  7. data/documentation/docs/api/experimental/android_device.md +1 -0
  8. data/documentation/docs/api/frame.md +28 -0
  9. data/documentation/docs/api/locator.md +29 -0
  10. data/documentation/docs/api/page.md +35 -2
  11. data/documentation/docs/api/request.md +27 -1
  12. data/documentation/docs/api/response.md +18 -1
  13. data/documentation/docs/api/tracing.md +42 -8
  14. data/documentation/docs/article/getting_started.md +10 -1
  15. data/documentation/docs/article/guides/download_playwright_driver.md +9 -0
  16. data/documentation/docs/article/guides/inspector.md +1 -1
  17. data/documentation/docs/article/guides/playwright_on_alpine_linux.md +56 -3
  18. data/documentation/docs/article/guides/rails_integration.md +4 -2
  19. data/documentation/docs/article/guides/rails_integration_with_null_driver.md +86 -0
  20. data/documentation/docs/article/guides/recording_video.md +1 -1
  21. data/documentation/docs/article/guides/semi_automation.md +1 -1
  22. data/documentation/docs/article/guides/use_storage_state.md +1 -1
  23. data/documentation/docs/include/api_coverage.md +11 -0
  24. data/documentation/docusaurus.config.js +1 -0
  25. data/documentation/package.json +2 -2
  26. data/documentation/src/pages/index.js +0 -1
  27. data/documentation/static/img/playwright-ruby-client.png +0 -0
  28. data/documentation/yarn.lock +625 -549
  29. data/lib/playwright/channel.rb +36 -2
  30. data/lib/playwright/channel_owners/artifact.rb +6 -2
  31. data/lib/playwright/channel_owners/browser.rb +4 -0
  32. data/lib/playwright/channel_owners/browser_context.rb +21 -14
  33. data/lib/playwright/channel_owners/element_handle.rb +10 -2
  34. data/lib/playwright/channel_owners/frame.rb +8 -0
  35. data/lib/playwright/channel_owners/page.rb +20 -4
  36. data/lib/playwright/channel_owners/request.rb +38 -17
  37. data/lib/playwright/channel_owners/response.rb +41 -5
  38. data/lib/playwright/connection.rb +5 -3
  39. data/lib/playwright/http_headers.rb +9 -4
  40. data/lib/playwright/locator_impl.rb +8 -0
  41. data/lib/playwright/{route_handler_entry.rb → route_handler.rb} +30 -2
  42. data/lib/playwright/tracing_impl.rb +18 -7
  43. data/lib/playwright/transport.rb +2 -0
  44. data/lib/playwright/utils.rb +8 -0
  45. data/lib/playwright/version.rb +1 -1
  46. data/lib/playwright/web_socket_transport.rb +2 -0
  47. data/lib/playwright.rb +1 -1
  48. data/lib/playwright_api/android.rb +6 -6
  49. data/lib/playwright_api/android_device.rb +10 -9
  50. data/lib/playwright_api/browser.rb +10 -8
  51. data/lib/playwright_api/browser_context.rb +12 -8
  52. data/lib/playwright_api/browser_type.rb +8 -7
  53. data/lib/playwright_api/cdp_session.rb +6 -6
  54. data/lib/playwright_api/console_message.rb +6 -6
  55. data/lib/playwright_api/dialog.rb +6 -6
  56. data/lib/playwright_api/element_handle.rb +31 -8
  57. data/lib/playwright_api/frame.rb +31 -6
  58. data/lib/playwright_api/js_handle.rb +6 -6
  59. data/lib/playwright_api/locator.rb +26 -0
  60. data/lib/playwright_api/page.rb +40 -10
  61. data/lib/playwright_api/playwright.rb +6 -6
  62. data/lib/playwright_api/request.rb +26 -4
  63. data/lib/playwright_api/response.rb +20 -4
  64. data/lib/playwright_api/route.rb +6 -6
  65. data/lib/playwright_api/selectors.rb +6 -6
  66. data/lib/playwright_api/tracing.rb +33 -4
  67. data/lib/playwright_api/web_socket.rb +6 -6
  68. data/lib/playwright_api/worker.rb +8 -8
  69. metadata +5 -3
@@ -30,16 +30,50 @@ module Playwright
30
30
  # @param params [Hash]
31
31
  # @return [Hash]
32
32
  def send_message_to_server_result(method, params)
33
- @connection.send_message_to_server(@guid, method, params)
33
+ with_logging do |metadata|
34
+ @connection.send_message_to_server(@guid, method, params, metadata: metadata)
35
+ end
34
36
  end
35
37
 
36
38
  # @param method [String]
37
39
  # @param params [Hash]
38
40
  # @returns nil
39
41
  def async_send_message_to_server(method, params = {})
40
- @connection.async_send_message_to_server(@guid, method, params)
42
+ with_logging do |metadata|
43
+ @connection.async_send_message_to_server(@guid, method, params, metadata: metadata)
44
+ end
41
45
 
42
46
  nil
43
47
  end
48
+
49
+ private def with_logging(&block)
50
+ locations = caller_locations
51
+ first_api_call_location_idx = locations.index { |loc| loc.absolute_path.include?('playwright_api') }
52
+ unless first_api_call_location_idx
53
+ return block.call(nil)
54
+ end
55
+
56
+ locations = locations.slice(first_api_call_location_idx..-1)
57
+
58
+ api_call_location = locations.shift
59
+
60
+ api_class = File.basename(api_call_location.absolute_path, '.rb')
61
+ api_method = api_call_location.label
62
+ api_name = "#{api_class}##{api_method}"
63
+
64
+ stacks = locations
65
+
66
+ metadata = build_metadata_payload_from(api_name, stacks)
67
+ block.call(metadata)
68
+ end
69
+
70
+ private def build_metadata_payload_from(api_name, stacks)
71
+ {
72
+ apiName: api_name,
73
+ stack: stacks.map do |loc|
74
+ { file: loc.absolute_path, line: loc.lineno, function: loc.label }
75
+ end,
76
+ }
77
+ end
44
78
  end
45
79
  end
@@ -1,14 +1,14 @@
1
1
  module Playwright
2
2
  define_channel_owner :Artifact do
3
3
  private def after_initialize
4
- @is_remote = false
4
+ @remote = false
5
5
  @absolute_path = @initializer['absolutePath']
6
6
  end
7
7
 
8
8
  attr_reader :absolute_path
9
9
 
10
10
  def path_after_finished
11
- if @is_remote
11
+ if @remote
12
12
  raise "Path is not available when using browser_type.connect(). Use save_as() to save a local copy."
13
13
  end
14
14
  @channel.send_message_to_server('pathAfterFinished')
@@ -30,5 +30,9 @@ module Playwright
30
30
  def cancel
31
31
  @channel.send_message_to_server('cancel')
32
32
  end
33
+
34
+ private def update_as_remote
35
+ @remote = true
36
+ end
33
37
  end
34
38
  end
@@ -104,6 +104,10 @@ module Playwright
104
104
  @remote = true
105
105
  end
106
106
 
107
+ private def remote?
108
+ @remote
109
+ end
110
+
107
111
  # called from BrowserContext#on_close with send(:remove_context), so keep private.
108
112
  private def remove_context(context)
109
113
  @contexts.delete(context)
@@ -30,7 +30,7 @@ module Playwright
30
30
  @channel.on('request', ->(params) {
31
31
  on_request(
32
32
  ChannelOwners::Request.from(params['request']),
33
- ChannelOwners::Request.from_nullable(params['page']),
33
+ ChannelOwners::Page.from_nullable(params['page']),
34
34
  )
35
35
  })
36
36
  @channel.on('requestFailed', ->(params) {
@@ -38,20 +38,14 @@ module Playwright
38
38
  ChannelOwners::Request.from(params['request']),
39
39
  params['responseEndTiming'],
40
40
  params['failureText'],
41
- ChannelOwners::Request.from_nullable(params['page']),
42
- )
43
- })
44
- @channel.on('requestFinished', ->(params) {
45
- on_request_finished(
46
- ChannelOwners::Request.from(params['request']),
47
- params['responseEndTiming'],
48
- ChannelOwners::Request.from_nullable(params['page']),
41
+ ChannelOwners::Page.from_nullable(params['page']),
49
42
  )
50
43
  })
44
+ @channel.on('requestFinished', method(:on_request_finished))
51
45
  @channel.on('response', ->(params) {
52
46
  on_response(
53
47
  ChannelOwners::Response.from(params['response']),
54
- ChannelOwners::Request.from_nullable(params['page']),
48
+ ChannelOwners::Page.from_nullable(params['page']),
55
49
  )
56
50
  })
57
51
 
@@ -95,10 +89,15 @@ module Playwright
95
89
  page&.emit(Events::Page::RequestFailed, request)
96
90
  end
97
91
 
98
- private def on_request_finished(request, response_end_timing, page)
99
- request.send(:update_response_end_timing, response_end_timing)
92
+ private def on_request_finished(params)
93
+ request = ChannelOwners::Request.from(params['request'])
94
+ response = ChannelOwners::Response.from_nullable(params['response'])
95
+ page = ChannelOwners::Page.from_nullable(params['page'])
96
+
97
+ request.send(:update_response_end_timing, params['responseEndTiming'])
100
98
  emit(Events::BrowserContext::RequestFinished, request)
101
99
  page&.emit(Events::Page::RequestFinished, request)
100
+ response&.send(:mark_as_finished)
102
101
  end
103
102
 
104
103
  private def on_request(request, page)
@@ -236,8 +235,8 @@ module Playwright
236
235
  expose_binding(name, ->(_source, *args) { callback.call(*args) }, )
237
236
  end
238
237
 
239
- def route(url, handler)
240
- entry = RouteHandlerEntry.new(url, base_url, handler)
238
+ def route(url, handler, times: nil)
239
+ entry = RouteHandler.new(url, base_url, handler, times)
241
240
  @routes.unshift(entry)
242
241
  if @routes.count >= 1
243
242
  @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
@@ -270,6 +269,14 @@ module Playwright
270
269
  end
271
270
 
272
271
  def close
272
+ if @options && @options.key?(:recordHar)
273
+ har = ChannelOwners::Artifact.from(@channel.send_message_to_server('harExport'))
274
+ if @browser.send(:remote?)
275
+ har.update_as_remote
276
+ end
277
+ har.save_as(@options[:recordHar][:path])
278
+ har.delete
279
+ end
273
280
  @channel.send_message_to_server('close')
274
281
  @closed_promise.value!
275
282
  nil
@@ -268,6 +268,14 @@ module Playwright
268
268
  nil
269
269
  end
270
270
 
271
+ def set_checked(checked, **options)
272
+ if checked
273
+ check(**options)
274
+ else
275
+ uncheck(**options)
276
+ end
277
+ end
278
+
271
279
  def bounding_box
272
280
  @channel.send_message_to_server('boundingBox')
273
281
  end
@@ -322,8 +330,8 @@ module Playwright
322
330
  nil
323
331
  end
324
332
 
325
- def wait_for_selector(selector, state: nil, timeout: nil)
326
- params = { selector: selector, state: state, timeout: timeout }.compact
333
+ def wait_for_selector(selector, state: nil, strict: nil, timeout: nil)
334
+ params = { selector: selector, state: state, strict: strict, timeout: timeout }.compact
327
335
  resp = @channel.send_message_to_server('waitForSelector', params)
328
336
 
329
337
  ChannelOwners::ElementHandle.from_nullable(resp)
@@ -584,6 +584,14 @@ module Playwright
584
584
  nil
585
585
  end
586
586
 
587
+ def set_checked(selector, checked, **options)
588
+ if checked
589
+ check(selector, **options)
590
+ else
591
+ uncheck(selector, **options)
592
+ end
593
+ end
594
+
587
595
  def wait_for_timeout(timeout)
588
596
  sleep(timeout / 1000.0)
589
597
  end
@@ -132,17 +132,24 @@ module Playwright
132
132
  end
133
133
 
134
134
  private def on_download(params)
135
+ artifact = ChannelOwners::Artifact.from(params['artifact'])
136
+ if @browser_context.browser.send(:remote?)
137
+ artifact.update_as_remote
138
+ end
135
139
  download = DownloadImpl.new(
136
140
  page: self,
137
141
  url: params['url'],
138
142
  suggested_filename: params['suggestedFilename'],
139
- artifact: ChannelOwners::Artifact.from(params['artifact']),
143
+ artifact: artifact,
140
144
  )
141
145
  emit(Events::Page::Download, download)
142
146
  end
143
147
 
144
148
  private def on_video(params)
145
149
  artifact = ChannelOwners::Artifact.from(params['artifact'])
150
+ if @browser_context.browser.send(:remote?)
151
+ artifact.update_as_remote
152
+ end
146
153
  video.send(:set_artifact, artifact)
147
154
  end
148
155
 
@@ -345,9 +352,10 @@ module Playwright
345
352
  ChannelOwners::Response.from_nullable(resp)
346
353
  end
347
354
 
348
- def emulate_media(colorScheme: nil, media: nil, reducedMotion: nil)
355
+ def emulate_media(colorScheme: nil, forcedColors: nil, media: nil, reducedMotion: nil)
349
356
  params = {
350
357
  colorScheme: colorScheme,
358
+ forcedColors: forcedColors,
351
359
  media: media,
352
360
  reducedMotion: reducedMotion,
353
361
  }.compact
@@ -381,8 +389,8 @@ module Playwright
381
389
  nil
382
390
  end
383
391
 
384
- def route(url, handler)
385
- entry = RouteHandlerEntry.new(url, @browser_context.send(:base_url), handler)
392
+ def route(url, handler, times: nil)
393
+ entry = RouteHandler.new(url, @browser_context.send(:base_url), handler, times)
386
394
  @routes.unshift(entry)
387
395
  if @routes.count >= 1
388
396
  @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
@@ -702,6 +710,14 @@ module Playwright
702
710
  trial: trial)
703
711
  end
704
712
 
713
+ def set_checked(selector, checked, **options)
714
+ if checked
715
+ check(selector, **options)
716
+ else
717
+ uncheck(selector, **options)
718
+ end
719
+ end
720
+
705
721
  def wait_for_timeout(timeout)
706
722
  @main_frame.wait_for_timeout(timeout)
707
723
  end
@@ -1,8 +1,11 @@
1
1
  require 'base64'
2
+ require_relative '../http_headers'
2
3
 
3
4
  module Playwright
4
5
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
5
6
  define_channel_owner :Request do
7
+ include HttpHeaders::Parser
8
+
6
9
  private def after_initialize
7
10
  @redirected_from = ChannelOwners::Request.from_nullable(@initializer['redirectedFrom'])
8
11
  @redirected_from&.send(:update_redirected_to, self)
@@ -17,7 +20,6 @@ module Playwright
17
20
  responseStart: -1,
18
21
  responseEnd: -1,
19
22
  }
20
- @headers = parse_headers(@initializer['headers'])
21
23
  end
22
24
 
23
25
  def url
@@ -40,7 +42,7 @@ module Playwright
40
42
  data = post_data
41
43
  return unless data
42
44
 
43
- content_type = @headers['content-type']
45
+ content_type = headers['content-type']
44
46
  return unless content_type
45
47
 
46
48
  if content_type == "application/x-www-form-urlencoded"
@@ -59,10 +61,6 @@ module Playwright
59
61
  end
60
62
  end
61
63
 
62
- def headers
63
- @headers
64
- end
65
-
66
64
  def response
67
65
  resp = @channel.send_message_to_server('response')
68
66
  ChannelOwners::Response.from_nullable(resp)
@@ -80,7 +78,40 @@ module Playwright
80
78
  @failure_text
81
79
  end
82
80
 
83
- attr_reader :headers, :redirected_from, :redirected_to, :timing
81
+ attr_reader :redirected_from, :redirected_to, :timing
82
+
83
+ def headers
84
+ parse_headers_as_array(@initializer['headers'], true).to_h
85
+ end
86
+
87
+ private def raw_headers
88
+ @raw_headers ||= response&.send(:raw_request_headers)
89
+ end
90
+
91
+ def all_headers
92
+ if raw_headers
93
+ parse_headers_as_array(raw_headers, true).to_h
94
+ else
95
+ parse_headers_as_array(@initializer['headers'], true).to_h
96
+ end
97
+ end
98
+
99
+ def headers_array
100
+ if raw_headers
101
+ parse_headers_as_array(raw_headers, false)
102
+ else
103
+ parse_headers_as_array(@initializer['headers'], false)
104
+ end
105
+ end
106
+
107
+ def sizes
108
+ res = response
109
+ unless res
110
+ raise 'Unable to fetch sizes for failed request'
111
+ end
112
+
113
+ res.send(:sizes)
114
+ end
84
115
 
85
116
  private def update_redirected_to(request)
86
117
  @redirected_to = request
@@ -110,18 +141,8 @@ module Playwright
110
141
  @timing[:responseStart] = response_start
111
142
  end
112
143
 
113
- private def update_headers(headers)
114
- @headers = parse_headers(headers)
115
- end
116
-
117
144
  private def update_response_end_timing(response_end_timing)
118
145
  @timing[:responseEnd] = response_end_timing
119
146
  end
120
-
121
- private def parse_headers(headers)
122
- headers.map do |header|
123
- [header['name'].downcase, header['value']]
124
- end.to_h
125
- end
126
147
  end
127
148
  end
@@ -1,9 +1,12 @@
1
1
  require 'base64'
2
2
  require 'json'
3
+ require_relative '../http_headers'
3
4
 
4
5
  module Playwright
5
6
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
6
7
  define_channel_owner :Response do
8
+ include HttpHeaders::Parser
9
+
7
10
  private def after_initialize
8
11
  @request = ChannelOwners::Request.from(@initializer['request'])
9
12
  timing = @initializer['timing']
@@ -17,7 +20,7 @@ module Playwright
17
20
  request_start: timing["requestStart"],
18
21
  response_start: timing["responseStart"],
19
22
  )
20
- @request.send(:update_headers, @initializer['requestHeaders'])
23
+ @finished_promise = Concurrent::Promises.resolvable_future
21
24
  end
22
25
  attr_reader :request
23
26
 
@@ -39,9 +42,27 @@ module Playwright
39
42
  end
40
43
 
41
44
  def headers
42
- @initializer['headers'].map do |header|
43
- [header['name'].downcase, header['value']]
44
- end.to_h
45
+ parse_headers_as_array(@initializer['headers'], true).to_h
46
+ end
47
+
48
+ private def raw_headers
49
+ @raw_headers ||= raw_response_headers
50
+ end
51
+
52
+ private def raw_request_headers
53
+ @channel.send_message_to_server('rawRequestHeaders')
54
+ end
55
+
56
+ private def raw_response_headers
57
+ @channel.send_message_to_server('rawResponseHeaders')
58
+ end
59
+
60
+ def all_headers
61
+ parse_headers_as_array(raw_headers, true).to_h
62
+ end
63
+
64
+ def headers_array
65
+ parse_headers_as_array(raw_headers, false)
45
66
  end
46
67
 
47
68
  def server_addr
@@ -53,7 +74,7 @@ module Playwright
53
74
  end
54
75
 
55
76
  def finished
56
- @channel.send_message_to_server('finished')
77
+ @finished_promise.value!
57
78
  end
58
79
 
59
80
  def body
@@ -69,5 +90,20 @@ module Playwright
69
90
  def frame
70
91
  @request.frame
71
92
  end
93
+
94
+ private def sizes
95
+ resp = @channel.send_message_to_server('sizes')
96
+
97
+ {
98
+ requestBodySize: resp['requestBodySize'],
99
+ requestHeadersSize: resp['requestHeadersSize'],
100
+ responseBodySize: resp['responseBodySize'],
101
+ responseHeadersSize: resp['responseHeadersSize'],
102
+ }
103
+ end
104
+
105
+ private def mark_as_finished
106
+ @finished_promise.fulfill(nil)
107
+ end
72
108
  end
73
109
  end