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,40 @@
1
+ require 'stringio'
2
+
3
+ module Playwright
4
+ define_channel_owner :Artifact do
5
+ private def after_initialize
6
+ @absolute_path = @initializer['absolutePath']
7
+ end
8
+
9
+ attr_reader :absolute_path
10
+
11
+ def path_after_finished
12
+ if @connection.remote?
13
+ raise "Path is not available when using browser_type.connect(). Use save_as() to save a local copy."
14
+ end
15
+ @channel.send_message_to_server('pathAfterFinished')
16
+ end
17
+
18
+ def save_as(path)
19
+ stream = ChannelOwners::Stream.from(@channel.send_message_to_server('saveAsStream'))
20
+ stream.save_as(path)
21
+ end
22
+
23
+ def read_into_buffer
24
+ stream = ChannelOwners::Stream.from(@channel.send_message_to_server('stream'))
25
+ stream.read_all
26
+ end
27
+
28
+ def failure
29
+ @channel.send_message_to_server('failure')
30
+ end
31
+
32
+ def delete
33
+ @channel.send_message_to_server('delete')
34
+ end
35
+
36
+ def cancel
37
+ @channel.send_message_to_server('cancel')
38
+ end
39
+ end
40
+ end
@@ -1,4 +1,74 @@
1
1
  module Playwright
2
2
  define_channel_owner :BindingCall do
3
+ class << self
4
+ def call_queue
5
+ @call_queue ||= Queue.new
6
+ end
7
+
8
+ def worker_mutex
9
+ @worker_mutex ||= Mutex.new
10
+ end
11
+
12
+ def ensure_worker
13
+ worker_mutex.synchronize do
14
+ return if @worker&.alive?
15
+
16
+ @worker = Thread.new do
17
+ loop do
18
+ job = call_queue.pop
19
+ begin
20
+ job.call
21
+ rescue => err
22
+ $stderr.write("BindingCall worker error: #{err.class}: #{err.message}\n")
23
+ err.backtrace&.each { |line| $stderr.write("#{line}\n") }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def name
32
+ @initializer['name']
33
+ end
34
+
35
+ def call_async(callback)
36
+ # Binding callbacks can be fired concurrently from multiple threads.
37
+ # Enqueue and execute them on a single worker thread so we:
38
+ # - preserve the delivery order of binding calls
39
+ # - avoid spawning a thread per call (bursty timers create many callbacks)
40
+ # - keep the protocol dispatch thread unblocked
41
+ self.class.ensure_worker
42
+ self.class.call_queue << -> { call(callback) }
43
+ end
44
+
45
+ # @param callback [Proc]
46
+ def call(callback)
47
+ frame = ChannelOwners::Frame.from(@initializer['frame'])
48
+ # It is not desired to use PlaywrightApi.wrap directly.
49
+ # However it is a little difficult to define wrapper for `source` parameter in generate_api.
50
+ # Just a workaround...
51
+ source = {
52
+ context: PlaywrightApi.wrap(frame.page.context),
53
+ page: PlaywrightApi.wrap(frame.page),
54
+ frame: PlaywrightApi.wrap(frame),
55
+ }
56
+ args =
57
+ if @initializer['handle']
58
+ handle = ChannelOwners::ElementHandle.from(@initializer['handle'])
59
+ [handle]
60
+ else
61
+ @initializer['args'].map do |arg|
62
+ JavaScript::ValueParser.new(arg).parse
63
+ end
64
+ end
65
+
66
+ begin
67
+ result = PlaywrightApi.unwrap(callback.call(source, *args))
68
+ @channel.async_send_message_to_server('resolve', result: JavaScript::ValueSerializer.new(result).serialize)
69
+ rescue => err
70
+ @channel.async_send_message_to_server('reject', error: { error: { message: err.message, name: 'Error' }})
71
+ end
72
+ end
3
73
  end
4
74
  end
@@ -1,68 +1,160 @@
1
+ require 'fileutils'
2
+
1
3
  module Playwright
2
4
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_browser.py
3
5
  define_channel_owner :Browser do
4
- include Utils::Errors::SafeCloseError
6
+ include Utils::Errors::TargetClosedErrorMethods
5
7
  include Utils::PrepareBrowserContextOptions
6
8
 
7
- def after_initialize
9
+ private def after_initialize
10
+ @connected = true
11
+ @should_close_connection_on_close = false
12
+
8
13
  @contexts = Set.new
14
+ @channel.on('context', ->(params) { did_create_context(ChannelOwners::BrowserContext.from(params['context'])) })
9
15
  @channel.on('close', method(:on_close))
16
+ @close_reason = nil
17
+ end
18
+
19
+ private def close_reason
20
+ @close_reason
10
21
  end
11
22
 
12
23
  def contexts
13
24
  @contexts.to_a
14
25
  end
15
26
 
27
+ def browser_type
28
+ @browser_type
29
+ end
30
+
16
31
  def connected?
17
32
  @connected
18
33
  end
19
34
 
20
35
  def new_context(**options, &block)
21
36
  params = options.dup
37
+ @browser_type.send(:update_with_playwright_selectors_options, params)
22
38
  prepare_browser_context_options(params)
23
39
 
24
40
  resp = @channel.send_message_to_server('newContext', params.compact)
25
41
  context = ChannelOwners::BrowserContext.from(resp)
26
- @contexts << context
27
- context.browser = self
28
- context.options = params
42
+ context.send(:initialize_har_from_options,
43
+ record_har_content: params[:record_har_content],
44
+ record_har_mode: params[:record_har_mode],
45
+ record_har_omit_content: params[:record_har_omit_content],
46
+ record_har_path: params[:record_har_path],
47
+ record_har_url_filter: params[:record_har_url_filter],
48
+ )
49
+ @browser_type.send(:did_create_context, context, params)
50
+ return context unless block
29
51
 
30
- if block
31
- begin
32
- block.call(context)
33
- ensure
34
- context.close
35
- end
36
- else
37
- context
52
+ begin
53
+ block.call(context)
54
+ ensure
55
+ context.close
38
56
  end
39
57
  end
40
58
 
41
- def new_page(**options)
59
+ def new_page(**options, &block)
42
60
  context = new_context(**options)
43
61
  page = context.new_page
44
62
  page.owned_context = context
45
63
  context.owner_page = page
46
- page
64
+
65
+ return page unless block
66
+
67
+ begin
68
+ block.call(page)
69
+ ensure
70
+ page.close
71
+ end
47
72
  end
48
73
 
49
- def close
50
- return if @closed_or_closing
51
- @closed_or_closing = true
52
- @channel.send_message_to_server('close')
74
+ def close(reason: nil)
75
+ @close_reason = reason
76
+ if @should_close_connection_on_close
77
+ @connection.stop
78
+ else
79
+ @channel.send_message_to_server('close', { reason: reason }.compact)
80
+ end
53
81
  nil
54
82
  rescue => err
55
- raise unless safe_close_error?(err)
83
+ raise unless target_closed_error?(err)
56
84
  end
57
85
 
58
86
  def version
59
87
  @initializer['version']
60
88
  end
61
89
 
90
+ def new_browser_cdp_session
91
+ resp = @channel.send_message_to_server('newBrowserCDPSession')
92
+ ChannelOwners::CDPSession.from(resp)
93
+ end
94
+
95
+ def start_tracing(page: nil, categories: nil, path: nil, screenshots: nil)
96
+ params = {
97
+ page: page&.channel,
98
+ categories: categories,
99
+ screenshots: screenshots,
100
+ }.compact
101
+ @cr_tracing_path = path
102
+
103
+ @channel.send_message_to_server('startTracing', params)
104
+ end
105
+
106
+ def stop_tracing
107
+ artifact = ChannelOwners::Artifact.from(@channel.send_message_to_server("stopTracing"))
108
+ data = artifact.read_into_buffer
109
+ if @cr_tracing_path
110
+ File.dirname(@cr_tracing_path).tap do |dir|
111
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
112
+ end
113
+ File.open(@cr_tracing_path, 'wb') { |f| f.write(data) }
114
+ end
115
+ data
116
+ end
117
+
118
+ # called from BrowserType
119
+ private def connect_to_browser_type(browser_type, traces_dir)
120
+ # Note: when using connect(), `browserType` is different from `this.parent`.
121
+ # This is why browser type is not wired up in the constructor, and instead this separate method is called later on.
122
+ @browser_type = browser_type
123
+ @traces_dir = traces_dir
124
+ @contexts.each do |context|
125
+ setup_browser_context(context)
126
+ end
127
+ end
128
+
129
+ private def did_create_context(context)
130
+ context.browser = self
131
+ @contexts << context
132
+
133
+ # Note: when connecting to a browser, initial contexts arrive before `_browserType` is set,
134
+ # and will be configured later in `ConnectToBrowserType`.
135
+ if @browser_type
136
+ setup_browser_context(context)
137
+ end
138
+ end
139
+
140
+ private def setup_browser_context(context)
141
+ context.tracing.send(:update_traces_dir, @traces_dir)
142
+ @browser_type.send(:playwright_selectors_browser_contexts) << context
143
+ end
144
+
62
145
  private def on_close(_ = {})
63
146
  @connected = false
64
- emit(Events::Browser::Disconnected)
65
- @closed_or_closing = false
147
+ emit(Events::Browser::Disconnected, self)
148
+ @closed_or_closing = true
149
+ end
150
+
151
+ # called from BrowserContext#initialize
152
+ private def add_context(context)
153
+ @contexts << context
154
+ end
155
+
156
+ private def should_close_connection_on_close!
157
+ @should_close_connection_on_close = true
66
158
  end
67
159
 
68
160
  # called from BrowserContext#on_close with send(:remove_context), so keep private.