playwright-ruby-client 0.0.3 → 0.0.8

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +119 -12
  3. data/docs/api_coverage.md +354 -0
  4. data/lib/playwright.rb +8 -0
  5. data/lib/playwright/channel_owner.rb +16 -2
  6. data/lib/playwright/channel_owners/android.rb +10 -1
  7. data/lib/playwright/channel_owners/android_device.rb +163 -0
  8. data/lib/playwright/channel_owners/browser.rb +22 -29
  9. data/lib/playwright/channel_owners/browser_context.rb +43 -0
  10. data/lib/playwright/channel_owners/console_message.rb +21 -0
  11. data/lib/playwright/channel_owners/element_handle.rb +314 -0
  12. data/lib/playwright/channel_owners/frame.rb +466 -7
  13. data/lib/playwright/channel_owners/js_handle.rb +55 -0
  14. data/lib/playwright/channel_owners/page.rb +353 -5
  15. data/lib/playwright/channel_owners/request.rb +90 -0
  16. data/lib/playwright/channel_owners/webkit_browser.rb +1 -1
  17. data/lib/playwright/connection.rb +15 -14
  18. data/lib/playwright/errors.rb +1 -1
  19. data/lib/playwright/event_emitter.rb +13 -0
  20. data/lib/playwright/input_files.rb +42 -0
  21. data/lib/playwright/input_type.rb +19 -0
  22. data/lib/playwright/input_types/android_input.rb +19 -0
  23. data/lib/playwright/input_types/keyboard.rb +32 -0
  24. data/lib/playwright/input_types/mouse.rb +4 -0
  25. data/lib/playwright/input_types/touchscreen.rb +4 -0
  26. data/lib/playwright/javascript.rb +13 -0
  27. data/lib/playwright/javascript/expression.rb +67 -0
  28. data/lib/playwright/javascript/function.rb +67 -0
  29. data/lib/playwright/javascript/value_parser.rb +75 -0
  30. data/lib/playwright/javascript/value_serializer.rb +54 -0
  31. data/lib/playwright/playwright_api.rb +45 -25
  32. data/lib/playwright/select_option_values.rb +32 -0
  33. data/lib/playwright/timeout_settings.rb +19 -0
  34. data/lib/playwright/url_matcher.rb +19 -0
  35. data/lib/playwright/utils.rb +37 -0
  36. data/lib/playwright/version.rb +1 -1
  37. data/lib/playwright/wait_helper.rb +73 -0
  38. data/lib/playwright_api/accessibility.rb +60 -6
  39. data/lib/playwright_api/android.rb +33 -0
  40. data/lib/playwright_api/android_device.rb +78 -0
  41. data/lib/playwright_api/android_input.rb +25 -0
  42. data/lib/playwright_api/binding_call.rb +18 -0
  43. data/lib/playwright_api/browser.rb +136 -44
  44. data/lib/playwright_api/browser_context.rb +378 -51
  45. data/lib/playwright_api/browser_type.rb +137 -55
  46. data/lib/playwright_api/cdp_session.rb +32 -7
  47. data/lib/playwright_api/chromium_browser_context.rb +31 -0
  48. data/lib/playwright_api/console_message.rb +27 -7
  49. data/lib/playwright_api/dialog.rb +47 -3
  50. data/lib/playwright_api/download.rb +29 -5
  51. data/lib/playwright_api/element_handle.rb +429 -143
  52. data/lib/playwright_api/file_chooser.rb +13 -2
  53. data/lib/playwright_api/frame.rb +633 -179
  54. data/lib/playwright_api/js_handle.rb +97 -17
  55. data/lib/playwright_api/keyboard.rb +152 -24
  56. data/lib/playwright_api/mouse.rb +28 -3
  57. data/lib/playwright_api/page.rb +1183 -317
  58. data/lib/playwright_api/playwright.rb +174 -13
  59. data/lib/playwright_api/request.rb +115 -30
  60. data/lib/playwright_api/response.rb +22 -3
  61. data/lib/playwright_api/route.rb +63 -4
  62. data/lib/playwright_api/selectors.rb +29 -7
  63. data/lib/playwright_api/touchscreen.rb +2 -1
  64. data/lib/playwright_api/video.rb +11 -1
  65. data/lib/playwright_api/web_socket.rb +5 -5
  66. data/lib/playwright_api/worker.rb +29 -5
  67. data/playwright.gemspec +3 -0
  68. metadata +68 -2
@@ -0,0 +1,54 @@
1
+ module Playwright
2
+ module JavaScript
3
+ class ValueSerializer
4
+ def initialize(ruby_value)
5
+ @value = ruby_value
6
+ end
7
+
8
+ # @return [Hash]
9
+ def serialize
10
+ @handles = []
11
+ { value: serialize_value(@value), handles: @handles }
12
+ end
13
+
14
+ # ref: https://github.com/microsoft/playwright/blob/b45905ae3f1a066a8ecb358035ce745ddd21cf3a/src/protocol/serializers.ts#L84
15
+ # ref: https://github.com/microsoft/playwright-python/blob/25a99d53e00e35365cf5113b9525272628c0e65f/playwright/_impl/_js_handle.py#L99
16
+ private def serialize_value(value)
17
+ case value
18
+ when ChannelOwners::JSHandle
19
+ index = @handles.count
20
+ @handles << value.channel
21
+ { h: index }
22
+ when nil
23
+ { v: 'undefined' }
24
+ when Float::NAN
25
+ { v: 'NaN'}
26
+ when Float::INFINITY
27
+ { v: 'Infinity' }
28
+ when -Float::INFINITY
29
+ { v: '-Infinity' }
30
+ when true, false
31
+ { b: value }
32
+ when Numeric
33
+ { n: value }
34
+ when String
35
+ { s: value }
36
+ when Time
37
+ require 'time'
38
+ { d: value.utc.iso8601 }
39
+ when Regexp
40
+ flags = []
41
+ flags << 'ms' if (value.options & Regexp::MULTILINE) != 0
42
+ flags << 'i' if (value.options & Regexp::IGNORECASE) != 0
43
+ { r: { p: value.source, f: flags.join('') } }
44
+ when Array
45
+ { a: value.map { |v| serialize_value(v) } }
46
+ when Hash
47
+ { o: value.map { |key, v| { k: key, v: serialize_value(v) } } }
48
+ else
49
+ raise ArgumentError.new("Unexpected value: #{value}")
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -7,66 +7,86 @@ module Playwright
7
7
  # @param channel_owner [ChannelOwner]
8
8
  # @note Intended for internal use only.
9
9
  def self.from_channel_owner(channel_owner)
10
- Factory.new(channel_owner).create
10
+ Factory.new(channel_owner, 'ChannelOwners').create
11
11
  end
12
12
 
13
- private
14
-
15
13
  class Factory
16
- def initialize(channel_owner)
17
- channel_owner_class_name = channel_owner.class.name
18
- raise "#{channel_owner_class_name} is not a channel owner" unless channel_owner_class_name.include?('::ChannelOwners::')
14
+ def initialize(impl, module_name)
15
+ impl_class_name = impl.class.name
16
+ unless impl_class_name.include?("::#{module_name}::")
17
+ raise "#{impl_class_name} is not #{module_name}"
18
+ end
19
19
 
20
- @channel_owner = channel_owner
20
+ @impl = impl
21
+ @module_name = module_name
21
22
  end
22
23
 
23
24
  def create
24
- api_class = detect_class_for(@channel_owner.class)
25
+ api_class = detect_class_for(@impl.class)
25
26
  if api_class
26
- api_class.new(@channel_owner)
27
+ api_class.new(@impl)
27
28
  else
28
- raise NotImplementedError.new("Playwright::#{expected_class_name_for(@channel_owner.class)} is not implemented")
29
+ raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
29
30
  end
30
31
  end
31
32
 
32
33
  private
33
34
 
34
35
  def expected_class_name_for(klass)
35
- klass.name.split('::ChannelOwners::').last
36
+ klass.name.split("::#{@module_name}::").last
37
+ end
38
+
39
+ def superclass_exist?(klass)
40
+ ![::Playwright::ChannelOwner, ::Playwright::InputType, Object].include?(klass.superclass)
36
41
  end
37
42
 
38
43
  def detect_class_for(klass)
39
44
  class_name = expected_class_name_for(klass)
40
45
  if ::Playwright.const_defined?(class_name)
41
46
  ::Playwright.const_get(class_name)
47
+ elsif superclass_exist?(klass)
48
+ detect_class_for(klass.superclass)
42
49
  else
43
- if [::Playwright::ChannelOwner, Object].include?(klass.superclass)
44
- nil
45
- else
46
- detect_class_for(klass.superclass)
47
- end
50
+ nil
48
51
  end
49
52
  end
50
53
  end
51
54
 
52
- # @param channel_owner [Playwright::ChannelOwner]
53
- def initialize(channel_owner)
54
- @channel_owner = channel_owner
55
+ # @param impl [Playwright::ChannelOwner|Playwright::InputType]
56
+ def initialize(impl)
57
+ @impl = impl
58
+ end
59
+
60
+ def ==(other)
61
+ @impl.to_s == other.instance_variable_get(:'@impl').to_s
55
62
  end
56
63
 
57
64
  # @param block [Proc]
58
- def wrap_block_call(block)
65
+ private def wrap_block_call(block)
66
+ return nil unless block.is_a?(Proc)
67
+
59
68
  -> (*args) {
60
- wrapped_args = args.map { |arg| wrap_channel_owner(arg) }
69
+ wrapped_args = args.map { |arg| wrap_impl(arg) }
61
70
  block.call(*wrapped_args)
62
71
  }
63
72
  end
64
73
 
65
- def wrap_channel_owner(object)
66
- if object.is_a?(ChannelOwner)
74
+ private def wrap_impl(object)
75
+ case object
76
+ when ChannelOwner
67
77
  PlaywrightApi.from_channel_owner(object)
68
- elsif object.is_a?(Array)
69
- object.map { |obj| wrap_channel_owner(obj) }
78
+ when InputType
79
+ Factory.new(object, 'InputTypes').create
80
+ when Array
81
+ object.map { |obj| wrap_impl(obj) }
82
+ else
83
+ object
84
+ end
85
+ end
86
+
87
+ private def unwrap_impl(object)
88
+ if object.is_a?(PlaywrightApi)
89
+ object.instance_variable_get(:@impl)
70
90
  else
71
91
  object
72
92
  end
@@ -0,0 +1,32 @@
1
+ module Playwright
2
+ class SelectOptionValues
3
+ def initialize(values)
4
+ @params = convert(values)
5
+ end
6
+
7
+ # @return [Hash]
8
+ def as_params
9
+ @params
10
+ end
11
+
12
+ private def convert(values)
13
+ return {} unless values
14
+ return convert([values]) unless values.is_a?('Array')
15
+ return {} if values.empty?
16
+ values.each_with_index do |value, index|
17
+ unless values
18
+ raise ArgumentError.new("options[#{index}]: expected object, got null")
19
+ end
20
+ end
21
+
22
+ case values.first
23
+ when ElementHandle
24
+ { elements: values.map(&:channel) }
25
+ when String
26
+ { options: values.map { |value| { value: value } } }
27
+ else
28
+ { options: values }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module Playwright
2
+ class TimeoutSettings
3
+ DEFAULT_TIMEOUT = 30000
4
+
5
+ def initialize(parent = nil)
6
+ @parent = parent
7
+ end
8
+
9
+ attr_writer :default_timeout, :default_navigation_timeout
10
+
11
+ def navigation_timeout
12
+ @default_navigation_timeout || @default_timeout || @parent&.navigation_timeout || DEFAULT_TIMEOUT
13
+ end
14
+
15
+ def timeout
16
+ @default_timeout || @parent&.timeout || DEFAULT_TIMEOUT
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Playwright
2
+ class UrlMatcher
3
+ # @param url [String|Regexp]
4
+ def initialize(url)
5
+ @url = url
6
+ end
7
+
8
+ def match?(target_url)
9
+ case @url
10
+ when String
11
+ @url == target_url
12
+ when Regexp
13
+ @url.match?(target_url)
14
+ else
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ module Playwright
2
+ module Utils
3
+ module PrepareBrowserContextOptions
4
+ # @see https://github.com/microsoft/playwright/blob/5a2cfdbd47ed3c3deff77bb73e5fac34241f649d/src/client/browserContext.ts#L265
5
+ private def prepare_browser_context_options(params)
6
+ if params[:viewport] == 0
7
+ params.delete(:viewport)
8
+ params[:noDefaultViewport] = true
9
+ end
10
+ if params[:extraHTTPHeaders]
11
+ # TODO
12
+ end
13
+ if params[:storageState].is_a?(String)
14
+ params[:storageState] = JSON.parse(File.read(params[:storageState]))
15
+ end
16
+
17
+ params
18
+ end
19
+ end
20
+
21
+ module Errors
22
+ module SafeCloseError
23
+ # @param err [Exception]
24
+ private def safe_close_error?(err)
25
+ return true if err.is_a?(Transport::AlreadyDisconnectedError)
26
+
27
+ [
28
+ 'Browser has been closed',
29
+ 'Target page, context or browser has been closed',
30
+ ].any? do |closed_message|
31
+ err.message.end_with?(closed_message)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.8'
5
5
  end
@@ -0,0 +1,73 @@
1
+ module Playwright
2
+ # ref: https://github.com/microsoft/playwright-python/blob/30946ae3099d51f9b7f355f9ae7e8c04d748ce36/playwright/_impl/_wait_helper.py
3
+ # ref: https://github.com/microsoft/playwright/blob/01fb3a6045cbdb4b5bcba0809faed85bd917ab87/src/client/waiter.ts#L21
4
+ class WaitHelper
5
+ def initialize
6
+ @promise = Concurrent::Promises.resolvable_future
7
+ @registered_listeners = Set.new
8
+ end
9
+
10
+ def reject_on_event(emitter, event, error, predicate: nil)
11
+ listener = -> (*args) {
12
+ if !predicate || predicate.call(*args)
13
+ reject(error)
14
+ end
15
+ }
16
+ emitter.on(event, listener)
17
+ @registered_listeners << [emitter, event, listener]
18
+
19
+ self
20
+ end
21
+
22
+ def reject_on_timeout(timeout_ms, message)
23
+ return if timeout_ms <= 0
24
+
25
+ Concurrent::Promises.schedule(timeout_ms / 1000.0) {
26
+ reject(TimeoutError.new(message))
27
+ }
28
+
29
+ self
30
+ end
31
+
32
+ # @param [Playwright::EventEmitter]
33
+ # @param
34
+ def wait_for_event(emitter, event, predicate: nil)
35
+ listener = -> (*args) {
36
+ begin
37
+ if !predicate || predicate.call(*args)
38
+ fulfill(*args)
39
+ end
40
+ rescue => err
41
+ reject(err)
42
+ end
43
+ }
44
+ emitter.on(event, listener)
45
+ @registered_listeners << [emitter, event, listener]
46
+
47
+ self
48
+ end
49
+
50
+ attr_reader :promise
51
+
52
+ private def cleanup
53
+ @registered_listeners.each do |emitter, event, listener|
54
+ emitter.off(event, listener)
55
+ end
56
+ @registered_listeners.clear
57
+ end
58
+
59
+ private def fulfill(*args)
60
+ cleanup
61
+ unless @promise.resolved?
62
+ @promise.fulfill(args.first)
63
+ end
64
+ end
65
+
66
+ private def reject(error)
67
+ cleanup
68
+ unless @promise.resolved?
69
+ @promise.reject(error)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,21 +1,45 @@
1
1
  module Playwright
2
- # The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by assistive technology such as screen readers or switches.
3
- # Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might have wildly different output.
4
- # Blink - Chromium's rendering engine - has a concept of "accessibility tree", which is then translated into different platform-specific APIs. Accessibility namespace gives users access to the Blink Accessibility Tree.
5
- # Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing only the "interesting" nodes of the tree.
2
+ # The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by
3
+ # assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Screen_reader) or
4
+ # [switches](https://en.wikipedia.org/wiki/Switch_access).
5
+ #
6
+ # Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
7
+ # have wildly different output.
8
+ #
9
+ # Rendering engines of Chromium, Firefox and Webkit have a concept of "accessibility tree", which is then translated into
10
+ # different platform-specific APIs. Accessibility namespace gives access to this Accessibility Tree.
11
+ #
12
+ # Most of the accessibility tree gets filtered out when converting from internal browser AX Tree to Platform-specific
13
+ # AX-Tree or by assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing
14
+ # only the "interesting" nodes of the tree.
6
15
  class Accessibility < PlaywrightApi
7
16
 
8
- # Captures the current state of the accessibility tree. The returned object represents the root accessible node of the page.
17
+ # Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
18
+ # page.
9
19
  #
10
- # **NOTE** The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers. Playwright will discard them as well for an easier to process tree, unless `interestingOnly` is set to `false`.
20
+ # > NOTE: The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
21
+ # Playwright will discard them as well for an easier to process tree, unless `interestingOnly` is set to `false`.
11
22
  #
12
23
  # An example of dumping the entire accessibility tree:
24
+ #
13
25
  #
14
26
  # ```js
15
27
  # const snapshot = await page.accessibility.snapshot();
16
28
  # console.log(snapshot);
17
29
  # ```
30
+ #
31
+ # ```python async
32
+ # snapshot = await page.accessibility.snapshot()
33
+ # print(snapshot)
34
+ # ```
35
+ #
36
+ # ```python sync
37
+ # snapshot = page.accessibility.snapshot()
38
+ # print(snapshot)
39
+ # ```
40
+ #
18
41
  # An example of logging the focused node's name:
42
+ #
19
43
  #
20
44
  # ```js
21
45
  # const snapshot = await page.accessibility.snapshot();
@@ -32,6 +56,36 @@ module Playwright
32
56
  # return null;
33
57
  # }
34
58
  # ```
59
+ #
60
+ # ```python async
61
+ # def find_focused_node(node):
62
+ # if (node.get("focused"))
63
+ # return node
64
+ # for child in (node.get("children") or []):
65
+ # found_node = find_focused_node(child)
66
+ # return found_node
67
+ # return None
68
+ #
69
+ # snapshot = await page.accessibility.snapshot()
70
+ # node = find_focused_node(snapshot)
71
+ # if node:
72
+ # print(node["name"])
73
+ # ```
74
+ #
75
+ # ```python sync
76
+ # def find_focused_node(node):
77
+ # if (node.get("focused"))
78
+ # return node
79
+ # for child in (node.get("children") or []):
80
+ # found_node = find_focused_node(child)
81
+ # return found_node
82
+ # return None
83
+ #
84
+ # snapshot = page.accessibility.snapshot()
85
+ # node = find_focused_node(snapshot)
86
+ # if node:
87
+ # print(node["name"])
88
+ # ```
35
89
  def snapshot(interestingOnly: nil, root: nil)
36
90
  raise NotImplementedError.new('snapshot is not implemented yet.')
37
91
  end