browserctl 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +27 -32
- data/bin/browserctl +117 -108
- data/bin/browserd +9 -3
- data/examples/cloudflare_hitl.rb +5 -5
- data/examples/smoke/params_file.rb +3 -2
- data/examples/smoke/store_fetch.rb +5 -5
- data/examples/test_automation_practices/checkboxes.rb +39 -0
- data/examples/test_automation_practices/dynamic_elements.rb +40 -0
- data/examples/test_automation_practices/key_press.rb +41 -0
- data/examples/test_automation_practices/login.rb +34 -0
- data/examples/test_automation_practices/login_negative.rb +28 -0
- data/examples/test_automation_practices/notifications.rb +57 -0
- data/examples/the_internet/add_remove_elements.rb +1 -1
- data/examples/the_internet/checkboxes.rb +1 -1
- data/examples/the_internet/dropdown.rb +1 -1
- data/examples/the_internet/dynamic_loading.rb +2 -2
- data/examples/the_internet/login.rb +1 -1
- data/lib/browserctl/client.rb +101 -28
- data/lib/browserctl/commands/cookie.rb +59 -0
- data/lib/browserctl/commands/daemon.rb +77 -0
- data/lib/browserctl/commands/page.rb +47 -0
- data/lib/browserctl/commands/record.rb +1 -1
- data/lib/browserctl/commands/screenshot.rb +2 -2
- data/lib/browserctl/commands/session.rb +69 -0
- data/lib/browserctl/commands/snapshot.rb +2 -2
- data/lib/browserctl/commands/storage.rb +67 -0
- data/lib/browserctl/commands/workflow.rb +64 -0
- data/lib/browserctl/constants.rb +20 -1
- data/lib/browserctl/logger.rb +4 -4
- data/lib/browserctl/recording.rb +4 -4
- data/lib/browserctl/server/command_dispatcher.rb +22 -9
- data/lib/browserctl/server/handlers/cookies.rb +1 -1
- data/lib/browserctl/server/handlers/devtools.rb +1 -1
- data/lib/browserctl/server/handlers/hitl.rb +2 -1
- data/lib/browserctl/server/handlers/navigation.rb +24 -2
- data/lib/browserctl/server/handlers/observation.rb +0 -26
- data/lib/browserctl/server/handlers/page_lifecycle.rb +10 -3
- data/lib/browserctl/server/handlers/session.rb +93 -0
- data/lib/browserctl/server/handlers/storage.rb +109 -0
- data/lib/browserctl/server.rb +2 -2
- data/lib/browserctl/session.rb +79 -0
- data/lib/browserctl/version.rb +1 -1
- data/lib/browserctl/workflow.rb +38 -11
- metadata +19 -11
- data/lib/browserctl/commands/export_cookies.rb +0 -18
- data/lib/browserctl/commands/import_cookies.rb +0 -23
- data/lib/browserctl/commands/inspect.rb +0 -21
- data/lib/browserctl/commands/open_page.rb +0 -21
- data/lib/browserctl/commands/pause.rb +0 -22
- data/lib/browserctl/commands/status.rb +0 -30
- data/lib/browserctl/commands/watch.rb +0 -27
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browserctl
|
|
4
|
+
class CommandDispatcher
|
|
5
|
+
module Handlers
|
|
6
|
+
module Storage
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Returns { ok: true, value: } or { error: }
|
|
10
|
+
def cmd_storage_get(req)
|
|
11
|
+
with_page(req[:name]) do |session|
|
|
12
|
+
store = req.fetch(:store, "local")
|
|
13
|
+
js = storage_js_get(store, req[:key])
|
|
14
|
+
return { error: "unknown store '#{store}' — use 'local' or 'session'" } unless js
|
|
15
|
+
|
|
16
|
+
value = session.page.evaluate(js)
|
|
17
|
+
{ ok: true, value: value }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns { ok: true } or { error: }
|
|
22
|
+
def cmd_storage_set(req)
|
|
23
|
+
with_page(req[:name]) do |session|
|
|
24
|
+
store = req.fetch(:store, "local")
|
|
25
|
+
js = storage_js_set(store, req[:key], req[:value])
|
|
26
|
+
return { error: "unknown store '#{store}' — use 'local' or 'session'" } unless js
|
|
27
|
+
|
|
28
|
+
session.page.evaluate(js)
|
|
29
|
+
{ ok: true }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Exports localStorage and/or sessionStorage to a JSON file.
|
|
34
|
+
# Returns { ok: true, path:, key_count: } or { error: }
|
|
35
|
+
def cmd_storage_export(req)
|
|
36
|
+
with_page(req[:name]) do |session|
|
|
37
|
+
stores = req.fetch(:stores, "all")
|
|
38
|
+
data = {}
|
|
39
|
+
|
|
40
|
+
origin = session.page.evaluate("location.origin")
|
|
41
|
+
data[origin] = {}
|
|
42
|
+
|
|
43
|
+
if %w[local all].include?(stores)
|
|
44
|
+
local = JSON.parse(session.page.evaluate("JSON.stringify({...localStorage})") || "{}")
|
|
45
|
+
data[origin].merge!(local)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if %w[session all].include?(stores)
|
|
49
|
+
sess = JSON.parse(session.page.evaluate("JSON.stringify({...sessionStorage})") || "{}")
|
|
50
|
+
data[origin].merge!(sess)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
path = File.expand_path(req[:path])
|
|
54
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
55
|
+
File.write(path, JSON.generate(data))
|
|
56
|
+
{ ok: true, path: path, key_count: data[origin].length }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Imports keys from a JSON file into the page's localStorage only.
|
|
61
|
+
# sessionStorage keys in the file are ignored (sessionStorage is tab-scoped and not restorable).
|
|
62
|
+
# Returns { ok: true, origins: N, key_count: M } or { error: }
|
|
63
|
+
def cmd_storage_import(req)
|
|
64
|
+
path = File.expand_path(req[:path])
|
|
65
|
+
return { error: "file not found: #{path}" } unless File.exist?(path)
|
|
66
|
+
|
|
67
|
+
data = JSON.parse(File.read(path))
|
|
68
|
+
return { error: "invalid storage file format" } unless data.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
with_page(req[:name]) do |session|
|
|
71
|
+
key_count = 0
|
|
72
|
+
data.each_value do |keys|
|
|
73
|
+
keys.each do |k, v|
|
|
74
|
+
session.page.evaluate("localStorage.setItem(#{k.to_json}, #{v.to_json})")
|
|
75
|
+
key_count += 1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
{ ok: true, origins: data.length, key_count: key_count }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Clears localStorage and/or sessionStorage for the page.
|
|
83
|
+
# Returns { ok: true } or { error: }
|
|
84
|
+
def cmd_storage_delete(req)
|
|
85
|
+
with_page(req[:name]) do |session|
|
|
86
|
+
stores = req.fetch(:stores, "all")
|
|
87
|
+
session.page.evaluate("localStorage.clear()") if %w[local all].include?(stores)
|
|
88
|
+
session.page.evaluate("sessionStorage.clear()") if %w[session all].include?(stores)
|
|
89
|
+
{ ok: true }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def storage_js_get(store, key)
|
|
94
|
+
case store
|
|
95
|
+
when "local" then "localStorage.getItem(#{key.to_json})"
|
|
96
|
+
when "session" then "sessionStorage.getItem(#{key.to_json})"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def storage_js_set(store, key, value)
|
|
101
|
+
case store
|
|
102
|
+
when "local" then "localStorage.setItem(#{key.to_json}, #{value.to_json})"
|
|
103
|
+
when "session" then "sessionStorage.setItem(#{key.to_json}, #{value.to_json})"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/browserctl/server.rb
CHANGED
|
@@ -76,7 +76,7 @@ module Browserctl
|
|
|
76
76
|
return unless pid.positive?
|
|
77
77
|
|
|
78
78
|
Process.kill(0, pid)
|
|
79
|
-
abort "browserd already running (PID #{pid}). Use 'browserctl
|
|
79
|
+
abort "browserd already running (PID #{pid}). Use 'browserctl daemon stop' first."
|
|
80
80
|
rescue Errno::ESRCH
|
|
81
81
|
# Dead process — stale PID file, safe to continue
|
|
82
82
|
rescue Errno::EPERM
|
|
@@ -125,7 +125,7 @@ module Browserctl
|
|
|
125
125
|
|
|
126
126
|
def quietly
|
|
127
127
|
yield
|
|
128
|
-
rescue Exception
|
|
128
|
+
rescue Exception
|
|
129
129
|
nil
|
|
130
130
|
end
|
|
131
131
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "constants"
|
|
6
|
+
|
|
7
|
+
module Browserctl
|
|
8
|
+
class Session
|
|
9
|
+
BASE_DIR = File.join(BROWSERCTL_DIR, "sessions")
|
|
10
|
+
|
|
11
|
+
SAFE_NAME = /\A[a-zA-Z0-9_-]{1,64}\z/
|
|
12
|
+
|
|
13
|
+
def self.path(name) = File.join(BASE_DIR, name)
|
|
14
|
+
def self.exist?(name) = Dir.exist?(path(name))
|
|
15
|
+
|
|
16
|
+
def self.delete(name)
|
|
17
|
+
validate_name!(name)
|
|
18
|
+
FileUtils.rm_rf(path(name))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.all
|
|
22
|
+
Dir[File.join(BASE_DIR, "*/metadata.json")].filter_map { |f| load_meta(f) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.save(session_name, metadata:, cookies:, local_storage:, session_storage:)
|
|
26
|
+
validate_name!(session_name)
|
|
27
|
+
dir = path(session_name)
|
|
28
|
+
FileUtils.mkdir_p(dir)
|
|
29
|
+
write_json(File.join(dir, "metadata.json"), metadata)
|
|
30
|
+
write_secret(File.join(dir, "cookies.json"), cookies)
|
|
31
|
+
write_secret(File.join(dir, "local_storage.json"), local_storage)
|
|
32
|
+
return if session_storage.empty?
|
|
33
|
+
|
|
34
|
+
write_secret(File.join(dir, "session_storage.json"), session_storage)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.load(session_name)
|
|
38
|
+
validate_name!(session_name)
|
|
39
|
+
dir = path(session_name)
|
|
40
|
+
raise "session '#{session_name}' not found" unless Dir.exist?(dir)
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
metadata: JSON.parse(File.read(File.join(dir, "metadata.json")), symbolize_names: true),
|
|
44
|
+
cookies: JSON.parse(File.read(File.join(dir, "cookies.json")), symbolize_names: true),
|
|
45
|
+
local_storage: JSON.parse(File.read(File.join(dir, "local_storage.json")), symbolize_names: false),
|
|
46
|
+
session_storage: load_session_storage(dir)
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.load_meta(path)
|
|
51
|
+
JSON.parse(File.read(path), symbolize_names: true)
|
|
52
|
+
rescue JSON::ParserError
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.validate_name!(name)
|
|
57
|
+
return if SAFE_NAME.match?(name.to_s)
|
|
58
|
+
|
|
59
|
+
raise ArgumentError, "invalid session name #{name.inspect} — use letters, digits, _ or - (max 64 chars)"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.load_session_storage(dir)
|
|
63
|
+
ss_path = File.join(dir, "session_storage.json")
|
|
64
|
+
File.exist?(ss_path) ? JSON.parse(File.read(ss_path), symbolize_names: false) : {}
|
|
65
|
+
end
|
|
66
|
+
private_class_method :load_session_storage
|
|
67
|
+
|
|
68
|
+
def self.write_json(path, data)
|
|
69
|
+
File.write(path, JSON.generate(data))
|
|
70
|
+
end
|
|
71
|
+
private_class_method :write_json
|
|
72
|
+
|
|
73
|
+
# Cookies and storage contain secrets — restrict to owner read/write only.
|
|
74
|
+
def self.write_secret(path, data)
|
|
75
|
+
File.open(path, "w", 0o600) { |f| f.write(JSON.generate(data)) }
|
|
76
|
+
end
|
|
77
|
+
private_class_method :write_secret
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/browserctl/version.rb
CHANGED
data/lib/browserctl/workflow.rb
CHANGED
|
@@ -47,19 +47,37 @@ module Browserctl
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def open_page(page_name, url: nil)
|
|
50
|
-
res = @client.
|
|
50
|
+
res = @client.page_open(page_name.to_s, url: url)
|
|
51
51
|
raise WorkflowError, res[:error] if res[:error]
|
|
52
52
|
|
|
53
53
|
res
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def close_page(page_name)
|
|
57
|
-
res = @client.
|
|
57
|
+
res = @client.page_close(page_name.to_s)
|
|
58
58
|
raise WorkflowError, res[:error] if res[:error]
|
|
59
59
|
|
|
60
60
|
res
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
def save_session(session_name)
|
|
64
|
+
res = @client.session_save(session_name)
|
|
65
|
+
raise WorkflowError, res[:error] if res[:error]
|
|
66
|
+
|
|
67
|
+
res
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_session(session_name)
|
|
71
|
+
res = @client.session_load(session_name)
|
|
72
|
+
raise WorkflowError, res[:error] if res[:error]
|
|
73
|
+
|
|
74
|
+
res
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def list_sessions
|
|
78
|
+
@client.session_list[:sessions]
|
|
79
|
+
end
|
|
80
|
+
|
|
63
81
|
def invoke(workflow_name, **override_params)
|
|
64
82
|
name = workflow_name.to_s
|
|
65
83
|
guard_circular!(name)
|
|
@@ -100,15 +118,24 @@ module Browserctl
|
|
|
100
118
|
@client = client
|
|
101
119
|
end
|
|
102
120
|
|
|
103
|
-
def
|
|
104
|
-
def fill(sel, val)
|
|
105
|
-
def click(sel)
|
|
106
|
-
def snapshot(**)
|
|
107
|
-
def screenshot(**)
|
|
108
|
-
def
|
|
109
|
-
def
|
|
110
|
-
def
|
|
111
|
-
def
|
|
121
|
+
def navigate(url) = unwrap @client.navigate(@name, url)
|
|
122
|
+
def fill(sel, val) = unwrap @client.fill(@name, sel, val)
|
|
123
|
+
def click(sel) = unwrap @client.click(@name, sel)
|
|
124
|
+
def snapshot(**) = unwrap @client.snapshot(@name, **)
|
|
125
|
+
def screenshot(**) = unwrap @client.screenshot(@name, **)
|
|
126
|
+
def wait(sel, timeout: 30) = unwrap @client.wait(@name, sel, timeout: timeout)
|
|
127
|
+
def delete_cookies = unwrap @client.delete_cookies(@name)
|
|
128
|
+
def devtools = @client.devtools(@name)[:devtools_url]
|
|
129
|
+
def url = @client.url(@name)[:url]
|
|
130
|
+
def evaluate(expr) = @client.evaluate(@name, expr)[:result]
|
|
131
|
+
|
|
132
|
+
def storage_get(key, store: "local")
|
|
133
|
+
@client.storage_get(@name, key, store: store)[:value]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def storage_set(key, value, store: "local")
|
|
137
|
+
unwrap @client.storage_set(@name, key, value, store: store)
|
|
138
|
+
end
|
|
112
139
|
|
|
113
140
|
private
|
|
114
141
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: browserctl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ferrum
|
|
@@ -58,14 +58,14 @@ dependencies:
|
|
|
58
58
|
requirements:
|
|
59
59
|
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '1
|
|
61
|
+
version: '2.1'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '1
|
|
68
|
+
version: '2.1'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: rake
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -141,6 +141,12 @@ files:
|
|
|
141
141
|
- examples/cloudflare_hitl.rb
|
|
142
142
|
- examples/smoke/params_file.rb
|
|
143
143
|
- examples/smoke/store_fetch.rb
|
|
144
|
+
- examples/test_automation_practices/checkboxes.rb
|
|
145
|
+
- examples/test_automation_practices/dynamic_elements.rb
|
|
146
|
+
- examples/test_automation_practices/key_press.rb
|
|
147
|
+
- examples/test_automation_practices/login.rb
|
|
148
|
+
- examples/test_automation_practices/login_negative.rb
|
|
149
|
+
- examples/test_automation_practices/notifications.rb
|
|
144
150
|
- examples/the_internet/add_remove_elements.rb
|
|
145
151
|
- examples/the_internet/checkboxes.rb
|
|
146
152
|
- examples/the_internet/dropdown.rb
|
|
@@ -150,19 +156,18 @@ files:
|
|
|
150
156
|
- lib/browserctl/client.rb
|
|
151
157
|
- lib/browserctl/commands/cli_output.rb
|
|
152
158
|
- lib/browserctl/commands/click.rb
|
|
153
|
-
- lib/browserctl/commands/
|
|
159
|
+
- lib/browserctl/commands/cookie.rb
|
|
160
|
+
- lib/browserctl/commands/daemon.rb
|
|
154
161
|
- lib/browserctl/commands/fill.rb
|
|
155
|
-
- lib/browserctl/commands/import_cookies.rb
|
|
156
162
|
- lib/browserctl/commands/init.rb
|
|
157
|
-
- lib/browserctl/commands/
|
|
158
|
-
- lib/browserctl/commands/open_page.rb
|
|
159
|
-
- lib/browserctl/commands/pause.rb
|
|
163
|
+
- lib/browserctl/commands/page.rb
|
|
160
164
|
- lib/browserctl/commands/record.rb
|
|
161
165
|
- lib/browserctl/commands/resume.rb
|
|
162
166
|
- lib/browserctl/commands/screenshot.rb
|
|
167
|
+
- lib/browserctl/commands/session.rb
|
|
163
168
|
- lib/browserctl/commands/snapshot.rb
|
|
164
|
-
- lib/browserctl/commands/
|
|
165
|
-
- lib/browserctl/commands/
|
|
169
|
+
- lib/browserctl/commands/storage.rb
|
|
170
|
+
- lib/browserctl/commands/workflow.rb
|
|
166
171
|
- lib/browserctl/constants.rb
|
|
167
172
|
- lib/browserctl/detectors.rb
|
|
168
173
|
- lib/browserctl/errors.rb
|
|
@@ -179,9 +184,12 @@ files:
|
|
|
179
184
|
- lib/browserctl/server/handlers/navigation.rb
|
|
180
185
|
- lib/browserctl/server/handlers/observation.rb
|
|
181
186
|
- lib/browserctl/server/handlers/page_lifecycle.rb
|
|
187
|
+
- lib/browserctl/server/handlers/session.rb
|
|
188
|
+
- lib/browserctl/server/handlers/storage.rb
|
|
182
189
|
- lib/browserctl/server/idle_watcher.rb
|
|
183
190
|
- lib/browserctl/server/page_session.rb
|
|
184
191
|
- lib/browserctl/server/snapshot_builder.rb
|
|
192
|
+
- lib/browserctl/session.rb
|
|
185
193
|
- lib/browserctl/version.rb
|
|
186
194
|
- lib/browserctl/workflow.rb
|
|
187
195
|
homepage: https://github.com/patrick204nqh/browserctl
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Browserctl
|
|
4
|
-
module Commands
|
|
5
|
-
class ExportCookies
|
|
6
|
-
def self.run(client, args)
|
|
7
|
-
page = args.shift or abort "usage: browserctl export-cookies <page> <path>"
|
|
8
|
-
path = args.shift or abort "usage: browserctl export-cookies <page> <path>"
|
|
9
|
-
result = client.export_cookies(page, path)
|
|
10
|
-
if result[:error]
|
|
11
|
-
warn "Error: #{result[:error]}"
|
|
12
|
-
exit 1
|
|
13
|
-
end
|
|
14
|
-
puts result.to_json
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Browserctl
|
|
4
|
-
module Commands
|
|
5
|
-
class ImportCookies
|
|
6
|
-
def self.run(client, args)
|
|
7
|
-
page = args.shift or abort "usage: browserctl import-cookies <page> <path>"
|
|
8
|
-
path = args.shift or abort "usage: browserctl import-cookies <page> <path>"
|
|
9
|
-
begin
|
|
10
|
-
result = client.import_cookies(page, path)
|
|
11
|
-
rescue StandardError => e
|
|
12
|
-
warn "Error: #{e.message}"
|
|
13
|
-
exit 1
|
|
14
|
-
end
|
|
15
|
-
if result[:error]
|
|
16
|
-
warn "Error: #{result[:error]}"
|
|
17
|
-
exit 1
|
|
18
|
-
end
|
|
19
|
-
puts result.to_json
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Browserctl
|
|
4
|
-
module Commands
|
|
5
|
-
class Inspect
|
|
6
|
-
def self.run(client, args)
|
|
7
|
-
name = args.shift or abort "usage: browserctl inspect <page>"
|
|
8
|
-
res = client.inspect_page(name)
|
|
9
|
-
if res[:error]
|
|
10
|
-
warn "Error: #{res[:error]}"
|
|
11
|
-
exit 1
|
|
12
|
-
end
|
|
13
|
-
url = res[:devtools_url]
|
|
14
|
-
puts "Opening DevTools for '#{name}':"
|
|
15
|
-
puts " #{url}"
|
|
16
|
-
opener = RUBY_PLATFORM =~ /darwin/ ? "open" : "xdg-open"
|
|
17
|
-
system(opener, url)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "optimist"
|
|
4
|
-
require_relative "cli_output"
|
|
5
|
-
|
|
6
|
-
module Browserctl
|
|
7
|
-
module Commands
|
|
8
|
-
class OpenPage
|
|
9
|
-
extend CliOutput
|
|
10
|
-
|
|
11
|
-
def self.run(client, args)
|
|
12
|
-
opts = Optimist.options(args) do
|
|
13
|
-
banner "Usage: browserctl open <page> [--url URL]"
|
|
14
|
-
opt :url, "URL to navigate to", type: :string, short: "-u"
|
|
15
|
-
end
|
|
16
|
-
name = args.shift or abort "usage: browserctl open <page> [--url URL]"
|
|
17
|
-
print_result(client.open_page(name, url: opts[:url]))
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "cli_output"
|
|
4
|
-
|
|
5
|
-
module Browserctl
|
|
6
|
-
module Commands
|
|
7
|
-
module Pause
|
|
8
|
-
extend CliOutput
|
|
9
|
-
|
|
10
|
-
def self.run(client, args)
|
|
11
|
-
name = args.shift or abort "usage: browserctl pause <page>"
|
|
12
|
-
res = client.pause(name)
|
|
13
|
-
if res[:error]
|
|
14
|
-
warn "Error: #{res[:error]}"
|
|
15
|
-
exit 1
|
|
16
|
-
end
|
|
17
|
-
puts "Page '#{name}' paused. Browser is live — interact freely."
|
|
18
|
-
puts "When done: browserctl resume #{name}"
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Browserctl
|
|
6
|
-
module Commands
|
|
7
|
-
module Status
|
|
8
|
-
def self.run(client)
|
|
9
|
-
ping = client.ping
|
|
10
|
-
pages = client.list_pages[:pages] || []
|
|
11
|
-
page_info = pages.map do |name|
|
|
12
|
-
url_res = client.url(name)
|
|
13
|
-
{ name: name, url: url_res[:url] || url_res[:error] }
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
puts JSON.pretty_generate(
|
|
17
|
-
daemon: "online",
|
|
18
|
-
pid: ping[:pid],
|
|
19
|
-
protocol_version: ping[:protocol_version],
|
|
20
|
-
pages: page_info
|
|
21
|
-
)
|
|
22
|
-
rescue RuntimeError => e
|
|
23
|
-
raise unless e.message.include?("browserd is not running")
|
|
24
|
-
|
|
25
|
-
puts JSON.pretty_generate(daemon: "offline", error: e.message)
|
|
26
|
-
exit 1
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "optimist"
|
|
4
|
-
require_relative "cli_output"
|
|
5
|
-
|
|
6
|
-
module Browserctl
|
|
7
|
-
module Commands
|
|
8
|
-
class Watch
|
|
9
|
-
extend CliOutput
|
|
10
|
-
|
|
11
|
-
def self.run(client, args)
|
|
12
|
-
opts = Optimist.options(args) do
|
|
13
|
-
banner "Usage: browserctl watch <page> <selector> [--timeout N]"
|
|
14
|
-
opt :timeout, "Seconds to wait (default: 30)", default: 30.0, short: "-t"
|
|
15
|
-
end
|
|
16
|
-
name = args.shift
|
|
17
|
-
selector = args.shift
|
|
18
|
-
abort "usage: browserctl watch <page> <selector> [--timeout N]" unless name && selector
|
|
19
|
-
unless opts[:timeout].positive?
|
|
20
|
-
warn "Error: --timeout must be a positive number"
|
|
21
|
-
exit 1
|
|
22
|
-
end
|
|
23
|
-
print_result(client.watch(name, selector, timeout: opts[:timeout]))
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|