playwright-ruby-client 0.0.3

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +49 -0
  7. data/Rakefile +3 -0
  8. data/bin/console +11 -0
  9. data/bin/setup +8 -0
  10. data/lib/playwright.rb +39 -0
  11. data/lib/playwright/channel.rb +28 -0
  12. data/lib/playwright/channel_owner.rb +80 -0
  13. data/lib/playwright/channel_owners/android.rb +3 -0
  14. data/lib/playwright/channel_owners/binding_call.rb +4 -0
  15. data/lib/playwright/channel_owners/browser.rb +80 -0
  16. data/lib/playwright/channel_owners/browser_context.rb +13 -0
  17. data/lib/playwright/channel_owners/browser_type.rb +26 -0
  18. data/lib/playwright/channel_owners/chromium_browser.rb +8 -0
  19. data/lib/playwright/channel_owners/chromium_browser_context.rb +8 -0
  20. data/lib/playwright/channel_owners/electron.rb +3 -0
  21. data/lib/playwright/channel_owners/firefox_browser.rb +8 -0
  22. data/lib/playwright/channel_owners/frame.rb +44 -0
  23. data/lib/playwright/channel_owners/page.rb +53 -0
  24. data/lib/playwright/channel_owners/playwright.rb +57 -0
  25. data/lib/playwright/channel_owners/request.rb +5 -0
  26. data/lib/playwright/channel_owners/response.rb +5 -0
  27. data/lib/playwright/channel_owners/selectors.rb +4 -0
  28. data/lib/playwright/channel_owners/webkit_browser.rb +8 -0
  29. data/lib/playwright/connection.rb +238 -0
  30. data/lib/playwright/errors.rb +35 -0
  31. data/lib/playwright/event_emitter.rb +62 -0
  32. data/lib/playwright/events.rb +86 -0
  33. data/lib/playwright/playwright_api.rb +75 -0
  34. data/lib/playwright/transport.rb +86 -0
  35. data/lib/playwright/version.rb +5 -0
  36. data/lib/playwright_api/accessibility.rb +39 -0
  37. data/lib/playwright_api/binding_call.rb +5 -0
  38. data/lib/playwright_api/browser.rb +123 -0
  39. data/lib/playwright_api/browser_context.rb +285 -0
  40. data/lib/playwright_api/browser_type.rb +144 -0
  41. data/lib/playwright_api/cdp_session.rb +34 -0
  42. data/lib/playwright_api/chromium_browser_context.rb +26 -0
  43. data/lib/playwright_api/console_message.rb +22 -0
  44. data/lib/playwright_api/dialog.rb +46 -0
  45. data/lib/playwright_api/download.rb +54 -0
  46. data/lib/playwright_api/element_handle.rb +361 -0
  47. data/lib/playwright_api/file_chooser.rb +31 -0
  48. data/lib/playwright_api/frame.rb +526 -0
  49. data/lib/playwright_api/js_handle.rb +69 -0
  50. data/lib/playwright_api/keyboard.rb +101 -0
  51. data/lib/playwright_api/mouse.rb +47 -0
  52. data/lib/playwright_api/page.rb +986 -0
  53. data/lib/playwright_api/playwright.rb +35 -0
  54. data/lib/playwright_api/request.rb +119 -0
  55. data/lib/playwright_api/response.rb +61 -0
  56. data/lib/playwright_api/route.rb +53 -0
  57. data/lib/playwright_api/selectors.rb +51 -0
  58. data/lib/playwright_api/touchscreen.rb +10 -0
  59. data/lib/playwright_api/video.rb +14 -0
  60. data/lib/playwright_api/web_socket.rb +21 -0
  61. data/lib/playwright_api/worker.rb +34 -0
  62. data/playwright.gemspec +35 -0
  63. metadata +216 -0
@@ -0,0 +1,26 @@
1
+ module Playwright
2
+ define_channel_owner :BrowserType do
3
+ def name
4
+ @initializer['name']
5
+ end
6
+
7
+ def executable_path
8
+ @initializer['executablePath']
9
+ end
10
+
11
+ def launch(options, &block)
12
+ resp = @channel.send_message_to_server('launch', options.compact)
13
+ browser = ChannelOwners::Browser.from(resp)
14
+
15
+ if block
16
+ begin
17
+ block.call(browser)
18
+ ensure
19
+ browser.close
20
+ end
21
+ else
22
+ browser
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ require_relative './browser'
2
+
3
+ module Playwright
4
+ module ChannelOwners
5
+ class ChromiumBrowser < Browser
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require_relative './browser_context'
2
+
3
+ module Playwright
4
+ module ChannelOwners
5
+ class ChromiumBrowserContext < BrowserContext
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Playwright
2
+ define_channel_owner :Electron
3
+ end
@@ -0,0 +1,8 @@
1
+ require_relative './browser'
2
+
3
+ module Playwright
4
+ module ChannelOwners
5
+ class FirefoxBrowser < Browser
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,44 @@
1
+ module Playwright
2
+ # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_frame.py
3
+ define_channel_owner :Frame do
4
+ def after_initialize
5
+ @event_emitter = Object.new.extend(EventEmitter)
6
+ if @initializer['parentFrame']
7
+ @parent_frame = self.from(@initializer['parentFrame'])
8
+ @parent_frame.send(:append_child_frame_from_child, self)
9
+ end
10
+ @name = @initializer['name']
11
+ @url = @initializer['url']
12
+ @detached = false
13
+ @child_frames = Set.new
14
+ @load_states = Set.new(@initializer['loadStates'])
15
+ end
16
+
17
+ attr_reader :page
18
+
19
+ def goto(url, timeout: nil, waitUntil: nil, referer: nil)
20
+ params = {
21
+ url: url,
22
+ timeout: timeout,
23
+ waitUntil: waitUntil,
24
+ referer: referer
25
+ }.compact
26
+ resp = @channel.send_message_to_server('goto', params)
27
+ ChannelOwners::Response.from(resp)
28
+ end
29
+
30
+ private
31
+
32
+ # @param page [Page]
33
+ # @note This method should be used internally. Accessed via .send method, so keep private!
34
+ def update_page_from_page(page)
35
+ @page = page
36
+ end
37
+
38
+ # @param child [Frame]
39
+ # @note This method should be used internally. Accessed via .send method, so keep private!
40
+ def append_child_frame_from_child(frame)
41
+ @child_frames << frame
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ require 'base64'
2
+
3
+ module Playwright
4
+ # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_page.py
5
+ define_channel_owner :Page do
6
+ attr_writer :owned_context
7
+
8
+ def after_initialize
9
+ @accessibility = Accessibility.new(@channel)
10
+ @keyboard = Keyboard.new(@channel)
11
+ @mouse = Mouse.new(@channel)
12
+ @touchscreen = Touchscreen.new(@channel)
13
+
14
+ @main_frame = ChannelOwners::Frame.from(@initializer['mainFrame'])
15
+ @main_frame.send(:update_page_from_page, self)
16
+ @frames = Set.new
17
+ @frames << @main_frame
18
+ end
19
+
20
+ attr_reader :accessibility, :keyboard, :mouse, :touchscreen, :main_frame
21
+
22
+ def goto(url, timeout: nil, waitUntil: nil, referer: nil)
23
+ @main_frame.goto(url, timeout: timeout, waitUntil: waitUntil, referer: referer)
24
+ end
25
+
26
+ def screenshot(
27
+ path: nil,
28
+ type: nil,
29
+ quality: nil,
30
+ fullPage: nil,
31
+ clip: nil,
32
+ omitBackground: nil,
33
+ timeout: nil)
34
+
35
+ params = {
36
+ type: type,
37
+ quality: quality,
38
+ fullPage: fullPage,
39
+ clip: clip,
40
+ omitBackground: omitBackground,
41
+ timeout: timeout,
42
+ }.compact
43
+ encoded_binary = @channel.send_message_to_server('screenshot', params)
44
+ decoded_binary = Base64.decode64(encoded_binary)
45
+ if path
46
+ File.open(path, 'wb') do |f|
47
+ f.write(decoded_binary)
48
+ end
49
+ end
50
+ decoded_binary
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,57 @@
1
+ module Playwright
2
+ define_channel_owner :Playwright do
3
+ def chromium
4
+ @chromium ||= ::Playwright::ChannelOwners::BrowserType.from(@initializer['chromium'])
5
+ end
6
+
7
+ def firefox
8
+ @firefox ||= ::Playwright::ChannelOwners::BrowserType.from(@initializer['firefox'])
9
+ end
10
+
11
+ def webkit
12
+ @webkit ||= ::Playwright::ChannelOwners::BrowserType.from(@initializer['webkit'])
13
+ end
14
+
15
+ def android
16
+ @android ||= ::Playwright::ChannelOwners::Android.from(@initializer['android'])
17
+ end
18
+
19
+ def electron
20
+ @electron ||= ::Playwright::ChannelOwners::Electron.from(@initializer['electron'])
21
+ end
22
+
23
+ class DeviceDescriptor
24
+ class Viewport
25
+ def initialize(hash)
26
+ @width = hash['width']
27
+ @heirhgt = hash['height']
28
+ end
29
+ attr_reader :width, :height
30
+ end
31
+
32
+ def initialize(hash)
33
+ @user_agent = hash["userAgent"]
34
+ @viewport = Viewport.new(hash["viewport"])
35
+ @device_scale_factor = hash["deviceScaleFactor"]
36
+ @is_mobile = hash["isMobile"]
37
+ @has_touch = hash["hasTouch"]
38
+ end
39
+
40
+ attr_reader :user_agent, :viewport, :device_scale_factor
41
+
42
+ def mobile?
43
+ @is_mobile
44
+ end
45
+
46
+ def has_touch?
47
+ @has_touch
48
+ end
49
+ end
50
+
51
+ def devices
52
+ @devices ||= @initializer['deviceDescriptors'].map do |item|
53
+ [item['name'], DeviceDescriptor.new(item['descriptor'])]
54
+ end.to_h
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ module Playwright
2
+ # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
3
+ define_channel_owner :Request do
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Playwright
2
+ # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
3
+ define_channel_owner :Response do
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Playwright
2
+ # https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_selectors.py
3
+ define_channel_owner :Selectors
4
+ end
@@ -0,0 +1,8 @@
1
+ require_relative './browser'
2
+
3
+ module Playwright
4
+ module ChannelOwners
5
+ class WebkitBrowser < Browser
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playwright
4
+ # https://github.com/microsoft/playwright/blob/master/src/client/connection.ts
5
+ # https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_connection.py
6
+ # https://github.com/microsoft/playwright-java/blob/master/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
7
+ class Connection
8
+ def initialize(playwright_cli_executable_path:)
9
+ @transport = Transport.new(
10
+ playwright_cli_executable_path: playwright_cli_executable_path
11
+ )
12
+ @transport.on_message_received do |message|
13
+ dispatch(message)
14
+ end
15
+
16
+ @objects = {} # Hash[ guid => ChannelOwner ]
17
+ @waiting_for_object = {} # Hash[ guid => Promise<ChannelOwner> ]
18
+ @callbacks = {} # Hash [ guid => Promise<ChannelOwner> ]
19
+ @root_object = RootChannelOwner.new(self)
20
+ end
21
+
22
+ def run
23
+ @transport.run
24
+ end
25
+
26
+ def stop
27
+ @transport.stop
28
+ end
29
+
30
+ def async_wait_for_object_with_known_name(guid)
31
+ if @objects[guid]
32
+ return @objects[guid]
33
+ end
34
+
35
+ callback = Concurrent::Promises.resolvable_future
36
+ @waiting_for_object[guid] = callback
37
+ callback
38
+ end
39
+
40
+ def wait_for_object_with_known_name(guid)
41
+ async_wait_for_object_with_known_name.value!
42
+ end
43
+
44
+ def async_send_message_to_server(guid, method, params)
45
+ callback = Concurrent::Promises.resolvable_future
46
+
47
+ with_generated_id do |id|
48
+ # register callback promise object first.
49
+ # @see https://github.com/YusukeIwaki/puppeteer-ruby/pull/34
50
+ @callbacks[id] = callback
51
+
52
+ message = {
53
+ id: id,
54
+ guid: guid,
55
+ method: method,
56
+ params: replace_channels_with_guids(params),
57
+ }
58
+ @transport.send_message(message)
59
+ rescue Transport::AlreadyDisconnectedError => err
60
+ @callbacks.delete(id)
61
+ callback.reject(err)
62
+ end
63
+
64
+ callback
65
+ end
66
+
67
+ def send_message_to_server(guid, method, params)
68
+ async_send_message_to_server(guid, method, params).value!
69
+ end
70
+
71
+ private
72
+
73
+ # ```usage
74
+ # connection.with_generated_id do |id|
75
+ # # play with id
76
+ # end
77
+ # ````
78
+ def with_generated_id(&block)
79
+ @last_id ||= 0
80
+ block.call(@last_id += 1)
81
+ end
82
+
83
+ # @param guid [String]
84
+ # @param parent [Playwright::ChannelOwner]
85
+ # @note This method should be used internally. Accessed via .send method from Playwright::ChannelOwner, so keep private!
86
+ def update_object_from_channel_owner(guid, parent)
87
+ @objects[guid] = parent
88
+ end
89
+
90
+ # @param guid [String]
91
+ # @note This method should be used internally. Accessed via .send method from Playwright::ChannelOwner, so keep private!
92
+ def delete_object_from_channel_owner(guid)
93
+ @objects.delete(guid)
94
+ end
95
+
96
+ def dispatch(msg)
97
+ id = msg['id']
98
+ if id
99
+ callback = @callbacks.delete(id)
100
+
101
+ unless callback
102
+ raise "Cannot find command to respond: #{id}"
103
+ end
104
+
105
+ error = msg['error']
106
+ if error
107
+ callback.reject(::Playwright::Error.parse(error['error']))
108
+ else
109
+ result = replace_guids_with_channels(msg['result'])
110
+ callback.fulfill(result)
111
+ end
112
+
113
+ return
114
+ end
115
+
116
+ guid = msg['guid']
117
+ method = msg['method']
118
+ params = msg['params']
119
+
120
+ if method == "__create__"
121
+ create_remote_object(
122
+ parent_guid: guid,
123
+ type: params["type"],
124
+ guid: params["guid"],
125
+ initializer: params["initializer"],
126
+ )
127
+ return
128
+ end
129
+
130
+ if method == "__dispose__"
131
+ object = @objects[guid]
132
+ unless object
133
+ raise "Cannot find object to dispose: #{guid}"
134
+ end
135
+ object.dispose
136
+ return
137
+ end
138
+
139
+ object = @objects[guid]
140
+ unless object
141
+ raise "Cannot find object to emit \"#{method}\": #{guid}"
142
+ end
143
+ object.channel.emit(method, replace_guids_with_channels(params))
144
+ end
145
+
146
+ def replace_channels_with_guids(payload)
147
+ if payload.nil?
148
+ return nil
149
+ end
150
+
151
+ if payload.is_a?(Array)
152
+ return payload.map{ |pl| replace_channels_with_guids(pl) }
153
+ end
154
+
155
+ if payload.is_a?(Channel)
156
+ { guid: payload.guid }
157
+ end
158
+
159
+ if payload.is_a?(Hash)
160
+ return payload.map { |k, v| [k, replace_channels_with_guids(v)] }.to_h
161
+ end
162
+
163
+ payload
164
+ end
165
+
166
+ def replace_guids_with_channels(payload)
167
+ if payload.nil?
168
+ return nil
169
+ end
170
+
171
+ if payload.is_a?(Array)
172
+ return payload.map{ |pl| replace_guids_with_channels(pl) }
173
+ end
174
+
175
+ if payload.is_a?(Hash)
176
+ guid = payload['guid']
177
+ if guid && @objects[guid]
178
+ return @objects[guid].channel
179
+ end
180
+
181
+ return payload.map { |k, v| [k, replace_guids_with_channels(v)] }.to_h
182
+ end
183
+
184
+ payload
185
+ end
186
+
187
+ # @return [Playwright::ChannelOwner|nil]
188
+ def create_remote_object(parent_guid:, type:, guid:, initializer:)
189
+ parent = @objects[parent_guid]
190
+ unless parent
191
+ raise "Cannot find parent object #{parent_guid} to create #{guid}"
192
+ end
193
+ initializer = replace_guids_with_channels(initializer)
194
+
195
+ params = [
196
+ parent,
197
+ type,
198
+ guid,
199
+ initializer,
200
+ ]
201
+ class_name = case type
202
+ when 'Browser'
203
+ case initializer['name']
204
+ when 'chromium'
205
+ 'ChromiumBrowser'
206
+ when 'webkit'
207
+ 'WebKitBrowser'
208
+ when 'firefox'
209
+ 'FirefoxBrowser'
210
+ else
211
+ 'Browser'
212
+ end
213
+ when 'BrowserContext'
214
+ browser_name = initializer['browserName']
215
+ if browser_name == 'chromium'
216
+ 'ChromiumBrowserContext'
217
+ else
218
+ params << browser_name
219
+ 'BrowserContext'
220
+ end
221
+ else
222
+ type
223
+ end
224
+
225
+ result =
226
+ begin
227
+ ChannelOwners.const_get(class_name).new(*params)
228
+ rescue NameError
229
+ raise "Missing type #{type}"
230
+ end
231
+
232
+ callback = @waiting_for_object.delete(guid)
233
+ callback&.fulfill(result)
234
+
235
+ result
236
+ end
237
+ end
238
+ end