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

Sign up to get free protection for your applications and to get access to all the features.
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