playwright-ruby-client 0.0.8 → 1.58.1.alpha1
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/AGENTS.md +4 -0
- data/CLAUDE/api_generation.md +28 -0
- data/CLAUDE/ci_expectations.md +23 -0
- data/CLAUDE/gem_release_flow.md +39 -0
- data/CLAUDE/past_upgrade_pr_patterns.md +42 -0
- data/CLAUDE/playwright_upgrade_workflow.md +35 -0
- data/CLAUDE/rspec_debugging.md +30 -0
- data/CLAUDE/unimplemented_examples.md +18 -0
- data/CLAUDE.md +32 -0
- data/CONTRIBUTING.md +5 -0
- data/README.md +60 -16
- data/documentation/README.md +33 -0
- data/documentation/babel.config.js +3 -0
- data/documentation/docs/api/api_request.md +7 -0
- data/documentation/docs/api/api_request_context.md +298 -0
- data/documentation/docs/api/api_response.md +114 -0
- data/documentation/docs/api/browser.md +237 -0
- data/documentation/docs/api/browser_context.md +503 -0
- data/documentation/docs/api/browser_type.md +184 -0
- data/documentation/docs/api/cdp_session.md +44 -0
- data/documentation/docs/api/clock.md +154 -0
- data/documentation/docs/api/console_message.md +85 -0
- data/documentation/docs/api/dialog.md +84 -0
- data/documentation/docs/api/download.md +111 -0
- data/documentation/docs/api/element_handle.md +694 -0
- data/documentation/docs/api/experimental/_category_.yml +3 -0
- data/documentation/docs/api/experimental/android.md +42 -0
- data/documentation/docs/api/experimental/android_device.md +109 -0
- data/documentation/docs/api/experimental/android_input.md +43 -0
- data/documentation/docs/api/experimental/android_socket.md +7 -0
- data/documentation/docs/api/experimental/android_web_view.md +7 -0
- data/documentation/docs/api/file_chooser.md +53 -0
- data/documentation/docs/api/frame.md +1218 -0
- data/documentation/docs/api/frame_locator.md +348 -0
- data/documentation/docs/api/js_handle.md +121 -0
- data/documentation/docs/api/keyboard.md +170 -0
- data/documentation/docs/api/locator.md +1495 -0
- data/documentation/docs/api/locator_assertions.md +827 -0
- data/documentation/docs/api/mouse.md +86 -0
- data/documentation/docs/api/page.md +1946 -0
- data/documentation/docs/api/page_assertions.md +65 -0
- data/documentation/docs/api/playwright.md +66 -0
- data/documentation/docs/api/request.md +255 -0
- data/documentation/docs/api/response.md +176 -0
- data/documentation/docs/api/route.md +205 -0
- data/documentation/docs/api/selectors.md +63 -0
- data/documentation/docs/api/touchscreen.md +22 -0
- data/documentation/docs/api/tracing.md +129 -0
- data/documentation/docs/api/web_socket.md +51 -0
- data/documentation/docs/api/worker.md +83 -0
- data/documentation/docs/article/api_coverage.mdx +11 -0
- data/documentation/docs/article/getting_started.md +161 -0
- data/documentation/docs/article/guides/_category_.yml +3 -0
- data/documentation/docs/article/guides/download_playwright_driver.md +55 -0
- data/documentation/docs/article/guides/inspector.md +31 -0
- data/documentation/docs/article/guides/launch_browser.md +121 -0
- data/documentation/docs/article/guides/playwright_on_alpine_linux.md +112 -0
- data/documentation/docs/article/guides/rails_integration.md +278 -0
- data/documentation/docs/article/guides/rails_integration_with_null_driver.md +145 -0
- data/documentation/docs/article/guides/recording_video.md +79 -0
- data/documentation/docs/article/guides/rspec_integration.md +59 -0
- data/documentation/docs/article/guides/semi_automation.md +71 -0
- data/documentation/docs/article/guides/use_storage_state.md +78 -0
- data/documentation/docs/include/api_coverage.md +671 -0
- data/documentation/docusaurus.config.js +114 -0
- data/documentation/package.json +39 -0
- data/documentation/sidebars.js +15 -0
- data/documentation/src/components/HomepageFeatures.js +61 -0
- data/documentation/src/components/HomepageFeatures.module.css +13 -0
- data/documentation/src/css/custom.css +44 -0
- data/documentation/src/pages/index.js +49 -0
- data/documentation/src/pages/index.module.css +41 -0
- data/documentation/src/pages/markdown-page.md +7 -0
- data/documentation/static/.nojekyll +0 -0
- data/documentation/static/img/playwright-logo.svg +9 -0
- data/documentation/static/img/playwright-ruby-client.png +0 -0
- data/documentation/static/img/undraw_dropdown_menu.svg +1 -0
- data/documentation/static/img/undraw_web_development.svg +1 -0
- data/documentation/static/img/undraw_windows.svg +1 -0
- data/documentation/yarn.lock +9005 -0
- data/lib/playwright/{input_types/android_input.rb → android_input_impl.rb} +5 -1
- data/lib/playwright/api_implementation.rb +18 -0
- data/lib/playwright/api_response_impl.rb +77 -0
- data/lib/playwright/channel.rb +62 -1
- data/lib/playwright/channel_owner.rb +70 -7
- data/lib/playwright/channel_owners/android.rb +16 -3
- data/lib/playwright/channel_owners/android_device.rb +22 -66
- data/lib/playwright/channel_owners/api_request_context.rb +247 -0
- data/lib/playwright/channel_owners/artifact.rb +40 -0
- data/lib/playwright/channel_owners/binding_call.rb +70 -0
- data/lib/playwright/channel_owners/browser.rb +114 -22
- data/lib/playwright/channel_owners/browser_context.rb +589 -15
- data/lib/playwright/channel_owners/browser_type.rb +90 -1
- data/lib/playwright/channel_owners/cdp_session.rb +19 -0
- data/lib/playwright/channel_owners/dialog.rb +32 -0
- data/lib/playwright/channel_owners/element_handle.rb +107 -43
- data/lib/playwright/channel_owners/fetch_request.rb +8 -0
- data/lib/playwright/channel_owners/frame.rb +334 -104
- data/lib/playwright/channel_owners/js_handle.rb +9 -13
- data/lib/playwright/channel_owners/local_utils.rb +82 -0
- data/lib/playwright/channel_owners/page.rb +778 -95
- data/lib/playwright/channel_owners/playwright.rb +25 -30
- data/lib/playwright/channel_owners/request.rb +120 -18
- data/lib/playwright/channel_owners/response.rb +113 -0
- data/lib/playwright/channel_owners/route.rb +181 -0
- data/lib/playwright/channel_owners/stream.rb +30 -0
- data/lib/playwright/channel_owners/tracing.rb +117 -0
- data/lib/playwright/channel_owners/web_socket.rb +96 -0
- data/lib/playwright/channel_owners/worker.rb +46 -0
- data/lib/playwright/channel_owners/writable_stream.rb +14 -0
- data/lib/playwright/clock_impl.rb +67 -0
- data/lib/playwright/connection.rb +111 -63
- data/lib/playwright/console_message_impl.rb +29 -0
- data/lib/playwright/download_impl.rb +32 -0
- data/lib/playwright/errors.rb +42 -5
- data/lib/playwright/event_emitter.rb +17 -3
- data/lib/playwright/event_emitter_proxy.rb +49 -0
- data/lib/playwright/events.rb +10 -5
- data/lib/playwright/file_chooser_impl.rb +24 -0
- data/lib/playwright/frame_locator_impl.rb +66 -0
- data/lib/playwright/har_router.rb +89 -0
- data/lib/playwright/http_headers.rb +14 -0
- data/lib/playwright/input_files.rb +102 -15
- data/lib/playwright/javascript/expression.rb +7 -11
- data/lib/playwright/javascript/regex.rb +23 -0
- data/lib/playwright/javascript/source_url.rb +16 -0
- data/lib/playwright/javascript/value_parser.rb +108 -19
- data/lib/playwright/javascript/value_serializer.rb +47 -8
- data/lib/playwright/javascript/visitor_info.rb +26 -0
- data/lib/playwright/javascript.rb +2 -10
- data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +6 -2
- data/lib/playwright/locator_assertions_impl.rb +571 -0
- data/lib/playwright/locator_impl.rb +544 -0
- data/lib/playwright/locator_utils.rb +136 -0
- data/lib/playwright/mouse_impl.rb +57 -0
- data/lib/playwright/page_assertions_impl.rb +154 -0
- data/lib/playwright/playwright_api.rb +102 -30
- data/lib/playwright/raw_headers.rb +61 -0
- data/lib/playwright/route_handler.rb +78 -0
- data/lib/playwright/select_option_values.rb +34 -13
- data/lib/playwright/selectors_impl.rb +45 -0
- data/lib/playwright/test.rb +102 -0
- data/lib/playwright/timeout_settings.rb +9 -4
- data/lib/playwright/touchscreen_impl.rb +14 -0
- data/lib/playwright/transport.rb +61 -10
- data/lib/playwright/url_matcher.rb +24 -2
- data/lib/playwright/utils.rb +48 -13
- data/lib/playwright/version.rb +2 -1
- data/lib/playwright/video.rb +54 -0
- data/lib/playwright/waiter.rb +166 -0
- data/lib/playwright/web_socket_client.rb +167 -0
- data/lib/playwright/web_socket_transport.rb +116 -0
- data/lib/playwright.rb +188 -11
- data/lib/playwright_api/android.rb +46 -11
- data/lib/playwright_api/android_device.rb +182 -31
- data/lib/playwright_api/android_input.rb +22 -13
- data/lib/playwright_api/android_socket.rb +18 -0
- data/lib/playwright_api/android_web_view.rb +24 -0
- data/lib/playwright_api/api_request.rb +26 -0
- data/lib/playwright_api/api_request_context.rb +311 -0
- data/lib/playwright_api/api_response.rb +92 -0
- data/lib/playwright_api/browser.rb +116 -103
- data/lib/playwright_api/browser_context.rb +290 -389
- data/lib/playwright_api/browser_type.rb +96 -118
- data/lib/playwright_api/cdp_session.rb +36 -39
- data/lib/playwright_api/clock.rb +121 -0
- data/lib/playwright_api/console_message.rb +35 -19
- data/lib/playwright_api/dialog.rb +53 -50
- data/lib/playwright_api/download.rb +49 -43
- data/lib/playwright_api/element_handle.rb +354 -402
- data/lib/playwright_api/file_chooser.rb +15 -18
- data/lib/playwright_api/frame.rb +703 -603
- data/lib/playwright_api/frame_locator.rb +285 -0
- data/lib/playwright_api/js_handle.rb +50 -76
- data/lib/playwright_api/keyboard.rb +67 -146
- data/lib/playwright_api/locator.rb +1304 -0
- data/lib/playwright_api/locator_assertions.rb +704 -0
- data/lib/playwright_api/mouse.rb +23 -29
- data/lib/playwright_api/page.rb +1196 -1176
- data/lib/playwright_api/page_assertions.rb +60 -0
- data/lib/playwright_api/playwright.rb +54 -122
- data/lib/playwright_api/request.rb +112 -74
- data/lib/playwright_api/response.rb +92 -20
- data/lib/playwright_api/route.rb +152 -62
- data/lib/playwright_api/selectors.rb +47 -61
- data/lib/playwright_api/touchscreen.rb +8 -2
- data/lib/playwright_api/tracing.rb +128 -0
- data/lib/playwright_api/web_socket.rb +43 -5
- data/lib/playwright_api/worker.rb +74 -34
- data/playwright.gemspec +14 -9
- data/sig/playwright.rbs +658 -0
- metadata +216 -50
- data/docs/api_coverage.md +0 -354
- 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/console_message.rb +0 -21
- data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
- data/lib/playwright/channel_owners/selectors.rb +0 -4
- data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
- data/lib/playwright/input_type.rb +0 -19
- data/lib/playwright/input_types/mouse.rb +0 -4
- data/lib/playwright/input_types/touchscreen.rb +0 -4
- data/lib/playwright/javascript/function.rb +0 -67
- data/lib/playwright/wait_helper.rb +0 -73
- data/lib/playwright_api/accessibility.rb +0 -93
- data/lib/playwright_api/binding_call.rb +0 -23
- data/lib/playwright_api/chromium_browser_context.rb +0 -57
- data/lib/playwright_api/video.rb +0 -24
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
define_api_implementation :DownloadImpl do
|
|
3
|
+
def initialize(page:, url:, suggested_filename:, artifact:)
|
|
4
|
+
@page = page
|
|
5
|
+
@url = url
|
|
6
|
+
@suggested_filename = suggested_filename
|
|
7
|
+
@artifact = artifact
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :page, :url, :suggested_filename
|
|
11
|
+
|
|
12
|
+
def delete
|
|
13
|
+
@artifact.delete
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def failure
|
|
17
|
+
@artifact.failure
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def path
|
|
21
|
+
@artifact.path_after_finished
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def save_as(path)
|
|
25
|
+
@artifact.save_as(path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cancel
|
|
29
|
+
@artifact.cancel
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/playwright/errors.rb
CHANGED
|
@@ -5,13 +5,18 @@ module Playwright
|
|
|
5
5
|
if error_payload['name'] == 'TimeoutError'
|
|
6
6
|
TimeoutError.new(
|
|
7
7
|
message: error_payload['message'],
|
|
8
|
-
stack: error_payload['stack']
|
|
8
|
+
stack: error_payload['stack'],
|
|
9
|
+
)
|
|
10
|
+
elsif error_payload['name'] == 'TargetClosedError'
|
|
11
|
+
TargetClosedError.new(
|
|
12
|
+
message: error_payload['message'],
|
|
13
|
+
stack: error_payload['stack'],
|
|
9
14
|
)
|
|
10
15
|
else
|
|
11
16
|
new(
|
|
12
17
|
name: error_payload['name'],
|
|
13
18
|
message: error_payload['message'],
|
|
14
|
-
stack: error_payload['stack']
|
|
19
|
+
stack: error_payload['stack'],
|
|
15
20
|
)
|
|
16
21
|
end
|
|
17
22
|
end
|
|
@@ -19,17 +24,49 @@ module Playwright
|
|
|
19
24
|
# @param name [String]
|
|
20
25
|
# @param message [String]
|
|
21
26
|
# @param stack [Array<String>]
|
|
22
|
-
def initialize(
|
|
23
|
-
super(
|
|
27
|
+
def initialize(message:, name: nil, stack: nil)
|
|
28
|
+
super(message)
|
|
24
29
|
@name = name
|
|
25
30
|
@message = message
|
|
26
31
|
@stack = stack
|
|
27
32
|
end
|
|
33
|
+
|
|
34
|
+
attr_reader :name, :message, :stack
|
|
35
|
+
|
|
36
|
+
def log=(log)
|
|
37
|
+
return unless log
|
|
38
|
+
format_call_log = log.join("\n - ")
|
|
39
|
+
@message = "#{@message}\nCall log:\n#{format_call_log}\n"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class DriverCrashedError < StandardError
|
|
44
|
+
def initialize
|
|
45
|
+
super("[BUG] Playwright driver is crashed!")
|
|
46
|
+
end
|
|
28
47
|
end
|
|
29
48
|
|
|
30
49
|
class TimeoutError < Error
|
|
31
|
-
def initialize(message:, stack:)
|
|
50
|
+
def initialize(message:, stack: [])
|
|
32
51
|
super(name: 'TimeoutError', message: message, stack: stack)
|
|
33
52
|
end
|
|
34
53
|
end
|
|
54
|
+
|
|
55
|
+
class TargetClosedError < Error
|
|
56
|
+
def initialize(message: nil, stack: [])
|
|
57
|
+
_message = message || 'Target page, context or browser has been closed'
|
|
58
|
+
super(name: 'TargetClosedError', message: _message, stack: stack)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class WebError
|
|
63
|
+
def initialize(error, page)
|
|
64
|
+
@error = error
|
|
65
|
+
@page = PlaywrightApi.wrap(page)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
attr_reader :error, :page
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class AssertionError < StandardError; end
|
|
35
72
|
end
|
|
@@ -34,11 +34,25 @@ module Playwright
|
|
|
34
34
|
# A subset of Events/EventEmitter in Node.js
|
|
35
35
|
module EventEmitter
|
|
36
36
|
# @param event [String]
|
|
37
|
+
# @returns [Boolean]
|
|
37
38
|
def emit(event, *args)
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
handled = false
|
|
40
|
+
if (callbacks = (@__event_emitter ||= {})[event.to_s])
|
|
41
|
+
callbacks.dup.each do |callback|
|
|
42
|
+
perform_event_emitter_callback(event, callback, args)
|
|
43
|
+
handled = true
|
|
44
|
+
end
|
|
40
45
|
end
|
|
41
|
-
|
|
46
|
+
handled
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private def listener_count(event)
|
|
50
|
+
((@__event_emitter ||= {})[event.to_s] ||= Set.new).count
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# can be overriden
|
|
54
|
+
private def perform_event_emitter_callback(event, callback, args)
|
|
55
|
+
callback.call(*args)
|
|
42
56
|
end
|
|
43
57
|
|
|
44
58
|
# @param event [String]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
class EventEmitterProxy
|
|
3
|
+
include EventEmitter
|
|
4
|
+
|
|
5
|
+
# @param src [PlaywrightApi]
|
|
6
|
+
# @param dest [EventEmitter]
|
|
7
|
+
def initialize(api, impl)
|
|
8
|
+
@api = api
|
|
9
|
+
@impl = impl
|
|
10
|
+
@listeners = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def on(event, callback)
|
|
14
|
+
if listener_count(event) == 0
|
|
15
|
+
subscribe(event)
|
|
16
|
+
end
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def once(event, callback)
|
|
21
|
+
if listener_count(event) == 0
|
|
22
|
+
subscribe(event)
|
|
23
|
+
end
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def off(event, callback)
|
|
28
|
+
super
|
|
29
|
+
if listener_count(event) == 0
|
|
30
|
+
unsubscribe(event)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def subscribe(event)
|
|
35
|
+
@listeners[event] = ->(*args) {
|
|
36
|
+
wrapped_args = args.map { |arg| ::Playwright::PlaywrightApi.wrap(arg) }
|
|
37
|
+
emit(event, *wrapped_args)
|
|
38
|
+
}
|
|
39
|
+
@impl.on(event, @listeners[event])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private def unsubscribe(event)
|
|
43
|
+
listener = @listeners.delete(event)
|
|
44
|
+
if listener
|
|
45
|
+
@impl.off(event, listener)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/playwright/events.rb
CHANGED
|
@@ -24,8 +24,17 @@ end
|
|
|
24
24
|
},
|
|
25
25
|
|
|
26
26
|
BrowserContext: {
|
|
27
|
+
BackgroundPage: 'backgroundpage',
|
|
27
28
|
Close: 'close',
|
|
29
|
+
Console: 'console',
|
|
30
|
+
Dialog: 'dialog',
|
|
28
31
|
Page: 'page',
|
|
32
|
+
WebError: 'weberror',
|
|
33
|
+
ServiceWorker: 'serviceworker',
|
|
34
|
+
Request: 'request',
|
|
35
|
+
Response: 'response',
|
|
36
|
+
RequestFailed: 'requestfailed',
|
|
37
|
+
RequestFinished: 'requestfinished',
|
|
29
38
|
},
|
|
30
39
|
|
|
31
40
|
BrowserServer: {
|
|
@@ -65,11 +74,7 @@ end
|
|
|
65
74
|
|
|
66
75
|
Worker: {
|
|
67
76
|
Close: 'close',
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ChromiumBrowserContext: {
|
|
71
|
-
BackgroundPage: 'backgroundpage',
|
|
72
|
-
ServiceWorker: 'serviceworker',
|
|
77
|
+
Console: 'console',
|
|
73
78
|
},
|
|
74
79
|
|
|
75
80
|
ElectronApplication: {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
define_api_implementation :FileChooserImpl do
|
|
3
|
+
def initialize(page:, timeout_settings:, element_handle:, is_multiple:)
|
|
4
|
+
@page = page
|
|
5
|
+
@timeout_settings = timeout_settings
|
|
6
|
+
@element_handle = element_handle
|
|
7
|
+
@is_multiple = is_multiple
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :page
|
|
11
|
+
|
|
12
|
+
def element
|
|
13
|
+
@element_handle
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def multiple?
|
|
17
|
+
@is_multiple
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def set_files(files, noWaitAfter: nil, timeout: nil)
|
|
21
|
+
@element_handle.set_input_files(files, noWaitAfter: noWaitAfter, timeout: @timeout_settings.timeout(timeout))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require_relative './locator_utils'
|
|
2
|
+
|
|
3
|
+
module Playwright
|
|
4
|
+
define_api_implementation :FrameLocatorImpl do
|
|
5
|
+
include LocatorUtils
|
|
6
|
+
|
|
7
|
+
def initialize(frame:, frame_selector:)
|
|
8
|
+
@frame = frame
|
|
9
|
+
@frame_selector = frame_selector
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private def _timeout(timeout)
|
|
13
|
+
@frame.send(:_timeout, timeout)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def locator(
|
|
17
|
+
selector,
|
|
18
|
+
has: nil,
|
|
19
|
+
hasNot: nil,
|
|
20
|
+
hasNotText: nil,
|
|
21
|
+
hasText: nil)
|
|
22
|
+
LocatorImpl.new(
|
|
23
|
+
frame: @frame,
|
|
24
|
+
selector: "#{@frame_selector} >> internal:control=enter-frame >> #{selector}",
|
|
25
|
+
has: has,
|
|
26
|
+
hasNot: hasNot,
|
|
27
|
+
hasNotText: hasNotText,
|
|
28
|
+
hasText: hasText)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def owner
|
|
32
|
+
LocatorImpl.new(
|
|
33
|
+
frame: @frame,
|
|
34
|
+
selector: @frame_selector,
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def frame_locator(selector)
|
|
39
|
+
FrameLocatorImpl.new(
|
|
40
|
+
frame: @frame,
|
|
41
|
+
frame_selector: "#{@frame_selector} >> internal:control=enter-frame >> #{selector}",
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def first
|
|
46
|
+
FrameLocatorImpl.new(
|
|
47
|
+
frame: @frame,
|
|
48
|
+
frame_selector: "#{@frame_selector} >> nth=0",
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def last
|
|
53
|
+
FrameLocatorImpl.new(
|
|
54
|
+
frame: @frame,
|
|
55
|
+
frame_selector: "#{@frame_selector} >> nth=-1",
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def nth(index)
|
|
60
|
+
FrameLocatorImpl.new(
|
|
61
|
+
frame: @frame,
|
|
62
|
+
frame_selector: "#{@frame_selector} >> nth=#{index}",
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
class HarRouter
|
|
3
|
+
# @param local_utils [LocalUtils]
|
|
4
|
+
# @param file [String]
|
|
5
|
+
# @param not_found_action [String] 'abort' or 'fallback'
|
|
6
|
+
# @param url_match [String||Regexp|nil]
|
|
7
|
+
def self.create(local_utils, file, not_found_action, url_match: nil)
|
|
8
|
+
har_id = local_utils.har_open(file)
|
|
9
|
+
|
|
10
|
+
new(
|
|
11
|
+
local_utils: local_utils,
|
|
12
|
+
har_id: har_id,
|
|
13
|
+
not_found_action: not_found_action,
|
|
14
|
+
url_match: url_match,
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param local_utils [LocalUtils]
|
|
19
|
+
# @param har_id [String]
|
|
20
|
+
# @param not_found_action [String] 'abort' or 'fallback'
|
|
21
|
+
# @param url_match [String||Regexp|nil]
|
|
22
|
+
def initialize(local_utils:, har_id:, not_found_action:, url_match: nil)
|
|
23
|
+
unless ['abort', 'fallback'].include?(not_found_action)
|
|
24
|
+
raise ArgumentError.new("not_found_action must be either 'abort' or 'fallback'. '#{not_found_action}' is specified.")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@local_utils = local_utils
|
|
28
|
+
@har_id = har_id
|
|
29
|
+
@not_found_action = not_found_action
|
|
30
|
+
@url_match = url_match || '**/*'
|
|
31
|
+
@debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def handle(route, request)
|
|
35
|
+
response = @local_utils.har_lookup(
|
|
36
|
+
har_id: @har_id,
|
|
37
|
+
url: request.url,
|
|
38
|
+
method: request.method,
|
|
39
|
+
headers: request.headers_array,
|
|
40
|
+
post_data: request.post_data_buffer,
|
|
41
|
+
is_navigation_request: request.navigation_request?,
|
|
42
|
+
)
|
|
43
|
+
case response['action']
|
|
44
|
+
when 'redirect'
|
|
45
|
+
redirect_url = response['redirectURL']
|
|
46
|
+
puts "pw:api HAR: #{request.url} redirected to #{redirect_url}" if @debug
|
|
47
|
+
route.redirect_navigation_request(redirect_url)
|
|
48
|
+
when 'fulfill'
|
|
49
|
+
# If the response status is -1, the request was canceled or stalled, so we just stall it here.
|
|
50
|
+
# See https://github.com/microsoft/playwright/issues/29311.
|
|
51
|
+
# TODO: it'd be better to abort such requests, but then we likely need to respect the timing,
|
|
52
|
+
# because the request might have been stalled for a long time until the very end of the
|
|
53
|
+
# test when HAR was recorded but we'd abort it immediately.
|
|
54
|
+
return if response['status'] == -1
|
|
55
|
+
|
|
56
|
+
route.fulfill(
|
|
57
|
+
status: response['status'],
|
|
58
|
+
headers: response['headers'].map { |header| [header['name'], header['value']] }.to_h,
|
|
59
|
+
body: Base64.strict_decode64(response['body']),
|
|
60
|
+
)
|
|
61
|
+
else
|
|
62
|
+
# Report the error, but fall through to the default handler.
|
|
63
|
+
if response['action'] == 'error'
|
|
64
|
+
puts "pw:api HAR: #{response['message']} redirected to #{redirect_url}" if @debug
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if @not_found_action == 'abort'
|
|
68
|
+
route.abort
|
|
69
|
+
else
|
|
70
|
+
route.fallback
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def add_context_route(context)
|
|
76
|
+
context.route(@url_match, method(:handle))
|
|
77
|
+
context.once(Events::BrowserContext::Close, method(:dispose))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def add_page_route(page)
|
|
81
|
+
page.route(@url_match, method(:handle))
|
|
82
|
+
page.once(Events::Page::Close, method(:dispose))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def dispose
|
|
86
|
+
@local_utils.async_har_close(@har_id)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -1,42 +1,129 @@
|
|
|
1
1
|
require 'base64'
|
|
2
|
-
require 'mime/types'
|
|
3
2
|
|
|
4
3
|
module Playwright
|
|
5
4
|
class InputFiles
|
|
6
|
-
def initialize(files)
|
|
7
|
-
@
|
|
5
|
+
def initialize(context, files)
|
|
6
|
+
@context = context
|
|
7
|
+
if files.is_a?(File)
|
|
8
|
+
files_to_resolve = [files]
|
|
9
|
+
elsif files.is_a?(Enumerable)
|
|
10
|
+
files_to_resolve = files
|
|
11
|
+
else
|
|
12
|
+
files_to_resolve = [files]
|
|
13
|
+
end
|
|
14
|
+
resolve_paths_and_directory_for_input_files(files_to_resolve)
|
|
8
15
|
end
|
|
9
16
|
|
|
10
|
-
def
|
|
11
|
-
@
|
|
17
|
+
def as_method_and_params
|
|
18
|
+
if @local_directory || has_large_file?
|
|
19
|
+
['setInputFiles', params_for_set_input_file_paths]
|
|
20
|
+
else
|
|
21
|
+
['setInputFiles', params_for_set_input_files]
|
|
22
|
+
end
|
|
12
23
|
end
|
|
13
24
|
|
|
14
|
-
private def
|
|
15
|
-
|
|
25
|
+
private def has_large_file?
|
|
26
|
+
max_bufsize = 1024 * 1024 # 1MB
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
@local_files.any? do |file|
|
|
29
|
+
case file
|
|
30
|
+
when String
|
|
31
|
+
File::Stat.new(file).size > max_bufsize
|
|
32
|
+
when File
|
|
33
|
+
file.stat.size > max_bufsize
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private def resolve_paths_and_directory_for_input_files(files)
|
|
39
|
+
# filter_map is not available in Ruby < 2.7
|
|
40
|
+
@local_files = files.map do |item|
|
|
41
|
+
case item
|
|
42
|
+
when String
|
|
43
|
+
if File.directory?(item)
|
|
44
|
+
raise ArgumentError.new('Multiple directories are not supported') if @local_directory
|
|
45
|
+
@local_directory = item
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
item
|
|
50
|
+
when File
|
|
51
|
+
if File.directory?(item.path)
|
|
52
|
+
raise ArgumentError.new('Multiple directories are not supported') if @local_directory
|
|
53
|
+
@local_directory = item
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
item
|
|
58
|
+
else
|
|
59
|
+
raise ArgumentError.new('file must be a String or File or Directory.')
|
|
60
|
+
end
|
|
61
|
+
end.compact
|
|
62
|
+
|
|
63
|
+
if @local_directory && !@local_files.empty?
|
|
64
|
+
raise ArgumentError.new('File paths must be all files or a single directory')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private def params_for_set_input_file_paths
|
|
69
|
+
if @local_directory
|
|
70
|
+
filenames = Dir["#{@local_directory}/**/*"].reject { |file| File.directory?(file) }
|
|
71
|
+
directory_stream, writable_streams = @context.send(:create_temp_files, @local_directory, filenames)
|
|
72
|
+
|
|
73
|
+
filenames.zip(writable_streams).each do |filename, writable_stream|
|
|
74
|
+
File.open(filename, 'rb') do |file|
|
|
75
|
+
writable_stream.write(file)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
{ directoryStream: directory_stream.channel }
|
|
80
|
+
else
|
|
81
|
+
filenames = @local_files.map do |file|
|
|
82
|
+
case file
|
|
83
|
+
when String
|
|
84
|
+
file
|
|
85
|
+
when File
|
|
86
|
+
file.path
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
_, writable_streams = @context.send(:create_temp_files, nil, filenames)
|
|
91
|
+
@local_files.zip(writable_streams).each do |file, writable_stream|
|
|
92
|
+
case file
|
|
93
|
+
when String
|
|
94
|
+
File.open(file, 'rb') do |file|
|
|
95
|
+
writable_stream.write(file)
|
|
96
|
+
end
|
|
97
|
+
when File
|
|
98
|
+
writable_stream.write(file)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
{ streams: writable_streams.map(&:channel) }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private def params_for_set_input_files
|
|
107
|
+
file_payloads = @local_files.map do |file|
|
|
18
108
|
case file
|
|
19
109
|
when String
|
|
20
110
|
{
|
|
21
111
|
name: File.basename(file),
|
|
22
|
-
mimeType: mime_type_for(file),
|
|
23
112
|
buffer: Base64.strict_encode64(File.read(file)),
|
|
24
113
|
}
|
|
25
114
|
when File
|
|
26
115
|
{
|
|
27
116
|
name: File.basename(file.path),
|
|
28
|
-
mimeType: mime_type_for(file.path),
|
|
29
117
|
buffer: Base64.strict_encode64(file.read),
|
|
30
118
|
}
|
|
31
|
-
else
|
|
32
|
-
raise ArgumentError.new('file must be a String or File.')
|
|
33
119
|
end
|
|
34
120
|
end
|
|
121
|
+
|
|
122
|
+
{ payloads: file_payloads }
|
|
35
123
|
end
|
|
36
124
|
|
|
37
|
-
private def
|
|
38
|
-
|
|
39
|
-
mime_types.first || 'application/octet-stream'
|
|
125
|
+
private def raise_argument_error
|
|
126
|
+
raise ArgumentError.new('file must be a String or File.')
|
|
40
127
|
end
|
|
41
128
|
end
|
|
42
129
|
end
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
module Playwright
|
|
2
2
|
module JavaScript
|
|
3
3
|
class Expression
|
|
4
|
-
def initialize(expression)
|
|
4
|
+
def initialize(expression, arg)
|
|
5
5
|
@expression = expression
|
|
6
|
-
@serialized_arg = ValueSerializer.new(
|
|
6
|
+
@serialized_arg = ValueSerializer.new(arg).serialize
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def evaluate(channel)
|
|
10
10
|
value = channel.send_message_to_server(
|
|
11
11
|
'evaluateExpression',
|
|
12
12
|
expression: @expression,
|
|
13
|
-
isFunction: false,
|
|
14
13
|
arg: @serialized_arg,
|
|
15
14
|
)
|
|
16
15
|
ValueParser.new(value).parse
|
|
@@ -20,20 +19,19 @@ module Playwright
|
|
|
20
19
|
resp = channel.send_message_to_server(
|
|
21
20
|
'evaluateExpressionHandle',
|
|
22
21
|
expression: @expression,
|
|
23
|
-
isFunction: false,
|
|
24
22
|
arg: @serialized_arg,
|
|
25
23
|
)
|
|
26
24
|
::Playwright::ChannelOwner.from(resp)
|
|
27
25
|
end
|
|
28
26
|
|
|
29
|
-
def eval_on_selector(channel, selector)
|
|
30
|
-
|
|
31
|
-
'evalOnSelector',
|
|
27
|
+
def eval_on_selector(channel, selector, strict: nil)
|
|
28
|
+
params = {
|
|
32
29
|
selector: selector,
|
|
33
30
|
expression: @expression,
|
|
34
|
-
isFunction: false,
|
|
35
31
|
arg: @serialized_arg,
|
|
36
|
-
|
|
32
|
+
}
|
|
33
|
+
params[:strict] = strict if strict
|
|
34
|
+
value = channel.send_message_to_server('evalOnSelector', params)
|
|
37
35
|
ValueParser.new(value).parse
|
|
38
36
|
end
|
|
39
37
|
|
|
@@ -42,7 +40,6 @@ module Playwright
|
|
|
42
40
|
'evalOnSelectorAll',
|
|
43
41
|
selector: selector,
|
|
44
42
|
expression: @expression,
|
|
45
|
-
isFunction: false,
|
|
46
43
|
arg: @serialized_arg,
|
|
47
44
|
)
|
|
48
45
|
ValueParser.new(value).parse
|
|
@@ -51,7 +48,6 @@ module Playwright
|
|
|
51
48
|
def wait_for_function(channel, polling:, timeout:)
|
|
52
49
|
params = {
|
|
53
50
|
expression: @expression,
|
|
54
|
-
isFunction: false,
|
|
55
51
|
arg: @serialized_arg,
|
|
56
52
|
polling: polling,
|
|
57
53
|
timeout: timeout,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
module JavaScript
|
|
3
|
+
class Regex
|
|
4
|
+
def initialize(regexp)
|
|
5
|
+
unless regexp.is_a?(Regexp)
|
|
6
|
+
raise ArgumentError("Argument must be a Regexp: #{regexp} (#{regexp.class})")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
@source = regexp.source
|
|
10
|
+
@flag = flag_for(regexp)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :source, :flag
|
|
14
|
+
|
|
15
|
+
private def flag_for(regexp)
|
|
16
|
+
flags = []
|
|
17
|
+
flags << 'ms' if (regexp.options & Regexp::MULTILINE) != 0
|
|
18
|
+
flags << 'i' if (regexp.options & Regexp::IGNORECASE) != 0
|
|
19
|
+
flags.join('')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
module JavaScript
|
|
3
|
+
class SourceUrl
|
|
4
|
+
# @param source [String]
|
|
5
|
+
# @param path [String]
|
|
6
|
+
def initialize(source, path)
|
|
7
|
+
@source = source
|
|
8
|
+
@source_url = path.to_s.gsub("\n", '')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
"#{@source}\n//# sourceURL=#{@source_url}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|