playwright-ruby-client 1.14.beta3 → 1.15.beta3

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -14
  3. data/documentation/docs/api/browser.md +4 -0
  4. data/documentation/docs/api/browser_context.md +5 -1
  5. data/documentation/docs/api/browser_type.md +2 -0
  6. data/documentation/docs/api/element_handle.md +30 -5
  7. data/documentation/docs/api/experimental/android_device.md +2 -0
  8. data/documentation/docs/api/frame.md +32 -6
  9. data/documentation/docs/api/locator.md +67 -38
  10. data/documentation/docs/api/mouse.md +11 -0
  11. data/documentation/docs/api/page.md +38 -7
  12. data/documentation/docs/api/request.md +34 -1
  13. data/documentation/docs/api/response.md +37 -2
  14. data/documentation/docs/api/selectors.md +29 -3
  15. data/documentation/docs/api/tracing.md +42 -8
  16. data/documentation/docs/api/worker.md +12 -11
  17. data/documentation/docs/article/getting_started.md +10 -1
  18. data/documentation/docs/article/guides/download_playwright_driver.md +9 -0
  19. data/documentation/docs/article/guides/inspector.md +1 -1
  20. data/documentation/docs/article/guides/playwright_on_alpine_linux.md +56 -3
  21. data/documentation/docs/article/guides/rails_integration.md +4 -2
  22. data/documentation/docs/article/guides/rails_integration_with_null_driver.md +86 -0
  23. data/documentation/docs/article/guides/recording_video.md +1 -1
  24. data/documentation/docs/article/guides/semi_automation.md +2 -2
  25. data/documentation/docs/article/guides/use_storage_state.md +78 -0
  26. data/documentation/docs/include/api_coverage.md +15 -0
  27. data/documentation/docusaurus.config.js +1 -0
  28. data/documentation/package.json +2 -2
  29. data/documentation/src/pages/index.js +0 -1
  30. data/documentation/static/img/playwright-ruby-client.png +0 -0
  31. data/documentation/yarn.lock +625 -549
  32. data/lib/playwright/channel.rb +36 -2
  33. data/lib/playwright/channel_owners/artifact.rb +6 -2
  34. data/lib/playwright/channel_owners/browser.rb +4 -0
  35. data/lib/playwright/channel_owners/browser_context.rb +21 -14
  36. data/lib/playwright/channel_owners/browser_type.rb +0 -1
  37. data/lib/playwright/channel_owners/element_handle.rb +10 -2
  38. data/lib/playwright/channel_owners/frame.rb +8 -0
  39. data/lib/playwright/channel_owners/page.rb +20 -4
  40. data/lib/playwright/channel_owners/playwright.rb +9 -0
  41. data/lib/playwright/channel_owners/request.rb +53 -17
  42. data/lib/playwright/channel_owners/response.rb +48 -5
  43. data/lib/playwright/connection.rb +8 -11
  44. data/lib/playwright/http_headers.rb +0 -6
  45. data/lib/playwright/locator_impl.rb +8 -0
  46. data/lib/playwright/mouse_impl.rb +9 -0
  47. data/lib/playwright/raw_headers.rb +61 -0
  48. data/lib/playwright/{route_handler_entry.rb → route_handler.rb} +30 -2
  49. data/lib/playwright/tracing_impl.rb +18 -7
  50. data/lib/playwright/transport.rb +2 -0
  51. data/lib/playwright/utils.rb +8 -1
  52. data/lib/playwright/version.rb +2 -2
  53. data/lib/playwright/web_socket_transport.rb +2 -0
  54. data/lib/playwright.rb +46 -5
  55. data/lib/playwright_api/android.rb +6 -6
  56. data/lib/playwright_api/android_device.rb +11 -9
  57. data/lib/playwright_api/browser.rb +12 -8
  58. data/lib/playwright_api/browser_context.rb +12 -8
  59. data/lib/playwright_api/browser_type.rb +9 -7
  60. data/lib/playwright_api/cdp_session.rb +7 -7
  61. data/lib/playwright_api/console_message.rb +6 -6
  62. data/lib/playwright_api/dialog.rb +6 -6
  63. data/lib/playwright_api/element_handle.rb +31 -8
  64. data/lib/playwright_api/frame.rb +35 -12
  65. data/lib/playwright_api/js_handle.rb +6 -6
  66. data/lib/playwright_api/locator.rb +39 -0
  67. data/lib/playwright_api/mouse.rb +8 -0
  68. data/lib/playwright_api/page.rb +43 -15
  69. data/lib/playwright_api/playwright.rb +6 -6
  70. data/lib/playwright_api/request.rb +33 -7
  71. data/lib/playwright_api/response.rb +31 -8
  72. data/lib/playwright_api/route.rb +6 -6
  73. data/lib/playwright_api/selectors.rb +38 -7
  74. data/lib/playwright_api/tracing.rb +33 -4
  75. data/lib/playwright_api/web_socket.rb +6 -6
  76. data/lib/playwright_api/worker.rb +8 -8
  77. metadata +8 -4
@@ -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
@@ -42,7 +42,6 @@ module Playwright
42
42
  raise 'Connecting over CDP is only supported in Chromium.' unless name == 'chromium'
43
43
 
44
44
  params = {
45
- sdkLanguage: 'ruby',
46
45
  endpointURL: endpointURL,
47
46
  headers: headers,
48
47
  slowMo: slowMo,
@@ -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
@@ -30,6 +30,15 @@ module Playwright
30
30
  end.to_h
31
31
  end
32
32
 
33
+ # used only from Playwright#connect_to_browser_server
34
+ private def pre_launched_browser
35
+ unless @initializer['preLaunchedBrowser']
36
+ raise 'Malformed endpoint. Did you use launchServer method?'
37
+ end
38
+
39
+ ::Playwright::ChannelOwners::Browser.from(@initializer['preLaunchedBrowser'])
40
+ end
41
+
33
42
  private def parse_device_descriptor(descriptor)
34
43
  # This return value can be passed into Browser#new_context as it is.
35
44
  # ex:
@@ -6,6 +6,7 @@ module Playwright
6
6
  private def after_initialize
7
7
  @redirected_from = ChannelOwners::Request.from_nullable(@initializer['redirectedFrom'])
8
8
  @redirected_from&.send(:update_redirected_to, self)
9
+ @provisional_headers = RawHeaders.new(@initializer['headers'])
9
10
  @timing = {
10
11
  startTime: 0,
11
12
  domainLookupStart: -1,
@@ -17,7 +18,6 @@ module Playwright
17
18
  responseStart: -1,
18
19
  responseEnd: -1,
19
20
  }
20
- @headers = parse_headers(@initializer['headers'])
21
21
  end
22
22
 
23
23
  def url
@@ -40,7 +40,7 @@ module Playwright
40
40
  data = post_data
41
41
  return unless data
42
42
 
43
- content_type = @headers['content-type']
43
+ content_type = headers['content-type']
44
44
  return unless content_type
45
45
 
46
46
  if content_type == "application/x-www-form-urlencoded"
@@ -59,10 +59,6 @@ module Playwright
59
59
  end
60
60
  end
61
61
 
62
- def headers
63
- @headers
64
- end
65
-
66
62
  def response
67
63
  resp = @channel.send_message_to_server('response')
68
64
  ChannelOwners::Response.from_nullable(resp)
@@ -80,7 +76,57 @@ module Playwright
80
76
  @failure_text
81
77
  end
82
78
 
83
- attr_reader :headers, :redirected_from, :redirected_to, :timing
79
+ attr_reader :redirected_from, :redirected_to, :timing
80
+
81
+ def headers
82
+ @provisional_headers.headers
83
+ end
84
+
85
+ # @return [RawHeaders|nil]
86
+ private def actual_headers
87
+ @actual_headers ||= response&.send(:raw_request_headers)
88
+ end
89
+
90
+ def all_headers
91
+ if actual_headers
92
+ actual_headers.headers
93
+ else
94
+ @provisional_headers.headers
95
+ end
96
+ end
97
+
98
+ def headers_array
99
+ if actual_headers
100
+ actual_headers.headers_array
101
+ else
102
+ @provisional_headers.headers_array
103
+ end
104
+ end
105
+
106
+ def header_value(name)
107
+ if actual_headers
108
+ actual_headers.get(name)
109
+ else
110
+ @provisional_headers.get(name)
111
+ end
112
+ end
113
+
114
+ def header_values(name)
115
+ if actual_headers
116
+ actual_headers.get_all(name)
117
+ else
118
+ @provisional_headers.get_all(name)
119
+ end
120
+ end
121
+
122
+ def sizes
123
+ res = response
124
+ unless res
125
+ raise 'Unable to fetch sizes for failed request'
126
+ end
127
+
128
+ res.send(:sizes)
129
+ end
84
130
 
85
131
  private def update_redirected_to(request)
86
132
  @redirected_to = request
@@ -110,18 +156,8 @@ module Playwright
110
156
  @timing[:responseStart] = response_start
111
157
  end
112
158
 
113
- private def update_headers(headers)
114
- @headers = parse_headers(headers)
115
- end
116
-
117
159
  private def update_response_end_timing(response_end_timing)
118
160
  @timing[:responseEnd] = response_end_timing
119
161
  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
162
  end
127
163
  end
@@ -5,6 +5,7 @@ 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
7
  private def after_initialize
8
+ @provisional_headers = RawHeaders.new(@initializer['headers'])
8
9
  @request = ChannelOwners::Request.from(@initializer['request'])
9
10
  timing = @initializer['timing']
10
11
  @request.send(:update_timings,
@@ -17,7 +18,7 @@ module Playwright
17
18
  request_start: timing["requestStart"],
18
19
  response_start: timing["responseStart"],
19
20
  )
20
- @request.send(:update_headers, @initializer['requestHeaders'])
21
+ @finished_promise = Concurrent::Promises.resolvable_future
21
22
  end
22
23
  attr_reader :request
23
24
 
@@ -39,9 +40,36 @@ module Playwright
39
40
  end
40
41
 
41
42
  def headers
42
- @initializer['headers'].map do |header|
43
- [header['name'].downcase, header['value']]
44
- end.to_h
43
+ @provisional_headers.headers
44
+ end
45
+
46
+ # @return [RawHeaders]
47
+ private def actual_headers
48
+ @actual_headers ||= raw_response_headers
49
+ end
50
+
51
+ private def raw_request_headers
52
+ RawHeaders.new(@channel.send_message_to_server('rawRequestHeaders'))
53
+ end
54
+
55
+ private def raw_response_headers
56
+ RawHeaders.new(@channel.send_message_to_server('rawResponseHeaders'))
57
+ end
58
+
59
+ def all_headers
60
+ actual_headers.headers
61
+ end
62
+
63
+ def headers_array
64
+ actual_headers.headers_array
65
+ end
66
+
67
+ def header_value(name)
68
+ actual_headers.get(name)
69
+ end
70
+
71
+ def header_values(name)
72
+ actual_headers.get_all(name)
45
73
  end
46
74
 
47
75
  def server_addr
@@ -53,7 +81,7 @@ module Playwright
53
81
  end
54
82
 
55
83
  def finished
56
- @channel.send_message_to_server('finished')
84
+ @finished_promise.value!
57
85
  end
58
86
 
59
87
  def body
@@ -69,5 +97,20 @@ module Playwright
69
97
  def frame
70
98
  @request.frame
71
99
  end
100
+
101
+ private def sizes
102
+ resp = @channel.send_message_to_server('sizes')
103
+
104
+ {
105
+ requestBodySize: resp['requestBodySize'],
106
+ requestHeadersSize: resp['requestHeadersSize'],
107
+ responseBodySize: resp['responseBodySize'],
108
+ responseHeadersSize: resp['responseHeadersSize'],
109
+ }
110
+ end
111
+
112
+ private def mark_as_finished
113
+ @finished_promise.fulfill(nil)
114
+ end
72
115
  end
73
116
  end