playwright-ruby-client 0.2.1 → 0.3.0

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/docs/api_coverage.md +102 -49
  3. data/lib/playwright.rb +1 -0
  4. data/lib/playwright/channel.rb +10 -10
  5. data/lib/playwright/channel_owners/binding_call.rb +3 -0
  6. data/lib/playwright/channel_owners/browser_context.rb +149 -3
  7. data/lib/playwright/channel_owners/dialog.rb +28 -0
  8. data/lib/playwright/channel_owners/page.rb +47 -10
  9. data/lib/playwright/channel_owners/playwright.rb +4 -0
  10. data/lib/playwright/channel_owners/request.rb +26 -2
  11. data/lib/playwright/channel_owners/response.rb +60 -0
  12. data/lib/playwright/channel_owners/route.rb +78 -0
  13. data/lib/playwright/channel_owners/selectors.rb +19 -1
  14. data/lib/playwright/route_handler_entry.rb +36 -0
  15. data/lib/playwright/utils.rb +1 -0
  16. data/lib/playwright/version.rb +1 -1
  17. data/lib/playwright_api/android.rb +80 -8
  18. data/lib/playwright_api/android_device.rb +145 -28
  19. data/lib/playwright_api/android_input.rb +17 -13
  20. data/lib/playwright_api/android_socket.rb +16 -0
  21. data/lib/playwright_api/android_web_view.rb +21 -0
  22. data/lib/playwright_api/binding_call.rb +11 -6
  23. data/lib/playwright_api/browser.rb +6 -6
  24. data/lib/playwright_api/browser_context.rb +33 -28
  25. data/lib/playwright_api/browser_type.rb +14 -14
  26. data/lib/playwright_api/chromium_browser_context.rb +6 -6
  27. data/lib/playwright_api/console_message.rb +6 -6
  28. data/lib/playwright_api/dialog.rb +32 -5
  29. data/lib/playwright_api/download.rb +6 -6
  30. data/lib/playwright_api/element_handle.rb +6 -6
  31. data/lib/playwright_api/file_chooser.rb +1 -1
  32. data/lib/playwright_api/frame.rb +13 -11
  33. data/lib/playwright_api/js_handle.rb +6 -6
  34. data/lib/playwright_api/page.rb +48 -46
  35. data/lib/playwright_api/playwright.rb +7 -7
  36. data/lib/playwright_api/request.rb +8 -8
  37. data/lib/playwright_api/response.rb +27 -17
  38. data/lib/playwright_api/route.rb +27 -5
  39. data/lib/playwright_api/selectors.rb +7 -7
  40. metadata +7 -2
@@ -0,0 +1,28 @@
1
+ module Playwright
2
+ define_channel_owner :Dialog do
3
+ def type
4
+ @initializer['type']
5
+ end
6
+
7
+ def message
8
+ @initializer['message']
9
+ end
10
+
11
+ def default_value
12
+ @initializer['defaultValue']
13
+ end
14
+
15
+ def accept(promptText: nil)
16
+ accept_async(prompt_text: promptText).value!
17
+ end
18
+
19
+ def accept_async(promptText: nil)
20
+ params = { promptText: promptText }.compact
21
+ @channel.async_send_message_to_server('accept', params)
22
+ end
23
+
24
+ def dismiss
25
+ @channel.async_send_message_to_server('dismiss')
26
+ end
27
+ end
28
+ end
@@ -14,8 +14,16 @@ module Playwright
14
14
  @mouse = MouseImpl.new(@channel)
15
15
  @touchscreen = TouchscreenImpl.new(@channel)
16
16
 
17
- @viewport_size = @initializer['viewportSize']
17
+ if @initializer['viewportSize']
18
+ @viewport_size = {
19
+ width: @initializer['viewportSize']['width'],
20
+ height: @initializer['viewportSize']['height'],
21
+ }
22
+ end
18
23
  @closed = false
24
+ @bindings = {}
25
+ @routes = Set.new
26
+
19
27
  @main_frame = ChannelOwners::Frame.from(@initializer['mainFrame'])
20
28
  @main_frame.send(:update_page_from_page, self)
21
29
  @frames = Set.new
@@ -96,12 +104,12 @@ module Playwright
96
104
  private def on_request_failed(request, response_end_timing, failure_text)
97
105
  request.send(:update_failure_text, failure_text)
98
106
  request.send(:update_response_end_timing, response_end_timing)
99
- emit(Events::Page::RequestFailed)
107
+ emit(Events::Page::RequestFailed, request)
100
108
  end
101
109
 
102
110
  private def on_request_finished(request, response_end_timing)
103
111
  request.send(:update_response_end_timing, response_end_timing)
104
- emit(Events::Page::RequestFinished)
112
+ emit(Events::Page::RequestFinished, request)
105
113
  end
106
114
 
107
115
  private def on_frame_attached(frame)
@@ -117,8 +125,15 @@ module Playwright
117
125
  end
118
126
 
119
127
  private def on_route(route, request)
120
- # @routes.each ...
121
- @browser_context.send(:on_route, route, request)
128
+ # It is not desired to use PlaywrightApi.wrap directly.
129
+ # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
130
+ # Just a workaround...
131
+ wrapped_route = PlaywrightApi.wrap(route)
132
+ wrapped_request = PlaywrightApi.wrap(request)
133
+
134
+ if @routes.none? { |handler_entry| handler_entry.handle(wrapped_route, wrapped_request) }
135
+ @browser_context.send(:on_route, route, request)
136
+ end
122
137
  end
123
138
 
124
139
  private def on_close
@@ -130,14 +145,14 @@ module Playwright
130
145
  private def on_dialog(params)
131
146
  dialog = ChannelOwners::Dialog.from(params['dialog'])
132
147
  unless emit(Events::Page::Dialog, dialog)
133
- dialog.dismiss # FIXME: this should be asynchronous
148
+ dialog.dismiss
134
149
  end
135
150
  end
136
151
 
137
152
  # @override
138
153
  def on(event, callback)
139
154
  if event == Events::Page::FileChooser && listener_count(event) == 0
140
- @channel.send_no_reply('setFileChooserInterceptedNoReply', intercepted: true)
155
+ @channel.async_send_message_to_server('setFileChooserInterceptedNoReply', intercepted: true)
141
156
  end
142
157
  super
143
158
  end
@@ -145,7 +160,7 @@ module Playwright
145
160
  # @override
146
161
  def once(event, callback)
147
162
  if event == Events::Page::FileChooser && listener_count(event) == 0
148
- @channel.send_no_reply('setFileChooserInterceptedNoReply', intercepted: true)
163
+ @channel.async_send_message_to_server('setFileChooserInterceptedNoReply', intercepted: true)
149
164
  end
150
165
  super
151
166
  end
@@ -154,7 +169,7 @@ module Playwright
154
169
  def off(event, callback)
155
170
  super
156
171
  if event == Events::Page::FileChooser && listener_count(event) == 0
157
- @channel.send_no_reply('setFileChooserInterceptedNoReply', intercepted: false)
172
+ @channel.async_send_message_to_server('setFileChooserInterceptedNoReply', intercepted: false)
158
173
  end
159
174
  end
160
175
 
@@ -347,7 +362,7 @@ module Playwright
347
362
  def add_init_script(path: nil, script: nil)
348
363
  source =
349
364
  if path
350
- File.read(path, 'r')
365
+ File.read(path)
351
366
  elsif script
352
367
  script
353
368
  else
@@ -358,6 +373,23 @@ module Playwright
358
373
  nil
359
374
  end
360
375
 
376
+ def route(url, handler)
377
+ entry = RouteHandlerEntry.new(url, handler)
378
+ @routes << entry
379
+ if @routes.count >= 1
380
+ @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: true)
381
+ end
382
+ end
383
+
384
+ def unroute(url, handler: nil)
385
+ @routes.reject! do |handler_entry|
386
+ handler_entry.same_value?(url: url, handler: handler)
387
+ end
388
+ if @routes.count == 0
389
+ @channel.send_message_to_server('setNetworkInterceptionEnabled', enabled: false)
390
+ end
391
+ end
392
+
361
393
  def screenshot(
362
394
  path: nil,
363
395
  type: nil,
@@ -698,5 +730,10 @@ module Playwright
698
730
  private def timeout_settings
699
731
  @timeout_settings
700
732
  end
733
+
734
+ # called from BrowserContext#expose_binding
735
+ private def has_bindings?(name)
736
+ @bindings.key?(name)
737
+ end
701
738
  end
702
739
  end
@@ -20,6 +20,10 @@ module Playwright
20
20
  @electron ||= ::Playwright::ChannelOwners::Electron.from(@initializer['electron'])
21
21
  end
22
22
 
23
+ def selectors
24
+ @selectors ||= ::Playwright::ChannelOwners::Selectors.from(@initializer['selectors'])
25
+ end
26
+
23
27
  class DeviceDescriptor
24
28
  class Viewport
25
29
  def initialize(hash)
@@ -29,7 +29,7 @@ module Playwright
29
29
  end
30
30
 
31
31
  def method
32
- @initialize['method']
32
+ @initializer['method']
33
33
  end
34
34
 
35
35
  def post_data
@@ -69,7 +69,7 @@ module Playwright
69
69
  end
70
70
 
71
71
  def frame
72
- @initializer['frame']
72
+ ChannelOwners::Frame.from(@initializer['frame'])
73
73
  end
74
74
 
75
75
  def navigation_request?
@@ -90,6 +90,30 @@ module Playwright
90
90
  @failure_text = failure_text
91
91
  end
92
92
 
93
+ private def update_timings(
94
+ start_time:,
95
+ domain_lookup_start:,
96
+ domain_lookup_end:,
97
+ connect_start:,
98
+ secure_connection_start:,
99
+ connect_end:,
100
+ request_start:,
101
+ response_start:)
102
+
103
+ @timing["startTime"] = start_time
104
+ @timing["domainLookupStart"] = domain_lookup_start
105
+ @timing["domainLookupEnd"] = domain_lookup_end
106
+ @timing["connectStart"] = connect_start
107
+ @timing["secureConnectionStart"] = secure_connection_start
108
+ @timing["connectEnd"] = connect_end
109
+ @timing["requestStart"] = request_start
110
+ @timing["responseStart"] = response_start
111
+ end
112
+
113
+ private def update_headers(headers)
114
+ @headers = parse_headers(headers)
115
+ end
116
+
93
117
  private def update_response_end_timing(response_end_timing)
94
118
  @timing[:responseEnd] = response_end_timing
95
119
  end
@@ -1,5 +1,65 @@
1
+ require 'base64'
2
+ require 'json'
3
+
1
4
  module Playwright
2
5
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
3
6
  define_channel_owner :Response do
7
+ def after_initialize
8
+ @request = ChannelOwners::Request.from(@initializer['request'])
9
+ timing = @initializer['timing']
10
+ @request.send(:update_timings,
11
+ start_time: timing["startTime"],
12
+ domain_lookup_start: timing["domainLookupStart"],
13
+ domain_lookup_end: timing["domainLookupEnd"],
14
+ connect_start: timing["connectStart"],
15
+ secure_connection_start: timing["secureConnectionStart"],
16
+ connect_end: timing["connectEnd"],
17
+ request_start: timing["requestStart"],
18
+ response_start: timing["responseStart"],
19
+ )
20
+ @request.send(:update_headers, @initializer['requestHeaders'])
21
+ end
22
+ attr_reader :request
23
+
24
+ def url
25
+ @initializer['url']
26
+ end
27
+
28
+ def ok
29
+ status == 0 || (200...300).include?(status)
30
+ end
31
+ alias_method :ok?, :ok
32
+
33
+ def status
34
+ @initializer['status']
35
+ end
36
+
37
+ def status_text
38
+ @initializer['statusText']
39
+ end
40
+
41
+ def headers
42
+ @initializer['headers'].map do |header|
43
+ [header['name'].downcase, header['value']]
44
+ end.to_h
45
+ end
46
+
47
+ def finished
48
+ @channel.send_message_to_server('finished')
49
+ end
50
+
51
+ def body
52
+ binary = @channel.send_message_to_server("body")
53
+ Base64.strict_decode64(binary)
54
+ end
55
+ alias_method :text, :body
56
+
57
+ def json
58
+ JSON.parse(text)
59
+ end
60
+
61
+ def frame
62
+ @request.frame
63
+ end
4
64
  end
5
65
  end
@@ -0,0 +1,78 @@
1
+ require 'base64'
2
+ require 'mime/types'
3
+
4
+ module Playwright
5
+ define_channel_owner :Route do
6
+ def request
7
+ ChannelOwners::Request.from(@initializer['request'])
8
+ end
9
+
10
+ def abort(errorCode: nil)
11
+ params = { errorCode: errorCode }.compact
12
+ @channel.async_send_message_to_server('abort', params)
13
+ end
14
+
15
+ def fulfill(
16
+ body: nil,
17
+ contentType: nil,
18
+ headers: nil,
19
+ path: nil,
20
+ status: nil)
21
+ params = {
22
+ contentType: contentType,
23
+ status: status,
24
+ }.compact
25
+
26
+ length = 0
27
+ content =
28
+ if body
29
+ body
30
+ elsif path
31
+ File.read(path)
32
+ else
33
+ nil
34
+ end
35
+
36
+ param_headers = headers || {}
37
+ if contentType
38
+ param_headers['content-type'] = contentType
39
+ elsif path
40
+ param_headers['content-type'] = mime_type_for(path)
41
+ end
42
+
43
+ if content
44
+ if content.is_a?(String)
45
+ params[:body] = content
46
+ params[:isBase64] = false
47
+ else
48
+ params[:body] = Base64.strict_encode64(content)
49
+ params[:isBase64] = true
50
+ end
51
+ param_headers['content-length'] ||= content.length.to_s
52
+ end
53
+
54
+ params[:headers] = HttpHeaders.new(param_headers).as_serialized
55
+
56
+ @channel.async_send_message_to_server('fulfill', params)
57
+ end
58
+
59
+ def continue(headers: nil, method: nil, postData: nil, url: nil)
60
+ overrides = { url: url, method: method }.compact
61
+
62
+ if headers
63
+ overrides[:headers] = HttpHeaders.new(headers).as_serialized
64
+ end
65
+
66
+ if postData
67
+ overrides[:postData] = Base64.strict_encode64(postData)
68
+ end
69
+
70
+ @channel.async_send_message_to_server('continue', overrides)
71
+ end
72
+
73
+ private def mime_type_for(filepath)
74
+ mime_types = MIME::Types.type_for(filepath)
75
+ mime_types.first.to_s || 'application/octet-stream'
76
+ end
77
+ end
78
+ end
@@ -1,4 +1,22 @@
1
1
  module Playwright
2
2
  # https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_selectors.py
3
- define_channel_owner :Selectors
3
+ define_channel_owner :Selectors do
4
+ def register(name, contentScript: nil, path: nil, script: nil)
5
+ source =
6
+ if path
7
+ File.read(path)
8
+ elsif script
9
+ script
10
+ else
11
+ raise ArgumentError.new('Either path or script parameter must be specified')
12
+ end
13
+ params = { name: name, source: source }
14
+ if contentScript
15
+ params[:contentScript] = true
16
+ end
17
+ @channel.send_message_to_server('register', params)
18
+
19
+ nil
20
+ end
21
+ end
4
22
  end
@@ -0,0 +1,36 @@
1
+ module Playwright
2
+ class RouteHandlerEntry
3
+ # @param url [String]
4
+ # @param handler [Proc]
5
+ def initialize(url, handler)
6
+ @url_value = url
7
+ @url_matcher = UrlMatcher.new(url)
8
+ @handler = handler
9
+ end
10
+
11
+ def handle(route, request)
12
+ if url_match?(request.url)
13
+ @handler.call(route, request)
14
+ true
15
+ else
16
+ false
17
+ end
18
+ end
19
+
20
+ def same_value?(url:, handler: nil)
21
+ if handler
22
+ @url_value == url && @handler == handler
23
+ else
24
+ @url_value == url
25
+ end
26
+ end
27
+
28
+ private def url_match?(request_url)
29
+ if @url_value.is_a?(Regexp)
30
+ @url_matcher.match?(request_url)
31
+ else
32
+ @url_matcher.match?(request_url) || File.fnmatch?(@url_value, request_url)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -3,6 +3,7 @@ module Playwright
3
3
  module PrepareBrowserContextOptions
4
4
  # @see https://github.com/microsoft/playwright/blob/5a2cfdbd47ed3c3deff77bb73e5fac34241f649d/src/client/browserContext.ts#L265
5
5
  private def prepare_browser_context_options(params)
6
+ params[:sdkLanguage] = 'ruby'
6
7
  if params[:noViewport] == 0
7
8
  params.delete(:noViewport)
8
9
  params[:noDefaultViewport] = true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -1,28 +1,100 @@
1
1
  module Playwright
2
- # @nodoc
2
+ # Playwright has **experimental** support for Android automation. You can access android namespace via:
3
+ #
4
+ #
5
+ # ```js
6
+ # const { _android } = require('playwright');
7
+ # ```
8
+ #
9
+ # An example of the Android automation script would be:
10
+ #
11
+ #
12
+ # ```js
13
+ # const { _android } = require('playwright');
14
+ #
15
+ # (async () => {
16
+ # // Connect to the device.
17
+ # const [device] = await playwright._android.devices();
18
+ # console.log(`Model: ${device.model()}`);
19
+ # console.log(`Serial: ${device.serial()}`);
20
+ # // Take screenshot of the whole device.
21
+ # await device.screenshot({ path: 'device.png' });
22
+ #
23
+ # {
24
+ # // --------------------- WebView -----------------------
25
+ #
26
+ # // Launch an application with WebView.
27
+ # await device.shell('am force-stop org.chromium.webview_shell');
28
+ # await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
29
+ # // Get the WebView.
30
+ # const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
31
+ #
32
+ # // Fill the input box.
33
+ # await device.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'github.com/microsoft/playwright');
34
+ # await device.press({ res: 'org.chromium.webview_shell:id/url_field' }, 'Enter');
35
+ #
36
+ # // Work with WebView's page as usual.
37
+ # const page = await webview.page();
38
+ # await page.page.waitForNavigation({ url: /.*microsoft\/playwright.*/ });
39
+ # console.log(await page.title());
40
+ # }
41
+ #
42
+ # {
43
+ # // --------------------- Browser -----------------------
44
+ #
45
+ # // Launch Chrome browser.
46
+ # await device.shell('am force-stop com.android.chrome');
47
+ # const context = await device.launchBrowser();
48
+ #
49
+ # // Use BrowserContext as usual.
50
+ # const page = await context.newPage();
51
+ # await page.goto('https://webkit.org/');
52
+ # console.log(await page.evaluate(() => window.location.href));
53
+ # await page.screenshot({ path: 'page.png' });
54
+ #
55
+ # await context.close();
56
+ # }
57
+ #
58
+ # // Close the device.
59
+ # await device.close();
60
+ # })();
61
+ # ```
62
+ #
63
+ # Note that since you don't need Playwright to install web browsers when testing Android, you can omit browser download
64
+ # via setting the following environment variable when installing Playwright:
65
+ #
66
+ # ```sh js
67
+ # $ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D playwright
68
+ # ```
3
69
  class Android < PlaywrightApi
4
70
 
5
- # @nodoc
71
+ # Returns the list of detected Android devices.
6
72
  def devices
7
73
  wrap_impl(@impl.devices)
8
74
  end
9
75
 
76
+ # This setting will change the default maximum time for all the methods accepting `timeout` option.
77
+ def set_default_timeout(timeout)
78
+ raise NotImplementedError.new('set_default_timeout is not implemented yet.')
79
+ end
80
+ alias_method :default_timeout=, :set_default_timeout
81
+
10
82
  # -- inherited from EventEmitter --
11
83
  # @nodoc
12
- def once(event, callback)
13
- event_emitter_proxy.once(event, callback)
84
+ def off(event, callback)
85
+ event_emitter_proxy.off(event, callback)
14
86
  end
15
87
 
16
88
  # -- inherited from EventEmitter --
17
89
  # @nodoc
18
- def on(event, callback)
19
- event_emitter_proxy.on(event, callback)
90
+ def once(event, callback)
91
+ event_emitter_proxy.once(event, callback)
20
92
  end
21
93
 
22
94
  # -- inherited from EventEmitter --
23
95
  # @nodoc
24
- def off(event, callback)
25
- event_emitter_proxy.off(event, callback)
96
+ def on(event, callback)
97
+ event_emitter_proxy.on(event, callback)
26
98
  end
27
99
 
28
100
  private def event_emitter_proxy