playwright-ruby-client 0.0.5 → 0.1.0

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +114 -2
  3. data/docs/api_coverage.md +351 -0
  4. data/lib/playwright.rb +7 -1
  5. data/lib/playwright/android_input_impl.rb +23 -0
  6. data/lib/playwright/api_implementation.rb +18 -0
  7. data/lib/playwright/channel.rb +7 -0
  8. data/lib/playwright/channel_owner.rb +3 -2
  9. data/lib/playwright/channel_owners/android.rb +10 -1
  10. data/lib/playwright/channel_owners/android_device.rb +163 -0
  11. data/lib/playwright/channel_owners/browser.rb +13 -13
  12. data/lib/playwright/channel_owners/browser_context.rb +9 -1
  13. data/lib/playwright/channel_owners/download.rb +27 -0
  14. data/lib/playwright/channel_owners/element_handle.rb +306 -0
  15. data/lib/playwright/channel_owners/frame.rb +371 -19
  16. data/lib/playwright/channel_owners/js_handle.rb +51 -0
  17. data/lib/playwright/channel_owners/page.rb +416 -19
  18. data/lib/playwright/channel_owners/request.rb +98 -0
  19. data/lib/playwright/channel_owners/webkit_browser.rb +1 -1
  20. data/lib/playwright/connection.rb +9 -6
  21. data/lib/playwright/errors.rb +2 -2
  22. data/lib/playwright/event_emitter.rb +8 -1
  23. data/lib/playwright/event_emitter_proxy.rb +49 -0
  24. data/lib/playwright/file_chooser_impl.rb +23 -0
  25. data/lib/playwright/http_headers.rb +20 -0
  26. data/lib/playwright/input_files.rb +42 -0
  27. data/lib/playwright/javascript/expression.rb +37 -0
  28. data/lib/playwright/javascript/function.rb +37 -0
  29. data/lib/playwright/javascript/value_parser.rb +1 -1
  30. data/lib/playwright/javascript/value_serializer.rb +11 -11
  31. data/lib/playwright/keyboard_impl.rb +36 -0
  32. data/lib/playwright/mouse_impl.rb +7 -0
  33. data/lib/playwright/playwright_api.rb +84 -29
  34. data/lib/playwright/select_option_values.rb +32 -0
  35. data/lib/playwright/timeout_settings.rb +2 -2
  36. data/lib/playwright/touchscreen_impl.rb +7 -0
  37. data/lib/playwright/url_matcher.rb +19 -0
  38. data/lib/playwright/utils.rb +18 -0
  39. data/lib/playwright/version.rb +1 -1
  40. data/lib/playwright/wait_helper.rb +1 -1
  41. data/lib/playwright_api/accessibility.rb +46 -6
  42. data/lib/playwright_api/android.rb +37 -0
  43. data/lib/playwright_api/android_device.rb +82 -0
  44. data/lib/playwright_api/android_input.rb +25 -0
  45. data/lib/playwright_api/binding_call.rb +10 -6
  46. data/lib/playwright_api/browser.rb +85 -18
  47. data/lib/playwright_api/browser_context.rb +269 -37
  48. data/lib/playwright_api/browser_type.rb +60 -11
  49. data/lib/playwright_api/cdp_session.rb +23 -1
  50. data/lib/playwright_api/chromium_browser_context.rb +18 -6
  51. data/lib/playwright_api/console_message.rb +14 -15
  52. data/lib/playwright_api/dialog.rb +48 -2
  53. data/lib/playwright_api/download.rb +47 -10
  54. data/lib/playwright_api/element_handle.rb +269 -110
  55. data/lib/playwright_api/file_chooser.rb +23 -7
  56. data/lib/playwright_api/frame.rb +439 -154
  57. data/lib/playwright_api/js_handle.rb +69 -24
  58. data/lib/playwright_api/keyboard.rb +99 -9
  59. data/lib/playwright_api/mouse.rb +22 -0
  60. data/lib/playwright_api/page.rb +856 -229
  61. data/lib/playwright_api/playwright.rb +108 -20
  62. data/lib/playwright_api/request.rb +77 -29
  63. data/lib/playwright_api/response.rb +10 -13
  64. data/lib/playwright_api/route.rb +49 -0
  65. data/lib/playwright_api/selectors.rb +20 -8
  66. data/lib/playwright_api/video.rb +8 -0
  67. data/lib/playwright_api/web_socket.rb +0 -8
  68. data/lib/playwright_api/worker.rb +25 -13
  69. data/playwright.gemspec +1 -0
  70. metadata +33 -2
@@ -0,0 +1,36 @@
1
+ module Playwright
2
+ define_api_implementation :KeyboardImpl do
3
+ def initialize(channel)
4
+ @channel = channel
5
+ end
6
+
7
+ def down(key)
8
+ @channel.send_message_to_server('keyboardDown', key: key)
9
+ nil
10
+ end
11
+
12
+ def up(key)
13
+ @channel.send_message_to_server('keyboardDown', key: key)
14
+ end
15
+
16
+ def insert_text(text)
17
+ @channel.send_message_to_server('keyboardInsertText', text: text)
18
+ end
19
+
20
+ def type(text, delay: nil)
21
+ params = {
22
+ text: text,
23
+ delay: delay,
24
+ }.compact
25
+ @channel.send_message_to_server('keyboardType', params)
26
+ end
27
+
28
+ def press(key, delay: nil)
29
+ params = {
30
+ key: key,
31
+ delay: delay,
32
+ }.compact
33
+ @channel.send_message_to_server('keyboardPress', params)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ module Playwright
2
+ define_api_implementation :MouseImpl do
3
+ def initialize(channel)
4
+ @channel = channel
5
+ end
6
+ end
7
+ end
@@ -1,36 +1,89 @@
1
1
  module Playwright
2
2
  class PlaywrightApi
3
- # Wrap ChannelOwner.
3
+ # Wrap ChannelOwner / ApiImplementation.
4
4
  # Playwright::ChannelOwners::XXXXX will be wrapped as Playwright::XXXXX
5
+ # Playwright::YYYYImpl will be wrapped as Playwright::YYYY
5
6
  # Playwright::XXXXX is automatically generated by development/generate_api
6
7
  #
7
- # @param channel_owner [ChannelOwner]
8
+ # @param channel_owner [ChannelOwner|ApiImplementation]
8
9
  # @note Intended for internal use only.
9
- def self.from_channel_owner(channel_owner)
10
- Factory.new(channel_owner).create
10
+ def self.wrap(channel_owner_or_api_implementation)
11
+ case channel_owner_or_api_implementation
12
+ when ChannelOwner
13
+ ChannelOwnerWrapper.new(channel_owner_or_api_implementation).wrap
14
+ when ApiImplementation
15
+ ApiImplementationWrapper.new(channel_owner_or_api_implementation).wrap
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
21
+ class ChannelOwnerWrapper
22
+ def initialize(impl)
23
+ impl_class_name = impl.class.name
24
+ unless impl_class_name.include?("::ChannelOwners::")
25
+ raise "#{impl_class_name} is not ChannelOwners"
26
+ end
27
+
28
+ @impl = impl
29
+ end
30
+
31
+ def wrap
32
+ api_class = detect_class_for(@impl.class)
33
+ if api_class
34
+ api_class.new(@impl)
35
+ else
36
+ raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def expected_class_name_for(klass)
43
+ klass.name.split("::ChannelOwners::").last
44
+ end
45
+
46
+ def superclass_exist?(klass)
47
+ ![::Playwright::ChannelOwner, Object].include?(klass.superclass)
48
+ end
49
+
50
+ def detect_class_for(klass)
51
+ class_name = expected_class_name_for(klass)
52
+ if ::Playwright.const_defined?(class_name)
53
+ ::Playwright.const_get(class_name)
54
+ elsif superclass_exist?(klass)
55
+ detect_class_for(klass.superclass)
56
+ else
57
+ nil
58
+ end
59
+ end
11
60
  end
12
61
 
13
- class Factory
14
- def initialize(channel_owner)
15
- channel_owner_class_name = channel_owner.class.name
16
- raise "#{channel_owner_class_name} is not a channel owner" unless channel_owner_class_name.include?('::ChannelOwners::')
62
+ class ApiImplementationWrapper
63
+ def initialize(impl)
64
+ impl_class_name = impl.class.name
65
+ unless impl_class_name.end_with?("Impl")
66
+ raise "#{impl_class_name} is not Impl"
67
+ end
17
68
 
18
- @channel_owner = channel_owner
69
+ @impl = impl
19
70
  end
20
71
 
21
- def create
22
- api_class = detect_class_for(@channel_owner.class)
72
+ def wrap
73
+ api_class = detect_class_for(@impl.class)
23
74
  if api_class
24
- api_class.new(@channel_owner)
75
+ api_class.new(@impl)
25
76
  else
26
- raise NotImplementedError.new("Playwright::#{expected_class_name_for(@channel_owner.class)} is not implemented")
77
+ raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
27
78
  end
28
79
  end
29
80
 
30
81
  private
31
82
 
32
83
  def expected_class_name_for(klass)
33
- klass.name.split('::ChannelOwners::').last
84
+ # KeyboardImpl -> Keyboard
85
+ # MouseImpl -> Mouse
86
+ klass.name[0...-4].split("::").last
34
87
  end
35
88
 
36
89
  def detect_class_for(klass)
@@ -38,22 +91,18 @@ module Playwright
38
91
  if ::Playwright.const_defined?(class_name)
39
92
  ::Playwright.const_get(class_name)
40
93
  else
41
- if [::Playwright::ChannelOwner, Object].include?(klass.superclass)
42
- nil
43
- else
44
- detect_class_for(klass.superclass)
45
- end
94
+ nil
46
95
  end
47
96
  end
48
97
  end
49
98
 
50
- # @param channel_owner [Playwright::ChannelOwner]
51
- def initialize(channel_owner)
52
- @channel_owner = channel_owner
99
+ # @param impl [Playwright::ChannelOwner|Playwright::ApiImplementation]
100
+ def initialize(impl)
101
+ @impl = impl
53
102
  end
54
103
 
55
104
  def ==(other)
56
- @channel_owner.to_s == other.instance_variable_get(:'@channel_owner').to_s
105
+ @impl.to_s == other.instance_variable_get(:'@impl').to_s
57
106
  end
58
107
 
59
108
  # @param block [Proc]
@@ -61,16 +110,22 @@ module Playwright
61
110
  return nil unless block.is_a?(Proc)
62
111
 
63
112
  -> (*args) {
64
- wrapped_args = args.map { |arg| wrap_channel_owner(arg) }
113
+ wrapped_args = args.map { |arg| wrap_impl(arg) }
65
114
  block.call(*wrapped_args)
66
115
  }
67
116
  end
68
117
 
69
- private def wrap_channel_owner(object)
70
- if object.is_a?(ChannelOwner)
71
- PlaywrightApi.from_channel_owner(object)
72
- elsif object.is_a?(Array)
73
- object.map { |obj| wrap_channel_owner(obj) }
118
+ private def wrap_impl(object)
119
+ if object.is_a?(Array)
120
+ object.map { |obj| wrap_impl(obj) }
121
+ else
122
+ ::Playwright::PlaywrightApi.wrap(object) || object
123
+ end
124
+ end
125
+
126
+ private def unwrap_impl(object)
127
+ if object.is_a?(PlaywrightApi)
128
+ object.instance_variable_get(:@impl)
74
129
  else
75
130
  object
76
131
  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
@@ -9,11 +9,11 @@ module Playwright
9
9
  attr_writer :default_timeout, :default_navigation_timeout
10
10
 
11
11
  def navigation_timeout
12
- @default_navigation_timeout || @default_timeout || @parent&.navigation_timeout || EFAULT_TIMEOUT
12
+ @default_navigation_timeout || @default_timeout || @parent&.navigation_timeout || DEFAULT_TIMEOUT
13
13
  end
14
14
 
15
15
  def timeout
16
- @default_timeout || @parent&.navigation_timeout || DEFAULT_TIMEOUT
16
+ @default_timeout || @parent&.timeout || DEFAULT_TIMEOUT
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,7 @@
1
+ module Playwright
2
+ define_api_implementation :TouchscreenImpl do
3
+ def initialize(channel)
4
+ @channel = channel
5
+ end
6
+ end
7
+ 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
@@ -1,5 +1,23 @@
1
1
  module Playwright
2
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
+ params[:extraHTTPHeaders] = ::Playwright::HttpHeaders.new(params[:extraHTTPHeaders]).as_serialized
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
+
3
21
  module Errors
4
22
  module SafeCloseError
5
23
  # @param err [Exception]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '0.0.5'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -23,7 +23,7 @@ module Playwright
23
23
  return if timeout_ms <= 0
24
24
 
25
25
  Concurrent::Promises.schedule(timeout_ms / 1000.0) {
26
- reject(TimeoutError.new(message))
26
+ reject(TimeoutError.new(message: message))
27
27
  }
28
28
 
29
29
  self
@@ -6,18 +6,18 @@ module Playwright
6
6
  # Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
7
7
  # have wildly different output.
8
8
  #
9
- # Blink - Chromium's rendering engine - has a concept of "accessibility tree", which is then translated into different
10
- # platform-specific APIs. Accessibility namespace gives users access to the Blink Accessibility Tree.
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
11
  #
12
- # Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by
13
- # assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing only the
14
- # "interesting" nodes of the tree.
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.
15
15
  class Accessibility < PlaywrightApi
16
16
 
17
17
  # Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
18
18
  # page.
19
19
  #
20
- # > **NOTE** The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
20
+ # > NOTE: The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
21
21
  # Playwright will discard them as well for an easier to process tree, unless `interestingOnly` is set to `false`.
22
22
  #
23
23
  # An example of dumping the entire accessibility tree:
@@ -28,6 +28,16 @@ module Playwright
28
28
  # console.log(snapshot);
29
29
  # ```
30
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
+ #
31
41
  # An example of logging the focused node's name:
32
42
  #
33
43
  #
@@ -46,6 +56,36 @@ module Playwright
46
56
  # return null;
47
57
  # }
48
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
+ # ```
49
89
  def snapshot(interestingOnly: nil, root: nil)
50
90
  raise NotImplementedError.new('snapshot is not implemented yet.')
51
91
  end
@@ -0,0 +1,37 @@
1
+ module Playwright
2
+ # @nodoc
3
+ class Android < PlaywrightApi
4
+
5
+ # @nodoc
6
+ def devices
7
+ wrap_impl(@impl.devices)
8
+ end
9
+
10
+ # @nodoc
11
+ def after_initialize
12
+ wrap_impl(@impl.after_initialize)
13
+ end
14
+
15
+ # -- inherited from EventEmitter --
16
+ # @nodoc
17
+ def once(event, callback)
18
+ event_emitter_proxy.once(event, callback)
19
+ end
20
+
21
+ # -- inherited from EventEmitter --
22
+ # @nodoc
23
+ def on(event, callback)
24
+ event_emitter_proxy.on(event, callback)
25
+ end
26
+
27
+ # -- inherited from EventEmitter --
28
+ # @nodoc
29
+ def off(event, callback)
30
+ event_emitter_proxy.off(event, callback)
31
+ end
32
+
33
+ private def event_emitter_proxy
34
+ @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,82 @@
1
+ module Playwright
2
+ # @nodoc
3
+ class AndroidDevice < PlaywrightApi
4
+
5
+ # @nodoc
6
+ def tree
7
+ wrap_impl(@impl.tree)
8
+ end
9
+
10
+ # @nodoc
11
+ def close
12
+ wrap_impl(@impl.close)
13
+ end
14
+
15
+ # @nodoc
16
+ def after_initialize
17
+ wrap_impl(@impl.after_initialize)
18
+ end
19
+
20
+ # @nodoc
21
+ def tap_on(selector, duration: nil, timeout: nil)
22
+ wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
23
+ end
24
+
25
+ # @nodoc
26
+ def screenshot(path: nil)
27
+ wrap_impl(@impl.screenshot(path: unwrap_impl(path)))
28
+ end
29
+
30
+ # @nodoc
31
+ def shell(command)
32
+ wrap_impl(@impl.shell(unwrap_impl(command)))
33
+ end
34
+
35
+ # @nodoc
36
+ def input
37
+ wrap_impl(@impl.input)
38
+ end
39
+
40
+ # @nodoc
41
+ def launch_browser(pkg: nil, acceptDownloads: nil, bypassCSP: nil, colorScheme: nil, deviceScaleFactor: nil, extraHTTPHeaders: nil, geolocation: nil, hasTouch: nil, httpCredentials: nil, ignoreHTTPSErrors: nil, isMobile: nil, javaScriptEnabled: nil, locale: nil, logger: nil, offline: nil, permissions: nil, proxy: nil, recordHar: nil, recordVideo: nil, storageState: nil, timezoneId: nil, userAgent: nil, videoSize: nil, videosPath: nil, viewport: nil, &block)
42
+ wrap_impl(@impl.launch_browser(pkg: unwrap_impl(pkg), acceptDownloads: unwrap_impl(acceptDownloads), bypassCSP: unwrap_impl(bypassCSP), colorScheme: unwrap_impl(colorScheme), deviceScaleFactor: unwrap_impl(deviceScaleFactor), extraHTTPHeaders: unwrap_impl(extraHTTPHeaders), geolocation: unwrap_impl(geolocation), hasTouch: unwrap_impl(hasTouch), httpCredentials: unwrap_impl(httpCredentials), ignoreHTTPSErrors: unwrap_impl(ignoreHTTPSErrors), isMobile: unwrap_impl(isMobile), javaScriptEnabled: unwrap_impl(javaScriptEnabled), locale: unwrap_impl(locale), logger: unwrap_impl(logger), offline: unwrap_impl(offline), permissions: unwrap_impl(permissions), proxy: unwrap_impl(proxy), recordHar: unwrap_impl(recordHar), recordVideo: unwrap_impl(recordVideo), storageState: unwrap_impl(storageState), timezoneId: unwrap_impl(timezoneId), userAgent: unwrap_impl(userAgent), videoSize: unwrap_impl(videoSize), videosPath: unwrap_impl(videosPath), viewport: unwrap_impl(viewport), &wrap_block_call(block)))
43
+ end
44
+
45
+ # @nodoc
46
+ def info(selector)
47
+ wrap_impl(@impl.info(unwrap_impl(selector)))
48
+ end
49
+
50
+ # @nodoc
51
+ def serial
52
+ wrap_impl(@impl.serial)
53
+ end
54
+
55
+ # @nodoc
56
+ def model
57
+ wrap_impl(@impl.model)
58
+ end
59
+
60
+ # -- inherited from EventEmitter --
61
+ # @nodoc
62
+ def once(event, callback)
63
+ event_emitter_proxy.once(event, callback)
64
+ end
65
+
66
+ # -- inherited from EventEmitter --
67
+ # @nodoc
68
+ def on(event, callback)
69
+ event_emitter_proxy.on(event, callback)
70
+ end
71
+
72
+ # -- inherited from EventEmitter --
73
+ # @nodoc
74
+ def off(event, callback)
75
+ event_emitter_proxy.off(event, callback)
76
+ end
77
+
78
+ private def event_emitter_proxy
79
+ @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
80
+ end
81
+ end
82
+ end