playwright-ruby-client 0.0.3

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