playwright-ruby-client 0.0.8 → 1.58.1.alpha1

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 (209) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +4 -0
  3. data/CLAUDE/api_generation.md +28 -0
  4. data/CLAUDE/ci_expectations.md +23 -0
  5. data/CLAUDE/gem_release_flow.md +39 -0
  6. data/CLAUDE/past_upgrade_pr_patterns.md +42 -0
  7. data/CLAUDE/playwright_upgrade_workflow.md +35 -0
  8. data/CLAUDE/rspec_debugging.md +30 -0
  9. data/CLAUDE/unimplemented_examples.md +18 -0
  10. data/CLAUDE.md +32 -0
  11. data/CONTRIBUTING.md +5 -0
  12. data/README.md +60 -16
  13. data/documentation/README.md +33 -0
  14. data/documentation/babel.config.js +3 -0
  15. data/documentation/docs/api/api_request.md +7 -0
  16. data/documentation/docs/api/api_request_context.md +298 -0
  17. data/documentation/docs/api/api_response.md +114 -0
  18. data/documentation/docs/api/browser.md +237 -0
  19. data/documentation/docs/api/browser_context.md +503 -0
  20. data/documentation/docs/api/browser_type.md +184 -0
  21. data/documentation/docs/api/cdp_session.md +44 -0
  22. data/documentation/docs/api/clock.md +154 -0
  23. data/documentation/docs/api/console_message.md +85 -0
  24. data/documentation/docs/api/dialog.md +84 -0
  25. data/documentation/docs/api/download.md +111 -0
  26. data/documentation/docs/api/element_handle.md +694 -0
  27. data/documentation/docs/api/experimental/_category_.yml +3 -0
  28. data/documentation/docs/api/experimental/android.md +42 -0
  29. data/documentation/docs/api/experimental/android_device.md +109 -0
  30. data/documentation/docs/api/experimental/android_input.md +43 -0
  31. data/documentation/docs/api/experimental/android_socket.md +7 -0
  32. data/documentation/docs/api/experimental/android_web_view.md +7 -0
  33. data/documentation/docs/api/file_chooser.md +53 -0
  34. data/documentation/docs/api/frame.md +1218 -0
  35. data/documentation/docs/api/frame_locator.md +348 -0
  36. data/documentation/docs/api/js_handle.md +121 -0
  37. data/documentation/docs/api/keyboard.md +170 -0
  38. data/documentation/docs/api/locator.md +1495 -0
  39. data/documentation/docs/api/locator_assertions.md +827 -0
  40. data/documentation/docs/api/mouse.md +86 -0
  41. data/documentation/docs/api/page.md +1946 -0
  42. data/documentation/docs/api/page_assertions.md +65 -0
  43. data/documentation/docs/api/playwright.md +66 -0
  44. data/documentation/docs/api/request.md +255 -0
  45. data/documentation/docs/api/response.md +176 -0
  46. data/documentation/docs/api/route.md +205 -0
  47. data/documentation/docs/api/selectors.md +63 -0
  48. data/documentation/docs/api/touchscreen.md +22 -0
  49. data/documentation/docs/api/tracing.md +129 -0
  50. data/documentation/docs/api/web_socket.md +51 -0
  51. data/documentation/docs/api/worker.md +83 -0
  52. data/documentation/docs/article/api_coverage.mdx +11 -0
  53. data/documentation/docs/article/getting_started.md +161 -0
  54. data/documentation/docs/article/guides/_category_.yml +3 -0
  55. data/documentation/docs/article/guides/download_playwright_driver.md +55 -0
  56. data/documentation/docs/article/guides/inspector.md +31 -0
  57. data/documentation/docs/article/guides/launch_browser.md +121 -0
  58. data/documentation/docs/article/guides/playwright_on_alpine_linux.md +112 -0
  59. data/documentation/docs/article/guides/rails_integration.md +278 -0
  60. data/documentation/docs/article/guides/rails_integration_with_null_driver.md +145 -0
  61. data/documentation/docs/article/guides/recording_video.md +79 -0
  62. data/documentation/docs/article/guides/rspec_integration.md +59 -0
  63. data/documentation/docs/article/guides/semi_automation.md +71 -0
  64. data/documentation/docs/article/guides/use_storage_state.md +78 -0
  65. data/documentation/docs/include/api_coverage.md +671 -0
  66. data/documentation/docusaurus.config.js +114 -0
  67. data/documentation/package.json +39 -0
  68. data/documentation/sidebars.js +15 -0
  69. data/documentation/src/components/HomepageFeatures.js +61 -0
  70. data/documentation/src/components/HomepageFeatures.module.css +13 -0
  71. data/documentation/src/css/custom.css +44 -0
  72. data/documentation/src/pages/index.js +49 -0
  73. data/documentation/src/pages/index.module.css +41 -0
  74. data/documentation/src/pages/markdown-page.md +7 -0
  75. data/documentation/static/.nojekyll +0 -0
  76. data/documentation/static/img/playwright-logo.svg +9 -0
  77. data/documentation/static/img/playwright-ruby-client.png +0 -0
  78. data/documentation/static/img/undraw_dropdown_menu.svg +1 -0
  79. data/documentation/static/img/undraw_web_development.svg +1 -0
  80. data/documentation/static/img/undraw_windows.svg +1 -0
  81. data/documentation/yarn.lock +9005 -0
  82. data/lib/playwright/{input_types/android_input.rb → android_input_impl.rb} +5 -1
  83. data/lib/playwright/api_implementation.rb +18 -0
  84. data/lib/playwright/api_response_impl.rb +77 -0
  85. data/lib/playwright/channel.rb +62 -1
  86. data/lib/playwright/channel_owner.rb +70 -7
  87. data/lib/playwright/channel_owners/android.rb +16 -3
  88. data/lib/playwright/channel_owners/android_device.rb +22 -66
  89. data/lib/playwright/channel_owners/api_request_context.rb +247 -0
  90. data/lib/playwright/channel_owners/artifact.rb +40 -0
  91. data/lib/playwright/channel_owners/binding_call.rb +70 -0
  92. data/lib/playwright/channel_owners/browser.rb +114 -22
  93. data/lib/playwright/channel_owners/browser_context.rb +589 -15
  94. data/lib/playwright/channel_owners/browser_type.rb +90 -1
  95. data/lib/playwright/channel_owners/cdp_session.rb +19 -0
  96. data/lib/playwright/channel_owners/dialog.rb +32 -0
  97. data/lib/playwright/channel_owners/element_handle.rb +107 -43
  98. data/lib/playwright/channel_owners/fetch_request.rb +8 -0
  99. data/lib/playwright/channel_owners/frame.rb +334 -104
  100. data/lib/playwright/channel_owners/js_handle.rb +9 -13
  101. data/lib/playwright/channel_owners/local_utils.rb +82 -0
  102. data/lib/playwright/channel_owners/page.rb +778 -95
  103. data/lib/playwright/channel_owners/playwright.rb +25 -30
  104. data/lib/playwright/channel_owners/request.rb +120 -18
  105. data/lib/playwright/channel_owners/response.rb +113 -0
  106. data/lib/playwright/channel_owners/route.rb +181 -0
  107. data/lib/playwright/channel_owners/stream.rb +30 -0
  108. data/lib/playwright/channel_owners/tracing.rb +117 -0
  109. data/lib/playwright/channel_owners/web_socket.rb +96 -0
  110. data/lib/playwright/channel_owners/worker.rb +46 -0
  111. data/lib/playwright/channel_owners/writable_stream.rb +14 -0
  112. data/lib/playwright/clock_impl.rb +67 -0
  113. data/lib/playwright/connection.rb +111 -63
  114. data/lib/playwright/console_message_impl.rb +29 -0
  115. data/lib/playwright/download_impl.rb +32 -0
  116. data/lib/playwright/errors.rb +42 -5
  117. data/lib/playwright/event_emitter.rb +17 -3
  118. data/lib/playwright/event_emitter_proxy.rb +49 -0
  119. data/lib/playwright/events.rb +10 -5
  120. data/lib/playwright/file_chooser_impl.rb +24 -0
  121. data/lib/playwright/frame_locator_impl.rb +66 -0
  122. data/lib/playwright/har_router.rb +89 -0
  123. data/lib/playwright/http_headers.rb +14 -0
  124. data/lib/playwright/input_files.rb +102 -15
  125. data/lib/playwright/javascript/expression.rb +7 -11
  126. data/lib/playwright/javascript/regex.rb +23 -0
  127. data/lib/playwright/javascript/source_url.rb +16 -0
  128. data/lib/playwright/javascript/value_parser.rb +108 -19
  129. data/lib/playwright/javascript/value_serializer.rb +47 -8
  130. data/lib/playwright/javascript/visitor_info.rb +26 -0
  131. data/lib/playwright/javascript.rb +2 -10
  132. data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +6 -2
  133. data/lib/playwright/locator_assertions_impl.rb +571 -0
  134. data/lib/playwright/locator_impl.rb +544 -0
  135. data/lib/playwright/locator_utils.rb +136 -0
  136. data/lib/playwright/mouse_impl.rb +57 -0
  137. data/lib/playwright/page_assertions_impl.rb +154 -0
  138. data/lib/playwright/playwright_api.rb +102 -30
  139. data/lib/playwright/raw_headers.rb +61 -0
  140. data/lib/playwright/route_handler.rb +78 -0
  141. data/lib/playwright/select_option_values.rb +34 -13
  142. data/lib/playwright/selectors_impl.rb +45 -0
  143. data/lib/playwright/test.rb +102 -0
  144. data/lib/playwright/timeout_settings.rb +9 -4
  145. data/lib/playwright/touchscreen_impl.rb +14 -0
  146. data/lib/playwright/transport.rb +61 -10
  147. data/lib/playwright/url_matcher.rb +24 -2
  148. data/lib/playwright/utils.rb +48 -13
  149. data/lib/playwright/version.rb +2 -1
  150. data/lib/playwright/video.rb +54 -0
  151. data/lib/playwright/waiter.rb +166 -0
  152. data/lib/playwright/web_socket_client.rb +167 -0
  153. data/lib/playwright/web_socket_transport.rb +116 -0
  154. data/lib/playwright.rb +188 -11
  155. data/lib/playwright_api/android.rb +46 -11
  156. data/lib/playwright_api/android_device.rb +182 -31
  157. data/lib/playwright_api/android_input.rb +22 -13
  158. data/lib/playwright_api/android_socket.rb +18 -0
  159. data/lib/playwright_api/android_web_view.rb +24 -0
  160. data/lib/playwright_api/api_request.rb +26 -0
  161. data/lib/playwright_api/api_request_context.rb +311 -0
  162. data/lib/playwright_api/api_response.rb +92 -0
  163. data/lib/playwright_api/browser.rb +116 -103
  164. data/lib/playwright_api/browser_context.rb +290 -389
  165. data/lib/playwright_api/browser_type.rb +96 -118
  166. data/lib/playwright_api/cdp_session.rb +36 -39
  167. data/lib/playwright_api/clock.rb +121 -0
  168. data/lib/playwright_api/console_message.rb +35 -19
  169. data/lib/playwright_api/dialog.rb +53 -50
  170. data/lib/playwright_api/download.rb +49 -43
  171. data/lib/playwright_api/element_handle.rb +354 -402
  172. data/lib/playwright_api/file_chooser.rb +15 -18
  173. data/lib/playwright_api/frame.rb +703 -603
  174. data/lib/playwright_api/frame_locator.rb +285 -0
  175. data/lib/playwright_api/js_handle.rb +50 -76
  176. data/lib/playwright_api/keyboard.rb +67 -146
  177. data/lib/playwright_api/locator.rb +1304 -0
  178. data/lib/playwright_api/locator_assertions.rb +704 -0
  179. data/lib/playwright_api/mouse.rb +23 -29
  180. data/lib/playwright_api/page.rb +1196 -1176
  181. data/lib/playwright_api/page_assertions.rb +60 -0
  182. data/lib/playwright_api/playwright.rb +54 -122
  183. data/lib/playwright_api/request.rb +112 -74
  184. data/lib/playwright_api/response.rb +92 -20
  185. data/lib/playwright_api/route.rb +152 -62
  186. data/lib/playwright_api/selectors.rb +47 -61
  187. data/lib/playwright_api/touchscreen.rb +8 -2
  188. data/lib/playwright_api/tracing.rb +128 -0
  189. data/lib/playwright_api/web_socket.rb +43 -5
  190. data/lib/playwright_api/worker.rb +74 -34
  191. data/playwright.gemspec +14 -9
  192. data/sig/playwright.rbs +658 -0
  193. metadata +216 -50
  194. data/docs/api_coverage.md +0 -354
  195. data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
  196. data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
  197. data/lib/playwright/channel_owners/console_message.rb +0 -21
  198. data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
  199. data/lib/playwright/channel_owners/selectors.rb +0 -4
  200. data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
  201. data/lib/playwright/input_type.rb +0 -19
  202. data/lib/playwright/input_types/mouse.rb +0 -4
  203. data/lib/playwright/input_types/touchscreen.rb +0 -4
  204. data/lib/playwright/javascript/function.rb +0 -67
  205. data/lib/playwright/wait_helper.rb +0 -73
  206. data/lib/playwright_api/accessibility.rb +0 -93
  207. data/lib/playwright_api/binding_call.rb +0 -23
  208. data/lib/playwright_api/chromium_browser_context.rb +0 -57
  209. data/lib/playwright_api/video.rb +0 -24
@@ -0,0 +1,57 @@
1
+ module Playwright
2
+ define_api_implementation :MouseImpl do
3
+ def initialize(channel)
4
+ @channel = channel
5
+ end
6
+
7
+ def move(x, y, steps: nil)
8
+ params = { x: x, y: y, steps: steps }.compact
9
+ @channel.send_message_to_server('mouseMove', params)
10
+ nil
11
+ end
12
+
13
+ def down(button: nil, clickCount: nil)
14
+ params = { button: button, clickCount: clickCount }.compact
15
+ @channel.send_message_to_server('mouseDown', params)
16
+ nil
17
+ end
18
+
19
+ def up(button: nil, clickCount: nil)
20
+ params = { button: button, clickCount: clickCount }.compact
21
+ @channel.send_message_to_server('mouseUp', params)
22
+ nil
23
+ end
24
+
25
+ def click(
26
+ x,
27
+ y,
28
+ button: nil,
29
+ clickCount: nil,
30
+ delay: nil)
31
+
32
+ params = {
33
+ x: x,
34
+ y: y,
35
+ button: button,
36
+ clickCount: clickCount,
37
+ delay: delay,
38
+ }.compact
39
+ @channel.send_message_to_server('mouseClick', params)
40
+
41
+ nil
42
+ end
43
+
44
+ def dblclick(x, y, button: nil, delay: nil)
45
+ click(x, y, button: button, clickCount: 2, delay: delay)
46
+ end
47
+
48
+ def wheel(deltaX, deltaY)
49
+ params = {
50
+ deltaX: deltaX,
51
+ deltaY: deltaY,
52
+ }
53
+ @channel.send_message_to_server('mouseWheel', params)
54
+ nil
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,154 @@
1
+ module Playwright
2
+ # ref: https://github.com/microsoft/playwright-python/blob/main/playwright/_impl/_assertions.py
3
+ define_api_implementation :PageAssertionsImpl do
4
+ def self._define_negation(method_name)
5
+ define_method("not_#{method_name}") do |*args, **kwargs|
6
+ if kwargs.empty? # for Ruby < 2.7
7
+ _not.public_send(method_name, *args)
8
+ else
9
+ _not.public_send(method_name, *args, **kwargs)
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize(page, default_expect_timeout, is_not, message)
15
+ @page = PlaywrightApi.unwrap(page)
16
+ @frame = @page.main_frame
17
+ @default_expect_timeout = default_expect_timeout
18
+ @is_not = is_not
19
+ @custom_message = message
20
+ end
21
+
22
+ private def expect_impl(expression, expect_options, expected, message, title)
23
+ expect_options[:timeout] ||= @default_expect_timeout
24
+ expect_options[:isNot] = @is_not
25
+ message = message.gsub("expected to", "not expected to") if @is_not
26
+ expect_options.delete(:useInnerText) if expect_options.key?(:useInnerText) && expect_options[:useInnerText].nil?
27
+
28
+ result = @frame.expect(nil, expression, expect_options, title)
29
+
30
+ if result["matches"] == @is_not
31
+ actual = result["received"]
32
+
33
+ log =
34
+ if result.key?("log")
35
+ log_contents = result["log"].join("\n").strip
36
+
37
+ "\nCall log:\n #{log_contents}"
38
+ else
39
+ ""
40
+ end
41
+
42
+ out_message =
43
+ if @custom_message && expected
44
+ "\nExpected value: '#{expected}'"
45
+ elsif @custom_message
46
+ @custom_message
47
+ elsif message != "" && expected
48
+ "\n#{message} '#{expected}'"
49
+ else
50
+ "\n#{message}"
51
+ end
52
+
53
+ if result['errorMessage']
54
+ error_message = "\n#{result['errorMessage']}"
55
+ end
56
+ out = "#{out_message}\nActual value #{actual}#{error_message} #{log}"
57
+ raise AssertionError.new(out)
58
+ else
59
+ true
60
+ end
61
+ end
62
+
63
+ private def _not # "not" is reserved in Ruby
64
+ PageAssertionsImpl.new(
65
+ @page,
66
+ @default_expect_timeout,
67
+ !@is_not,
68
+ @message
69
+ )
70
+ end
71
+
72
+ private def expected_regex(pattern, match_substring, normalize_white_space, ignore_case)
73
+ regex = JavaScript::Regex.new(pattern)
74
+ expected = {
75
+ regexSource: regex.source,
76
+ regexFlags: regex.flag,
77
+ matchSubstring: match_substring,
78
+ normalizeWhiteSpace: normalize_white_space,
79
+ ignoreCase: ignore_case
80
+ }
81
+ expected.delete(:ignoreCase) if ignore_case.nil?
82
+
83
+ expected
84
+ end
85
+
86
+ private def to_expected_text_values(items, match_substring: false, normalize_white_space: false, ignore_case: false)
87
+ return [] unless items.respond_to?(:each)
88
+
89
+ items.each.with_object([]) do |item, out|
90
+ out <<
91
+ if item.is_a?(String) && ignore_case
92
+ {
93
+ string: item,
94
+ matchSubstring: match_substring,
95
+ normalizeWhiteSpace: normalize_white_space,
96
+ ignoreCase: ignore_case,
97
+ }
98
+ elsif item.is_a?(String)
99
+ {
100
+ string: item,
101
+ matchSubstring: match_substring,
102
+ normalizeWhiteSpace: normalize_white_space,
103
+ }
104
+ elsif item.is_a?(Regexp)
105
+ expected_regex(item, match_substring, normalize_white_space, ignore_case)
106
+ end
107
+ end
108
+ end
109
+
110
+ def to_have_title(title_or_regex, timeout: nil)
111
+ expected_text = to_expected_text_values(
112
+ [title_or_regex],
113
+ normalize_white_space: true,
114
+ )
115
+
116
+ expect_impl(
117
+ "to.have.title",
118
+ {
119
+ expectedText: expected_text,
120
+ timeout: timeout,
121
+ },
122
+ title_or_regex,
123
+ "Page title expected to be",
124
+ 'Expect "to_have_title"',
125
+ )
126
+ end
127
+ _define_negation :to_have_title
128
+
129
+ def to_have_url(url_or_regex, ignoreCase: nil, timeout: nil)
130
+ base_url = @page.context.send(:base_url)
131
+ if url_or_regex.is_a?(String) && base_url
132
+ expected = URI.join(base_url, url_or_regex).to_s
133
+ else
134
+ expected = url_or_regex
135
+ end
136
+ expected_text = to_expected_text_values(
137
+ [expected],
138
+ ignore_case: ignoreCase,
139
+ )
140
+
141
+ expect_impl(
142
+ "to.have.url",
143
+ {
144
+ expectedText: expected_text,
145
+ timeout: timeout,
146
+ },
147
+ expected,
148
+ "Page URL expected to be",
149
+ 'Expect "to_have_url"',
150
+ )
151
+ end
152
+ _define_negation :to_have_url
153
+ end
154
+ end
@@ -1,30 +1,48 @@
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, 'ChannelOwners').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
+ channel_owner_or_api_implementation
18
+ end
19
+ end
20
+
21
+ # Unwrap ChannelOwner / ApiImplementation.
22
+ # @note Intended for internal use only.
23
+ def self.unwrap(api)
24
+ case api
25
+ when PlaywrightApi
26
+ api.instance_variable_get(:@impl)
27
+ else
28
+ api
29
+ end
11
30
  end
12
31
 
13
- class Factory
14
- def initialize(impl, module_name)
32
+ class ChannelOwnerWrapper
33
+ def initialize(impl)
15
34
  impl_class_name = impl.class.name
16
- unless impl_class_name.include?("::#{module_name}::")
17
- raise "#{impl_class_name} is not #{module_name}"
35
+ unless impl_class_name.include?("::ChannelOwners::")
36
+ raise "#{impl_class_name} is not ChannelOwners"
18
37
  end
19
38
 
20
39
  @impl = impl
21
- @module_name = module_name
22
40
  end
23
41
 
24
- def create
42
+ def wrap
25
43
  api_class = detect_class_for(@impl.class)
26
44
  if api_class
27
- api_class.new(@impl)
45
+ @impl._api ||= api_class.new(@impl)
28
46
  else
29
47
  raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
30
48
  end
@@ -33,11 +51,11 @@ module Playwright
33
51
  private
34
52
 
35
53
  def expected_class_name_for(klass)
36
- klass.name.split("::#{@module_name}::").last
54
+ klass.name.split("::ChannelOwners::").last
37
55
  end
38
56
 
39
57
  def superclass_exist?(klass)
40
- ![::Playwright::ChannelOwner, ::Playwright::InputType, Object].include?(klass.superclass)
58
+ ![::Playwright::ChannelOwner, Object].include?(klass.superclass)
41
59
  end
42
60
 
43
61
  def detect_class_for(klass)
@@ -52,13 +70,46 @@ module Playwright
52
70
  end
53
71
  end
54
72
 
55
- # @param impl [Playwright::ChannelOwner|Playwright::InputType]
56
- def initialize(impl)
57
- @impl = impl
73
+ class ApiImplementationWrapper
74
+ def initialize(impl)
75
+ impl_class_name = impl.class.name
76
+ unless impl_class_name.end_with?("Impl")
77
+ raise "#{impl_class_name} is not Impl"
78
+ end
79
+
80
+ @impl = impl
81
+ end
82
+
83
+ def wrap
84
+ api_class = detect_class_for(@impl.class)
85
+ if api_class
86
+ api_class.new(@impl)
87
+ else
88
+ raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def expected_class_name_for(klass)
95
+ # KeyboardImpl -> Keyboard
96
+ # MouseImpl -> Mouse
97
+ klass.name[0...-4].split("::").last
98
+ end
99
+
100
+ def detect_class_for(klass)
101
+ class_name = expected_class_name_for(klass)
102
+ if ::Playwright.const_defined?(class_name)
103
+ ::Playwright.const_get(class_name)
104
+ else
105
+ nil
106
+ end
107
+ end
58
108
  end
59
109
 
60
- def ==(other)
61
- @impl.to_s == other.instance_variable_get(:'@impl').to_s
110
+ # @param impl [Playwright::ChannelOwner|Playwright::ApiImplementation]
111
+ def initialize(impl)
112
+ @impl = impl
62
113
  end
63
114
 
64
115
  # @param block [Proc]
@@ -71,22 +122,43 @@ module Playwright
71
122
  }
72
123
  end
73
124
 
74
- private def wrap_impl(object)
75
- case object
76
- when ChannelOwner
77
- PlaywrightApi.from_channel_owner(object)
78
- when InputType
79
- Factory.new(object, 'InputTypes').create
80
- when Array
81
- object.map { |obj| wrap_impl(obj) }
125
+ private def wrap_impl(object, visited: {})
126
+ if object.is_a?(Array)
127
+ unless visited[object]
128
+ visited[object] = []
129
+ object.each { |obj| visited[object] << wrap_impl(obj) }
130
+ end
131
+ visited[object]
132
+ elsif object.is_a?(Hash)
133
+ unless visited[object]
134
+ visited[object] = {}
135
+ object.each do |key, obj|
136
+ visited[object][key] = wrap_impl(obj, visited: visited)
137
+ end
138
+ end
139
+ visited[object]
82
140
  else
83
- object
141
+ ::Playwright::PlaywrightApi.wrap(object)
84
142
  end
85
143
  end
86
144
 
87
- private def unwrap_impl(object)
88
- if object.is_a?(PlaywrightApi)
89
- object.instance_variable_get(:@impl)
145
+ private def unwrap_impl(object, visited: {})
146
+ if object.is_a?(Array)
147
+ unless visited[object]
148
+ visited[object] = []
149
+ object.each { |obj| visited[object] << unwrap_impl(obj) }
150
+ end
151
+ visited[object]
152
+ elsif object.is_a?(Hash)
153
+ unless visited[object]
154
+ visited[object] = {}
155
+ object.each do |key, obj|
156
+ visited[object][key] = unwrap_impl(obj, visited: visited)
157
+ end
158
+ end
159
+ visited[object]
160
+ elsif object.is_a?(PlaywrightApi)
161
+ ::Playwright::PlaywrightApi.unwrap(object)
90
162
  else
91
163
  object
92
164
  end
@@ -0,0 +1,61 @@
1
+ module Playwright
2
+ class RawHeaders
3
+ def initialize(headers_array)
4
+ h_array = []
5
+ h_map = {}
6
+ headers_array.each do |header|
7
+ name = header['name']
8
+ key = name.downcase
9
+ value = header['value']
10
+
11
+ h_array << [name, value]
12
+ h_map[key] ||= []
13
+ h_map[key] << value
14
+ end
15
+
16
+ @headers_array = h_array
17
+ @headers_map = h_map
18
+ end
19
+
20
+ # @return [String|nil]
21
+ def get(name)
22
+ key = name.downcase
23
+ values = @headers_map[key]
24
+ if values
25
+ join(key, values)
26
+ else
27
+ nil
28
+ end
29
+ end
30
+
31
+ # @return [Array<String>]
32
+ def get_all(name)
33
+ values = @headers_map[name.downcase]
34
+ if values
35
+ values.dup
36
+ else
37
+ []
38
+ end
39
+ end
40
+
41
+ def headers
42
+ @headers_map.map do |key, values|
43
+ [key, join(key, values)]
44
+ end.to_h
45
+ end
46
+
47
+ def headers_array
48
+ @headers_array.map do |name, value|
49
+ { name: name, value: value }
50
+ end
51
+ end
52
+
53
+ private def join(key, values)
54
+ if key == 'set-cookie'
55
+ values.join("\n")
56
+ else
57
+ values.join(", ")
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,78 @@
1
+ module Playwright
2
+ class RouteHandler
3
+ class CountDown
4
+ def initialize(count)
5
+ @count = count
6
+ end
7
+
8
+ def increment
9
+ return false if expired?
10
+
11
+ @count = @count - 1
12
+ true
13
+ end
14
+
15
+ def expired?
16
+ @count <= 0
17
+ end
18
+ end
19
+
20
+ class StubCounter
21
+ def increment
22
+ true
23
+ end
24
+
25
+ def expired?
26
+ false
27
+ end
28
+ end
29
+
30
+ # @param url [String]
31
+ # @param base_url [String|nil]
32
+ # @param handler [Proc]
33
+ # @param times [Integer|nil]
34
+ def initialize(url, base_url, handler, times)
35
+ @url_value = url
36
+ @url_matcher = UrlMatcher.new(url, base_url: base_url)
37
+ @handler = handler
38
+ @counter =
39
+ if times
40
+ CountDown.new(times)
41
+ else
42
+ StubCounter.new
43
+ end
44
+ end
45
+
46
+ def as_pattern
47
+ @url_matcher.as_pattern
48
+ end
49
+
50
+ def match?(url)
51
+ @url_matcher.match?(url)
52
+ end
53
+
54
+ def async_handle(route)
55
+ @counter.increment
56
+
57
+ Concurrent::Promises.future { @handler.call(route, route.request) }.rescue do |err|
58
+ puts err, err.backtrace
59
+ end
60
+ end
61
+
62
+ def expired?
63
+ @counter.expired?
64
+ end
65
+
66
+ def same_value?(url:, handler: nil)
67
+ if handler
68
+ @url_value == url && @handler == handler
69
+ else
70
+ @url_value == url
71
+ end
72
+ end
73
+
74
+ def self.prepare_interception_patterns(handlers)
75
+ handlers.map(&:as_pattern).compact
76
+ end
77
+ end
78
+ end
@@ -1,7 +1,30 @@
1
1
  module Playwright
2
2
  class SelectOptionValues
3
- def initialize(values)
4
- @params = convert(values)
3
+ def initialize(element: nil, index: nil, value: nil, label: nil)
4
+ params = {}
5
+
6
+ options = []
7
+ if value
8
+ options.concat(convert(:value, value))
9
+ end
10
+
11
+ if index
12
+ options.concat(convert(:index, index))
13
+ end
14
+
15
+ if label
16
+ options.concat(convert(:label, label))
17
+ end
18
+
19
+ unless options.empty?
20
+ params[:options] = options
21
+ end
22
+
23
+ if element
24
+ params[:elements] = convert(:element, element)
25
+ end
26
+
27
+ @params = params
5
28
  end
6
29
 
7
30
  # @return [Hash]
@@ -9,23 +32,21 @@ module Playwright
9
32
  @params
10
33
  end
11
34
 
12
- private def convert(values)
13
- return {} unless values
14
- return convert([values]) unless values.is_a?('Array')
15
- return {} if values.empty?
35
+ private def convert(key, values)
36
+ return convert(key, [values]) unless values.is_a?(Enumerable)
37
+ return [] if values.empty?
16
38
  values.each_with_index do |value, index|
17
- unless values
39
+ unless value
18
40
  raise ArgumentError.new("options[#{index}]: expected object, got null")
19
41
  end
20
42
  end
21
43
 
22
- case values.first
23
- when ElementHandle
24
- { elements: values.map(&:channel) }
25
- when String
26
- { options: values.map { |value| { value: value } } }
44
+ if key == :element
45
+ values.map(&:channel)
46
+ elsif key == :value
47
+ values.map { |value| { valueOrLabel: value } }
27
48
  else
28
- { options: values }
49
+ values.map { |value| { key => value } }
29
50
  end
30
51
  end
31
52
  end
@@ -0,0 +1,45 @@
1
+ module Playwright
2
+ # https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_selectors.py
3
+ define_api_implementation :SelectorsImpl do
4
+ def initialize
5
+ @selector_engines = []
6
+ @contexts_for_selectors = Set.new
7
+ end
8
+
9
+ def register(name, contentScript: nil, path: nil, script: nil)
10
+ source =
11
+ if path
12
+ File.read(path)
13
+ elsif script
14
+ script
15
+ else
16
+ raise ArgumentError.new('Either path or script parameter must be specified')
17
+ end
18
+ selector_engine = { name: name, source: source }
19
+ if contentScript
20
+ selector_engine[:contentScript] = true
21
+ end
22
+ @contexts_for_selectors.each do |context|
23
+ context.send(:register_selector_engine, selector_engine)
24
+ end
25
+ @selector_engines << selector_engine
26
+
27
+ nil
28
+ end
29
+
30
+ def set_test_id_attribute(attribute_name)
31
+ @test_id_attribute_name = attribute_name
32
+ ::Playwright::LocatorUtils.instance_variable_set(:@test_id_attribute_name, attribute_name)
33
+ end
34
+
35
+ private def update_with_selector_options(options)
36
+ options[:selectorEngines] = @selector_engines unless @selector_engines.empty?
37
+ options[:testIdAttributeName] = @test_id_attribute_name if @test_id_attribute_name
38
+ options
39
+ end
40
+
41
+ private def contexts_for_selectors
42
+ @contexts_for_selectors
43
+ end
44
+ end
45
+ end