playwright-ruby-client 0.3.0 → 0.5.6
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.
- checksums.yaml +4 -4
- data/README.md +17 -5
- data/docs/api_coverage.md +14 -25
- data/lib/playwright.rb +47 -9
- data/lib/playwright/channel.rb +19 -9
- data/lib/playwright/channel_owners/artifact.rb +30 -0
- data/lib/playwright/channel_owners/browser.rb +21 -0
- data/lib/playwright/channel_owners/browser_context.rb +5 -0
- data/lib/playwright/channel_owners/browser_type.rb +28 -0
- data/lib/playwright/channel_owners/element_handle.rb +7 -7
- data/lib/playwright/channel_owners/frame.rb +26 -5
- data/lib/playwright/channel_owners/js_handle.rb +2 -2
- data/lib/playwright/channel_owners/page.rb +78 -15
- data/lib/playwright/channel_owners/playwright.rb +22 -29
- data/lib/playwright/channel_owners/stream.rb +15 -0
- data/lib/playwright/connection.rb +11 -32
- data/lib/playwright/download.rb +27 -0
- data/lib/playwright/errors.rb +6 -0
- data/lib/playwright/events.rb +2 -5
- data/lib/playwright/keyboard_impl.rb +1 -1
- data/lib/playwright/mouse_impl.rb +41 -0
- data/lib/playwright/playwright_api.rb +5 -3
- data/lib/playwright/route_handler_entry.rb +1 -9
- data/lib/playwright/select_option_values.rb +31 -22
- data/lib/playwright/transport.rb +29 -7
- data/lib/playwright/url_matcher.rb +1 -1
- data/lib/playwright/utils.rb +8 -0
- data/lib/playwright/version.rb +1 -1
- data/lib/playwright/video.rb +51 -0
- data/lib/playwright/wait_helper.rb +2 -2
- data/lib/playwright_api/accessibility.rb +39 -1
- data/lib/playwright_api/android.rb +10 -10
- data/lib/playwright_api/android_device.rb +10 -9
- data/lib/playwright_api/browser.rb +83 -8
- data/lib/playwright_api/browser_context.rb +157 -9
- data/lib/playwright_api/browser_type.rb +35 -4
- data/lib/playwright_api/console_message.rb +6 -6
- data/lib/playwright_api/dialog.rb +28 -8
- data/lib/playwright_api/element_handle.rb +111 -37
- data/lib/playwright_api/file_chooser.rb +5 -0
- data/lib/playwright_api/frame.rb +228 -37
- data/lib/playwright_api/js_handle.rb +26 -3
- data/lib/playwright_api/keyboard.rb +48 -1
- data/lib/playwright_api/mouse.rb +26 -5
- data/lib/playwright_api/page.rb +454 -46
- data/lib/playwright_api/playwright.rb +26 -9
- data/lib/playwright_api/request.rb +34 -6
- data/lib/playwright_api/response.rb +6 -6
- data/lib/playwright_api/route.rb +30 -6
- data/lib/playwright_api/selectors.rb +32 -6
- data/lib/playwright_api/touchscreen.rb +1 -1
- data/lib/playwright_api/worker.rb +25 -1
- data/playwright.gemspec +4 -2
- metadata +37 -14
- data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
- data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
- data/lib/playwright/channel_owners/download.rb +0 -27
- data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
- data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
- data/lib/playwright_api/binding_call.rb +0 -32
- data/lib/playwright_api/chromium_browser_context.rb +0 -59
- data/lib/playwright_api/download.rb +0 -95
- data/lib/playwright_api/video.rb +0 -24
@@ -3,5 +3,46 @@ module Playwright
|
|
3
3
|
def initialize(channel)
|
4
4
|
@channel = channel
|
5
5
|
end
|
6
|
+
|
7
|
+
def move(x, y, steps: nil)
|
8
|
+
params = { x: x, y: y, steps: steps }.compact
|
9
|
+
@channel.send_message_to_server('mouseMove', params)
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def down(button: nil, clickCount: nil)
|
14
|
+
params = { button: button, clickCount: clickCount }.compact
|
15
|
+
@channel.send_message_to_server('mouseDown', params)
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def up(button: nil, clickCount: nil)
|
20
|
+
params = { button: button, clickCount: clickCount }.compact
|
21
|
+
@channel.send_message_to_server('mouseUp', params)
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def click(
|
26
|
+
x,
|
27
|
+
y,
|
28
|
+
button: nil,
|
29
|
+
clickCount: nil,
|
30
|
+
delay: nil)
|
31
|
+
|
32
|
+
params = {
|
33
|
+
x: x,
|
34
|
+
y: y,
|
35
|
+
button: button,
|
36
|
+
clickCount: clickCount,
|
37
|
+
delay: delay,
|
38
|
+
}.compact
|
39
|
+
@channel.send_message_to_server('mouseClick', params)
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def dblclick(x, y, button: nil, delay: nil)
|
45
|
+
click(x, y, button: button, clickCount: 2, delay: delay)
|
46
|
+
end
|
6
47
|
end
|
7
48
|
end
|
@@ -14,7 +14,7 @@ module Playwright
|
|
14
14
|
when ApiImplementation
|
15
15
|
ApiImplementationWrapper.new(channel_owner_or_api_implementation).wrap
|
16
16
|
else
|
17
|
-
|
17
|
+
channel_owner_or_api_implementation
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -119,12 +119,14 @@ module Playwright
|
|
119
119
|
if object.is_a?(Array)
|
120
120
|
object.map { |obj| wrap_impl(obj) }
|
121
121
|
else
|
122
|
-
::Playwright::PlaywrightApi.wrap(object)
|
122
|
+
::Playwright::PlaywrightApi.wrap(object)
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
126
|
private def unwrap_impl(object)
|
127
|
-
if object.is_a?(
|
127
|
+
if object.is_a?(Array)
|
128
|
+
object.map { |obj| unwrap_impl(obj) }
|
129
|
+
elsif object.is_a?(PlaywrightApi)
|
128
130
|
object.instance_variable_get(:@impl)
|
129
131
|
else
|
130
132
|
object
|
@@ -9,7 +9,7 @@ module Playwright
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def handle(route, request)
|
12
|
-
if
|
12
|
+
if @url_matcher.match?(request.url)
|
13
13
|
@handler.call(route, request)
|
14
14
|
true
|
15
15
|
else
|
@@ -24,13 +24,5 @@ module Playwright
|
|
24
24
|
@url_value == url
|
25
25
|
end
|
26
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
27
|
end
|
36
28
|
end
|
@@ -1,18 +1,30 @@
|
|
1
1
|
module Playwright
|
2
2
|
class SelectOptionValues
|
3
3
|
def initialize(element: nil, index: nil, value: nil, label: nil)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
4
|
+
params = {}
|
5
|
+
|
6
|
+
options = []
|
7
|
+
if value
|
8
|
+
options.concat(convert(:value, value))
|
9
|
+
end
|
10
|
+
|
11
|
+
if index
|
12
|
+
options.concat(convert(:index, index))
|
13
|
+
end
|
14
|
+
|
15
|
+
if label
|
16
|
+
options.concat(convert(:label, label))
|
17
|
+
end
|
18
|
+
|
19
|
+
unless options.empty?
|
20
|
+
params[:options] = options
|
21
|
+
end
|
22
|
+
|
23
|
+
if element
|
24
|
+
params[:elements] = convert(:element, element)
|
25
|
+
end
|
26
|
+
|
27
|
+
@params = params
|
16
28
|
end
|
17
29
|
|
18
30
|
# @return [Hash]
|
@@ -20,22 +32,19 @@ module Playwright
|
|
20
32
|
@params
|
21
33
|
end
|
22
34
|
|
23
|
-
private def convert(values)
|
24
|
-
return convert([values]) unless values.is_a?(Enumerable)
|
25
|
-
return
|
35
|
+
private def convert(key, values)
|
36
|
+
return convert(key, [values]) unless values.is_a?(Enumerable)
|
37
|
+
return [] if values.empty?
|
26
38
|
values.each_with_index do |value, index|
|
27
|
-
unless
|
39
|
+
unless value
|
28
40
|
raise ArgumentError.new("options[#{index}]: expected object, got null")
|
29
41
|
end
|
30
42
|
end
|
31
43
|
|
32
|
-
|
33
|
-
|
34
|
-
{ elements: values.map(&:channel) }
|
35
|
-
when String
|
36
|
-
{ options: values.map { |value| { value: value } } }
|
44
|
+
if key == :element
|
45
|
+
values.map(&:channel)
|
37
46
|
else
|
38
|
-
{
|
47
|
+
values.map { |value| { key => value } }
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
data/lib/playwright/transport.rb
CHANGED
@@ -12,39 +12,46 @@ module Playwright
|
|
12
12
|
def initialize(playwright_cli_executable_path:)
|
13
13
|
@driver_executable_path = playwright_cli_executable_path
|
14
14
|
@debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
|
15
|
+
@mutex = Mutex.new
|
15
16
|
end
|
16
17
|
|
17
18
|
def on_message_received(&block)
|
18
19
|
@on_message = block
|
19
20
|
end
|
20
21
|
|
22
|
+
def on_driver_crashed(&block)
|
23
|
+
@on_driver_crashed = block
|
24
|
+
end
|
25
|
+
|
21
26
|
class AlreadyDisconnectedError < StandardError ; end
|
22
27
|
|
23
28
|
# @param message [Hash]
|
24
29
|
def send_message(message)
|
25
30
|
debug_send_message(message) if @debug
|
26
31
|
msg = JSON.dump(message)
|
27
|
-
@
|
28
|
-
|
29
|
-
|
32
|
+
@mutex.synchronize {
|
33
|
+
@stdin.write([msg.bytes.length].pack('V')) # unsigned 32bit, little endian, real byte size instead of chars
|
34
|
+
@stdin.write(msg) # write UTF-8 in binary mode as byte stream
|
35
|
+
}
|
36
|
+
rescue Errno::EPIPE, IOError
|
30
37
|
raise AlreadyDisconnectedError.new('send_message failed')
|
31
38
|
end
|
32
39
|
|
33
40
|
# Terminate playwright-cli driver.
|
34
41
|
def stop
|
35
42
|
[@stdin, @stdout, @stderr].each { |io| io.close unless io.closed? }
|
43
|
+
@thread&.terminate
|
36
44
|
end
|
37
45
|
|
38
46
|
# Start `playwright-cli run-driver`
|
39
47
|
#
|
40
48
|
# @note This method blocks until playwright-cli exited. Consider using Thread or Future.
|
41
|
-
def
|
42
|
-
@stdin, @stdout, @stderr, @thread = Open3.popen3(@driver_executable_path
|
49
|
+
def async_run
|
50
|
+
@stdin, @stdout, @stderr, @thread = Open3.popen3("#{@driver_executable_path} run-driver")
|
51
|
+
@stdin.binmode # Ensure Strings are written 1:1 without encoding conversion, necessary for integer values
|
43
52
|
|
44
53
|
Thread.new { handle_stdout }
|
45
54
|
Thread.new { handle_stderr }
|
46
|
-
|
47
|
-
@thread.join
|
48
55
|
end
|
49
56
|
|
50
57
|
private
|
@@ -69,6 +76,21 @@ module Playwright
|
|
69
76
|
|
70
77
|
def handle_stderr
|
71
78
|
while err = @stderr.read
|
79
|
+
# sometimed driver crashes with the error below.
|
80
|
+
# --------
|
81
|
+
# undefined:1
|
82
|
+
# �
|
83
|
+
# ^
|
84
|
+
|
85
|
+
# SyntaxError: Unexpected token � in JSON at position 0
|
86
|
+
# at JSON.parse (<anonymous>)
|
87
|
+
# at Transport.transport.onmessage (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/cli/driver.js:42:73)
|
88
|
+
# at Immediate.<anonymous> (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/protocol/transport.js:74:26)
|
89
|
+
# at processImmediate (internal/timers.js:461:21)
|
90
|
+
if err.include?('undefined:1')
|
91
|
+
@on_driver_crashed&.call
|
92
|
+
break
|
93
|
+
end
|
72
94
|
$stderr.write(err)
|
73
95
|
end
|
74
96
|
rescue IOError
|
data/lib/playwright/utils.rb
CHANGED
@@ -11,6 +11,14 @@ module Playwright
|
|
11
11
|
if params[:extraHTTPHeaders]
|
12
12
|
params[:extraHTTPHeaders] = ::Playwright::HttpHeaders.new(params[:extraHTTPHeaders]).as_serialized
|
13
13
|
end
|
14
|
+
if params[:record_video_dir]
|
15
|
+
params[:recordVideo] = {
|
16
|
+
dir: params.delete(:record_video_dir)
|
17
|
+
}
|
18
|
+
if params[:record_video_size]
|
19
|
+
params[:recordVideo][:size] = params.delete(:record_video_size)
|
20
|
+
end
|
21
|
+
end
|
14
22
|
if params[:storageState].is_a?(String)
|
15
23
|
params[:storageState] = JSON.parse(File.read(params[:storageState]))
|
16
24
|
end
|
data/lib/playwright/version.rb
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Playwright
|
2
|
+
class Video
|
3
|
+
def initialize(page)
|
4
|
+
@page = page
|
5
|
+
@artifact = Concurrent::Promises.resolvable_future
|
6
|
+
if @page.closed?
|
7
|
+
on_page_closed
|
8
|
+
else
|
9
|
+
page.once('close', -> { on_page_closed })
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private def on_page_closed
|
14
|
+
unless @artifact.resolved?
|
15
|
+
@artifact.reject('Page closed')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# called only from Page#on_video via send(:set_artifact, artifact)
|
20
|
+
private def set_artifact(artifact)
|
21
|
+
@artifact.fulfill(artifact)
|
22
|
+
end
|
23
|
+
|
24
|
+
def path
|
25
|
+
wait_for_artifact_and do |artifact|
|
26
|
+
artifact.absolute_path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def save_as(path)
|
31
|
+
wait_for_artifact_and do |artifact|
|
32
|
+
artifact.save_as(path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
wait_for_artifact_and do |artifact|
|
38
|
+
artifact.delete
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private def wait_for_artifact_and(&block)
|
43
|
+
artifact = @artifact.value!
|
44
|
+
unless artifact
|
45
|
+
raise 'Page did not produce any video frames'
|
46
|
+
end
|
47
|
+
|
48
|
+
block.call(artifact)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -22,9 +22,9 @@ module Playwright
|
|
22
22
|
def reject_on_timeout(timeout_ms, message)
|
23
23
|
return if timeout_ms <= 0
|
24
24
|
|
25
|
-
Concurrent::Promises.schedule(timeout_ms / 1000.0)
|
25
|
+
Concurrent::Promises.schedule(timeout_ms / 1000.0) do
|
26
26
|
reject(TimeoutError.new(message: message))
|
27
|
-
|
27
|
+
end
|
28
28
|
|
29
29
|
self
|
30
30
|
end
|
@@ -6,7 +6,7 @@ module Playwright
|
|
6
6
|
# Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
|
7
7
|
# have wildly different output.
|
8
8
|
#
|
9
|
-
# Rendering engines of Chromium, Firefox and
|
9
|
+
# Rendering engines of Chromium, Firefox and WebKit have a concept of "accessibility tree", which is then translated into
|
10
10
|
# different platform-specific APIs. Accessibility namespace gives access to this Accessibility Tree.
|
11
11
|
#
|
12
12
|
# Most of the accessibility tree gets filtered out when converting from internal browser AX Tree to Platform-specific
|
@@ -28,6 +28,11 @@ module Playwright
|
|
28
28
|
# console.log(snapshot);
|
29
29
|
# ```
|
30
30
|
#
|
31
|
+
# ```java
|
32
|
+
# String snapshot = page.accessibility().snapshot();
|
33
|
+
# System.out.println(snapshot);
|
34
|
+
# ```
|
35
|
+
#
|
31
36
|
# ```python async
|
32
37
|
# snapshot = await page.accessibility.snapshot()
|
33
38
|
# print(snapshot)
|
@@ -38,6 +43,11 @@ module Playwright
|
|
38
43
|
# print(snapshot)
|
39
44
|
# ```
|
40
45
|
#
|
46
|
+
# ```csharp
|
47
|
+
# var accessibilitySnapshot = await Page.Accessibility.SnapshotAsync();
|
48
|
+
# Console.WriteLine(accessibilitySnapshot);
|
49
|
+
# ```
|
50
|
+
#
|
41
51
|
# An example of logging the focused node's name:
|
42
52
|
#
|
43
53
|
#
|
@@ -57,6 +67,34 @@ module Playwright
|
|
57
67
|
# }
|
58
68
|
# ```
|
59
69
|
#
|
70
|
+
# ```csharp
|
71
|
+
# Func<AccessibilitySnapshotResult, AccessibilitySnapshotResult> findFocusedNode = root =>
|
72
|
+
# {
|
73
|
+
# var nodes = new Stack<AccessibilitySnapshotResult>(new[] { root });
|
74
|
+
# while (nodes.Count > 0)
|
75
|
+
# {
|
76
|
+
# var node = nodes.Pop();
|
77
|
+
# if (node.Focused) return node;
|
78
|
+
# foreach (var innerNode in node.Children)
|
79
|
+
# {
|
80
|
+
# nodes.Push(innerNode);
|
81
|
+
# }
|
82
|
+
# }
|
83
|
+
#
|
84
|
+
# return null;
|
85
|
+
# };
|
86
|
+
#
|
87
|
+
# var accessibilitySnapshot = await Page.Accessibility.SnapshotAsync();
|
88
|
+
# var focusedNode = findFocusedNode(accessibilitySnapshot);
|
89
|
+
# if(focusedNode != null)
|
90
|
+
# Console.WriteLine(focusedNode.Name);
|
91
|
+
# ```
|
92
|
+
#
|
93
|
+
# ```java
|
94
|
+
# // FIXME
|
95
|
+
# String snapshot = page.accessibility().snapshot();
|
96
|
+
# ```
|
97
|
+
#
|
60
98
|
# ```python async
|
61
99
|
# def find_focused_node(node):
|
62
100
|
# if (node.get("focused"))
|
@@ -3,18 +3,18 @@ module Playwright
|
|
3
3
|
#
|
4
4
|
#
|
5
5
|
# ```js
|
6
|
-
# const { _android } = require('playwright');
|
6
|
+
# const { _android: android } = require('playwright');
|
7
7
|
# ```
|
8
8
|
#
|
9
9
|
# An example of the Android automation script would be:
|
10
10
|
#
|
11
11
|
#
|
12
12
|
# ```js
|
13
|
-
# const { _android } = require('playwright');
|
13
|
+
# const { _android: android } = require('playwright');
|
14
14
|
#
|
15
15
|
# (async () => {
|
16
16
|
# // Connect to the device.
|
17
|
-
# const [device] = await
|
17
|
+
# const [device] = await android.devices();
|
18
18
|
# console.log(`Model: ${device.model()}`);
|
19
19
|
# console.log(`Serial: ${device.serial()}`);
|
20
20
|
# // Take screenshot of the whole device.
|
@@ -35,7 +35,7 @@ module Playwright
|
|
35
35
|
#
|
36
36
|
# // Work with WebView's page as usual.
|
37
37
|
# const page = await webview.page();
|
38
|
-
# await page.
|
38
|
+
# await page.waitForNavigation({ url: /.*microsoft\/playwright.*/ });
|
39
39
|
# console.log(await page.title());
|
40
40
|
# }
|
41
41
|
#
|
@@ -79,12 +79,6 @@ module Playwright
|
|
79
79
|
end
|
80
80
|
alias_method :default_timeout=, :set_default_timeout
|
81
81
|
|
82
|
-
# -- inherited from EventEmitter --
|
83
|
-
# @nodoc
|
84
|
-
def off(event, callback)
|
85
|
-
event_emitter_proxy.off(event, callback)
|
86
|
-
end
|
87
|
-
|
88
82
|
# -- inherited from EventEmitter --
|
89
83
|
# @nodoc
|
90
84
|
def once(event, callback)
|
@@ -97,6 +91,12 @@ module Playwright
|
|
97
91
|
event_emitter_proxy.on(event, callback)
|
98
92
|
end
|
99
93
|
|
94
|
+
# -- inherited from EventEmitter --
|
95
|
+
# @nodoc
|
96
|
+
def off(event, callback)
|
97
|
+
event_emitter_proxy.off(event, callback)
|
98
|
+
end
|
99
|
+
|
100
100
|
private def event_emitter_proxy
|
101
101
|
@event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
|
102
102
|
end
|