playwright-ruby-client 0.0.5 → 0.1.0

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