cssprobe-cli 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.
- package/AGENTS.md +59 -6
- package/CHANGELOG.md +21 -0
- package/README.md +111 -2
- package/dist/browser/frames.d.ts +15 -0
- package/dist/browser/frames.js +60 -0
- package/dist/browser/interactions.d.ts +49 -0
- package/dist/browser/interactions.js +96 -0
- package/dist/browser/launcher.d.ts +30 -2
- package/dist/browser/launcher.js +141 -34
- package/dist/browser/sampler.d.ts +43 -0
- package/dist/browser/sampler.js +111 -0
- package/dist/cli/commands.d.ts +3 -0
- package/dist/cli/commands.js +162 -4
- package/dist/cli/input.d.ts +6 -0
- package/dist/cli/input.js +24 -0
- package/dist/cli/program.js +171 -1
- package/dist/cli/session-ops.d.ts +10 -1
- package/dist/cli/session-ops.js +27 -56
- package/dist/cli/state-check.d.ts +17 -0
- package/dist/cli/state-check.js +43 -0
- package/dist/cli/url-guard.d.ts +2 -0
- package/dist/cli/url-guard.js +24 -0
- package/dist/daemon/daemon.js +162 -18
- package/dist/daemon/expect.d.ts +8 -0
- package/dist/daemon/expect.js +47 -0
- package/dist/daemon/flowRunner.d.ts +73 -0
- package/dist/daemon/flowRunner.js +177 -0
- package/dist/engine/evalSerializer.d.ts +1 -0
- package/dist/engine/evalSerializer.js +43 -0
- package/dist/help.json +156 -16
- package/dist/installer/skill-template.js +43 -0
- package/dist/scenario/assertions.d.ts +13 -0
- package/dist/scenario/assertions.js +111 -0
- package/dist/scenario/reporters.d.ts +13 -0
- package/dist/scenario/reporters.js +79 -0
- package/dist/scenario/runner.d.ts +10 -0
- package/dist/scenario/runner.js +244 -0
- package/dist/scenario/schema.d.ts +1210 -0
- package/dist/scenario/schema.js +145 -0
- package/dist/utils/screenshot.d.ts +1 -0
- package/dist/utils/screenshot.js +58 -0
- package/docs/guide/installation.md +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/dist/help.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
"global": "Usage: cssprobe-cli <command> [args] [options]\n\n\nSession Management:\n open [url] open browser in session mode (non-blocking)\n close close the browser session\n status show current session status\n resize <width> <height> resize the browser viewport\n eval <expression> evaluate javascript expression on the page (browser context)\n playwright <call> execute playwright api call (node.js context)\n state-import [file] import cookies from netscape format file\n state-save save current browser session state (cookies + localstorage)\n mcp start mcp server (stdio transport) for ai clients\n mcp-install install mcp server config into ai client configs\n mcp-uninstall remove mcp server config from ai client configs\n\nCSS Inspection:\n inspect <selector> inspect the runtime css of a page element\n tree <selector> show dom tree structure\n layout <selector> show ascii layout diagram\n findings <selector> show only issues/warnings/errors\n inject-css <css> inject css into the current page\n\nExport:\n screenshot take a screenshot of the current page\n\nConfiguration:\n config-show show current configuration\n config-set <key> <value> set a configuration value\n config-list list all profiles\n config-use <name> switch active profile\n config-new <name> create a new profile\n config-path show configuration file path\n\nAgent Skills:\n skill-install install agent skill for cssprobe-cli\n skill-uninstall remove agent skill for cssprobe-cli\n\nGlobal options:\n --help [command] print help\n --json output response as JSON\n --raw output only the result value\n --version print version\n -p, --profile <name> use named profile (or CSSPROBE_PROFILE env)",
|
|
2
|
+
"global": "Usage: cssprobe-cli <command> [args] [options]\n\n\nSession Management:\n open [url] open browser in session mode (non-blocking)\n close close the browser session\n status show current session status\n frames list frames in the current page\n doctor diagnose browser installation and playwright revision mismatch\n scenario <action> <file> run a json scenario suite (regression cases, artifacts, junit)\n resize <width> <height> resize the browser viewport\n eval <expression> evaluate javascript expression on the page (browser context)\n playwright <call> execute playwright api call (node.js context)\n click <selector> click an element with real mouse events (works with custom react components)\n hover <selector> hover an element with real mouse events\n type <selector> <text> type text into an element (fill by default; --sequential for real key events)\n wait-for [selector] wait for a condition: selector state, js predicate, or network idle\n sample <expression> sample a js expression over time and return a timeline with summary stats\n wait-stable <expression> wait until a js expression stops changing (tolerance + stable window)\n run <file> run a multi-step flow script (default-exports an async function receiving the api object)\n state-import [file] import cookies from netscape format file\n state-save save current browser session state (cookies + localstorage)\n state-check validate a saved state file: cookies, expiry, and target domain coverage\n mcp start mcp server (stdio transport) for ai clients\n mcp-install install mcp server config into ai client configs\n mcp-uninstall remove mcp server config from ai client configs\n\nCSS Inspection:\n inspect <selector> inspect the runtime css of a page element\n tree <selector> show dom tree structure\n layout <selector> show ascii layout diagram\n findings <selector> show only issues/warnings/errors\n inject-css <css> inject css into the current page\n\nExport:\n screenshot take a screenshot of the current page\n\nConfiguration:\n config-show show current configuration\n config-set <key> <value> set a configuration value\n config-list list all profiles\n config-use <name> switch active profile\n config-new <name> create a new profile\n config-path show configuration file path\n\nAgent Skills:\n skill-install install agent skill for cssprobe-cli\n skill-uninstall remove agent skill for cssprobe-cli\n\nGlobal options:\n --help [command] print help\n --json output response as JSON\n --raw output only the result value\n --version print version\n -p, --profile <name> use named profile (or CSSPROBE_PROFILE env)",
|
|
3
3
|
"commands": {
|
|
4
4
|
"open": {
|
|
5
|
-
"help": "cssprobe-cli open [url]\n\nOpen browser in session mode (non-blocking)\n\nArguments:\n [url] url, file:// path, or local html file path\nOptions:\n --browser browser engine (default chromium)\n --headed show the browser window\n --viewport viewport size as wxh (e.g. 1280x720)\n --state path to saved state file (cookies + localstorage)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
5
|
+
"help": "cssprobe-cli open [url]\n\nOpen browser in session mode (non-blocking)\n\nArguments:\n [url] url, file:// path, or local html file path\nOptions:\n --browser browser engine (default chromium)\n --headed show the browser window\n --viewport viewport size as wxh (e.g. 1280x720)\n --state path to saved state file (cookies + localstorage)\n --expect-url regex the final page url must match\n --fail-on-login fail when the page redirects to a login url\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
6
6
|
"flags": {
|
|
7
7
|
"browser": "string",
|
|
8
8
|
"headed": "boolean",
|
|
9
9
|
"viewport": "string",
|
|
10
|
-
"state": "string"
|
|
10
|
+
"state": "string",
|
|
11
|
+
"expect-url": "string",
|
|
12
|
+
"fail-on-login": "boolean"
|
|
11
13
|
},
|
|
12
14
|
"args": [
|
|
13
15
|
"url"
|
|
@@ -25,44 +27,80 @@
|
|
|
25
27
|
"flags": {},
|
|
26
28
|
"args": []
|
|
27
29
|
},
|
|
30
|
+
"frames": {
|
|
31
|
+
"help": "cssprobe-cli frames \n\nList frames in the current page\n\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
32
|
+
"flags": {},
|
|
33
|
+
"args": []
|
|
34
|
+
},
|
|
35
|
+
"doctor": {
|
|
36
|
+
"help": "cssprobe-cli doctor \n\nDiagnose browser installation and Playwright revision mismatch\n\nOptions:\n --engine browser engine: chromium, firefox, webkit (default: chromium)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
37
|
+
"flags": {
|
|
38
|
+
"engine": "string"
|
|
39
|
+
},
|
|
40
|
+
"args": []
|
|
41
|
+
},
|
|
42
|
+
"scenario": {
|
|
43
|
+
"help": "cssprobe-cli scenario <action> <file>\n\nRun a JSON scenario suite (regression cases, artifacts, JUnit)\n\nArguments:\n <action> only \"run\" is supported\n <file> scenario suite json path\nOptions:\n --state fallback state file when the suite/case does not set one\n --artifacts artifact directory (default: ~/.cssprobe-cli/scenario-runs/<suite>-<timestamp>)\n --junit write a junit xml report to this path\n --bail stop after the first failing case\n --headed show the browser window\n --browser browser engine (default chromium)\n --viewport viewport size as wxh (default: suite defaults or 1280x720)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
44
|
+
"flags": {
|
|
45
|
+
"state": "string",
|
|
46
|
+
"artifacts": "string",
|
|
47
|
+
"junit": "string",
|
|
48
|
+
"bail": "boolean",
|
|
49
|
+
"headed": "boolean",
|
|
50
|
+
"browser": "string",
|
|
51
|
+
"viewport": "string"
|
|
52
|
+
},
|
|
53
|
+
"args": [
|
|
54
|
+
"action",
|
|
55
|
+
"file"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
28
58
|
"inspect": {
|
|
29
|
-
"help": "cssprobe-cli inspect <selector>\n\nInspect the runtime CSS of a page element\n\nArguments:\n <selector> css selector for the root element\nOptions:\n --json output structured json instead of markdown\n --brief compact output: tree sketch + warnings/errors only\n --layout ascii layout diagram showing element positions and sizes\n --depth dom tree depth (default: auto, max 20)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
59
|
+
"help": "cssprobe-cli inspect <selector>\n\nInspect the runtime CSS of a page element\n\nArguments:\n <selector> css selector for the root element\nOptions:\n --json output structured json instead of markdown\n --brief compact output: tree sketch + warnings/errors only\n --layout ascii layout diagram showing element positions and sizes\n --depth dom tree depth (default: auto, max 20)\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
30
60
|
"flags": {
|
|
31
61
|
"json": "boolean",
|
|
32
62
|
"brief": "boolean",
|
|
33
63
|
"layout": "boolean",
|
|
34
|
-
"depth": "string"
|
|
64
|
+
"depth": "string",
|
|
65
|
+
"frame": "string"
|
|
35
66
|
},
|
|
36
67
|
"args": [
|
|
37
68
|
"selector"
|
|
38
69
|
]
|
|
39
70
|
},
|
|
40
71
|
"tree": {
|
|
41
|
-
"help": "cssprobe-cli tree <selector>\n\nShow DOM tree structure\n\nArguments:\n <selector> css selector for the root element\nOptions:\n --depth tree depth (default: auto, max 20)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
72
|
+
"help": "cssprobe-cli tree <selector>\n\nShow DOM tree structure\n\nArguments:\n <selector> css selector for the root element\nOptions:\n --depth tree depth (default: auto, max 20)\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
42
73
|
"flags": {
|
|
43
|
-
"depth": "string"
|
|
74
|
+
"depth": "string",
|
|
75
|
+
"frame": "string"
|
|
44
76
|
},
|
|
45
77
|
"args": [
|
|
46
78
|
"selector"
|
|
47
79
|
]
|
|
48
80
|
},
|
|
49
81
|
"layout": {
|
|
50
|
-
"help": "cssprobe-cli layout <selector>\n\nShow ASCII layout diagram\n\nArguments:\n <selector> css selector for the root element\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
51
|
-
"flags": {
|
|
82
|
+
"help": "cssprobe-cli layout <selector>\n\nShow ASCII layout diagram\n\nArguments:\n <selector> css selector for the root element\nOptions:\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
83
|
+
"flags": {
|
|
84
|
+
"frame": "string"
|
|
85
|
+
},
|
|
52
86
|
"args": [
|
|
53
87
|
"selector"
|
|
54
88
|
]
|
|
55
89
|
},
|
|
56
90
|
"findings": {
|
|
57
|
-
"help": "cssprobe-cli findings <selector>\n\nShow only issues/warnings/errors\n\nArguments:\n <selector> css selector for the root element\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
58
|
-
"flags": {
|
|
91
|
+
"help": "cssprobe-cli findings <selector>\n\nShow only issues/warnings/errors\n\nArguments:\n <selector> css selector for the root element\nOptions:\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
92
|
+
"flags": {
|
|
93
|
+
"frame": "string"
|
|
94
|
+
},
|
|
59
95
|
"args": [
|
|
60
96
|
"selector"
|
|
61
97
|
]
|
|
62
98
|
},
|
|
63
99
|
"inject-css": {
|
|
64
|
-
"help": "cssprobe-cli inject-css <css>\n\nInject CSS into the current page\n\nArguments:\n <css> css code to inject\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
65
|
-
"flags": {
|
|
100
|
+
"help": "cssprobe-cli inject-css <css>\n\nInject CSS into the current page\n\nArguments:\n <css> css code to inject\nOptions:\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
101
|
+
"flags": {
|
|
102
|
+
"frame": "string"
|
|
103
|
+
},
|
|
66
104
|
"args": [
|
|
67
105
|
"css"
|
|
68
106
|
]
|
|
@@ -76,14 +114,16 @@
|
|
|
76
114
|
]
|
|
77
115
|
},
|
|
78
116
|
"eval": {
|
|
79
|
-
"help": "cssprobe-cli eval <expression>\n\nEvaluate JavaScript expression on the page (browser context)\n\nArguments:\n <expression> javascript expression
|
|
80
|
-
"flags": {
|
|
117
|
+
"help": "cssprobe-cli eval <expression>\n\nEvaluate JavaScript expression on the page (browser context)\n\nArguments:\n <expression> javascript expression, or @file / - (stdin) for long scripts\nOptions:\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
118
|
+
"flags": {
|
|
119
|
+
"frame": "string"
|
|
120
|
+
},
|
|
81
121
|
"args": [
|
|
82
122
|
"expression"
|
|
83
123
|
]
|
|
84
124
|
},
|
|
85
125
|
"playwright": {
|
|
86
|
-
"help": "cssprobe-cli playwright <call>\n\nExecute Playwright API call (Node.js context)\n\nArguments:\n <call> playwright api call, e.g. \"page.setviewportsize({width: 1280, height: 720})\"\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
126
|
+
"help": "cssprobe-cli playwright <call>\n\nExecute Playwright API call (Node.js context)\n\nArguments:\n <call> playwright api call, or @file / - (stdin); e.g. \"page.setviewportsize({width: 1280, height: 720})\"\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
87
127
|
"flags": {},
|
|
88
128
|
"args": [
|
|
89
129
|
"call"
|
|
@@ -97,6 +137,91 @@
|
|
|
97
137
|
},
|
|
98
138
|
"args": []
|
|
99
139
|
},
|
|
140
|
+
"click": {
|
|
141
|
+
"help": "cssprobe-cli click <selector>\n\nClick an element with real mouse events (works with custom React components)\n\nArguments:\n <selector> css selector\nOptions:\n --nth 1-based match index (default: first match)\n --force skip actionability checks\n --timeout action timeout in ms (default 30000)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
142
|
+
"flags": {
|
|
143
|
+
"nth": "string",
|
|
144
|
+
"force": "boolean",
|
|
145
|
+
"timeout": "string"
|
|
146
|
+
},
|
|
147
|
+
"args": [
|
|
148
|
+
"selector"
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
"hover": {
|
|
152
|
+
"help": "cssprobe-cli hover <selector>\n\nHover an element with real mouse events\n\nArguments:\n <selector> css selector\nOptions:\n --nth 1-based match index (default: first match)\n --force skip actionability checks\n --timeout action timeout in ms (default 30000)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
153
|
+
"flags": {
|
|
154
|
+
"nth": "string",
|
|
155
|
+
"force": "boolean",
|
|
156
|
+
"timeout": "string"
|
|
157
|
+
},
|
|
158
|
+
"args": [
|
|
159
|
+
"selector"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
"type": {
|
|
163
|
+
"help": "cssprobe-cli type <selector> <text>\n\nType text into an element (fill by default; --sequential for real key events)\n\nArguments:\n <selector> css selector\n <text> text to type\nOptions:\n --press key to press after typing (e.g. enter, tab, control+a)\n --sequential type with real key events (for custom editors)\n --clear clear existing content before sequential typing\n --delay delay between keystrokes in ms (--sequential, default 0)\n --nth 1-based match index (default: first match)\n --timeout action timeout in ms (default 30000)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
164
|
+
"flags": {
|
|
165
|
+
"press": "string",
|
|
166
|
+
"sequential": "boolean",
|
|
167
|
+
"clear": "boolean",
|
|
168
|
+
"delay": "string",
|
|
169
|
+
"nth": "string",
|
|
170
|
+
"timeout": "string"
|
|
171
|
+
},
|
|
172
|
+
"args": [
|
|
173
|
+
"selector",
|
|
174
|
+
"text"
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
"wait-for": {
|
|
178
|
+
"help": "cssprobe-cli wait-for [selector]\n\nWait for a condition: selector state, JS predicate, or network idle\n\nArguments:\n [selector] css selector to wait for\nOptions:\n --state selector state to wait for (default visible)\n --js js predicate, e.g. \"() => document.queryselector('.x')?.offsetheight > 100\"\n --network-idle wait until the page is network-idle\n --timeout timeout in ms (default 30000)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
179
|
+
"flags": {
|
|
180
|
+
"state": "string",
|
|
181
|
+
"js": "string",
|
|
182
|
+
"network-idle": "boolean",
|
|
183
|
+
"timeout": "string"
|
|
184
|
+
},
|
|
185
|
+
"args": [
|
|
186
|
+
"selector"
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
"sample": {
|
|
190
|
+
"help": "cssprobe-cli sample <expression>\n\nSample a JS expression over time and return a timeline with summary stats\n\nArguments:\n <expression> js expression sampled on an interval\nOptions:\n --interval sampling interval in ms (default 250)\n --duration total sampling duration in ms (default 5000)\n --key dot path for numeric extraction, e.g. \"top\" or \"box.y\"\n --tail-from tail starts at this elapsed ms (default: last 5000ms)\n --ignore-errors record evaluation errors as sample points instead of aborting\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
191
|
+
"flags": {
|
|
192
|
+
"interval": "string",
|
|
193
|
+
"duration": "string",
|
|
194
|
+
"key": "string",
|
|
195
|
+
"tail-from": "string",
|
|
196
|
+
"ignore-errors": "boolean",
|
|
197
|
+
"frame": "string"
|
|
198
|
+
},
|
|
199
|
+
"args": [
|
|
200
|
+
"expression"
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
"wait-stable": {
|
|
204
|
+
"help": "cssprobe-cli wait-stable <expression>\n\nWait until a JS expression stops changing (tolerance + stable window)\n\nArguments:\n <expression> js expression polled until stable\nOptions:\n --tolerance absolute tolerance for numeric values (default 0)\n --window stable window in ms (default 1500)\n --timeout timeout in ms (default 30000)\n --interval polling interval in ms (default 200)\n --frame target frame: index, name, or url substring (default: main frame)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
205
|
+
"flags": {
|
|
206
|
+
"tolerance": "string",
|
|
207
|
+
"window": "string",
|
|
208
|
+
"timeout": "string",
|
|
209
|
+
"interval": "string",
|
|
210
|
+
"frame": "string"
|
|
211
|
+
},
|
|
212
|
+
"args": [
|
|
213
|
+
"expression"
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
"run": {
|
|
217
|
+
"help": "cssprobe-cli run <file>\n\nRun a multi-step flow script (default-exports an async function receiving the API object)\n\nArguments:\n <file> flow script path (.js / .mjs / .cjs)\nOptions:\n --timeout whole-script timeout in ms (default 300000, 0 = unlimited)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
218
|
+
"flags": {
|
|
219
|
+
"timeout": "string"
|
|
220
|
+
},
|
|
221
|
+
"args": [
|
|
222
|
+
"file"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
100
225
|
"state-import": {
|
|
101
226
|
"help": "cssprobe-cli state-import [file]\n\nImport cookies from Netscape format file\n\nArguments:\n [file] netscape cookie file path (reads from stdin if omitted)\nOptions:\n --out output state file path (default: ~/.cssprobe-cli/states/imported.json)\n --name custom name for the state file (e.g. mysite or mysite.json)\n --merge existing state file to merge into\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
102
227
|
"flags": {
|
|
@@ -115,6 +240,14 @@
|
|
|
115
240
|
},
|
|
116
241
|
"args": []
|
|
117
242
|
},
|
|
243
|
+
"state-check": {
|
|
244
|
+
"help": "cssprobe-cli state-check \n\nValidate a saved state file: cookies, expiry, and target domain coverage\n\nOptions:\n --state state file path (default: ~/.cssprobe-cli/states/session.json)\n --url target url whose hostname must be covered by the cookies\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
|
|
245
|
+
"flags": {
|
|
246
|
+
"state": "string",
|
|
247
|
+
"url": "string"
|
|
248
|
+
},
|
|
249
|
+
"args": []
|
|
250
|
+
},
|
|
118
251
|
"config-show": {
|
|
119
252
|
"help": "cssprobe-cli config-show \n\nShow current configuration\n\nOptions:\n --profile profile name",
|
|
120
253
|
"flags": {
|
|
@@ -198,11 +331,18 @@
|
|
|
198
331
|
},
|
|
199
332
|
"booleanOptions": [
|
|
200
333
|
"headed",
|
|
334
|
+
"fail-on-login",
|
|
201
335
|
"all",
|
|
336
|
+
"bail",
|
|
202
337
|
"json",
|
|
203
338
|
"brief",
|
|
204
339
|
"layout",
|
|
205
340
|
"full-page",
|
|
341
|
+
"force",
|
|
342
|
+
"sequential",
|
|
343
|
+
"clear",
|
|
344
|
+
"network-idle",
|
|
345
|
+
"ignore-errors",
|
|
206
346
|
"local"
|
|
207
347
|
]
|
|
208
348
|
}
|
|
@@ -127,6 +127,49 @@ function buildSkillMd() {
|
|
|
127
127
|
'cssprobe-cli close # stop browser + daemon',
|
|
128
128
|
BT3,
|
|
129
129
|
'',
|
|
130
|
+
'## Multi-step UI Flows',
|
|
131
|
+
'',
|
|
132
|
+
'Beyond inspection, cssprobe-cli drives the page: click, hover, type, wait, and',
|
|
133
|
+
'run scripted flows -- all in the same session.',
|
|
134
|
+
'',
|
|
135
|
+
BT3 + 'bash',
|
|
136
|
+
'cssprobe-cli click ".layout-switch" # real mouse click (auto-scroll + actionability wait)',
|
|
137
|
+
'cssprobe-cli click ".item" --nth 2 # 1-based match index; multi-match without --nth fails (strict)',
|
|
138
|
+
'cssprobe-cli hover ".menu-item"',
|
|
139
|
+
'cssprobe-cli type ".block-title input" "Hi" --press Enter # fill by default',
|
|
140
|
+
'cssprobe-cli type ".editor" "text" --sequential --clear # real key events for custom editors',
|
|
141
|
+
'cssprobe-cli wait-for ".modal" --state visible --timeout 5000',
|
|
142
|
+
'cssprobe-cli wait-for --js "() => ready === true"',
|
|
143
|
+
'cssprobe-cli wait-for --network-idle',
|
|
144
|
+
BT3,
|
|
145
|
+
'',
|
|
146
|
+
'### Scripted flows: ' + BT + 'run <file>' + BT,
|
|
147
|
+
'',
|
|
148
|
+
'A flow script default-exports an async function receiving the API object and',
|
|
149
|
+
'runs inside the daemon session:',
|
|
150
|
+
'',
|
|
151
|
+
BT3 + 'js',
|
|
152
|
+
'export default async ({ page, click, hover, type, waitFor, inspect, screenshot, log }) => {',
|
|
153
|
+
' await waitFor(".editor-canvas");',
|
|
154
|
+
' await click(".layout-switch", { nth: 1 });',
|
|
155
|
+
' await type(".block-title input", "Hello", { press: "Enter", sequential: true, clear: true });',
|
|
156
|
+
' await waitFor({ networkIdle: true });',
|
|
157
|
+
' const { findings } = await inspect(".block-grid", { format: "json" });',
|
|
158
|
+
' await screenshot({ path: "./after.png" });',
|
|
159
|
+
' return { findings: findings.length };',
|
|
160
|
+
'};',
|
|
161
|
+
BT3,
|
|
162
|
+
'',
|
|
163
|
+
BT3 + 'bash',
|
|
164
|
+
'cssprobe-cli run ./flow.js # --timeout ms (default 300000, 0 = unlimited)',
|
|
165
|
+
BT3,
|
|
166
|
+
'',
|
|
167
|
+
'Notes:',
|
|
168
|
+
'',
|
|
169
|
+
'- Scripts run on your machine with full Node privileges -- only run trusted scripts.',
|
|
170
|
+
'- ' + BT + 'screenshot' + BT + ' relative paths resolve against the script directory.',
|
|
171
|
+
'- On failure the command exits 1, prints collected logs, and keeps the session alive.',
|
|
172
|
+
'',
|
|
130
173
|
'## Output Modes',
|
|
131
174
|
'',
|
|
132
175
|
'Pass ' + BT + '--json' + BT + ' when consuming output programmatically:',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AssertStep } from './schema';
|
|
2
|
+
export interface AssertionState {
|
|
3
|
+
url: string;
|
|
4
|
+
title: string;
|
|
5
|
+
frames: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AssertionContext {
|
|
8
|
+
vars: Record<string, unknown>;
|
|
9
|
+
state: AssertionState;
|
|
10
|
+
globals: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolvePath(value: unknown, path?: string): unknown;
|
|
13
|
+
export declare function evaluateAssertion(assertion: AssertStep, context: AssertionContext): string | null;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvePath = resolvePath;
|
|
4
|
+
exports.evaluateAssertion = evaluateAssertion;
|
|
5
|
+
function resolvePath(value, path) {
|
|
6
|
+
if (!path)
|
|
7
|
+
return value;
|
|
8
|
+
let current = value;
|
|
9
|
+
for (const part of path.split('.')) {
|
|
10
|
+
if (current === null || current === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
current = current[part];
|
|
13
|
+
}
|
|
14
|
+
return current;
|
|
15
|
+
}
|
|
16
|
+
function deepEqual(a, b) {
|
|
17
|
+
if (a === b)
|
|
18
|
+
return true;
|
|
19
|
+
if (typeof a !== typeof b)
|
|
20
|
+
return false;
|
|
21
|
+
if (a === null || b === null)
|
|
22
|
+
return false;
|
|
23
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
24
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
25
|
+
return false;
|
|
26
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
27
|
+
}
|
|
28
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
29
|
+
const aKeys = Object.keys(a).sort();
|
|
30
|
+
const bKeys = Object.keys(b).sort();
|
|
31
|
+
if (aKeys.length !== bKeys.length || aKeys.some((key, index) => key !== bKeys[index]))
|
|
32
|
+
return false;
|
|
33
|
+
return aKeys.every(key => deepEqual(a[key], b[key]));
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
function label(assertion) {
|
|
38
|
+
return assertion.label ?? 'assert';
|
|
39
|
+
}
|
|
40
|
+
function evaluateAssertion(assertion, context) {
|
|
41
|
+
const errors = [];
|
|
42
|
+
if (assertion.url) {
|
|
43
|
+
let regex;
|
|
44
|
+
try {
|
|
45
|
+
regex = new RegExp(assertion.url);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return `${label(assertion)}: invalid url pattern ${assertion.url}`;
|
|
49
|
+
}
|
|
50
|
+
if (!regex.test(context.state.url)) {
|
|
51
|
+
errors.push(`url ${JSON.stringify(context.state.url)} does not match ${assertion.url}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [name, expected] of Object.entries(assertion.globals ?? {})) {
|
|
55
|
+
const actual = context.globals[name] ?? 'undefined';
|
|
56
|
+
if (actual !== expected) {
|
|
57
|
+
errors.push(`global ${name}: expected ${expected}, received ${actual}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const name of assertion.absent ?? []) {
|
|
61
|
+
const actual = context.globals[name] ?? 'undefined';
|
|
62
|
+
if (actual !== 'undefined') {
|
|
63
|
+
errors.push(`global ${name}: expected absent, received ${actual}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const hasValueCheck = ['eq', 'ne', 'match', 'within', 'min', 'max'].some(key => key in assertion);
|
|
67
|
+
if (!hasValueCheck) {
|
|
68
|
+
return errors.length ? `${label(assertion)}: ${errors.join('; ')}` : null;
|
|
69
|
+
}
|
|
70
|
+
let value;
|
|
71
|
+
if (assertion.on) {
|
|
72
|
+
if (assertion.on === 'state') {
|
|
73
|
+
value = context.state;
|
|
74
|
+
}
|
|
75
|
+
else if (assertion.on in context.vars) {
|
|
76
|
+
value = context.vars[assertion.on];
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return `${label(assertion)}: unknown variable "${assertion.on}"`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
value = context.state;
|
|
84
|
+
}
|
|
85
|
+
value = resolvePath(value, assertion.path);
|
|
86
|
+
if ('eq' in assertion && !deepEqual(value, assertion.eq)) {
|
|
87
|
+
errors.push(`expected ${JSON.stringify(assertion.eq)}, received ${JSON.stringify(value)}`);
|
|
88
|
+
}
|
|
89
|
+
if ('ne' in assertion && deepEqual(value, assertion.ne)) {
|
|
90
|
+
errors.push(`expected value different from ${JSON.stringify(assertion.ne)}`);
|
|
91
|
+
}
|
|
92
|
+
if (assertion.match !== undefined) {
|
|
93
|
+
const actual = typeof value === 'string' ? value : JSON.stringify(value);
|
|
94
|
+
if (!new RegExp(assertion.match).test(actual)) {
|
|
95
|
+
errors.push(`expected ${JSON.stringify(actual)} to match ${assertion.match}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (assertion.within) {
|
|
99
|
+
if (typeof value !== 'number' || Math.abs(value - assertion.within.expected) > assertion.within.tolerance) {
|
|
100
|
+
errors.push(`expected ${assertion.within.expected} ±${assertion.within.tolerance}, received ${JSON.stringify(value)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (assertion.min !== undefined && (typeof value !== 'number' || value < assertion.min)) {
|
|
104
|
+
errors.push(`expected >= ${assertion.min}, received ${JSON.stringify(value)}`);
|
|
105
|
+
}
|
|
106
|
+
if (assertion.max !== undefined && (typeof value !== 'number' || value > assertion.max)) {
|
|
107
|
+
errors.push(`expected <= ${assertion.max}, received ${JSON.stringify(value)}`);
|
|
108
|
+
}
|
|
109
|
+
return errors.length ? `${label(assertion)}: ${errors.join('; ')}` : null;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=assertions.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CaseResult } from './schema';
|
|
2
|
+
export declare function slugify(value: string): string;
|
|
3
|
+
export interface ScenarioSummary {
|
|
4
|
+
total: number;
|
|
5
|
+
passed: number;
|
|
6
|
+
failed: number;
|
|
7
|
+
durationMs: number;
|
|
8
|
+
ok: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function summarize(results: CaseResult[]): ScenarioSummary;
|
|
11
|
+
export declare function formatConsole(suiteName: string, results: CaseResult[]): string;
|
|
12
|
+
export declare function escapeXml(value: string): string;
|
|
13
|
+
export declare function toJunit(suiteName: string, results: CaseResult[]): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.slugify = slugify;
|
|
4
|
+
exports.summarize = summarize;
|
|
5
|
+
exports.formatConsole = formatConsole;
|
|
6
|
+
exports.escapeXml = escapeXml;
|
|
7
|
+
exports.toJunit = toJunit;
|
|
8
|
+
function slugify(value) {
|
|
9
|
+
return value
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
12
|
+
.replace(/^-+|-+$/g, '')
|
|
13
|
+
.slice(0, 60) || 'case';
|
|
14
|
+
}
|
|
15
|
+
function summarize(results) {
|
|
16
|
+
const passed = results.filter(result => result.ok).length;
|
|
17
|
+
const durationMs = results.reduce((total, result) => total + result.durationMs, 0);
|
|
18
|
+
return {
|
|
19
|
+
total: results.length,
|
|
20
|
+
passed,
|
|
21
|
+
failed: results.length - passed,
|
|
22
|
+
durationMs,
|
|
23
|
+
ok: passed === results.length,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function formatConsole(suiteName, results) {
|
|
27
|
+
const summary = summarize(results);
|
|
28
|
+
const lines = [];
|
|
29
|
+
for (const result of results) {
|
|
30
|
+
const marker = result.ok ? 'PASS' : result.skipped ? 'SKIP' : 'FAIL';
|
|
31
|
+
lines.push(`${marker} ${result.name} (${result.durationMs}ms)`);
|
|
32
|
+
if (result.error)
|
|
33
|
+
lines.push(` ${result.error}`);
|
|
34
|
+
for (const step of result.steps) {
|
|
35
|
+
if (!step.ok)
|
|
36
|
+
lines.push(` step ${step.index + 1} (${step.action}): ${step.error}`);
|
|
37
|
+
}
|
|
38
|
+
if (result.artifactsDir)
|
|
39
|
+
lines.push(` artifacts: ${result.artifactsDir}`);
|
|
40
|
+
}
|
|
41
|
+
lines.push('');
|
|
42
|
+
lines.push(`${suiteName}: ${summary.passed}/${summary.total} passed, ${summary.failed} failed in ${summary.durationMs}ms`);
|
|
43
|
+
return lines.join('\n');
|
|
44
|
+
}
|
|
45
|
+
function escapeXml(value) {
|
|
46
|
+
return value
|
|
47
|
+
.replace(/&/g, '&')
|
|
48
|
+
.replace(/</g, '<')
|
|
49
|
+
.replace(/>/g, '>')
|
|
50
|
+
.replace(/"/g, '"')
|
|
51
|
+
.replace(/'/g, ''');
|
|
52
|
+
}
|
|
53
|
+
function toJunit(suiteName, results) {
|
|
54
|
+
const summary = summarize(results);
|
|
55
|
+
const cases = results
|
|
56
|
+
.map(result => {
|
|
57
|
+
const time = (result.durationMs / 1000).toFixed(3);
|
|
58
|
+
const failureDetails = [
|
|
59
|
+
result.error,
|
|
60
|
+
...result.steps.filter(step => !step.ok).map(step => `step ${step.index + 1} (${step.action}): ${step.error}`),
|
|
61
|
+
]
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
.join('\n');
|
|
64
|
+
const body = result.ok
|
|
65
|
+
? ''
|
|
66
|
+
: `<failure message="${escapeXml(result.error ?? 'scenario case failed')}">${escapeXml(failureDetails)}</failure>`;
|
|
67
|
+
return ` <testcase classname="${escapeXml(suiteName)}" name="${escapeXml(result.name)}" time="${time}">${body}</testcase>`;
|
|
68
|
+
})
|
|
69
|
+
.join('\n');
|
|
70
|
+
return [
|
|
71
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
72
|
+
`<testsuites tests="${summary.total}" failures="${summary.failed}" time="${(summary.durationMs / 1000).toFixed(3)}">`,
|
|
73
|
+
` <testsuite name="${escapeXml(suiteName)}" tests="${summary.total}" failures="${summary.failed}" time="${(summary.durationMs / 1000).toFixed(3)}">`,
|
|
74
|
+
cases,
|
|
75
|
+
' </testsuite>',
|
|
76
|
+
'</testsuites>',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=reporters.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ScenarioSummary } from './reporters';
|
|
2
|
+
import type { CaseResult, ScenarioRunOptions, Suite } from './schema';
|
|
3
|
+
export interface ScenarioRunResult {
|
|
4
|
+
suite: string;
|
|
5
|
+
ok: boolean;
|
|
6
|
+
summary: ScenarioSummary;
|
|
7
|
+
results: CaseResult[];
|
|
8
|
+
artifactsDir: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function runScenario(suite: Suite, suitePath: string, options?: ScenarioRunOptions): Promise<ScenarioRunResult>;
|