browserctl 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac5d5a587f5d9862ef7bd7839f7e7e817acd742ed0a1c6d6e33913eac1be1444
4
- data.tar.gz: f28af579b05237429959a444c0369550bcc848c04644bf172b95a4927657b40c
3
+ metadata.gz: aa3b71160a355802bb8e1fd6667506af34dc354c97b60eb2e82e0b418c02dbe6
4
+ data.tar.gz: 83da78edf2d59d84be5a6fd617e5b2388928c77b005d7735f466ce4aad19699b
5
5
  SHA512:
6
- metadata.gz: eca2babe456ed7a818ab908bbb9d0ec5fecb0a0474cdfe5335dbc5112667b8bf2e031a5a278dd8369b2009b284def98839ea26f712d88d94c2b89c061b5718f7
7
- data.tar.gz: da2d6b4eff687f5257e178a00a96b3ec9b93256fc191b386848328874bd5070cb2631a594b039550a8e3ec6ff9f00fbbfbd088d9ee594cc1d04166c6074f0c60
6
+ metadata.gz: 1ff6294fbc0284a342d9d2377077a02f004aec2acfeb3c1908039b09753d203e8a641d1dd2cd8ab2fd1fc607c42af3aa86537fb47361bb6820cf9a04fa8d6159
7
+ data.tar.gz: d029df7a2d280172543cb52dcee92326c97464d3b8d750d2f718710d46db16402b8e1a235c83c5da229a2ae047a4ab92738c3d736f741a4964c1d324f7871c15
data/CHANGELOG.md CHANGED
@@ -1,10 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ > **Do not edit this file manually.** It is generated automatically by
4
+ > [release-please](https://github.com/googleapis/release-please) on every merge to `main`.
5
+ > To include a change in the next release, write a
6
+ > [Conventional Commit](https://www.conventionalcommits.org/) message (`feat:`, `fix:`, `chore:`, etc.).
7
+
3
8
  All notable changes to this project will be documented in this file.
4
9
 
5
10
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
11
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
12
 
13
+ ## [0.4.0](https://github.com/patrick204nqh/browserctl/compare/v0.3.1...v0.4.0) (2026-04-25)
14
+
15
+
16
+ ### Features
17
+
18
+ * add Claude Code plugin support with installation instructions and plugin metadata ([b568ed2](https://github.com/patrick204nqh/browserctl/commit/b568ed29015d77bc521e4484aada1054b73362e6))
19
+ * add PRODUCT and standardization plan documentation; update VISION, README, and command references ([5594b52](https://github.com/patrick204nqh/browserctl/commit/5594b520f65d19f6c0c022e7944f9a560044549f))
20
+ * allow .browserctl/screenshots/ as project-scoped screenshot directory ([b6914f4](https://github.com/patrick204nqh/browserctl/commit/b6914f42ead76865b8c6e8e8c0e0f88516cb53e3))
21
+ * browserctl v0.4 hardening (security, cookie I/O, store/fetch, params file) ([#15](https://github.com/patrick204nqh/browserctl/issues/15)) ([c702661](https://github.com/patrick204nqh/browserctl/commit/c702661196e0784179afdbd824e6064c1db047bc))
22
+ * DX improvements, doc refresh, and hardening ([#16](https://github.com/patrick204nqh/browserctl/issues/16)) ([6e32e00](https://github.com/patrick204nqh/browserctl/commit/6e32e0038abfa28be9e92ec94f9a7642e29803c4))
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * add enabledPlugins section to settings.json for browserctl plugin activation ([924dbee](https://github.com/patrick204nqh/browserctl/commit/924dbeef9c86ea2c2486d4d93118045acc042833))
28
+ * add marketplace.json and correct plugin metadata ([5cfc1d6](https://github.com/patrick204nqh/browserctl/commit/5cfc1d621bc162fb200150c71ad4f5b62b4bdcc7))
29
+ * allow any path within daemon CWD for screenshots, not just .browserctl/screenshots ([817a492](https://github.com/patrick204nqh/browserctl/commit/817a49211e991493b6018b663fc198c4a0ff3742))
30
+ * save login screenshot to ~/.browserctl/screenshots/ instead of docs/ path ([246688e](https://github.com/patrick204nqh/browserctl/commit/246688e5c586d2028b90c178994d0eb7f1e9c626))
31
+ * update marketplace.json to set strict mode to true for browserctl plugin ([99db2ad](https://github.com/patrick204nqh/browserctl/commit/99db2ada2aae08f8bceb78658e15a267246df973))
32
+ * use screenshot_path param in all examples; CI passes docs/screenshots/ explicitly ([64e073d](https://github.com/patrick204nqh/browserctl/commit/64e073d74979d869560d814b9a986c001b400238))
33
+
34
+ ## [0.3.1](https://github.com/patrick204nqh/browserctl/compare/v0.3.0...v0.3.1) (2026-04-20)
35
+
36
+
37
+ ### Bug Fixes
38
+
39
+ * allow pre-registered workflows with slash names (e.g. the_internet/login) ([e755a4a](https://github.com/patrick204nqh/browserctl/commit/e755a4aa20608fb11ce425c81ed8c0a43f3b13fe))
40
+ * remove unused client variable in runner spec ([94e1a31](https://github.com/patrick204nqh/browserctl/commit/94e1a310acc1d5ddb5e538860907040d3504f2c9))
41
+
8
42
  ## [0.3.0](https://github.com/patrick204nqh/browserctl/compare/v0.2.2...v0.3.0) (2026-04-20)
9
43
 
10
44
 
data/README.md CHANGED
@@ -13,10 +13,10 @@ A persistent browser automation daemon and CLI, purpose-built for AI agents and
13
13
  Unlike tools that restart the browser on every script run, **browserctl keeps a named browser session alive** — preserving cookies, localStorage, open tabs, and page state across discrete commands.
14
14
 
15
15
  ```bash
16
- browserd & # start the daemon (headless)
16
+ browserd & # start the daemon (headless)
17
17
  browserctl open login --url https://example.com/login
18
- browserctl snap login # AI-friendly JSON snapshot with ref IDs
19
- browserctl fill login --ref e1 --value me@example.com # interact by ref
18
+ browserctl snap login # AI-friendly JSON snapshot with ref IDs
19
+ browserctl fill login --ref e1 --value me@example.com # interact by ref, no selectors needed
20
20
  browserctl click login --ref e2
21
21
  browserctl shutdown
22
22
  ```
@@ -35,11 +35,12 @@ Most automation tools are stateless — every script spins up a fresh browser an
35
35
  | Session persists across commands | ✓ | ✗ (per-script lifecycle) |
36
36
  | Named page handles | ✓ | ✗ |
37
37
  | AI-friendly DOM snapshot | ✓ | ✗ |
38
+ | Human-in-the-loop pause/resume | ✓ | ✗ |
38
39
  | Lightweight CLI interface | ✓ | ✗ |
39
40
  | Full browser automation API | — | ✓ |
40
41
  | Parallel multi-browser testing | — | ✓ |
41
42
 
42
- **Use browserctl when** you need a browser that stays alive and remembers state — for AI agents, iterative dev workflows, or lightweight smoke tests.
43
+ **Use browserctl when** you need a browser that stays alive and remembers state — for AI agents, iterative dev workflows, or tasks that mix automation with human judgment.
43
44
 
44
45
  **Use Playwright/Selenium when** you need parallel test suites, multi-browser support, or a full programmatic API.
45
46
 
@@ -47,8 +48,8 @@ Most automation tools are stateless — every script spins up a fresh browser an
47
48
 
48
49
  ## Requirements
49
50
 
50
- - Ruby >= 3.2
51
- - Chrome or Chromium installed and on `PATH`
51
+ - Ruby >= 3.3
52
+ - Chrome or Chromium on your `PATH`
52
53
 
53
54
  ---
54
55
 
@@ -66,223 +67,70 @@ gem "browserctl"
66
67
 
67
68
  ---
68
69
 
69
- ## Quick Start
70
-
71
- **1. Start the daemon**
72
-
73
- ```bash
74
- browserd # headless (default)
75
- browserd --headed # visible browser window
76
- ```
77
-
78
- **2. Open a named page**
79
-
80
- ```bash
81
- browserctl open login --url https://app.example.com/login
82
- ```
70
+ ## Claude Code Plugin
83
71
 
84
- **3. Snapshot the page to discover refs**
85
-
86
- ```bash
87
- browserctl snap login # AI-friendly JSON with ref IDs (default)
88
- browserctl snap login --format html
89
- ```
90
-
91
- **4. Interact using refs or selectors**
92
-
93
- ```bash
94
- browserctl fill login --ref e1 --value user@example.com
95
- browserctl fill login --ref e2 --value s3cr3t
96
- browserctl click login --ref e3
97
-
98
- # or using explicit CSS selectors
99
- browserctl fill login "input[name=email]" user@example.com
100
- browserctl click login "button[type=submit]"
101
- ```
72
+ browserctl ships as a Claude Code plugin. Install it once and Claude automatically knows how to use the daemon, ref-based interaction, HITL patterns, and workflow authoring.
102
73
 
103
- **5. Observe the result**
74
+ **Install (interactive)**
104
75
 
105
- ```bash
106
- browserctl snap login --diff # only changed elements since last snap
107
- browserctl shot login --out /tmp/after-login.png --full
108
- browserctl url login
109
76
  ```
110
-
111
- **6. Manage pages and daemon**
112
-
113
- ```bash
114
- browserctl pages
115
- browserctl close login
116
- browserctl ping
117
- browserctl shutdown
77
+ /plugin marketplace add patrick204nqh/browserctl
78
+ /plugin install browserctl@browserctl
118
79
  ```
119
80
 
120
- ---
121
-
122
- ## All Commands
123
-
124
- ### Browser commands _(require `browserd` running)_
125
-
126
- | Command | Description |
127
- |---|---|
128
- | `open <page> [--url URL]` | Open or focus a named page |
129
- | `close <page>` | Close a named page |
130
- | `pages` | List open pages |
131
- | `goto <page> <url>` | Navigate a page to a URL |
132
- | `fill <page> <selector> <value>` | Fill an input field by CSS selector |
133
- | `fill <page> --ref <id> --value <v>` | Fill an input field by snapshot ref |
134
- | `click <page> <selector>` | Click an element by CSS selector |
135
- | `click <page> --ref <id>` | Click an element by snapshot ref |
136
- | `snap <page> [--format ai\|html] [--diff]` | Snapshot DOM; `--diff` returns only changed elements |
137
- | `watch <page> <selector> [--timeout N]` | Poll until selector appears (default timeout: 30s) |
138
- | `shot <page> [--out PATH] [--full]` | Take a screenshot |
139
- | `url <page>` | Print current URL |
140
- | `eval <page> <expression>` | Evaluate a JS expression |
141
- | `pause <page>` | Pause automation — browser stays live for manual interaction |
142
- | `resume <page>` | Resume automation after manual action |
143
- | `inspect <page>` | Open Chrome DevTools for a named page |
144
- | `cookies <page>` | List all cookies as JSON |
145
- | `set_cookie <page> <name> <value> <domain>` | Set a cookie (path defaults to `/`) |
146
- | `clear_cookies <page>` | Clear all cookies for a page |
147
- | `record start <name>` | Begin recording commands as a replayable workflow |
148
- | `record stop [--out path]` | End recording; saves to `.browserctl/workflows/` or custom path |
149
- | `record status` | Show whether a recording is active |
150
-
151
- ### Daemon commands
152
-
153
- | Command | Description |
154
- |---|---|
155
- | `ping` | Check if `browserd` is alive |
156
- | `shutdown` | Stop `browserd` |
157
-
158
- ### Workflow commands
159
-
160
- | Command | Description |
161
- |---|---|
162
- | `run <name\|file.rb> [--key value ...]` | Run a named workflow or workflow file |
163
- | `workflows` | List available workflows |
164
- | `describe <name>` | Show workflow params and steps |
165
-
166
- ---
167
-
168
- ## AI Snapshot Format
169
-
170
- `browserctl snap <page>` returns a compact JSON array of interactable elements — designed to be token-efficient for AI agents:
81
+ **Install (project settings** — commit `.claude/settings.json` to share with your team)
171
82
 
172
83
  ```json
173
- [
174
- {
175
- "ref": "e1",
176
- "tag": "input",
177
- "text": "",
178
- "selector": "form > input[name=email]",
179
- "attrs": {
180
- "type": "email",
181
- "name": "email",
182
- "placeholder": "Enter email"
84
+ {
85
+ "extraKnownMarketplaces": {
86
+ "browserctl": {
87
+ "source": { "source": "github", "repo": "patrick204nqh/browserctl" }
183
88
  }
184
89
  },
185
- {
186
- "ref": "e2",
187
- "tag": "button",
188
- "text": "Sign in",
189
- "selector": "form > button",
190
- "attrs": {
191
- "type": "submit"
192
- }
90
+ "enabledPlugins": {
91
+ "browserctl@browserctl": true
193
92
  }
194
- ]
195
- ```
196
-
197
- Use `ref` values directly with `--ref` for zero-fragility interactions, or use `selector` values with `fill` and `click`.
198
-
199
- ### Ref-based interaction
200
-
201
- After a `snap`, use ref IDs instead of CSS selectors — no selector knowledge required:
202
-
203
- ```bash
204
- browserctl fill login --ref e1 --value user@example.com
205
- browserctl click login --ref e2
93
+ }
206
94
  ```
207
95
 
208
- ### Diff snapshots
209
-
210
- Track only what changed since the last snapshot — useful for AI agents monitoring async updates:
211
-
212
- ```bash
213
- browserctl snap login --diff
214
- ```
96
+ Once installed, Claude Code loads the `browserctl` skill automatically — no `/invoke` needed.
215
97
 
216
98
  ---
217
99
 
218
- ## Workflows
219
-
220
- Workflows are Ruby files using the `Browserctl.workflow` DSL. Place them in any of:
221
-
222
- - `./.browserctl/workflows/`
223
- - `~/.browserctl/workflows/`
224
-
225
- ### Example
226
-
227
- ```ruby
228
- # .browserctl/workflows/smoke_login.rb
229
- Browserctl.workflow "smoke_login" do
230
- desc "Log in and confirm the dashboard loads"
231
-
232
- param :email, required: true
233
- param :password, required: true, secret: true
234
- param :base_url, default: "https://app.example.com"
235
-
236
- step "open login page" do
237
- page(:login).goto("#{base_url}/login")
238
- end
239
-
240
- step "submit credentials" do
241
- page(:login).fill("input[name=email]", email)
242
- page(:login).fill("input[name=password]", password)
243
- page(:login).click("button[type=submit]")
244
- end
245
-
246
- step "verify dashboard" do
247
- page(:login).wait_for("[data-test=dashboard]", timeout: 10)
248
- assert page(:login).url.include?("/dashboard")
249
- end
250
- end
251
- ```
100
+ ## Quick Start
252
101
 
253
102
  ```bash
254
- browserctl run smoke_login --email me@example.com --password s3cr3t
255
- ```
103
+ # 1. Start the daemon
104
+ browserd &
256
105
 
257
- ### Workflow DSL reference
106
+ # 2. Open a named page
107
+ browserctl open main --url https://the-internet.herokuapp.com/login
258
108
 
259
- | Method | Description |
260
- |---|---|
261
- | `desc "text"` | Human-readable description |
262
- | `param :name, required:, secret:, default:` | Declare a parameter |
263
- | `step "label" { }` | Add a step (runs in order, halts on failure) |
264
- | `step "label", retry_count: N, timeout: S { }` | Step with retry and/or timeout |
265
- | `page(:name)` | Returns a `PageProxy` for the named page |
266
- | `invoke "other_workflow", **overrides` | Call another workflow |
267
- | `assert condition, "message"` | Raise `WorkflowError` if condition is false |
268
-
269
- ### PageProxy methods
109
+ # 3. Snapshot the page — get AI-friendly JSON with ref IDs
110
+ browserctl snap main
270
111
 
271
- `goto(url)` · `fill(selector, value)` · `click(selector)` · `snapshot(**opts)` · `screenshot(**opts)` · `wait_for(selector, timeout: 10)` · `url` · `evaluate(expression)` · `pause` · `resume` · `inspect_page` · `cookies` · `set_cookie(name, value, domain, path: "/")` · `clear_cookies`
272
-
273
- ---
112
+ # 4. Interact using refs
113
+ browserctl fill main --ref e1 --value tomsmith
114
+ browserctl fill main --ref e2 --value SuperSecretPassword!
115
+ browserctl click main --ref e3
274
116
 
275
- ## Examples
117
+ # 5. Observe
118
+ browserctl url main
119
+ browserctl snap main --diff # only what changed
276
120
 
277
- Ready-to-run smoke tests against [the-internet.herokuapp.com](https://the-internet.herokuapp.com) are included in `examples/the_internet/`. See [docs/smoke-testing-the-internet.md](docs/smoke-testing-the-internet.md) for annotated output and auto-generated screenshots of each scenario.
121
+ # 6. Done
122
+ browserctl shutdown
123
+ ```
278
124
 
279
- For a full guide on building your own workflows, see [docs/writing-workflows.md](docs/writing-workflows.md).
125
+ [Full Getting Started guide](docs/getting-started.md)
280
126
 
281
127
  ---
282
128
 
283
129
  ## How it works
284
130
 
285
- `browserd` runs as a background process, listening on a Unix socket at `~/.browserctl/browserd.sock`. Start multiple named instances for agent isolation:
131
+ `browserd` runs as a background process, listening on a Unix socket at `~/.browserctl/browserd.sock`. It manages a Ferrum (Chrome DevTools Protocol) browser instance with named page handles. `browserctl` sends JSON-RPC commands over the socket and prints the result.
132
+
133
+ Start multiple named instances for agent isolation:
286
134
 
287
135
  ```bash
288
136
  browserd --name agent-a &
@@ -290,11 +138,22 @@ browserd --name agent-b &
290
138
  browserctl --daemon agent-a open main --url https://app.example.com
291
139
  ```
292
140
 
293
- It manages a Ferrum (Chrome DevTools Protocol) browser instance with named page handles.
141
+ The daemon shuts itself down after 30 minutes of inactivity.
142
+
143
+ ---
294
144
 
295
- `browserctl` sends JSON-RPC commands over the socket and prints the result. Workflows run in-process through the same client.
145
+ ## Documentation
296
146
 
297
- The daemon shuts itself down after 30 minutes of inactivity.
147
+ | | |
148
+ |---|---|
149
+ | [Getting Started](docs/getting-started.md) | Install, first session, first snapshot |
150
+ | [Concepts](docs/concepts/) | Sessions, snapshots, human-in-the-loop |
151
+ | [Guides](docs/guides/) | Writing workflows, handling challenges, smoke testing |
152
+ | [Command Reference](docs/reference/commands.md) | Every command and flag |
153
+ | [API Stability](docs/reference/api-stability.md) | Wire protocol contract and stability zones |
154
+ | [Product](docs/product.md) | What browserctl is and who it's for |
155
+ | [Vision & Roadmap](docs/vision.md) | Philosophy and release roadmap |
156
+ | [vs. agent-browser](docs/vs-agent-browser.md) | How browserctl differs from Vercel's agent-browser |
298
157
 
299
158
  ---
300
159
 
data/bin/browserctl CHANGED
@@ -21,9 +21,13 @@ require "browserctl/commands/snapshot"
21
21
  require "browserctl/commands/screenshot"
22
22
  require "browserctl/commands/watch"
23
23
  require "browserctl/commands/record"
24
- require "browserctl/commands/pause_resume"
24
+ require "browserctl/commands/pause"
25
+ require "browserctl/commands/resume"
25
26
  require "browserctl/commands/init"
26
27
  require "browserctl/commands/inspect"
28
+ require "browserctl/commands/export_cookies"
29
+ require "browserctl/commands/import_cookies"
30
+ require "browserctl/commands/status"
27
31
 
28
32
  def print_result(res)
29
33
  if res.is_a?(Hash) && res[:error]
@@ -59,8 +63,10 @@ def usage
59
63
  resume <page> Resume automation after manual action
60
64
  inspect <page> Open Chrome DevTools for a named page
61
65
  cookies <page> List all cookies as JSON
62
- set_cookie <page> <name> <value> <domain> Set a cookie (path defaults to /)
63
- clear_cookies <page> Clear all cookies for a page
66
+ set-cookie <page> <name> <value> <domain> Set a cookie (path defaults to /)
67
+ clear-cookies <page> Clear all cookies for a page
68
+ export-cookies <page> <path> Export cookies to a JSON file
69
+ import-cookies <page> <path> Import cookies from a JSON file
64
70
 
65
71
  Recording commands:
66
72
  record start <name> Start recording browser commands
@@ -68,12 +74,13 @@ def usage
68
74
  record status Show active recording name
69
75
 
70
76
  Workflow commands:
71
- run <name|file> [--key value ...] Run a workflow
77
+ run <name|file> [--params file] [--key value ...] Run a workflow
72
78
  workflows List available workflows
73
79
  describe <name> Describe a workflow
74
80
 
75
81
  Daemon commands:
76
82
  ping Check if browserd is alive
83
+ status Show daemon status, PID, and open pages
77
84
  shutdown Stop browserd
78
85
 
79
86
  Options:
@@ -105,11 +112,23 @@ when "run"
105
112
  load File.expand_path(name)
106
113
  name = (Browserctl::REGISTRY.keys - before).first || File.basename(name, ".rb")
107
114
  end
108
- params = {}
115
+ params_file_idx = args.index("--params")
116
+ file_params = {}
117
+ if params_file_idx
118
+ params_path = args.delete_at(params_file_idx + 1)
119
+ args.delete_at(params_file_idx)
120
+ begin
121
+ file_params = Browserctl::Runner.load_params_file(params_path)
122
+ rescue StandardError => e
123
+ abort "Error loading params file: #{e.message}"
124
+ end
125
+ end
126
+ cli_params = {}
109
127
  args.each_slice(2) do |flag, val|
110
128
  key = flag.sub(/\A--/, "").to_sym
111
- params[key] = val
129
+ cli_params[key] = val
112
130
  end
131
+ params = file_params.merge(cli_params)
113
132
  success = runner.run_workflow(name, **params)
114
133
  exit(success ? 0 : 1)
115
134
 
@@ -139,13 +158,16 @@ else
139
158
  when "url" then print_result(client.url(args[0]))
140
159
  when "eval" then print_result(client.evaluate(args[0], args[1]))
141
160
  when "watch" then Browserctl::Commands::Watch.run(client, args)
142
- when "pause" then Browserctl::Commands::PauseResume.pause(client, args)
143
- when "resume" then Browserctl::Commands::PauseResume.resume(client, args)
161
+ when "pause" then Browserctl::Commands::Pause.run(client, args)
162
+ when "resume" then Browserctl::Commands::Resume.run(client, args)
144
163
  when "inspect" then Browserctl::Commands::Inspect.run(client, args)
145
- when "cookies" then print_result(client.cookies(args[0]))
146
- when "set_cookie" then print_result(client.set_cookie(args[0], args[1], args[2], args[3]))
147
- when "clear_cookies" then print_result(client.clear_cookies(args[0]))
164
+ when "cookies" then print_result(client.cookies(args[0]))
165
+ when "set-cookie" then print_result(client.set_cookie(args[0], args[1], args[2], args[3]))
166
+ when "clear-cookies" then print_result(client.clear_cookies(args[0]))
167
+ when "export-cookies" then Browserctl::Commands::ExportCookies.run(client, args)
168
+ when "import-cookies" then Browserctl::Commands::ImportCookies.run(client, args)
148
169
  when "ping" then print_result(client.ping)
170
+ when "status" then Browserctl::Commands::Status.run(client)
149
171
  when "shutdown" then print_result(client.shutdown)
150
172
  else
151
173
  abort "unknown command: #{cmd}\nRun 'browserctl --help' for usage."
data/bin/browserd CHANGED
@@ -16,7 +16,13 @@ opts = Optimist.options do
16
16
  opt :name, "Daemon instance name for multi-agent use", default: nil, short: "-n", type: :string
17
17
  end
18
18
 
19
- Browserctl.logger = Browserctl.build_logger(opts[:log_level])
19
+ if opts[:name] && opts[:name] !~ /\A[a-zA-Z0-9_-]{1,64}\z/
20
+ abort "Invalid daemon name #{opts[:name].inspect} — use only letters, digits, _ or -"
21
+ end
22
+
23
+ log_path = Browserctl.log_path(opts[:name])
24
+ warn "browserd starting — log: #{log_path}"
25
+ Browserctl.logger = Browserctl.build_logger(opts[:log_level], log_path: log_path)
20
26
  Browserctl::Server.new(
21
27
  headless: !opts[:headed],
22
28
  socket_path: Browserctl.socket_path(opts[:name]),
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Smoke test for --params file loading (Task 7.5).
5
+ #
6
+ # Run with:
7
+ # browserctl run examples/smoke/params_file.rb --params examples/smoke/params_file.yml
8
+ #
9
+ # The workflow logs in using credentials from the params file and asserts
10
+ # the secure area is reached — proving the params were loaded and available.
11
+
12
+ Browserctl.workflow "smoke/params_file" do
13
+ desc "Smoke: load credentials from a --params file and use them in a workflow"
14
+
15
+ param :username, required: true
16
+ param :password, required: true, secret: true
17
+ param :base_url, default: "https://the-internet.herokuapp.com"
18
+
19
+ step "open login page" do
20
+ client.open_page("main", url: "#{base_url}/login")
21
+ end
22
+
23
+ step "fill credentials from params file" do
24
+ puts " [params] username = #{username.inspect}"
25
+ puts " [params] password = (#{password.length} chars, secret)"
26
+ page(:main).fill("input#username", username)
27
+ page(:main).fill("input#password", password)
28
+ page(:main).click("button[type=submit]")
29
+ end
30
+
31
+ step "assert login succeeded" do
32
+ assert page(:main).url.include?("/secure"), "expected redirect to /secure — params may not have loaded"
33
+ puts " [ok] reached secure area — params file loaded correctly"
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Smoke test for WorkflowContext#store / #fetch (Task 7.3).
5
+ #
6
+ # Uses the-internet's dynamic loading example: click Start, wait for "Hello World!",
7
+ # capture the text in step 1, assert it is still accessible in step 2 via fetch.
8
+
9
+ Browserctl.workflow "smoke/store_fetch" do
10
+ desc "Smoke: store a value in one step and retrieve it in a later step"
11
+
12
+ param :base_url, default: "https://the-internet.herokuapp.com"
13
+
14
+ step "open dynamic loading page" do
15
+ client.open_page("main", url: "#{base_url}/dynamic_loading/1")
16
+ end
17
+
18
+ step "click start and capture loaded text" do
19
+ page(:main).click("div#start button")
20
+ page(:main).wait_for("div#finish", timeout: 10)
21
+ text = client.evaluate("main", "document.querySelector('div#finish h4')?.innerText?.trim()")[:result]
22
+ assert text && !text.empty?, "expected loaded text, got: #{text.inspect}"
23
+ store(:loaded_text, text)
24
+ puts " [store] loaded_text = #{text.inspect}"
25
+ end
26
+
27
+ step "fetch value from previous step and assert" do
28
+ text = fetch(:loaded_text)
29
+ puts " [fetch] loaded_text = #{text.inspect}"
30
+ assert text == "Hello World!", "expected 'Hello World!', got: #{text.inspect}"
31
+ end
32
+
33
+ step "confirm fetch raises for unknown key" do
34
+ fetch(:nonexistent_key)
35
+ assert false, "expected KeyError was not raised"
36
+ rescue KeyError => e
37
+ puts " [ok] KeyError raised as expected: #{e.message}"
38
+ end
39
+ end
@@ -3,7 +3,8 @@
3
3
  Browserctl.workflow "the_internet/add_remove_elements" do
4
4
  desc "Add/Remove Elements: add several elements, remove some, assert final count"
5
5
 
6
- param :base_url, default: "https://the-internet.herokuapp.com"
6
+ param :base_url, default: "https://the-internet.herokuapp.com"
7
+ param :screenshot_path, default: File.expand_path(".browserctl/screenshots/the_internet_add_remove_elements.png")
7
8
 
8
9
  step "open add/remove elements page" do
9
10
  client.open_page("main", url: "#{base_url}/add_remove_elements/")
@@ -13,8 +14,7 @@ Browserctl.workflow "the_internet/add_remove_elements" do
13
14
  3.times { page(:main).click("button[onclick]") }
14
15
  count = client.evaluate("main", "document.querySelectorAll('#elements button').length")[:result]
15
16
  assert count == 3, "expected 3 elements, got: #{count}"
16
- screenshots_dir = File.expand_path("../../docs/screenshots", __dir__)
17
- page(:main).screenshot(path: "#{screenshots_dir}/the_internet_add_remove_elements.png")
17
+ page(:main).screenshot(path: screenshot_path)
18
18
  end
19
19
 
20
20
  step "remove one element" do
@@ -3,7 +3,8 @@
3
3
  Browserctl.workflow "the_internet/checkboxes" do
4
4
  desc "Checkboxes: read state, toggle each, verify both checked"
5
5
 
6
- param :base_url, default: "https://the-internet.herokuapp.com"
6
+ param :base_url, default: "https://the-internet.herokuapp.com"
7
+ param :screenshot_path, default: File.expand_path(".browserctl/screenshots/the_internet_checkboxes.png")
7
8
 
8
9
  step "open checkboxes page" do
9
10
  client.open_page("main", url: "#{base_url}/checkboxes")
@@ -29,7 +30,6 @@ Browserctl.workflow "the_internet/checkboxes" do
29
30
  end
30
31
 
31
32
  step "capture screenshot" do
32
- screenshots_dir = File.expand_path("../../docs/screenshots", __dir__)
33
- page(:main).screenshot(path: "#{screenshots_dir}/the_internet_checkboxes.png")
33
+ page(:main).screenshot(path: screenshot_path)
34
34
  end
35
35
  end
@@ -3,7 +3,8 @@
3
3
  Browserctl.workflow "the_internet/dropdown" do
4
4
  desc "Dropdown: select each option via JS, assert selected value"
5
5
 
6
- param :base_url, default: "https://the-internet.herokuapp.com"
6
+ param :base_url, default: "https://the-internet.herokuapp.com"
7
+ param :screenshot_path, default: File.expand_path(".browserctl/screenshots/the_internet_dropdown.png")
7
8
 
8
9
  step "open dropdown page" do
9
10
  client.open_page("main", url: "#{base_url}/dropdown")
@@ -27,7 +28,6 @@ Browserctl.workflow "the_internet/dropdown" do
27
28
  end
28
29
 
29
30
  step "capture screenshot" do
30
- screenshots_dir = File.expand_path("../../docs/screenshots", __dir__)
31
- page(:main).screenshot(path: "#{screenshots_dir}/the_internet_dropdown.png")
31
+ page(:main).screenshot(path: screenshot_path)
32
32
  end
33
33
  end
@@ -3,7 +3,8 @@
3
3
  Browserctl.workflow "the_internet/dynamic_loading" do
4
4
  desc "Dynamic loading: click Start, wait for hidden element to appear"
5
5
 
6
- param :base_url, default: "https://the-internet.herokuapp.com"
6
+ param :base_url, default: "https://the-internet.herokuapp.com"
7
+ param :screenshot_path, default: File.expand_path(".browserctl/screenshots/the_internet_dynamic_loading.png")
7
8
 
8
9
  step "open dynamic loading page" do
9
10
  client.open_page("main", url: "#{base_url}/dynamic_loading/1")
@@ -27,7 +28,6 @@ Browserctl.workflow "the_internet/dynamic_loading" do
27
28
  sleep 0.2 until client.evaluate("main",
28
29
  "document.querySelector('#loading')?.style?.display")[:result] == "none" ||
29
30
  Time.now > deadline
30
- screenshots_dir = File.expand_path("../../docs/screenshots", __dir__)
31
- page(:main).screenshot(path: "#{screenshots_dir}/the_internet_dynamic_loading.png")
31
+ page(:main).screenshot(path: screenshot_path)
32
32
  end
33
33
  end