playwright-ruby-client 0.2.1 → 0.3.0

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