playwright-ruby-client 1.22.0 → 1.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/accessibility.md +8 -0
  3. data/documentation/docs/api/api_request_context.md +8 -8
  4. data/documentation/docs/api/browser.md +30 -5
  5. data/documentation/docs/api/browser_context.md +20 -7
  6. data/documentation/docs/api/browser_type.md +4 -0
  7. data/documentation/docs/api/download.md +1 -1
  8. data/documentation/docs/api/element_handle.md +1 -6
  9. data/documentation/docs/api/experimental/android_device.md +4 -0
  10. data/documentation/docs/api/file_chooser.md +1 -1
  11. data/documentation/docs/api/locator.md +15 -7
  12. data/documentation/docs/api/page.md +21 -4
  13. data/documentation/docs/api/request.md +3 -1
  14. data/documentation/docs/api/response.md +12 -1
  15. data/documentation/docs/api/route.md +65 -0
  16. data/documentation/docs/api/selectors.md +2 -2
  17. data/documentation/docs/api/tracing.md +1 -1
  18. data/documentation/docs/include/api_coverage.md +5 -3
  19. data/documentation/package.json +4 -4
  20. data/documentation/yarn.lock +1876 -1304
  21. data/lib/playwright/channel.rb +1 -3
  22. data/lib/playwright/channel_owner.rb +13 -1
  23. data/lib/playwright/channel_owners/browser.rb +13 -0
  24. data/lib/playwright/channel_owners/browser_context.rb +82 -14
  25. data/lib/playwright/channel_owners/browser_type.rb +4 -0
  26. data/lib/playwright/channel_owners/frame.rb +16 -2
  27. data/lib/playwright/channel_owners/local_utils.rb +29 -0
  28. data/lib/playwright/channel_owners/page.rb +43 -15
  29. data/lib/playwright/channel_owners/request.rb +31 -6
  30. data/lib/playwright/channel_owners/response.rb +6 -0
  31. data/lib/playwright/channel_owners/route.rb +104 -45
  32. data/lib/playwright/connection.rb +20 -9
  33. data/lib/playwright/har_router.rb +82 -0
  34. data/lib/playwright/http_headers.rb +1 -1
  35. data/lib/playwright/javascript/regex.rb +23 -0
  36. data/lib/playwright/javascript/value_parser.rb +4 -0
  37. data/lib/playwright/javascript/value_serializer.rb +5 -4
  38. data/lib/playwright/javascript.rb +1 -0
  39. data/lib/playwright/locator_impl.rb +3 -5
  40. data/lib/playwright/route_handler.rb +2 -6
  41. data/lib/playwright/utils.rb +31 -6
  42. data/lib/playwright/version.rb +2 -2
  43. data/lib/playwright.rb +2 -0
  44. data/lib/playwright_api/accessibility.rb +8 -0
  45. data/lib/playwright_api/android_device.rb +5 -1
  46. data/lib/playwright_api/api_request_context.rb +8 -8
  47. data/lib/playwright_api/browser.rb +28 -2
  48. data/lib/playwright_api/browser_context.rb +19 -9
  49. data/lib/playwright_api/browser_type.rb +8 -2
  50. data/lib/playwright_api/download.rb +1 -1
  51. data/lib/playwright_api/element_handle.rb +1 -12
  52. data/lib/playwright_api/file_chooser.rb +1 -1
  53. data/lib/playwright_api/locator.rb +13 -13
  54. data/lib/playwright_api/page.rb +19 -6
  55. data/lib/playwright_api/request.rb +8 -1
  56. data/lib/playwright_api/response.rb +14 -1
  57. data/lib/playwright_api/route.rb +61 -2
  58. data/lib/playwright_api/selectors.rb +1 -1
  59. data/lib/playwright_api/tracing.rb +1 -1
  60. metadata +5 -4
  61. data/lib/playwright_api/local_utils.rb +0 -9
@@ -37,13 +37,11 @@ module Playwright
37
37
 
38
38
  # @param method [String]
39
39
  # @param params [Hash]
40
- # @returns nil
40
+ # @returns [Concurrent::Promises::Future]
41
41
  def async_send_message_to_server(method, params = {})
42
42
  with_logging do |metadata|
43
43
  @connection.async_send_message_to_server(@guid, method, params, metadata: metadata)
44
44
  end
45
-
46
- nil
47
45
  end
48
46
 
49
47
  private def with_logging(&block)
@@ -42,10 +42,16 @@ module Playwright
42
42
 
43
43
  attr_reader :channel
44
44
 
45
+ private def adopt!(child)
46
+ unless child.is_a?(ChannelOwner)
47
+ raise ArgumentError.new("child must be a ChannelOwner: #{child.inspect}")
48
+ end
49
+ child.send(:update_parent, self)
50
+ end
51
+
45
52
  # used only from Connection. Not intended for public use. So keep private.
46
53
  private def dispose!
47
54
  # Clean up from parent and connection.
48
- @parent&.send(:delete_object_from_child, @guid)
49
55
  @connection.send(:delete_object_from_channel_owner, @guid)
50
56
 
51
57
  # Dispose all children.
@@ -65,6 +71,12 @@ module Playwright
65
71
  private def after_initialize
66
72
  end
67
73
 
74
+ private def update_parent(new_parent)
75
+ @parent.send(:delete_object_from_child, @guid)
76
+ new_parent.send(:update_object_from_child, @guid, self)
77
+ @parent = new_parent
78
+ end
79
+
68
80
  private def update_object_from_child(guid, child)
69
81
  @objects[guid] = child
70
82
  end
@@ -5,6 +5,7 @@ module Playwright
5
5
  include Utils::PrepareBrowserContextOptions
6
6
 
7
7
  private def after_initialize
8
+ @browser_type = @parent
8
9
  @connected = true
9
10
  @closed_or_closing = false
10
11
  @should_close_connection_on_close = false
@@ -17,6 +18,10 @@ module Playwright
17
18
  @contexts.to_a
18
19
  end
19
20
 
21
+ def browser_type
22
+ @browser_type
23
+ end
24
+
20
25
  def connected?
21
26
  @connected
22
27
  end
@@ -30,6 +35,7 @@ module Playwright
30
35
  @contexts << context
31
36
  context.browser = self
32
37
  context.options = params
38
+ context.send(:update_browser_type, @browser_type)
33
39
  return context unless block
34
40
 
35
41
  begin
@@ -54,6 +60,13 @@ module Playwright
54
60
  end
55
61
  end
56
62
 
63
+ private def update_browser_type(browser_type)
64
+ @browser_type = browser_type
65
+ @contexts.each do |context|
66
+ context.send(:update_browser_type, browser_type)
67
+ end
68
+ end
69
+
57
70
  def close
58
71
  return if @closed_or_closing
59
72
  @closed_or_closing = true
@@ -2,6 +2,8 @@ module Playwright
2
2
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_browser_context.py
3
3
  define_channel_owner :BrowserContext do
4
4
  include Utils::Errors::SafeCloseError
5
+ include Utils::PrepareBrowserContextOptions
6
+
5
7
  attr_accessor :browser
6
8
  attr_writer :owner_page, :options
7
9
  attr_reader :tracing, :request
@@ -16,13 +18,18 @@ module Playwright
16
18
  @owner_page = nil
17
19
 
18
20
  @tracing = ChannelOwners::Tracing.from(@initializer['tracing'])
19
- @request = ChannelOwners::APIRequestContext.from(@initializer['APIRequestContext'])
21
+ @request = ChannelOwners::APIRequestContext.from(@initializer['requestContext'])
22
+ @har_recorders = {}
20
23
 
21
24
  @channel.on('bindingCall', ->(params) { on_binding(ChannelOwners::BindingCall.from(params['binding'])) })
22
25
  @channel.once('close', ->(_) { on_close })
23
26
  @channel.on('page', ->(params) { on_page(ChannelOwners::Page.from(params['page']) )})
24
27
  @channel.on('route', ->(params) {
25
- on_route(ChannelOwners::Route.from(params['route']), ChannelOwners::Request.from(params['request']))
28
+ Concurrent::Promises.future {
29
+ on_route(ChannelOwners::Route.from(params['route']), ChannelOwners::Request.from(params['request']))
30
+ }.rescue do |err|
31
+ puts err, err.backtrace
32
+ end
26
33
  })
27
34
  @channel.on('backgroundPage', ->(params) {
28
35
  on_background_page(ChannelOwners::Page.from(params['page']))
@@ -55,6 +62,16 @@ module Playwright
55
62
  @closed_promise = Concurrent::Promises.resolvable_future
56
63
  end
57
64
 
65
+ private def update_browser_type(browser_type)
66
+ @browser_type = browser_type
67
+ if @options[:recordHar]
68
+ @har_recorders[''] = {
69
+ path: @options[:recordHar][:path],
70
+ content: @options[:recordHar][:content]
71
+ }
72
+ end
73
+ end
74
+
58
75
  private def on_page(page)
59
76
  @pages << page
60
77
  emit(Events::BrowserContext::Page, page)
@@ -73,19 +90,29 @@ module Playwright
73
90
  wrapped_route = PlaywrightApi.wrap(route)
74
91
  wrapped_request = PlaywrightApi.wrap(request)
75
92
 
76
- handler_entry = @routes.find do |entry|
77
- entry.match?(request.url)
93
+ handled = @routes.any? do |handler_entry|
94
+ next false unless handler_entry.match?(request.url)
95
+
96
+ promise = Concurrent::Promises.resolvable_future
97
+ route.send(:set_handling_future, promise)
98
+
99
+ promise_handled = Concurrent::Promises.zip(
100
+ promise,
101
+ handler_entry.async_handle(wrapped_route, wrapped_request)
102
+ ).value!.first
103
+
104
+ promise_handled
78
105
  end
79
106
 
80
- if handler_entry
81
- handler_entry.async_handle(wrapped_route, wrapped_request)
107
+ @routes.reject!(&:expired?)
108
+ if @routes.count == 0
109
+ @channel.async_send_message_to_server('setNetworkInterceptionEnabled', enabled: false)
110
+ end
82
111
 
83
- @routes.reject!(&:expired?)
84
- if @routes.count == 0
85
- @channel.async_send_message_to_server('setNetworkInterceptionEnabled', enabled: false)
112
+ unless handled
113
+ route.send(:async_continue_route).rescue do |err|
114
+ puts err, err.backtrace
86
115
  end
87
- else
88
- route.continue
89
116
  end
90
117
  end
91
118
 
@@ -266,6 +293,37 @@ module Playwright
266
293
  end
267
294
  end
268
295
 
296
+ private def record_into_har(har, page, notFound:, url:)
297
+ params = {
298
+ options: prepare_record_har_options(
299
+ record_har_path: har,
300
+ record_har_content: "attach",
301
+ record_har_mode: "minimal",
302
+ record_har_url_filter: url,
303
+ )
304
+ }
305
+ if page
306
+ params[:page] = page.channel
307
+ end
308
+ har_id = @channel.send_message_to_server('harStart', params)
309
+ @har_recorders[har_id] = { path: har, content: 'attach' }
310
+ end
311
+
312
+ def route_from_har(har, notFound: nil, update: nil, url: nil)
313
+ if update
314
+ record_into_har(har, nil, notFound: notFound, url: url)
315
+ return
316
+ end
317
+
318
+ router = HarRouter.create(
319
+ @connection.local_utils,
320
+ har.to_s,
321
+ notFound || "abort",
322
+ url_match: url,
323
+ )
324
+ router.add_context_route(self)
325
+ end
326
+
269
327
  def expect_event(event, predicate: nil, timeout: nil, &block)
270
328
  wait_helper = WaitHelper.new
271
329
  wait_helper.reject_on_timeout(timeout || @timeout_settings.timeout, "Timeout while waiting for event \"#{event}\"")
@@ -283,9 +341,19 @@ module Playwright
283
341
  end
284
342
 
285
343
  def close
286
- if @options && @options.key?(:recordHar)
287
- har = ChannelOwners::Artifact.from(@channel.send_message_to_server('harExport'))
288
- har.save_as(@options[:recordHar][:path])
344
+ @har_recorders.each do |har_id, params|
345
+ har = ChannelOwners::Artifact.from(@channel.send_message_to_server('harExport', harId: har_id))
346
+ # Server side will compress artifact if content is attach or if file is .zip.
347
+ compressed = params[:content] == "attach" || params[:path].end_with?('.zip')
348
+ need_comppressed = params[:path].end_with?('.zip')
349
+ if compressed && !need_comppressed
350
+ tmp_path = "#{params[:path]}.tmp"
351
+ har.save_as(tmp_path)
352
+ @connection.local_utils.har_unzip(tmp_path, params[:path])
353
+ else
354
+ har.save_as(params[:path])
355
+ end
356
+
289
357
  har.delete
290
358
  end
291
359
  @channel.send_message_to_server('close')
@@ -13,6 +13,7 @@ module Playwright
13
13
  def launch(options, &block)
14
14
  resp = @channel.send_message_to_server('launch', options.compact)
15
15
  browser = ChannelOwners::Browser.from(resp)
16
+ browser.send(:update_browser_type, self)
16
17
  return browser unless block
17
18
 
18
19
  begin
@@ -29,6 +30,8 @@ module Playwright
29
30
 
30
31
  resp = @channel.send_message_to_server('launchPersistentContext', params.compact)
31
32
  context = ChannelOwners::Browser.from(resp)
33
+ context.options = params
34
+ context.send(:update_browser_type, self)
32
35
  return context unless block
33
36
 
34
37
  begin
@@ -54,6 +57,7 @@ module Playwright
54
57
 
55
58
  result = @channel.send_message_to_server_result('connectOverCDP', params)
56
59
  browser = ChannelOwners::Browser.from(result['browser'])
60
+ browser.send(:update_browser_type, self)
57
61
 
58
62
  if result['defaultContext']
59
63
  context = ChannelOwners::BrowserContext.from(result['defaultContext'])
@@ -25,11 +25,25 @@ module Playwright
25
25
  private def on_load_state(add:, remove:)
26
26
  if add
27
27
  @load_states << add
28
- @event_emitter.emit('loadstate', add)
28
+
29
+ # Original JS version of Playwright emit event here.
30
+ # @event_emitter.emit('loadstate', add)
29
31
  end
30
32
  if remove
31
33
  @load_states.delete(remove)
32
34
  end
35
+ unless @parent_frame
36
+ if add == 'load'
37
+ @page&.emit(Events::Page::Load, @page)
38
+ elsif add == 'domcontentloaded'
39
+ @page&.emit(Events::Page::DOMContentLoaded, @page)
40
+ end
41
+ end
42
+
43
+ # emit to waitForLoadState(load) listeners explicitly after waitForEvent(load) listeners
44
+ if add
45
+ @event_emitter.emit('loadstate', add)
46
+ end
33
47
  end
34
48
 
35
49
  private def on_frame_navigated(event)
@@ -38,7 +52,7 @@ module Playwright
38
52
  @event_emitter.emit('navigated', event)
39
53
 
40
54
  unless event['error']
41
- @page&.emit('framenavigated', self)
55
+ @page&.emit(Events::Page::FrameNavigated, self)
42
56
  end
43
57
  end
44
58
 
@@ -10,5 +10,34 @@ module Playwright
10
10
  @channel.send_message_to_server('zip', params)
11
11
  nil
12
12
  end
13
+
14
+
15
+ # @param file [String]
16
+ # @return [String] har ID
17
+ def har_open(file)
18
+ @channel.send_message_to_server('harOpen', file: file)
19
+ end
20
+
21
+ def har_lookup(har_id:, url:, method:, headers:, is_navigation_request:, post_data: nil)
22
+ params = {
23
+ harId: har_id,
24
+ url: url,
25
+ method: method,
26
+ headers: headers,
27
+ postData: post_data,
28
+ isNavigationRequest: is_navigation_request,
29
+ }.compact
30
+
31
+ @channel.send_message_to_server_result('harLookup', params)
32
+ end
33
+
34
+ # @param har_id [String]
35
+ def async_har_close(har_id)
36
+ @channel.async_send_message_to_server('harClose', harId: har_id)
37
+ end
38
+
39
+ def har_unzip(zip_file, har_file)
40
+ @channel.send_message_to_server('harUnzip', zipFile: zip_file, harFile: har_file)
41
+ end
13
42
  end
14
43
  end
@@ -39,7 +39,6 @@ module Playwright
39
39
  })
40
40
  @channel.on('crash', ->(_) { emit(Events::Page::Crash) })
41
41
  @channel.on('dialog', method(:on_dialog))
42
- @channel.on('domcontentloaded', ->(_) { emit(Events::Page::DOMContentLoaded) })
43
42
  @channel.on('download', method(:on_download))
44
43
  @channel.on('fileChooser', ->(params) {
45
44
  chooser = FileChooserImpl.new(
@@ -54,12 +53,15 @@ module Playwright
54
53
  @channel.on('frameDetached', ->(params) {
55
54
  on_frame_detached(ChannelOwners::Frame.from(params['frame']))
56
55
  })
57
- @channel.on('load', ->(_) { emit(Events::Page::Load) })
58
56
  @channel.on('pageError', ->(params) {
59
57
  emit(Events::Page::PageError, Error.parse(params['error']['error']))
60
58
  })
61
59
  @channel.on('route', ->(params) {
62
- on_route(ChannelOwners::Route.from(params['route']), ChannelOwners::Request.from(params['request']))
60
+ Concurrent::Promises.future {
61
+ on_route(ChannelOwners::Route.from(params['route']), ChannelOwners::Request.from(params['request']))
62
+ }.rescue do |err|
63
+ puts err, err.backtrace
64
+ end
63
65
  })
64
66
  @channel.on('video', method(:on_video))
65
67
  @channel.on('webSocket', ->(params) {
@@ -98,18 +100,26 @@ module Playwright
98
100
  wrapped_route = PlaywrightApi.wrap(route)
99
101
  wrapped_request = PlaywrightApi.wrap(request)
100
102
 
101
- handler_entry = @routes.find do |entry|
102
- entry.match?(request.url)
103
+ handled = @routes.any? do |handler_entry|
104
+ next false unless handler_entry.match?(request.url)
105
+
106
+ promise = Concurrent::Promises.resolvable_future
107
+ route.send(:set_handling_future, promise)
108
+
109
+ promise_handled = Concurrent::Promises.zip(
110
+ promise,
111
+ handler_entry.async_handle(wrapped_route, wrapped_request)
112
+ ).value!.first
113
+
114
+ promise_handled
103
115
  end
104
116
 
105
- if handler_entry
106
- handler_entry.async_handle(wrapped_route, wrapped_request)
117
+ @routes.reject!(&:expired?)
118
+ if @routes.count == 0
119
+ @channel.async_send_message_to_server('setNetworkInterceptionEnabled', enabled: false)
120
+ end
107
121
 
108
- @routes.reject!(&:expired?)
109
- if @routes.count == 0
110
- @channel.async_send_message_to_server('setNetworkInterceptionEnabled', enabled: false)
111
- end
112
- else
122
+ unless handled
113
123
  @browser_context.send(:on_route, route, request)
114
124
  end
115
125
  end
@@ -407,6 +417,21 @@ module Playwright
407
417
  end
408
418
  end
409
419
 
420
+ def route_from_har(har, notFound: nil, update: nil, url: nil)
421
+ if update
422
+ @browser_context.send(:record_into_har, har, self, notFound: notFound, url: url)
423
+ return
424
+ end
425
+
426
+ router = HarRouter.create(
427
+ @connection.local_utils,
428
+ har.to_s,
429
+ notFound || "abort",
430
+ url_match: url,
431
+ )
432
+ router.add_page_route(self)
433
+ end
434
+
410
435
  def screenshot(
411
436
  animations: nil,
412
437
  caret: nil,
@@ -451,9 +476,12 @@ module Playwright
451
476
  end
452
477
 
453
478
  def close(runBeforeUnload: nil)
454
- options = { runBeforeUnload: runBeforeUnload }.compact
455
- @channel.send_message_to_server('close', options)
456
- @owned_context&.close
479
+ if @owned_context
480
+ @owned_context.close
481
+ else
482
+ options = { runBeforeUnload: runBeforeUnload }.compact
483
+ @channel.send_message_to_server('close', options)
484
+ end
457
485
  nil
458
486
  rescue => err
459
487
  raise unless safe_close_error?(err)
@@ -18,10 +18,24 @@ module Playwright
18
18
  responseStart: -1,
19
19
  responseEnd: -1,
20
20
  }
21
+ @fallback_overrides = {}
22
+ end
23
+
24
+ private def fallback_overrides
25
+ @fallback_overrides
26
+ end
27
+
28
+ def apply_fallback_overrides(overrides)
29
+ allowed_key = %i[url method headers postData]
30
+ overrides.each do |key, value|
31
+ raise ArgumentError.new("invalid key: #{key}") unless allowed_key.include?(key)
32
+ @fallback_overrides[key] = value
33
+ end
34
+ @fallback_overrides
21
35
  end
22
36
 
23
37
  def url
24
- @initializer['url']
38
+ @fallback_overrides[:url] || @initializer['url']
25
39
  end
26
40
 
27
41
  def resource_type
@@ -29,7 +43,7 @@ module Playwright
29
43
  end
30
44
 
31
45
  def method
32
- @initializer['method']
46
+ @fallback_overrides[:method] || @initializer['method']
33
47
  end
34
48
 
35
49
  def post_data
@@ -51,8 +65,11 @@ module Playwright
51
65
  end
52
66
 
53
67
  def post_data_buffer
54
- base64_content = @initializer['postData']
55
- if base64_content
68
+ if (override = @fallback_overrides[:postData])
69
+ return override
70
+ end
71
+
72
+ if (base64_content = @initializer['postData'])
56
73
  Base64.strict_decode64(base64_content)
57
74
  else
58
75
  nil
@@ -79,12 +96,20 @@ module Playwright
79
96
  attr_reader :redirected_from, :redirected_to, :timing
80
97
 
81
98
  def headers
82
- @provisional_headers.headers
99
+ if (override = @fallback_overrides[:headers])
100
+ RawHeaders.new(HttpHeaders.new(override).as_serialized).headers
101
+ else
102
+ @provisional_headers.headers
103
+ end
83
104
  end
84
105
 
85
106
  # @return [RawHeaders|nil]
86
107
  private def actual_headers
87
- @actual_headers ||= raw_request_headers
108
+ if (override = @fallback_overrides[:headers])
109
+ RawHeaders.new(HttpHeaders.new(override).as_serialized)
110
+ else
111
+ @actual_headers ||= raw_request_headers
112
+ end
88
113
  end
89
114
 
90
115
  private def raw_request_headers
@@ -52,6 +52,12 @@ module Playwright
52
52
  RawHeaders.new(@channel.send_message_to_server('rawResponseHeaders'))
53
53
  end
54
54
 
55
+ def from_service_worker
56
+ @initializer['fromServiceWorker']
57
+ end
58
+
59
+ alias_method :from_service_worker?, :from_service_worker
60
+
55
61
  def all_headers
56
62
  actual_headers.headers
57
63
  end