ghostrun-cli 1.0.1 → 1.0.3
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.
- package/README.md +252 -431
- package/REFERENCE.md +165 -0
- package/ghostrun.js +65 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,9 +5,60 @@
|
|
|
5
5
|
[](http://makeapullrequest.com)
|
|
6
6
|
[](https://www.npmjs.com/package/ghostrun-cli)
|
|
7
7
|
|
|
8
|
-
Record once. Replay as a ghost
|
|
8
|
+
**Record once. Replay as a ghost.**
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
GhostRun is a local-first CLI for browser automation, API testing, and load testing — all in one tool. Record a real browser flow, replay it headlessly, test REST APIs with assertions, and run VU-based load tests. No cloud. No account. Runs entirely on your machine.
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
- [What is GhostRun?](#what-is-ghostrun)
|
|
19
|
+
- [Install](#install)
|
|
20
|
+
- [Quick Start](#quick-start)
|
|
21
|
+
- [The Three Modes](#the-three-modes)
|
|
22
|
+
- [Browser Automation](#1-browser-automation)
|
|
23
|
+
- [API Testing](#2-api-testing)
|
|
24
|
+
- [Load Testing](#3-load-testing)
|
|
25
|
+
- [Commands](#commands)
|
|
26
|
+
- [Setup](#setup)
|
|
27
|
+
- [Recording](#recording)
|
|
28
|
+
- [Running Flows](#running-flows)
|
|
29
|
+
- [Flow Management](#flow-management)
|
|
30
|
+
- [Environments](#environments)
|
|
31
|
+
- [Run History](#run-history)
|
|
32
|
+
- [Scheduling](#scheduling)
|
|
33
|
+
- [Test Suites](#test-suites)
|
|
34
|
+
- [Web Dashboard](#web-dashboard)
|
|
35
|
+
- [Chat Assistant](#chat-assistant)
|
|
36
|
+
- [MCP Server](#mcp-server)
|
|
37
|
+
- [Reports](#reports)
|
|
38
|
+
- [Selector Repair](#selector-repair)
|
|
39
|
+
- [Screenshot Diff](#screenshot-diff)
|
|
40
|
+
- [CI/CD Integration](#cicd-integration)
|
|
41
|
+
- [AI Setup](#ai-setup)
|
|
42
|
+
- [Data & Privacy](#data--privacy)
|
|
43
|
+
- [Contributing](#contributing)
|
|
44
|
+
- [Trust & Transparency](#trust--transparency)
|
|
45
|
+
- [License](#license)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## What is GhostRun?
|
|
50
|
+
|
|
51
|
+
Most testing tools make you choose — browser OR API OR load testing. GhostRun does all three from a single CLI, using the same flow format.
|
|
52
|
+
|
|
53
|
+
| What you want to do | How GhostRun helps |
|
|
54
|
+
|---------------------|-------------------|
|
|
55
|
+
| Test a web UI regression | Record a browser flow once, replay headlessly on every deploy |
|
|
56
|
+
| Test a REST API | Write or import flows with HTTP requests, assertions, and variable extraction |
|
|
57
|
+
| Stress-test an endpoint | Run any API flow as a load test with configurable VUs and duration |
|
|
58
|
+
| Monitor a live site | Schedule flows to run on a cron and alert on failure |
|
|
59
|
+
| Give AI agents your test suite | MCP server exposes all flows as tools for Claude, Cursor, etc. |
|
|
60
|
+
|
|
61
|
+
Everything is stored locally in SQLite (`~/.ghostrun/`). No accounts, no telemetry, no cloud.
|
|
11
62
|
|
|
12
63
|
---
|
|
13
64
|
|
|
@@ -15,105 +66,116 @@ Browser automation + API testing + load testing in one CLI — record real brows
|
|
|
15
66
|
|
|
16
67
|
```bash
|
|
17
68
|
npm install -g ghostrun-cli
|
|
69
|
+
ghostrun init # guided setup: installs Chromium, configures AI (optional)
|
|
18
70
|
```
|
|
19
71
|
|
|
20
72
|
Or run from source:
|
|
21
73
|
|
|
22
74
|
```bash
|
|
23
75
|
git clone https://github.com/TechBuiltBySharan/ghostrun
|
|
24
|
-
cd ghostrun
|
|
25
|
-
|
|
26
|
-
npm run build
|
|
27
|
-
node ghostrun.js init # guided setup
|
|
76
|
+
cd ghostrun && npm install && npm run build
|
|
77
|
+
node ghostrun.js init
|
|
28
78
|
```
|
|
29
79
|
|
|
80
|
+
**Requirements:** Node 18+, macOS/Linux/Windows
|
|
81
|
+
|
|
30
82
|
---
|
|
31
83
|
|
|
32
84
|
## Quick Start
|
|
33
85
|
|
|
86
|
+
**Record a browser flow:**
|
|
87
|
+
|
|
34
88
|
```bash
|
|
35
|
-
ghostrun
|
|
36
|
-
ghostrun
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
89
|
+
ghostrun learn https://yourapp.com # real browser opens, you interact, GhostRun records
|
|
90
|
+
ghostrun run "My Flow" # replay headlessly
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Test an API:**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
ghostrun flow:from-curl "curl -X POST https://api.example.com/login \
|
|
97
|
+
-H 'Content-Type: application/json' \
|
|
98
|
+
-d '{\"email\":\"user@example.com\",\"password\":\"secret\"}'"
|
|
99
|
+
ghostrun run "POST /login"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Run a load test:**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
ghostrun perf:run "POST /login" --vus 20 --duration 30
|
|
42
106
|
```
|
|
43
107
|
|
|
44
108
|
---
|
|
45
109
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| Web dashboard | No | `ghostrun serve --ui` |
|
|
62
|
-
| **API testing** | No | HTTP requests, assertions, variable extraction |
|
|
63
|
-
| **Environment profiles** | No | Named env sets, injected at runtime |
|
|
64
|
-
| **Load testing** | No | VU-based perf runs, p50/p95/p99 stats |
|
|
65
|
-
| **k6 export** | No | `perf:export` generates a k6 script |
|
|
66
|
-
| **Failure analysis** | Optional ✨ | Plain-English explanation of why it failed |
|
|
67
|
-
| **Auto run summary** | Optional ✨ | Attached to every failed run automatically |
|
|
68
|
-
| **Chat assistant** | Optional ✨ | Q&A + run flows by name via `ghostrun chat` |
|
|
69
|
-
|
|
70
|
-
**Bottom line:** Record, replay, schedule, diff, and fix flows entirely offline. AI adds explanations and a conversational interface.
|
|
110
|
+
## The Three Modes
|
|
111
|
+
|
|
112
|
+
### 1. Browser Automation
|
|
113
|
+
|
|
114
|
+
GhostRun opens a real browser (Playwright/Chromium), watches your interactions, and saves them as a flow. Replay runs headlessly.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
ghostrun learn https://yourapp.com # record
|
|
118
|
+
ghostrun run <id|name> # replay headlessly
|
|
119
|
+
ghostrun run <id|name> --visible # replay with browser window visible
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**What gets recorded:** clicks, form fills, navigation, waits, checkboxes, dropdowns, keyboard input, file uploads, scroll, drag and drop.
|
|
123
|
+
|
|
124
|
+
**Selector repair:** If a flow breaks after a UI update, `ghostrun flow:fix <id>` opens the browser, replays up to the broken step, and lets you click the correct element. Selector is updated automatically — no manual JSON editing.
|
|
71
125
|
|
|
72
126
|
---
|
|
73
127
|
|
|
74
|
-
|
|
128
|
+
### 2. API Testing
|
|
75
129
|
|
|
76
|
-
|
|
130
|
+
When all steps in a flow are API actions (no `click`, `fill`, etc.), GhostRun skips Playwright entirely. Execution is ~30ms per run.
|
|
77
131
|
|
|
78
|
-
|
|
132
|
+
**Three ways to create an API flow:**
|
|
79
133
|
|
|
80
134
|
```bash
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
135
|
+
# From a curl command
|
|
136
|
+
ghostrun flow:from-curl "curl -X GET https://api.example.com/users \
|
|
137
|
+
-H 'Authorization: Bearer {{token}}'"
|
|
138
|
+
|
|
139
|
+
# From an OpenAPI/Swagger spec
|
|
140
|
+
ghostrun flow:from-spec openapi.json # JSON or YAML
|
|
141
|
+
ghostrun flow:from-spec swagger.yaml
|
|
142
|
+
|
|
143
|
+
# Import a hand-crafted .flow.json
|
|
144
|
+
ghostrun flow:import my-api-tests.flow.json
|
|
85
145
|
```
|
|
86
146
|
|
|
87
|
-
**
|
|
147
|
+
**API flow features:**
|
|
148
|
+
- HTTP requests with custom headers, JSON body, bearer auth
|
|
149
|
+
- Response assertions (status code, JSON path, headers, response time)
|
|
150
|
+
- Variable extraction from responses — use in subsequent steps
|
|
151
|
+
- Named environment profiles (dev / staging / prod)
|
|
88
152
|
|
|
89
|
-
|
|
90
|
-
|-------|------|---------|
|
|
91
|
-
| `gemma3:4b` | 2.6 GB | Apple Silicon M1/M2/M3, fast |
|
|
92
|
-
| `gemma2:9b` | 5.4 GB | Better quality, more RAM needed |
|
|
93
|
-
| `llama3.2:3b` | 2.0 GB | Fastest, lighter quality |
|
|
153
|
+
---
|
|
94
154
|
|
|
95
|
-
|
|
155
|
+
### 3. Load Testing
|
|
96
156
|
|
|
97
|
-
|
|
157
|
+
Run any API flow as a load test. GhostRun sends parallel VU requests, collects timing, and prints a latency breakdown.
|
|
98
158
|
|
|
99
159
|
```bash
|
|
100
|
-
|
|
160
|
+
ghostrun perf:run <id|name> # defaults: 10 VUs, 30s
|
|
161
|
+
ghostrun perf:run <id|name> --vus 50 --duration 60 --ramp-up 10
|
|
162
|
+
ghostrun perf:compare <run-A-id> <run-B-id> # diff two runs
|
|
163
|
+
ghostrun perf:export <id|name> # generate a k6 script
|
|
101
164
|
```
|
|
102
165
|
|
|
103
|
-
|
|
166
|
+
**Output includes:** HTTP requests, success rate, avg RPS, p50 / p95 / p99 latency, min/max, per-step breakdown, checks passed/failed.
|
|
167
|
+
|
|
168
|
+
**perf:compare** shows side-by-side deltas with color-coded improvement/regression:
|
|
104
169
|
|
|
105
170
|
```
|
|
106
|
-
|
|
171
|
+
Before After Delta
|
|
172
|
+
p50 latency 142ms 98ms ↓ 44ms ✓
|
|
173
|
+
p95 latency 310ms 201ms ↓ 109ms ✓
|
|
174
|
+
p99 latency 580ms 390ms ↓ 190ms ✓
|
|
175
|
+
avg RPS 47.2 68.1 ↑ 20.9 ✓
|
|
107
176
|
```
|
|
108
177
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
| Variable | Default | Description |
|
|
112
|
-
|----------|---------|-------------|
|
|
113
|
-
| `GHOSTRUN_AI_PROVIDER` | auto | `ollama`, `anthropic`, or auto |
|
|
114
|
-
| `GHOSTRUN_OLLAMA_URL` | `http://localhost:11434` | Ollama server URL |
|
|
115
|
-
| `GHOSTRUN_OLLAMA_MODEL` | auto-detected | Model to use |
|
|
116
|
-
| `ANTHROPIC_API_KEY` | — | Anthropic API key (cloud fallback) |
|
|
178
|
+
**perf:export** generates a valid k6 script with VU stages, `http.get`/`http.post` calls, `check()` assertions, and `Trend` metrics per step.
|
|
117
179
|
|
|
118
180
|
---
|
|
119
181
|
|
|
@@ -123,65 +185,87 @@ run → try Ollama → if down → try Anthropic → if no key → skip AI silen
|
|
|
123
185
|
|
|
124
186
|
```bash
|
|
125
187
|
ghostrun init # guided setup wizard
|
|
126
|
-
ghostrun status # stats
|
|
188
|
+
ghostrun status # stats, AI provider, data path
|
|
127
189
|
```
|
|
128
190
|
|
|
129
191
|
### Recording
|
|
130
192
|
|
|
131
193
|
```bash
|
|
132
|
-
ghostrun learn <url>
|
|
133
|
-
ghostrun learn <url> --name "Login" # with explicit name
|
|
194
|
+
ghostrun learn <url> # open browser and record a flow
|
|
195
|
+
ghostrun learn <url> --name "Login" # with an explicit name
|
|
134
196
|
```
|
|
135
197
|
|
|
136
|
-
### Running
|
|
198
|
+
### Running Flows
|
|
137
199
|
|
|
138
200
|
```bash
|
|
139
|
-
ghostrun run <id|name>
|
|
140
|
-
ghostrun run <id|name> --visible
|
|
141
|
-
ghostrun run <id|name> --
|
|
142
|
-
ghostrun run <id|name> --
|
|
143
|
-
ghostrun run <id|name> --
|
|
144
|
-
ghostrun run <id|name> --session-
|
|
201
|
+
ghostrun run <id|name> # headless execution
|
|
202
|
+
ghostrun run <id|name> --visible # show the browser window
|
|
203
|
+
ghostrun run <id|name> --var key=val # inject a variable
|
|
204
|
+
ghostrun run <id|name> --output json # JSON output (for scripting/CI)
|
|
205
|
+
ghostrun run <id|name> --report html # save an HTML run report
|
|
206
|
+
ghostrun run <id|name> --session-save <name> # save browser cookies/storage
|
|
207
|
+
ghostrun run <id|name> --session-load <name> # restore browser cookies/storage
|
|
145
208
|
```
|
|
146
209
|
|
|
147
210
|
### Flow Management
|
|
148
211
|
|
|
149
212
|
```bash
|
|
150
|
-
ghostrun flow:list
|
|
151
|
-
ghostrun flow:
|
|
152
|
-
ghostrun flow:
|
|
153
|
-
ghostrun flow:delete <id|name>
|
|
154
|
-
ghostrun flow:export <id|name>
|
|
155
|
-
ghostrun flow:import <file>
|
|
156
|
-
ghostrun flow:
|
|
157
|
-
ghostrun flow:
|
|
213
|
+
ghostrun flow:list # list all flows
|
|
214
|
+
ghostrun flow:rename <id|name> <new-name> # rename a flow
|
|
215
|
+
ghostrun flow:clone <id|name> # duplicate a flow
|
|
216
|
+
ghostrun flow:delete <id|name> # delete a flow
|
|
217
|
+
ghostrun flow:export <id|name> # export to .flow.json
|
|
218
|
+
ghostrun flow:import <file> # import from .flow.json
|
|
219
|
+
ghostrun flow:fix <id|name> # fix broken selectors interactively
|
|
220
|
+
ghostrun flow:from-curl "<curl>" # create a flow from a curl command
|
|
221
|
+
ghostrun flow:from-spec <file> # create flows from an OpenAPI spec
|
|
158
222
|
```
|
|
159
223
|
|
|
160
|
-
###
|
|
224
|
+
### Environments
|
|
225
|
+
|
|
226
|
+
Named variable sets injected at run time. Perfect for dev / staging / prod.
|
|
161
227
|
|
|
162
228
|
```bash
|
|
163
|
-
ghostrun
|
|
164
|
-
ghostrun
|
|
229
|
+
ghostrun env:create <name> # create an environment (dev/staging/prod)
|
|
230
|
+
ghostrun env:set <name> <key> <value> # add or update a variable
|
|
231
|
+
ghostrun env:list # list all environments
|
|
232
|
+
ghostrun env:show <name> # show variables in an environment
|
|
233
|
+
ghostrun env:use <name> # set as the active environment
|
|
165
234
|
```
|
|
166
235
|
|
|
236
|
+
Reference variables in any URL, header, or body field with `{{variableName}}`. The active environment's variables are injected automatically before each run.
|
|
237
|
+
|
|
167
238
|
### Run History
|
|
168
239
|
|
|
169
240
|
```bash
|
|
170
241
|
ghostrun run:list # list recent runs
|
|
171
|
-
ghostrun run:show <id> # step
|
|
172
|
-
ghostrun run:diff <id1> <id2> #
|
|
173
|
-
ghostrun run:analyze <id> # AI failure analysis (
|
|
242
|
+
ghostrun run:show <id> # step-by-step detail, screenshots, extracted data
|
|
243
|
+
ghostrun run:diff <id1> <id2> # pixel-level screenshot comparison
|
|
244
|
+
ghostrun run:analyze <id> # AI failure analysis (requires AI setup)
|
|
245
|
+
ghostrun var:dump <run-id> # show all variables extracted during a run
|
|
174
246
|
```
|
|
175
247
|
|
|
176
248
|
### Scheduling
|
|
177
249
|
|
|
178
250
|
```bash
|
|
179
|
-
ghostrun flow:schedule <id> "<cron>" # e.g. "0 9 * * *" = daily 9am
|
|
251
|
+
ghostrun flow:schedule <id> "<cron>" # e.g. "0 9 * * *" = daily at 9am
|
|
180
252
|
ghostrun schedule:list # list all schedules
|
|
181
253
|
ghostrun schedule:remove <id> # remove a schedule
|
|
182
254
|
ghostrun serve # start the scheduler daemon
|
|
183
255
|
```
|
|
184
256
|
|
|
257
|
+
### Test Suites
|
|
258
|
+
|
|
259
|
+
Group flows and run them together:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
ghostrun suite:create <name> # create a suite
|
|
263
|
+
ghostrun suite:add <suite> <flow> # add a flow to the suite
|
|
264
|
+
ghostrun suite:run <suite> # run all flows in the suite
|
|
265
|
+
ghostrun suite:list # list suites
|
|
266
|
+
ghostrun suite:show <suite> # show flows in a suite
|
|
267
|
+
```
|
|
268
|
+
|
|
185
269
|
### Web Dashboard
|
|
186
270
|
|
|
187
271
|
```bash
|
|
@@ -189,152 +273,91 @@ ghostrun serve --ui # launch dashboard at http://localhost:30
|
|
|
189
273
|
ghostrun serve --ui --port 8080 # custom port
|
|
190
274
|
```
|
|
191
275
|
|
|
192
|
-
The dashboard shows
|
|
193
|
-
- All flows with one-click run buttons
|
|
194
|
-
- Live run log with SSE streaming
|
|
195
|
-
- Run history with status and duration
|
|
196
|
-
- Chat tab for natural-language interaction
|
|
276
|
+
The dashboard shows all flows with one-click run, a live log stream, run history, and a chat tab.
|
|
197
277
|
|
|
198
278
|
### Chat Assistant
|
|
199
279
|
|
|
280
|
+
Ask questions about your flows in plain English:
|
|
281
|
+
|
|
200
282
|
```bash
|
|
201
|
-
ghostrun chat
|
|
283
|
+
ghostrun chat
|
|
202
284
|
```
|
|
203
285
|
|
|
204
|
-
|
|
286
|
+
Examples:
|
|
205
287
|
- `did my login flow pass recently?`
|
|
206
288
|
- `what flows do I have?`
|
|
207
|
-
- `run the login flow` ← executes
|
|
289
|
+
- `run the login flow` ← executes with confirmation
|
|
208
290
|
|
|
209
|
-
|
|
291
|
+
Requires Ollama (local, free) or an Anthropic API key. See [AI Setup](#ai-setup).
|
|
210
292
|
|
|
211
|
-
|
|
212
|
-
ghostrun suite:create <name> # create a suite
|
|
213
|
-
ghostrun suite:add <suite> <flow> # add a flow to a suite
|
|
214
|
-
ghostrun suite:list # list suites
|
|
215
|
-
ghostrun suite:show <suite> # show flows in suite
|
|
216
|
-
ghostrun suite:run <suite> # run all flows in suite
|
|
217
|
-
```
|
|
293
|
+
### MCP Server
|
|
218
294
|
|
|
219
|
-
|
|
295
|
+
Expose GhostRun to AI agents (Claude Desktop, Cursor, etc.):
|
|
220
296
|
|
|
221
297
|
```bash
|
|
222
|
-
|
|
223
|
-
ghostrun baseline:clear <flow-id> # clear baselines
|
|
224
|
-
ghostrun baseline:show <flow-id> # list baselines
|
|
298
|
+
node mcp-server.js
|
|
225
299
|
```
|
|
226
300
|
|
|
227
|
-
|
|
301
|
+
Tools exposed: `list_flows`, `get_flow`, `run_flow`, `get_run_result`, `list_runs`, `delete_flow`, `get_status`.
|
|
228
302
|
|
|
229
|
-
|
|
230
|
-
ghostrun flow:from-curl # paste a curl command → instant flow
|
|
231
|
-
ghostrun flow:from-curl "curl -X POST https://api.example.com/users -H 'Content-Type: application/json' -d '{\"name\":\"Alice\"}'"
|
|
232
|
-
ghostrun flow:from-spec openapi.json # import all endpoints from an OpenAPI spec
|
|
233
|
-
ghostrun flow:from-spec swagger.yaml # YAML supported too
|
|
234
|
-
ghostrun flow:import <file> # import a .flow.json directly
|
|
235
|
-
```
|
|
303
|
+
See [MCP-SETUP.md](MCP-SETUP.md) for connection setup.
|
|
236
304
|
|
|
237
|
-
|
|
305
|
+
---
|
|
238
306
|
|
|
239
|
-
|
|
307
|
+
## Reports
|
|
240
308
|
|
|
241
309
|
```bash
|
|
242
|
-
ghostrun run <id> --report html
|
|
243
|
-
ghostrun perf:run <id> --report html
|
|
310
|
+
ghostrun run <id> --report html # browser or API run report
|
|
311
|
+
ghostrun perf:run <id> --report html # load test report
|
|
244
312
|
```
|
|
245
313
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
### API Testing
|
|
249
|
-
|
|
250
|
-
Import an API flow from a `.flow.json` file (no browser needed):
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
ghostrun api:learn # interactive: pick a .flow.json file to import
|
|
254
|
-
ghostrun flow:import <file> # import directly by path
|
|
255
|
-
ghostrun run <id|name> # runs pure API flows without launching a browser
|
|
256
|
-
```
|
|
314
|
+
Dark-themed, self-contained HTML files saved to the current directory. Include per-step timing, status, screenshots (for browser flows), and extracted data. Shareable without any external dependencies.
|
|
257
315
|
|
|
258
|
-
|
|
316
|
+
---
|
|
259
317
|
|
|
260
|
-
|
|
318
|
+
## Selector Repair
|
|
261
319
|
|
|
262
|
-
|
|
320
|
+
When a UI update breaks a selector:
|
|
263
321
|
|
|
264
322
|
```bash
|
|
265
|
-
ghostrun
|
|
266
|
-
ghostrun env:list # list all environments
|
|
267
|
-
ghostrun env:show <name> # show variables in an environment
|
|
268
|
-
ghostrun env:set <name> <key=value> # add or update a variable
|
|
269
|
-
ghostrun env:use <name> # set as active environment
|
|
270
|
-
ghostrun env:delete <name> # delete an environment
|
|
323
|
+
ghostrun flow:fix <id|name>
|
|
271
324
|
```
|
|
272
325
|
|
|
273
|
-
The
|
|
326
|
+
The browser opens, replays all passing steps automatically, then **pauses on the broken step** and asks you to click the correct element. The selector is updated and saved. No JSON editing needed.
|
|
274
327
|
|
|
275
|
-
|
|
328
|
+
---
|
|
276
329
|
|
|
277
|
-
|
|
278
|
-
ghostrun var:dump <run-id> # show all variables extracted during a run
|
|
279
|
-
```
|
|
330
|
+
## Screenshot Diff
|
|
280
331
|
|
|
281
|
-
|
|
332
|
+
Compare any two runs pixel-by-pixel — no AI needed:
|
|
282
333
|
|
|
283
334
|
```bash
|
|
284
|
-
ghostrun
|
|
285
|
-
ghostrun perf:run <id|name> --vus 20 --duration 30 --ramp-up 5
|
|
286
|
-
ghostrun perf:export <id|name> # generate a k6 script from the flow
|
|
287
|
-
ghostrun perf:export <id|name> --output mytest.js
|
|
288
|
-
ghostrun perf:list # list past perf runs
|
|
289
|
-
ghostrun perf:show <perf-run-id> # show stats for a specific perf run
|
|
335
|
+
ghostrun run:diff <run1-id> <run2-id>
|
|
290
336
|
```
|
|
291
337
|
|
|
292
|
-
**perf:run options:**
|
|
293
|
-
|
|
294
|
-
| Flag | Default | Description |
|
|
295
|
-
|------|---------|-------------|
|
|
296
|
-
| `--vus <n>` | 10 | Number of virtual users |
|
|
297
|
-
| `--duration <s>` | 30 | Test duration in seconds |
|
|
298
|
-
| `--ramp-up <s>` | 5 | Ramp-up time (VUs staggered over this window) |
|
|
299
|
-
| `--timeout <ms>` | 10000 | Per-request timeout in ms |
|
|
300
|
-
|
|
301
|
-
Output includes: HTTP Requests, HTTP Success Rate, Avg RPS, p50/p95/p99 latency, min/max, per-step breakdown, and separate Checks Passed/Failed count.
|
|
302
|
-
|
|
303
|
-
**perf:compare** — diff two runs side by side to see if a deploy made things faster or slower:
|
|
304
|
-
|
|
305
|
-
```bash
|
|
306
|
-
ghostrun perf:compare <run-A-id> <run-B-id>
|
|
307
338
|
```
|
|
339
|
+
Step Status Diff % Name
|
|
340
|
+
───────────────────────────────────────────
|
|
341
|
+
1 same 0.0% Navigate to homepage
|
|
342
|
+
2 same 0.1% Click Login
|
|
343
|
+
3 changed 12.4% Fill email field
|
|
344
|
+
4 same 0.0% Submit form
|
|
308
345
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
**perf:export** generates a valid k6 JavaScript file with:
|
|
312
|
-
- VU stages matching your `--vus`/`--duration`/`--ramp-up` config
|
|
313
|
-
- `http.get`/`http.post` calls with headers and JSON body
|
|
314
|
-
- `check()` assertions mapped to your `assert:response` steps
|
|
315
|
-
- `Trend` metrics per step for p95 thresholds
|
|
316
|
-
- `{{variable}}` → template literal interpolation
|
|
317
|
-
|
|
318
|
-
### MCP Server
|
|
319
|
-
|
|
320
|
-
```bash
|
|
321
|
-
node mcp-server.js # start MCP server (Claude Desktop, Cursor, etc.)
|
|
346
|
+
3 same 1 changed
|
|
347
|
+
Diff images: ~/.ghostrun/diffs/abc123_vs_def456/
|
|
322
348
|
```
|
|
323
349
|
|
|
324
|
-
See [MCP-SETUP.md](MCP-SETUP.md) for connection setup.
|
|
325
|
-
|
|
326
|
-
Tools exposed: `list_flows`, `get_flow`, `run_flow`, `get_run_result`, `list_runs`, `delete_flow`, `get_status`
|
|
327
|
-
|
|
328
350
|
---
|
|
329
351
|
|
|
330
352
|
## CI/CD Integration
|
|
331
353
|
|
|
332
|
-
|
|
354
|
+
GhostRun exits with code `1` on failure and `0` on success — standard CI behaviour, no extra flags needed.
|
|
355
|
+
|
|
356
|
+
### GitHub Actions example
|
|
333
357
|
|
|
334
358
|
```yaml
|
|
335
359
|
# .github/workflows/ghostrun.yml
|
|
336
360
|
name: GhostRun Tests
|
|
337
|
-
|
|
338
361
|
on: [push, pull_request]
|
|
339
362
|
|
|
340
363
|
jobs:
|
|
@@ -342,7 +365,6 @@ jobs:
|
|
|
342
365
|
runs-on: ubuntu-latest
|
|
343
366
|
steps:
|
|
344
367
|
- uses: actions/checkout@v4
|
|
345
|
-
|
|
346
368
|
- uses: actions/setup-node@v4
|
|
347
369
|
with:
|
|
348
370
|
node-version: 20
|
|
@@ -352,16 +374,12 @@ jobs:
|
|
|
352
374
|
|
|
353
375
|
- name: Install Playwright browsers
|
|
354
376
|
run: npx playwright install chromium --with-deps
|
|
377
|
+
# Skip this step for pure API flows — no browser needed
|
|
355
378
|
|
|
356
|
-
- name: Import
|
|
357
|
-
run: |
|
|
358
|
-
ghostrun flow:import test-flows/health-check.flow.json
|
|
359
|
-
ghostrun flow:import test-flows/auth-and-users.flow.json
|
|
360
|
-
|
|
361
|
-
- name: Run flows
|
|
379
|
+
- name: Import and run flows
|
|
362
380
|
run: |
|
|
363
|
-
ghostrun
|
|
364
|
-
ghostrun run "Auth
|
|
381
|
+
ghostrun flow:import test-flows/auth.flow.json
|
|
382
|
+
ghostrun run "Auth Flow" --report html
|
|
365
383
|
|
|
366
384
|
- name: Upload reports
|
|
367
385
|
uses: actions/upload-artifact@v4
|
|
@@ -371,279 +389,82 @@ jobs:
|
|
|
371
389
|
path: ghostrun-report-*.html
|
|
372
390
|
```
|
|
373
391
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
GhostRun exits with code `1` on failure and `0` on pass — standard CI behaviour, no extra flags needed.
|
|
377
|
-
|
|
378
|
-
### API-only flows in CI
|
|
379
|
-
|
|
380
|
-
API flows skip Playwright entirely — no `playwright install` step needed:
|
|
381
|
-
|
|
382
|
-
```yaml
|
|
383
|
-
- name: Install GhostRun (API testing only)
|
|
384
|
-
run: npm install -g ghostrun-cli
|
|
385
|
-
# No playwright install needed for pure API flows
|
|
392
|
+
---
|
|
386
393
|
|
|
387
|
-
|
|
388
|
-
run: ghostrun run "Auth + User List"
|
|
389
|
-
```
|
|
394
|
+
## AI Setup
|
|
390
395
|
|
|
391
|
-
|
|
396
|
+
Every core feature works with zero AI. AI adds failure explanations and a chat interface — both are optional.
|
|
392
397
|
|
|
393
|
-
|
|
398
|
+
### Option 1 — Ollama (local, recommended)
|
|
394
399
|
|
|
395
|
-
|
|
400
|
+
No API key, no internet required. Runs on your machine.
|
|
396
401
|
|
|
397
402
|
```bash
|
|
398
|
-
|
|
403
|
+
brew install ollama
|
|
404
|
+
ollama serve &
|
|
405
|
+
ollama pull gemma3:4b # 2.6 GB, fast on Apple Silicon
|
|
399
406
|
```
|
|
400
407
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
408
|
+
| Model | Size | Best for |
|
|
409
|
+
|-------|------|---------|
|
|
410
|
+
| `gemma3:4b` | 2.6 GB | Apple Silicon M1/M2/M3 |
|
|
411
|
+
| `gemma2:9b` | 5.4 GB | Better quality |
|
|
412
|
+
| `llama3.2:3b` | 2.0 GB | Fastest, lighter quality |
|
|
404
413
|
|
|
405
|
-
|
|
414
|
+
Override: `export GHOSTRUN_OLLAMA_MODEL=llama3.2:3b`
|
|
406
415
|
|
|
407
|
-
|
|
416
|
+
### Option 2 — Anthropic (cloud fallback)
|
|
408
417
|
|
|
409
418
|
```bash
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
Step Status Diff % Screenshot
|
|
413
|
-
──────────────────────────────────────────────────────────
|
|
414
|
-
1 same 0.0% Navigate to homepage
|
|
415
|
-
2 same 0.1% Click Login
|
|
416
|
-
3 changed 12.4% Fill email field
|
|
417
|
-
4 same 0.0% Submit form
|
|
418
|
-
|
|
419
|
-
3 same 1 changed
|
|
420
|
-
Diff images: ~/.ghostrun/diffs/abc123_vs_def456/
|
|
419
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
421
420
|
```
|
|
422
421
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
## Flow Actions Reference
|
|
426
|
-
|
|
427
|
-
All actions you can use in recorded or imported `.flow.json` files:
|
|
428
|
-
|
|
429
|
-
### Navigation
|
|
430
|
-
|
|
431
|
-
| Action | Fields | Description |
|
|
432
|
-
|--------|--------|-------------|
|
|
433
|
-
| `navigate` | `url` | Go to URL |
|
|
434
|
-
| `reload` | — | Reload the current page |
|
|
435
|
-
| `back` | — | Browser back |
|
|
436
|
-
| `forward` | — | Browser forward |
|
|
437
|
-
|
|
438
|
-
### Interaction
|
|
439
|
-
|
|
440
|
-
| Action | Fields | Description |
|
|
441
|
-
|--------|--------|-------------|
|
|
442
|
-
| `click` | `selector` | Left-click an element |
|
|
443
|
-
| `dblclick` | `selector` | Double-click an element |
|
|
444
|
-
| `fill` | `selector`, `value` | Clear field and type value |
|
|
445
|
-
| `type` | `selector`, `value`, `delay?` | Type with configurable key delay (ms) |
|
|
446
|
-
| `clear` | `selector` | Clear a field |
|
|
447
|
-
| `select` | `selector`, `value` | Select a dropdown option by value |
|
|
448
|
-
| `check` | `selector`, `value: "true"\|"false"` | Check/uncheck a checkbox |
|
|
449
|
-
| `focus` | `selector` | Focus an element |
|
|
450
|
-
| `hover` | `selector` | Mouse hover |
|
|
451
|
-
| `drag` | `selector`, `targetSelector` | Drag one element to another |
|
|
452
|
-
| `keyboard` | `key`, `selector?` | Press a key (e.g. `Enter`, `Tab`, `Control+a`) |
|
|
453
|
-
| `upload` | `selector`, `value` | Set file input (comma-separated paths) |
|
|
454
|
-
|
|
455
|
-
### Waiting
|
|
456
|
-
|
|
457
|
-
| Action | Fields | Description |
|
|
458
|
-
|--------|--------|-------------|
|
|
459
|
-
| `wait` | `selector` | Wait for element to appear |
|
|
460
|
-
| `wait:text` | `selector`, `value` | Wait until element contains text |
|
|
461
|
-
| `wait:url` | `value` | Wait for URL to match pattern |
|
|
462
|
-
| `wait:ms` | `value` | Wait for N milliseconds |
|
|
463
|
-
|
|
464
|
-
### Scrolling
|
|
422
|
+
### Fallback order
|
|
465
423
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
| `scroll:element` | `selector` | Scroll element into view |
|
|
470
|
-
| `scroll:bottom` | — | Scroll to bottom of page |
|
|
471
|
-
| `scroll:load` | `value?` | Scroll to bottom, wait for load (repeat N times) |
|
|
472
|
-
| `next:page` | `selector?` | Click next page link and wait |
|
|
473
|
-
|
|
474
|
-
### Assertions
|
|
475
|
-
|
|
476
|
-
| Action | Fields | Description |
|
|
477
|
-
|--------|--------|-------------|
|
|
478
|
-
| `assert:visible` | `selector` | Assert element is visible |
|
|
479
|
-
| `assert:hidden` | `selector` | Assert element is not visible |
|
|
480
|
-
| `assert:text` | `selector`, `value` | Assert element contains text |
|
|
481
|
-
| `assert:not-text` | `selector`, `value` | Assert element does NOT contain text |
|
|
482
|
-
| `assert:value` | `selector`, `value` | Assert input value |
|
|
483
|
-
| `assert:count` | `selector`, `value` | Assert number of matching elements |
|
|
484
|
-
| `assert:attr` | `selector`, `value: "attr=expected"` | Assert element attribute |
|
|
485
|
-
|
|
486
|
-
### Data Extraction
|
|
487
|
-
|
|
488
|
-
| Action | Fields | Description |
|
|
489
|
-
|--------|--------|-------------|
|
|
490
|
-
| `extract` | `selector`, `value: "variableName"` | Extract text → variable |
|
|
491
|
-
| `screenshot` | — | Capture screenshot at this step |
|
|
492
|
-
|
|
493
|
-
### Browser State
|
|
494
|
-
|
|
495
|
-
| Action | Fields | Description |
|
|
496
|
-
|--------|--------|-------------|
|
|
497
|
-
| `cookie:set` | `value: "name=value; domain=..."` | Set a cookie |
|
|
498
|
-
| `cookie:clear` | — | Clear all cookies |
|
|
499
|
-
| `storage:set` | `selector: "key"`, `value: "val"` | Set localStorage item |
|
|
500
|
-
| `eval` | `value` | Execute JavaScript on the page |
|
|
501
|
-
| `iframe:enter` | `selector` | Enter an iframe context |
|
|
502
|
-
| `iframe:exit` | — | Exit iframe context, return to main frame |
|
|
503
|
-
|
|
504
|
-
### API — HTTP Requests
|
|
505
|
-
|
|
506
|
-
| Action | Fields | Description |
|
|
507
|
-
|--------|--------|-------------|
|
|
508
|
-
| `http:request` | `method`, `url`, `headers?`, `body?`, `auth?`, `extract?` | Make an HTTP request. `auth` supports `{ type: "bearer", token: "{{var}}" }`. `extract` is a map of `variableName → $.jsonPath`. |
|
|
509
|
-
|
|
510
|
-
### API — Assertions
|
|
511
|
-
|
|
512
|
-
| Action | Fields | Description |
|
|
513
|
-
|--------|--------|-------------|
|
|
514
|
-
| `assert:response` | `assert: "status"`, `expected` | Assert HTTP status code |
|
|
515
|
-
| `assert:response` | `assert: "json:path"`, `path`, `expected` | Assert JSONPath value equals expected |
|
|
516
|
-
| `assert:response` | `assert: "json:exists"`, `path` | Assert JSONPath exists in response |
|
|
517
|
-
| `assert:response` | `assert: "header"`, `header`, `expected` | Assert response header value |
|
|
518
|
-
| `assert:response` | `assert: "body:contains"`, `expected` | Assert raw body contains string |
|
|
519
|
-
| `assert:response` | `assert: "time"`, `expected` | Assert response time < expected ms |
|
|
520
|
-
|
|
521
|
-
### API — Variables & Flow Control
|
|
522
|
-
|
|
523
|
-
| Action | Fields | Description |
|
|
524
|
-
|--------|--------|-------------|
|
|
525
|
-
| `set:variable` | `variable`, `value` | Set a named variable (supports `{{interpolation}}`) |
|
|
526
|
-
| `extract:json` | `variable`, `path` | Extract a value from the last response body via JSONPath |
|
|
527
|
-
| `env:switch` | `value` | Switch active environment mid-flow |
|
|
528
|
-
|
|
529
|
-
### Variables
|
|
530
|
-
|
|
531
|
-
Use `{{variableName}}` in any `value`, `url`, `selector`, or `body` field to inject variables:
|
|
532
|
-
|
|
533
|
-
```json
|
|
534
|
-
{ "action": "fill", "selector": "#email", "value": "{{userEmail}}" }
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
Pass at runtime: `ghostrun run <id> --var userEmail=user@example.com`
|
|
538
|
-
|
|
539
|
-
Extracted values (from `extract:` steps) are automatically available as variables in subsequent steps.
|
|
424
|
+
```
|
|
425
|
+
Run → try Ollama → if down → try Anthropic → if no key → skip AI silently
|
|
426
|
+
```
|
|
540
427
|
|
|
541
|
-
|
|
428
|
+
### Environment variables
|
|
542
429
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
|
548
|
-
|
|
549
|
-
| Canvas drawing | ❌ Not supported | `<canvas>` elements — no visual capture |
|
|
550
|
-
| WebGL / Three.js | ❌ Not supported | GPU-rendered content |
|
|
551
|
-
| Browser native dialogs | ⚠️ Partial | `alert()`/`confirm()`/`prompt()` auto-dismissed |
|
|
552
|
-
| File download verification | ⚠️ Partial | Download triggers but content is not validated |
|
|
553
|
-
| WebRTC / media streams | ❌ Not supported | Camera, mic, screen capture APIs |
|
|
554
|
-
| Browser extensions | ❌ Not supported | Extension UI is not accessible via Playwright |
|
|
555
|
-
| Shadow DOM (closed mode) | ⚠️ Limited | Open shadow DOM works; closed mode requires `eval:` workaround |
|
|
556
|
-
| Multi-tab / popup flows | ⚠️ Partial | New tabs opened by click are not automatically followed |
|
|
557
|
-
| OS-level dialogs | ❌ Not supported | Native file picker, print dialog, OS auth prompts |
|
|
558
|
-
| CAPTCHAs | ❌ Not supported | By design — no circumvention |
|
|
559
|
-
| Biometric auth | ❌ Not supported | Touch ID, Face ID, WebAuthn |
|
|
560
|
-
| Browser gestures (pinch/zoom) | ❌ Not supported | Mobile multi-touch gestures |
|
|
561
|
-
| Hover-only menus (CSS `:hover`) | ✅ Works | Use `hover` action before clicking submenu items |
|
|
562
|
-
| Right-click context menus | ⚠️ Limited | Browser context menus not accessible; app-level menus often work |
|
|
563
|
-
| Drag and drop | ✅ Works | Use `drag` action with `selector` + `targetSelector` |
|
|
564
|
-
| Infinite scroll / lazy load | ✅ Works | Use `scroll:load` with repeat count |
|
|
565
|
-
|
|
566
|
-
**Workarounds for unsupported interactions:**
|
|
567
|
-
- Use `eval:` to run JavaScript directly: `{ "action": "eval", "value": "document.querySelector('#btn').click()" }`
|
|
568
|
-
- Use `wait:ms:` to pause before difficult timing-sensitive interactions
|
|
569
|
-
- For shadow DOM: `{ "action": "eval", "value": "document.querySelector('my-el').shadowRoot.querySelector('button').click()" }`
|
|
430
|
+
| Variable | Default | Description |
|
|
431
|
+
|----------|---------|-------------|
|
|
432
|
+
| `GHOSTRUN_AI_PROVIDER` | `auto` | `ollama`, `anthropic`, or `auto` |
|
|
433
|
+
| `GHOSTRUN_OLLAMA_URL` | `http://localhost:11434` | Ollama server URL |
|
|
434
|
+
| `GHOSTRUN_OLLAMA_MODEL` | auto-detected | Model to use with Ollama |
|
|
435
|
+
| `ANTHROPIC_API_KEY` | — | Anthropic API key (cloud fallback) |
|
|
570
436
|
|
|
571
437
|
---
|
|
572
438
|
|
|
573
|
-
## Data
|
|
439
|
+
## Data & Privacy
|
|
574
440
|
|
|
575
|
-
All data
|
|
441
|
+
All data lives locally in `~/.ghostrun/`:
|
|
576
442
|
|
|
577
443
|
```
|
|
578
444
|
~/.ghostrun/
|
|
579
|
-
├── data/ghostrun.db # SQLite: flows, runs, steps, schedules,
|
|
580
|
-
│ # environments, api_responses, perf_runs
|
|
445
|
+
├── data/ghostrun.db # SQLite: flows, runs, steps, schedules, environments
|
|
581
446
|
├── screenshots/ # PNG per step per run
|
|
582
447
|
├── baselines/ # Visual baseline reference screenshots
|
|
583
448
|
└── diffs/ # Screenshot diff images
|
|
584
449
|
```
|
|
585
450
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
## Privacy
|
|
589
|
-
|
|
590
|
-
PII is sanitized before storage — emails, phones, cards, JWTs, API keys, passwords are replaced with `[EMAIL]`, `[PHONE]`, etc. Nothing sensitive is sent to AI unless you explicitly call `run:analyze`.
|
|
591
|
-
|
|
592
|
-
---
|
|
593
|
-
|
|
594
|
-
## Build from Source
|
|
595
|
-
|
|
596
|
-
```bash
|
|
597
|
-
npm install
|
|
598
|
-
npm run build # compiles .ts → .js via esbuild
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
---
|
|
602
|
-
|
|
603
|
-
## Publishing to npm
|
|
604
|
-
|
|
605
|
-
```bash
|
|
606
|
-
# 1. Log in to npm
|
|
607
|
-
npm login
|
|
608
|
-
|
|
609
|
-
# 2. Make sure the build is fresh
|
|
610
|
-
npm run build
|
|
611
|
-
|
|
612
|
-
# 3. Dry-run to verify what gets published
|
|
613
|
-
npm publish --dry-run
|
|
614
|
-
|
|
615
|
-
# 4. Publish
|
|
616
|
-
npm publish --access public
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
After publishing, users can install with:
|
|
620
|
-
```bash
|
|
621
|
-
npm install -g ghostrun-cli
|
|
622
|
-
ghostrun init
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
---
|
|
626
|
-
|
|
627
|
-
## Trust & Transparency
|
|
628
|
-
|
|
629
|
-
- **100% local by default** — No cloud, no telemetry, no tracking. Everything runs on your machine.
|
|
630
|
-
- **Open source (MIT)** — Full source code at [github.com/TechBuiltBySharan/ghostrun](https://github.com/TechBuiltBySharan/ghostrun)
|
|
631
|
-
- **No surprise costs** — AI works offline with [Ollama](https://ollama.com) (free). Anthropic API key is optional.
|
|
632
|
-
- **PII sanitization built-in** — Emails, passwords, tokens, and API keys are redacted before being stored in the local SQLite database.
|
|
633
|
-
- **No vendor lock-in** — Flows are plain JSON files you own. Export, import, version-control them like code.
|
|
634
|
-
- **I use this for** — Regression testing my own web apps, monitoring live API endpoints, and running load tests before deploys.
|
|
451
|
+
**PII sanitization:** Emails, phone numbers, credit cards, JWTs, API keys, and passwords are redacted before storage. Nothing sensitive is sent to AI unless you explicitly call `run:analyze`.
|
|
635
452
|
|
|
636
453
|
---
|
|
637
454
|
|
|
638
455
|
## Contributing
|
|
639
456
|
|
|
640
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to get started.
|
|
457
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to get started, and [REFERENCE.md](REFERENCE.md) for the full flow actions reference.
|
|
641
458
|
|
|
642
459
|
---
|
|
643
460
|
|
|
644
|
-
##
|
|
461
|
+
## Trust & Transparency
|
|
645
462
|
|
|
646
|
-
|
|
463
|
+
- **100% local by default** — no cloud, no telemetry, no tracking
|
|
464
|
+
- **Open source (MIT)** — full source at [github.com/TechBuiltBySharan/ghostrun](https://github.com/TechBuiltBySharan/ghostrun)
|
|
465
|
+
- **No surprise costs** — AI works offline via [Ollama](https://ollama.com) (free); Anthropic key is optional
|
|
466
|
+
- **No vendor lock-in** — flows are plain JSON files you own; export, import, version-control them like code
|
|
467
|
+
- Built with the help of [Claude](https://claude.ai) and [Goose](https://goose-docs.ai)
|
|
647
468
|
|
|
648
469
|
---
|
|
649
470
|
|
package/REFERENCE.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# GhostRun — Flow Actions Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for all actions you can use in recorded or hand-crafted `.flow.json` files.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Browser Actions
|
|
8
|
+
|
|
9
|
+
### Navigation
|
|
10
|
+
|
|
11
|
+
| Action | Fields | Description |
|
|
12
|
+
|--------|--------|-------------|
|
|
13
|
+
| `navigate` | `url` | Go to URL |
|
|
14
|
+
| `reload` | — | Reload the current page |
|
|
15
|
+
| `back` | — | Browser back |
|
|
16
|
+
| `forward` | — | Browser forward |
|
|
17
|
+
|
|
18
|
+
### Interaction
|
|
19
|
+
|
|
20
|
+
| Action | Fields | Description |
|
|
21
|
+
|--------|--------|-------------|
|
|
22
|
+
| `click` | `selector` | Left-click an element |
|
|
23
|
+
| `dblclick` | `selector` | Double-click an element |
|
|
24
|
+
| `fill` | `selector`, `value` | Clear field and type value |
|
|
25
|
+
| `type` | `selector`, `value`, `delay?` | Type with configurable key delay (ms) |
|
|
26
|
+
| `clear` | `selector` | Clear a field |
|
|
27
|
+
| `select` | `selector`, `value` | Select a dropdown option by value |
|
|
28
|
+
| `check` | `selector`, `value: "true"\|"false"` | Check/uncheck a checkbox |
|
|
29
|
+
| `focus` | `selector` | Focus an element |
|
|
30
|
+
| `hover` | `selector` | Mouse hover |
|
|
31
|
+
| `drag` | `selector`, `targetSelector` | Drag one element to another |
|
|
32
|
+
| `keyboard` | `key`, `selector?` | Press a key (e.g. `Enter`, `Tab`, `Control+a`) |
|
|
33
|
+
| `upload` | `selector`, `value` | Set file input (comma-separated paths) |
|
|
34
|
+
|
|
35
|
+
### Waiting
|
|
36
|
+
|
|
37
|
+
| Action | Fields | Description |
|
|
38
|
+
|--------|--------|-------------|
|
|
39
|
+
| `wait` | `selector` | Wait for element to appear |
|
|
40
|
+
| `wait:text` | `selector`, `value` | Wait until element contains text |
|
|
41
|
+
| `wait:url` | `value` | Wait for URL to match pattern |
|
|
42
|
+
| `wait:ms` | `value` | Wait for N milliseconds |
|
|
43
|
+
|
|
44
|
+
### Scrolling
|
|
45
|
+
|
|
46
|
+
| Action | Fields | Description |
|
|
47
|
+
|--------|--------|-------------|
|
|
48
|
+
| `scroll` | `selector?` | Scroll to element (or page) |
|
|
49
|
+
| `scroll:element` | `selector` | Scroll element into view |
|
|
50
|
+
| `scroll:bottom` | — | Scroll to bottom of page |
|
|
51
|
+
| `scroll:load` | `value?` | Scroll to bottom, wait for load (repeat N times) |
|
|
52
|
+
| `next:page` | `selector?` | Click next page link and wait |
|
|
53
|
+
|
|
54
|
+
### Assertions
|
|
55
|
+
|
|
56
|
+
| Action | Fields | Description |
|
|
57
|
+
|--------|--------|-------------|
|
|
58
|
+
| `assert:visible` | `selector` | Assert element is visible |
|
|
59
|
+
| `assert:hidden` | `selector` | Assert element is not visible |
|
|
60
|
+
| `assert:text` | `selector`, `value` | Assert element contains text |
|
|
61
|
+
| `assert:not-text` | `selector`, `value` | Assert element does NOT contain text |
|
|
62
|
+
| `assert:value` | `selector`, `value` | Assert input value |
|
|
63
|
+
| `assert:count` | `selector`, `value` | Assert number of matching elements |
|
|
64
|
+
| `assert:attr` | `selector`, `value: "attr=expected"` | Assert element attribute |
|
|
65
|
+
|
|
66
|
+
### Data Extraction
|
|
67
|
+
|
|
68
|
+
| Action | Fields | Description |
|
|
69
|
+
|--------|--------|-------------|
|
|
70
|
+
| `extract` | `selector`, `variable: "variableName"` | Extract element text → variable |
|
|
71
|
+
| `screenshot` | — | Capture screenshot at this step |
|
|
72
|
+
|
|
73
|
+
### Browser State
|
|
74
|
+
|
|
75
|
+
| Action | Fields | Description |
|
|
76
|
+
|--------|--------|-------------|
|
|
77
|
+
| `cookie:set` | `value: "name=value; domain=..."` | Set a cookie |
|
|
78
|
+
| `cookie:clear` | — | Clear all cookies |
|
|
79
|
+
| `storage:set` | `selector: "key"`, `value: "val"` | Set localStorage item |
|
|
80
|
+
| `eval` | `value` | Execute JavaScript on the page |
|
|
81
|
+
| `iframe:enter` | `selector` | Enter an iframe context |
|
|
82
|
+
| `iframe:exit` | — | Exit iframe context, return to main frame |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## API Actions
|
|
87
|
+
|
|
88
|
+
### HTTP Requests
|
|
89
|
+
|
|
90
|
+
| Action | Fields | Description |
|
|
91
|
+
|--------|--------|-------------|
|
|
92
|
+
| `http:request` | `method`, `url`, `headers?`, `body?`, `auth?`, `extract?` | Make an HTTP request. `auth` supports `{ type: "bearer", token: "{{var}}" }`. `extract` is a map of `variableName → $.jsonPath`. |
|
|
93
|
+
|
|
94
|
+
### Assertions
|
|
95
|
+
|
|
96
|
+
| Action | Fields | Description |
|
|
97
|
+
|--------|--------|-------------|
|
|
98
|
+
| `assert:response` | `assert: "status"`, `expected` | Assert HTTP status code |
|
|
99
|
+
| `assert:response` | `assert: "json:path"`, `path`, `expected` | Assert JSONPath value equals expected |
|
|
100
|
+
| `assert:response` | `assert: "json:exists"`, `path` | Assert JSONPath exists in response |
|
|
101
|
+
| `assert:response` | `assert: "header"`, `header`, `expected` | Assert response header value |
|
|
102
|
+
| `assert:response` | `assert: "body:contains"`, `expected` | Assert raw body contains string |
|
|
103
|
+
| `assert:response` | `assert: "time"`, `expected` | Assert response time < expected ms |
|
|
104
|
+
|
|
105
|
+
### Variables & Flow Control
|
|
106
|
+
|
|
107
|
+
| Action | Fields | Description |
|
|
108
|
+
|--------|--------|-------------|
|
|
109
|
+
| `set:variable` | `variable`, `value` | Set a named variable (supports `{{interpolation}}`) |
|
|
110
|
+
| `extract:json` | `variable`, `path` | Extract a value from the last response body via JSONPath |
|
|
111
|
+
| `env:switch` | `value` | Switch active environment mid-flow |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Variables
|
|
116
|
+
|
|
117
|
+
Use `{{variableName}}` in any `value`, `url`, `selector`, or `body` field:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{ "action": "fill", "selector": "#email", "value": "{{userEmail}}" }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Pass at runtime:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
ghostrun run <id> --var userEmail=user@example.com
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Values extracted with `extract:` and `extract:json` are automatically available as variables in subsequent steps.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Limitations
|
|
134
|
+
|
|
135
|
+
| Interaction | Status | Notes |
|
|
136
|
+
|------------|--------|-------|
|
|
137
|
+
| Canvas drawing | ❌ | `<canvas>` elements — no visual capture |
|
|
138
|
+
| WebGL / Three.js | ❌ | GPU-rendered content |
|
|
139
|
+
| Browser native dialogs | ⚠️ Partial | `alert()`/`confirm()`/`prompt()` auto-dismissed |
|
|
140
|
+
| File download verification | ⚠️ Partial | Download triggers but content is not validated |
|
|
141
|
+
| WebRTC / media streams | ❌ | Camera, mic, screen capture APIs |
|
|
142
|
+
| Browser extensions | ❌ | Extension UI not accessible via Playwright |
|
|
143
|
+
| Shadow DOM (closed mode) | ⚠️ Limited | Open shadow DOM works; closed mode needs `eval` workaround |
|
|
144
|
+
| Multi-tab / popup flows | ⚠️ Partial | New tabs opened by click are not automatically followed |
|
|
145
|
+
| OS-level dialogs | ❌ | Native file picker, print dialog, OS auth prompts |
|
|
146
|
+
| CAPTCHAs | ❌ | By design — no circumvention |
|
|
147
|
+
| Biometric auth | ❌ | Touch ID, Face ID, WebAuthn |
|
|
148
|
+
| Browser gestures (pinch/zoom) | ❌ | Mobile multi-touch gestures |
|
|
149
|
+
| Hover-only menus (CSS `:hover`) | ✅ | Use `hover` action before clicking submenu items |
|
|
150
|
+
| Right-click context menus | ⚠️ Limited | Browser context menus inaccessible; app-level menus often work |
|
|
151
|
+
| Drag and drop | ✅ | Use `drag` with `selector` + `targetSelector` |
|
|
152
|
+
| Infinite scroll / lazy load | ✅ | Use `scroll:load` with repeat count |
|
|
153
|
+
|
|
154
|
+
**Workarounds:**
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
// Run JS directly
|
|
158
|
+
{ "action": "eval", "value": "document.querySelector('#btn').click()" }
|
|
159
|
+
|
|
160
|
+
// Shadow DOM
|
|
161
|
+
{ "action": "eval", "value": "document.querySelector('my-el').shadowRoot.querySelector('button').click()" }
|
|
162
|
+
|
|
163
|
+
// Timing-sensitive steps
|
|
164
|
+
{ "action": "wait:ms", "value": "500" }
|
|
165
|
+
```
|
package/ghostrun.js
CHANGED
|
@@ -3644,7 +3644,7 @@ var import_uuid = require("uuid");
|
|
|
3644
3644
|
var HOME_DIR = process.env.HOME || process.env.USERPROFILE || ".";
|
|
3645
3645
|
var DATA_PATH = path.join(HOME_DIR, ".ghostrun");
|
|
3646
3646
|
var DB_PATH = path.join(DATA_PATH, "data", "ghostrun.db");
|
|
3647
|
-
var DatabaseManager = class {
|
|
3647
|
+
var DatabaseManager = class _DatabaseManager {
|
|
3648
3648
|
db;
|
|
3649
3649
|
constructor() {
|
|
3650
3650
|
fs.mkdirSync(path.join(DATA_PATH, "data"), { recursive: true });
|
|
@@ -3982,35 +3982,72 @@ var DatabaseManager = class {
|
|
|
3982
3982
|
};
|
|
3983
3983
|
}
|
|
3984
3984
|
// ---- DB migrations ----
|
|
3985
|
+
//
|
|
3986
|
+
// Uses SQLite's built-in PRAGMA user_version as a schema version counter.
|
|
3987
|
+
// Each migration runs exactly once: we read the current version, apply every
|
|
3988
|
+
// migration whose index is >= that version (in order), then write the new version.
|
|
3989
|
+
//
|
|
3990
|
+
// HOW TO ADD A NEW MIGRATION:
|
|
3991
|
+
// 1. Append a new string to the MIGRATIONS array below.
|
|
3992
|
+
// 2. That's it. The runner handles the rest.
|
|
3993
|
+
//
|
|
3994
|
+
// Never edit or reorder existing entries — just append.
|
|
3995
|
+
static MIGRATIONS = [
|
|
3996
|
+
// v1: add diff_percent to steps
|
|
3997
|
+
"ALTER TABLE steps ADD COLUMN diff_percent REAL",
|
|
3998
|
+
// v2: add created_by to flows
|
|
3999
|
+
"ALTER TABLE flows ADD COLUMN created_by TEXT NOT NULL DEFAULT 'human'",
|
|
4000
|
+
// v3: add verified flag to flows
|
|
4001
|
+
"ALTER TABLE flows ADD COLUMN verified INTEGER NOT NULL DEFAULT 0",
|
|
4002
|
+
// v4: add captured_at to run_data
|
|
4003
|
+
"ALTER TABLE run_data ADD COLUMN captured_at TEXT DEFAULT (datetime('now'))",
|
|
4004
|
+
// v5: environments table
|
|
4005
|
+
`CREATE TABLE IF NOT EXISTS environments (
|
|
4006
|
+
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, base_url TEXT,
|
|
4007
|
+
variables TEXT NOT NULL DEFAULT '{}', is_active INTEGER NOT NULL DEFAULT 0,
|
|
4008
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
4009
|
+
)`,
|
|
4010
|
+
// v6: api_responses table
|
|
4011
|
+
`CREATE TABLE IF NOT EXISTS api_responses (
|
|
4012
|
+
id TEXT PRIMARY KEY, run_id TEXT NOT NULL, step_number INTEGER NOT NULL,
|
|
4013
|
+
method TEXT NOT NULL, url TEXT NOT NULL, status_code INTEGER,
|
|
4014
|
+
response_time_ms INTEGER, response_headers TEXT, response_body TEXT,
|
|
4015
|
+
error_message TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
4016
|
+
)`,
|
|
4017
|
+
// v7: perf_runs table
|
|
4018
|
+
`CREATE TABLE IF NOT EXISTS perf_runs (
|
|
4019
|
+
id TEXT PRIMARY KEY, flow_id TEXT NOT NULL, flow_name TEXT NOT NULL,
|
|
4020
|
+
config TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'running',
|
|
4021
|
+
total_requests INTEGER, success_requests INTEGER, failed_requests INTEGER,
|
|
4022
|
+
avg_rps REAL, p50_ms INTEGER, p95_ms INTEGER, p99_ms INTEGER,
|
|
4023
|
+
min_ms INTEGER, max_ms INTEGER, per_step_stats TEXT,
|
|
4024
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')), completed_at TEXT
|
|
4025
|
+
)`
|
|
4026
|
+
// --- add new migrations below this line ---
|
|
4027
|
+
];
|
|
4028
|
+
// Number of migrations that existed before we introduced versioning.
|
|
4029
|
+
// Existing databases have these applied already (via old try/catch approach)
|
|
4030
|
+
// but their user_version is 0. We detect this and fast-forward rather than
|
|
4031
|
+
// re-running them (which would throw "duplicate column" errors).
|
|
4032
|
+
static LEGACY_MIGRATION_COUNT = 7;
|
|
4033
|
+
columnExists(table, column) {
|
|
4034
|
+
const cols = this.db.pragma(`table_info(${table})`);
|
|
4035
|
+
return cols.some((c) => c.name === column);
|
|
4036
|
+
}
|
|
3985
4037
|
runMigrations() {
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
try {
|
|
3991
|
-
this.db.exec("ALTER TABLE flows ADD COLUMN created_by TEXT NOT NULL DEFAULT 'human'");
|
|
3992
|
-
} catch {
|
|
3993
|
-
}
|
|
3994
|
-
try {
|
|
3995
|
-
this.db.exec("ALTER TABLE flows ADD COLUMN verified INTEGER NOT NULL DEFAULT 0");
|
|
3996
|
-
} catch {
|
|
3997
|
-
}
|
|
3998
|
-
try {
|
|
3999
|
-
this.db.exec("ALTER TABLE run_data ADD COLUMN captured_at TEXT DEFAULT (datetime('now'))");
|
|
4000
|
-
} catch {
|
|
4001
|
-
}
|
|
4002
|
-
try {
|
|
4003
|
-
this.db.exec(`CREATE TABLE IF NOT EXISTS environments (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, base_url TEXT, variables TEXT NOT NULL DEFAULT '{}', is_active INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')))`);
|
|
4004
|
-
} catch {
|
|
4005
|
-
}
|
|
4006
|
-
try {
|
|
4007
|
-
this.db.exec(`CREATE TABLE IF NOT EXISTS api_responses (id TEXT PRIMARY KEY, run_id TEXT NOT NULL, step_number INTEGER NOT NULL, method TEXT NOT NULL, url TEXT NOT NULL, status_code INTEGER, response_time_ms INTEGER, response_headers TEXT, response_body TEXT, error_message TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')))`);
|
|
4008
|
-
} catch {
|
|
4009
|
-
}
|
|
4010
|
-
try {
|
|
4011
|
-
this.db.exec(`CREATE TABLE IF NOT EXISTS perf_runs (id TEXT PRIMARY KEY, flow_id TEXT NOT NULL, flow_name TEXT NOT NULL, config TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'running', total_requests INTEGER, success_requests INTEGER, failed_requests INTEGER, avg_rps REAL, p50_ms INTEGER, p95_ms INTEGER, p99_ms INTEGER, min_ms INTEGER, max_ms INTEGER, per_step_stats TEXT, started_at TEXT NOT NULL DEFAULT (datetime('now')), completed_at TEXT)`);
|
|
4012
|
-
} catch {
|
|
4038
|
+
let currentVersion = this.db.pragma("user_version", { simple: true }) ?? 0;
|
|
4039
|
+
if (currentVersion === 0 && this.columnExists("steps", "diff_percent")) {
|
|
4040
|
+
currentVersion = _DatabaseManager.LEGACY_MIGRATION_COUNT;
|
|
4041
|
+
this.db.pragma(`user_version = ${currentVersion}`);
|
|
4013
4042
|
}
|
|
4043
|
+
if (currentVersion >= _DatabaseManager.MIGRATIONS.length) return;
|
|
4044
|
+
const applyAll = this.db.transaction(() => {
|
|
4045
|
+
for (let i = currentVersion; i < _DatabaseManager.MIGRATIONS.length; i++) {
|
|
4046
|
+
this.db.exec(_DatabaseManager.MIGRATIONS[i]);
|
|
4047
|
+
}
|
|
4048
|
+
this.db.pragma(`user_version = ${_DatabaseManager.MIGRATIONS.length}`);
|
|
4049
|
+
});
|
|
4050
|
+
applyAll();
|
|
4014
4051
|
}
|
|
4015
4052
|
// ---- Suites ----
|
|
4016
4053
|
createSuite(data) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghostrun-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Browser automation + API testing + load testing in one tool. Record flows, test REST APIs, run VU-based load tests, export to k6. Entirely local.",
|
|
5
5
|
"main": "ghostrun.js",
|
|
6
6
|
"bin": {
|