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/browser/launcher.js
CHANGED
|
@@ -6,6 +6,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
};
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.BrowserLauncher = void 0;
|
|
9
|
+
exports.browserCacheRoots = browserCacheRoots;
|
|
10
|
+
exports.resolveExecutableIn = resolveExecutableIn;
|
|
11
|
+
exports.listBrowserInstalls = listBrowserInstalls;
|
|
12
|
+
exports.expectedBrowsers = expectedBrowsers;
|
|
13
|
+
exports.diagnoseBrowser = diagnoseBrowser;
|
|
14
|
+
exports.findBrowserExecutable = findBrowserExecutable;
|
|
9
15
|
const fs_1 = __importDefault(require("fs"));
|
|
10
16
|
const path_1 = __importDefault(require("path"));
|
|
11
17
|
const os_1 = __importDefault(require("os"));
|
|
@@ -21,39 +27,133 @@ function loadBundle() {
|
|
|
21
27
|
bundleContent = fs_1.default.readFileSync(COLLECTOR_BUNDLE_PATH, 'utf-8');
|
|
22
28
|
return bundleContent;
|
|
23
29
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
30
|
+
function browserCacheRoots() {
|
|
31
|
+
const fromEnv = process.env.PLAYWRIGHT_BROWSERS_PATH;
|
|
32
|
+
if (fromEnv)
|
|
33
|
+
return [fromEnv];
|
|
34
|
+
if (process.platform === 'darwin') {
|
|
35
|
+
return [path_1.default.join(os_1.default.homedir(), 'Library', 'Caches', 'ms-playwright')];
|
|
36
|
+
}
|
|
37
|
+
if (process.platform === 'win32') {
|
|
38
|
+
const localAppData = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
|
|
39
|
+
return [path_1.default.join(localAppData, 'ms-playwright')];
|
|
40
|
+
}
|
|
41
|
+
return [path_1.default.join(os_1.default.homedir(), '.cache', 'ms-playwright')];
|
|
42
|
+
}
|
|
43
|
+
function resolveExecutableIn(dir, platform) {
|
|
44
|
+
const candidates = [];
|
|
45
|
+
if (platform === 'darwin') {
|
|
46
|
+
candidates.push(path_1.default.join(dir, 'chrome-mac-arm64', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing'));
|
|
47
|
+
candidates.push(path_1.default.join(dir, 'chrome-mac', 'Google Chrome for Testing.app', 'Contents', 'MacOS', 'Google Chrome for Testing'));
|
|
48
|
+
}
|
|
49
|
+
else if (platform === 'linux') {
|
|
50
|
+
candidates.push(path_1.default.join(dir, 'chrome-linux', 'chrome'));
|
|
51
|
+
candidates.push(path_1.default.join(dir, 'chrome-linux64', 'chrome'));
|
|
52
|
+
}
|
|
53
|
+
else if (platform === 'win32') {
|
|
54
|
+
candidates.push(path_1.default.join(dir, 'chrome-win', 'chrome.exe'));
|
|
55
|
+
candidates.push(path_1.default.join(dir, 'chrome-win64', 'chrome.exe'));
|
|
56
|
+
}
|
|
57
|
+
for (const candidate of candidates) {
|
|
58
|
+
if (fs_1.default.existsSync(candidate))
|
|
59
|
+
return candidate;
|
|
54
60
|
}
|
|
55
61
|
return null;
|
|
56
62
|
}
|
|
63
|
+
function listBrowserInstalls(cacheRoots = browserCacheRoots()) {
|
|
64
|
+
const installs = [];
|
|
65
|
+
for (const root of cacheRoots) {
|
|
66
|
+
if (!fs_1.default.existsSync(root))
|
|
67
|
+
continue;
|
|
68
|
+
for (const entry of fs_1.default.readdirSync(root)) {
|
|
69
|
+
const match = /^(chromium|chromium_headless_shell)-(\d+)$/.exec(entry);
|
|
70
|
+
if (!match)
|
|
71
|
+
continue;
|
|
72
|
+
const dir = path_1.default.join(root, entry);
|
|
73
|
+
installs.push({
|
|
74
|
+
kind: match[1] === 'chromium' ? 'chromium' : 'chromium-headless-shell',
|
|
75
|
+
revision: parseInt(match[2], 10),
|
|
76
|
+
dir,
|
|
77
|
+
executable: resolveExecutableIn(dir, process.platform),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return installs.sort((a, b) => b.revision - a.revision);
|
|
82
|
+
}
|
|
83
|
+
function expectedBrowsers() {
|
|
84
|
+
try {
|
|
85
|
+
const pkgPath = require.resolve('playwright-core/package.json');
|
|
86
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
87
|
+
const browsersPath = path_1.default.join(path_1.default.dirname(pkgPath), 'browsers.json');
|
|
88
|
+
const browsers = JSON.parse(fs_1.default.readFileSync(browsersPath, 'utf-8'));
|
|
89
|
+
const chromium = browsers.browsers?.find(browser => browser.name === 'chromium');
|
|
90
|
+
const headlessShell = browsers.browsers?.find(browser => browser.name === 'chromium-headless-shell');
|
|
91
|
+
return {
|
|
92
|
+
chromium: chromium ? parseInt(chromium.revision, 10) : undefined,
|
|
93
|
+
headlessShell: headlessShell ? parseInt(headlessShell.revision, 10) : undefined,
|
|
94
|
+
playwrightVersion: pkg.version,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function installCommand(expected) {
|
|
102
|
+
return expected.playwrightVersion
|
|
103
|
+
? `npx playwright@${expected.playwrightVersion} install chromium`
|
|
104
|
+
: 'npx playwright install chromium';
|
|
105
|
+
}
|
|
106
|
+
function diagnoseBrowser(engine = 'chromium', cacheRoots = browserCacheRoots()) {
|
|
107
|
+
const expected = expectedBrowsers();
|
|
108
|
+
const installs = listBrowserInstalls(cacheRoots);
|
|
109
|
+
const chromiumInstalls = installs.filter(install => install.kind === 'chromium' && install.executable);
|
|
110
|
+
const exact = expected.chromium
|
|
111
|
+
? chromiumInstalls.some(install => install.revision === expected.chromium)
|
|
112
|
+
: chromiumInstalls.length > 0;
|
|
113
|
+
if (engine !== 'chromium') {
|
|
114
|
+
return {
|
|
115
|
+
platform: process.platform,
|
|
116
|
+
cacheRoots,
|
|
117
|
+
expected,
|
|
118
|
+
installs,
|
|
119
|
+
status: 'missing',
|
|
120
|
+
hint: `Automatic discovery only covers chromium; run "npx playwright install ${engine}" if launch fails.`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (exact) {
|
|
124
|
+
return { platform: process.platform, cacheRoots, expected, installs, status: 'exact', hint: null };
|
|
125
|
+
}
|
|
126
|
+
if (chromiumInstalls.length > 0) {
|
|
127
|
+
const found = chromiumInstalls.map(install => install.revision).join(', ');
|
|
128
|
+
return {
|
|
129
|
+
platform: process.platform,
|
|
130
|
+
cacheRoots,
|
|
131
|
+
expected,
|
|
132
|
+
installs,
|
|
133
|
+
status: 'fallback',
|
|
134
|
+
hint: `Found chromium revision(s) ${found}, expected ${expected.chromium ?? 'unknown'}. Launch may work through the fallback. To install the expected build: ${installCommand(expected)}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
platform: process.platform,
|
|
139
|
+
cacheRoots,
|
|
140
|
+
expected,
|
|
141
|
+
installs,
|
|
142
|
+
status: 'missing',
|
|
143
|
+
hint: `No chromium install found. Install it with: ${installCommand(expected)}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Try to find an existing browser executable in the Playwright cache. */
|
|
147
|
+
function findBrowserExecutable(engine, cacheRoots = browserCacheRoots()) {
|
|
148
|
+
if (engine !== 'chromium')
|
|
149
|
+
return null;
|
|
150
|
+
const installs = listBrowserInstalls(cacheRoots).filter(install => install.kind === 'chromium' && install.executable);
|
|
151
|
+
if (!installs.length)
|
|
152
|
+
return null;
|
|
153
|
+
const expected = expectedBrowsers().chromium;
|
|
154
|
+
const exact = installs.find(install => install.revision === expected);
|
|
155
|
+
return (exact ?? installs[0]).executable;
|
|
156
|
+
}
|
|
57
157
|
class BrowserLauncher {
|
|
58
158
|
options;
|
|
59
159
|
browser = null;
|
|
@@ -74,7 +174,13 @@ class BrowserLauncher {
|
|
|
74
174
|
if (execPath) {
|
|
75
175
|
launchOptions.executablePath = execPath;
|
|
76
176
|
}
|
|
77
|
-
|
|
177
|
+
try {
|
|
178
|
+
this.browser = await factory.launch(launchOptions);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const diagnosis = diagnoseBrowser(engine);
|
|
182
|
+
throw new Error(`${error.message}\nBrowser diagnosis: ${diagnosis.status}. Run "cssprobe-cli doctor" for details.`);
|
|
183
|
+
}
|
|
78
184
|
// Load saved state if provided
|
|
79
185
|
const vp = this.options.viewport || { width: 1280, height: 720 };
|
|
80
186
|
const contextOptions = { viewport: vp };
|
|
@@ -98,12 +204,13 @@ class BrowserLauncher {
|
|
|
98
204
|
await this.page.goto(`file://${resolved}`, { waitUntil: 'load', timeout: 30000 });
|
|
99
205
|
}
|
|
100
206
|
}
|
|
101
|
-
async collect(cfg) {
|
|
207
|
+
async collect(cfg, target) {
|
|
102
208
|
if (!this.page)
|
|
103
209
|
throw new Error('Browser not opened. Call open() first.');
|
|
210
|
+
const host = target ?? this.page;
|
|
104
211
|
const bundle = loadBundle();
|
|
105
|
-
await
|
|
106
|
-
const snapshot = await
|
|
212
|
+
await host.addScriptTag({ content: bundle });
|
|
213
|
+
const snapshot = await host.evaluate((config) => {
|
|
107
214
|
return window.__cssprobe_cli.collect(config);
|
|
108
215
|
}, cfg);
|
|
109
216
|
return snapshot;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Frame } from 'playwright-core';
|
|
2
|
+
export interface SamplePoint {
|
|
3
|
+
t: number;
|
|
4
|
+
value: unknown;
|
|
5
|
+
visibility: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SampleSummary {
|
|
9
|
+
samples: number;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
first: SamplePoint | null;
|
|
12
|
+
last: SamplePoint | null;
|
|
13
|
+
min: number | null;
|
|
14
|
+
max: number | null;
|
|
15
|
+
numeric: Array<[number, number]>;
|
|
16
|
+
changes: number;
|
|
17
|
+
hiddenMs: number;
|
|
18
|
+
tail: {
|
|
19
|
+
fromT: number;
|
|
20
|
+
min: number | null;
|
|
21
|
+
max: number | null;
|
|
22
|
+
windowMs: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function extractNumber(value: unknown, key?: string): number | null;
|
|
26
|
+
export declare function summarizeSamples(points: SamplePoint[], key?: string, tailWindowMs?: number, tailFromMs?: number): SampleSummary;
|
|
27
|
+
export declare function isWithinTolerance(previous: unknown, next: unknown, tolerance: number): boolean;
|
|
28
|
+
export declare function sampleFrame(frame: Frame, expression: string, opts: {
|
|
29
|
+
intervalMs: number;
|
|
30
|
+
durationMs: number;
|
|
31
|
+
ignoreErrors?: boolean;
|
|
32
|
+
}): Promise<SamplePoint[]>;
|
|
33
|
+
export declare function waitForStableValue(frame: Frame, expression: string, opts: {
|
|
34
|
+
toleranceMs?: number;
|
|
35
|
+
windowMs?: number;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
intervalMs?: number;
|
|
38
|
+
tolerance?: number;
|
|
39
|
+
}): Promise<{
|
|
40
|
+
elapsedMs: number;
|
|
41
|
+
value: unknown;
|
|
42
|
+
samples: number;
|
|
43
|
+
}>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractNumber = extractNumber;
|
|
4
|
+
exports.summarizeSamples = summarizeSamples;
|
|
5
|
+
exports.isWithinTolerance = isWithinTolerance;
|
|
6
|
+
exports.sampleFrame = sampleFrame;
|
|
7
|
+
exports.waitForStableValue = waitForStableValue;
|
|
8
|
+
function extractNumber(value, key) {
|
|
9
|
+
if (key) {
|
|
10
|
+
let current = value;
|
|
11
|
+
for (const part of key.split('.')) {
|
|
12
|
+
if (current === null || current === undefined)
|
|
13
|
+
return null;
|
|
14
|
+
current = current[part];
|
|
15
|
+
}
|
|
16
|
+
return typeof current === 'number' && Number.isFinite(current) ? current : null;
|
|
17
|
+
}
|
|
18
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
19
|
+
}
|
|
20
|
+
function summarizeSamples(points, key, tailWindowMs = 5000, tailFromMs) {
|
|
21
|
+
const numeric = [];
|
|
22
|
+
for (const point of points) {
|
|
23
|
+
const n = extractNumber(point.value, key);
|
|
24
|
+
if (n !== null)
|
|
25
|
+
numeric.push([point.t, n]);
|
|
26
|
+
}
|
|
27
|
+
const tailFrom = Math.max(0, tailFromMs ?? (points.length ? points[points.length - 1].t - tailWindowMs : 0));
|
|
28
|
+
const tailNumeric = numeric.filter(([t]) => t >= tailFrom).map(([, value]) => value);
|
|
29
|
+
let changes = 0;
|
|
30
|
+
let hiddenMs = 0;
|
|
31
|
+
for (let i = 0; i < points.length; i++) {
|
|
32
|
+
if (i > 0 && JSON.stringify(points[i].value) !== JSON.stringify(points[i - 1].value))
|
|
33
|
+
changes++;
|
|
34
|
+
if (i < points.length - 1 && points[i].visibility !== 'visible') {
|
|
35
|
+
hiddenMs += points[i + 1].t - points[i].t;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
samples: points.length,
|
|
40
|
+
durationMs: points.length ? points[points.length - 1].t : 0,
|
|
41
|
+
first: points[0] ?? null,
|
|
42
|
+
last: points[points.length - 1] ?? null,
|
|
43
|
+
min: numeric.length ? Math.min(...numeric.map(item => item[1])) : null,
|
|
44
|
+
max: numeric.length ? Math.max(...numeric.map(item => item[1])) : null,
|
|
45
|
+
numeric,
|
|
46
|
+
changes,
|
|
47
|
+
hiddenMs,
|
|
48
|
+
tail: {
|
|
49
|
+
fromT: tailFrom,
|
|
50
|
+
min: tailNumeric.length ? Math.min(...tailNumeric) : null,
|
|
51
|
+
max: tailNumeric.length ? Math.max(...tailNumeric) : null,
|
|
52
|
+
windowMs: tailWindowMs,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function isWithinTolerance(previous, next, tolerance) {
|
|
57
|
+
if (typeof previous === 'number' && typeof next === 'number') {
|
|
58
|
+
if (Number.isNaN(previous) || Number.isNaN(next))
|
|
59
|
+
return false;
|
|
60
|
+
return Math.abs(next - previous) <= tolerance;
|
|
61
|
+
}
|
|
62
|
+
return JSON.stringify(previous) === JSON.stringify(next);
|
|
63
|
+
}
|
|
64
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
65
|
+
async function readValue(frame, expression) {
|
|
66
|
+
return frame.evaluate(`(async () => ({ value: await (${expression}), visibility: document.visibilityState }))()`);
|
|
67
|
+
}
|
|
68
|
+
async function sampleFrame(frame, expression, opts) {
|
|
69
|
+
const intervalMs = Math.max(1, Math.floor(opts.intervalMs));
|
|
70
|
+
const started = Date.now();
|
|
71
|
+
const points = [];
|
|
72
|
+
while (Date.now() - started < opts.durationMs) {
|
|
73
|
+
const t = Date.now() - started;
|
|
74
|
+
try {
|
|
75
|
+
const { value, visibility } = await readValue(frame, expression);
|
|
76
|
+
points.push({ t, value, visibility });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!opts.ignoreErrors)
|
|
80
|
+
throw error;
|
|
81
|
+
points.push({ t, value: null, visibility: 'unknown', error: error.message });
|
|
82
|
+
}
|
|
83
|
+
await sleep(intervalMs);
|
|
84
|
+
}
|
|
85
|
+
return points;
|
|
86
|
+
}
|
|
87
|
+
async function waitForStableValue(frame, expression, opts) {
|
|
88
|
+
const intervalMs = Math.max(1, opts.intervalMs ?? 200);
|
|
89
|
+
const windowMs = Math.max(1, opts.windowMs ?? 1500);
|
|
90
|
+
const timeoutMs = Math.max(1, opts.timeoutMs ?? 30000);
|
|
91
|
+
const tolerance = opts.tolerance ?? 0;
|
|
92
|
+
const started = Date.now();
|
|
93
|
+
let previous;
|
|
94
|
+
let stableSince = started;
|
|
95
|
+
let samples = 0;
|
|
96
|
+
while (Date.now() - started < timeoutMs) {
|
|
97
|
+
const { value } = await readValue(frame, expression);
|
|
98
|
+
samples++;
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
if (samples === 1 || !isWithinTolerance(previous, value, tolerance)) {
|
|
101
|
+
previous = value;
|
|
102
|
+
stableSince = now;
|
|
103
|
+
}
|
|
104
|
+
else if (now - stableSince >= windowMs) {
|
|
105
|
+
return { elapsedMs: now - started, value, samples };
|
|
106
|
+
}
|
|
107
|
+
await sleep(intervalMs);
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`wait-stable timed out after ${timeoutMs}ms; last value: ${JSON.stringify(previous)}`);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=sampler.js.map
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -14,14 +14,17 @@ export declare const inspectCommand: import("./command").CommandSchema<z.ZodObje
|
|
|
14
14
|
brief: z.ZodOptional<z.ZodBoolean>;
|
|
15
15
|
layout: z.ZodOptional<z.ZodBoolean>;
|
|
16
16
|
depth: z.ZodOptional<z.ZodEffects<z.ZodNumber, number, unknown>>;
|
|
17
|
+
frame: z.ZodOptional<z.ZodString>;
|
|
17
18
|
}, "strip", z.ZodTypeAny, {
|
|
18
19
|
json?: boolean | undefined;
|
|
19
20
|
brief?: boolean | undefined;
|
|
20
21
|
layout?: boolean | undefined;
|
|
21
22
|
depth?: number | undefined;
|
|
23
|
+
frame?: string | undefined;
|
|
22
24
|
}, {
|
|
23
25
|
json?: boolean | undefined;
|
|
24
26
|
brief?: boolean | undefined;
|
|
25
27
|
layout?: boolean | undefined;
|
|
26
28
|
depth?: unknown;
|
|
29
|
+
frame?: string | undefined;
|
|
27
30
|
}>>;
|
package/dist/cli/commands.js
CHANGED
|
@@ -57,6 +57,8 @@ const open = (0, command_1.declareCommand)({
|
|
|
57
57
|
headed: z.boolean().optional().describe('show the browser window'),
|
|
58
58
|
viewport: z.string().optional().describe('viewport size as WxH (e.g. 1280x720)'),
|
|
59
59
|
state: z.string().optional().describe('path to saved state file (cookies + localStorage)'),
|
|
60
|
+
'expect-url': z.string().optional().describe('regex the final page URL must match'),
|
|
61
|
+
'fail-on-login': z.boolean().optional().describe('fail when the page redirects to a login URL'),
|
|
60
62
|
}),
|
|
61
63
|
});
|
|
62
64
|
const close = (0, command_1.declareCommand)({
|
|
@@ -72,6 +74,37 @@ const status = (0, command_1.declareCommand)({
|
|
|
72
74
|
category: 'core',
|
|
73
75
|
description: 'Show current session status',
|
|
74
76
|
});
|
|
77
|
+
const framesCmd = (0, command_1.declareCommand)({
|
|
78
|
+
name: 'frames',
|
|
79
|
+
category: 'core',
|
|
80
|
+
description: 'List frames in the current page',
|
|
81
|
+
});
|
|
82
|
+
const scenarioCmd = (0, command_1.declareCommand)({
|
|
83
|
+
name: 'scenario',
|
|
84
|
+
category: 'core',
|
|
85
|
+
description: 'Run a JSON scenario suite (regression cases, artifacts, JUnit)',
|
|
86
|
+
args: z.object({
|
|
87
|
+
action: z.literal('run').describe('only "run" is supported'),
|
|
88
|
+
file: z.string().describe('scenario suite JSON path'),
|
|
89
|
+
}),
|
|
90
|
+
options: z.object({
|
|
91
|
+
state: z.string().optional().describe('fallback state file when the suite/case does not set one'),
|
|
92
|
+
artifacts: z.string().optional().describe('artifact directory (default: ~/.cssprobe-cli/scenario-runs/<suite>-<timestamp>)'),
|
|
93
|
+
junit: z.string().optional().describe('write a JUnit XML report to this path'),
|
|
94
|
+
bail: z.boolean().optional().describe('stop after the first failing case'),
|
|
95
|
+
headed: z.boolean().optional().describe('show the browser window'),
|
|
96
|
+
browser: z.enum(['chromium', 'firefox', 'webkit']).optional().describe('browser engine (default chromium)'),
|
|
97
|
+
viewport: z.string().optional().describe('viewport size as WxH (default: suite defaults or 1280x720)'),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
const doctor = (0, command_1.declareCommand)({
|
|
101
|
+
name: 'doctor',
|
|
102
|
+
category: 'core',
|
|
103
|
+
description: 'Diagnose browser installation and Playwright revision mismatch',
|
|
104
|
+
options: z.object({
|
|
105
|
+
engine: z.string().optional().describe('browser engine: chromium, firefox, webkit (default: chromium)'),
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
75
108
|
// ── cssprobe commands ──
|
|
76
109
|
const inspect = (0, command_1.declareCommand)({
|
|
77
110
|
name: 'inspect',
|
|
@@ -85,6 +118,7 @@ const inspect = (0, command_1.declareCommand)({
|
|
|
85
118
|
brief: z.boolean().optional().describe('compact output: tree sketch + warnings/errors only'),
|
|
86
119
|
layout: z.boolean().optional().describe('ASCII layout diagram showing element positions and sizes'),
|
|
87
120
|
depth: numberArg.optional().describe('DOM tree depth (default: auto, max 20)'),
|
|
121
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
88
122
|
}),
|
|
89
123
|
});
|
|
90
124
|
const tree = (0, command_1.declareCommand)({
|
|
@@ -96,6 +130,7 @@ const tree = (0, command_1.declareCommand)({
|
|
|
96
130
|
}),
|
|
97
131
|
options: z.object({
|
|
98
132
|
depth: numberArg.optional().describe('tree depth (default: auto, max 20)'),
|
|
133
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
99
134
|
}),
|
|
100
135
|
});
|
|
101
136
|
const layout = (0, command_1.declareCommand)({
|
|
@@ -105,6 +140,9 @@ const layout = (0, command_1.declareCommand)({
|
|
|
105
140
|
args: z.object({
|
|
106
141
|
selector: z.string().describe('CSS selector for the root element'),
|
|
107
142
|
}),
|
|
143
|
+
options: z.object({
|
|
144
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
145
|
+
}),
|
|
108
146
|
});
|
|
109
147
|
const findings = (0, command_1.declareCommand)({
|
|
110
148
|
name: 'findings',
|
|
@@ -113,6 +151,9 @@ const findings = (0, command_1.declareCommand)({
|
|
|
113
151
|
args: z.object({
|
|
114
152
|
selector: z.string().describe('CSS selector for the root element'),
|
|
115
153
|
}),
|
|
154
|
+
options: z.object({
|
|
155
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
156
|
+
}),
|
|
116
157
|
});
|
|
117
158
|
const injectCss = (0, command_1.declareCommand)({
|
|
118
159
|
name: 'inject-css',
|
|
@@ -121,6 +162,9 @@ const injectCss = (0, command_1.declareCommand)({
|
|
|
121
162
|
args: z.object({
|
|
122
163
|
css: z.string().describe('CSS code to inject'),
|
|
123
164
|
}),
|
|
165
|
+
options: z.object({
|
|
166
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
167
|
+
}),
|
|
124
168
|
});
|
|
125
169
|
// ── browser commands ──
|
|
126
170
|
const screenshot = (0, command_1.declareCommand)({
|
|
@@ -146,7 +190,10 @@ const evalCmd = (0, command_1.declareCommand)({
|
|
|
146
190
|
category: 'core',
|
|
147
191
|
description: 'Evaluate JavaScript expression on the page (browser context)',
|
|
148
192
|
args: z.object({
|
|
149
|
-
expression: z.string().describe('JavaScript expression
|
|
193
|
+
expression: z.string().describe('JavaScript expression, or @file / - (stdin) for long scripts'),
|
|
194
|
+
}),
|
|
195
|
+
options: z.object({
|
|
196
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
150
197
|
}),
|
|
151
198
|
});
|
|
152
199
|
const playwrightCmd = (0, command_1.declareCommand)({
|
|
@@ -154,7 +201,107 @@ const playwrightCmd = (0, command_1.declareCommand)({
|
|
|
154
201
|
category: 'core',
|
|
155
202
|
description: 'Execute Playwright API call (Node.js context)',
|
|
156
203
|
args: z.object({
|
|
157
|
-
call: z.string().describe('Playwright API call, e.g. "page.setViewportSize({width: 1280, height: 720})"'),
|
|
204
|
+
call: z.string().describe('Playwright API call, or @file / - (stdin); e.g. "page.setViewportSize({width: 1280, height: 720})"'),
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
// ── interaction ──
|
|
208
|
+
const click = (0, command_1.declareCommand)({
|
|
209
|
+
name: 'click',
|
|
210
|
+
category: 'core',
|
|
211
|
+
description: 'Click an element with real mouse events (works with custom React components)',
|
|
212
|
+
args: z.object({
|
|
213
|
+
selector: z.string().describe('CSS selector'),
|
|
214
|
+
}),
|
|
215
|
+
options: z.object({
|
|
216
|
+
nth: numberArg.optional().describe('1-based match index (default: first match)'),
|
|
217
|
+
force: z.boolean().optional().describe('skip actionability checks'),
|
|
218
|
+
timeout: numberArg.optional().describe('action timeout in ms (default 30000)'),
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
const hover = (0, command_1.declareCommand)({
|
|
222
|
+
name: 'hover',
|
|
223
|
+
category: 'core',
|
|
224
|
+
description: 'Hover an element with real mouse events',
|
|
225
|
+
args: z.object({
|
|
226
|
+
selector: z.string().describe('CSS selector'),
|
|
227
|
+
}),
|
|
228
|
+
options: z.object({
|
|
229
|
+
nth: numberArg.optional().describe('1-based match index (default: first match)'),
|
|
230
|
+
force: z.boolean().optional().describe('skip actionability checks'),
|
|
231
|
+
timeout: numberArg.optional().describe('action timeout in ms (default 30000)'),
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
const typeCmd = (0, command_1.declareCommand)({
|
|
235
|
+
name: 'type',
|
|
236
|
+
category: 'core',
|
|
237
|
+
description: 'Type text into an element (fill by default; --sequential for real key events)',
|
|
238
|
+
args: z.object({
|
|
239
|
+
selector: z.string().describe('CSS selector'),
|
|
240
|
+
text: z.string().describe('text to type'),
|
|
241
|
+
}),
|
|
242
|
+
options: z.object({
|
|
243
|
+
press: z.string().optional().describe('key to press after typing (e.g. Enter, Tab, Control+A)'),
|
|
244
|
+
sequential: z.boolean().optional().describe('type with real key events (for custom editors)'),
|
|
245
|
+
clear: z.boolean().optional().describe('clear existing content before sequential typing'),
|
|
246
|
+
delay: numberArg.optional().describe('delay between keystrokes in ms (--sequential, default 0)'),
|
|
247
|
+
nth: numberArg.optional().describe('1-based match index (default: first match)'),
|
|
248
|
+
timeout: numberArg.optional().describe('action timeout in ms (default 30000)'),
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
const waitForCmd = (0, command_1.declareCommand)({
|
|
252
|
+
name: 'wait-for',
|
|
253
|
+
category: 'core',
|
|
254
|
+
description: 'Wait for a condition: selector state, JS predicate, or network idle',
|
|
255
|
+
args: z.object({
|
|
256
|
+
selector: z.string().optional().describe('CSS selector to wait for'),
|
|
257
|
+
}),
|
|
258
|
+
options: z.object({
|
|
259
|
+
state: z.enum(['visible', 'hidden', 'attached', 'detached']).optional().describe('selector state to wait for (default visible)'),
|
|
260
|
+
js: z.string().optional().describe('JS predicate, e.g. "() => document.querySelector(\'.x\')?.offsetHeight > 100"'),
|
|
261
|
+
'network-idle': z.boolean().optional().describe('wait until the page is network-idle'),
|
|
262
|
+
timeout: numberArg.optional().describe('timeout in ms (default 30000)'),
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
const sampleCmd = (0, command_1.declareCommand)({
|
|
266
|
+
name: 'sample',
|
|
267
|
+
category: 'core',
|
|
268
|
+
description: 'Sample a JS expression over time and return a timeline with summary stats',
|
|
269
|
+
args: z.object({
|
|
270
|
+
expression: z.string().describe('JS expression sampled on an interval'),
|
|
271
|
+
}),
|
|
272
|
+
options: z.object({
|
|
273
|
+
interval: numberArg.optional().describe('sampling interval in ms (default 250)'),
|
|
274
|
+
duration: numberArg.optional().describe('total sampling duration in ms (default 5000)'),
|
|
275
|
+
key: z.string().optional().describe('dot path for numeric extraction, e.g. "top" or "box.y"'),
|
|
276
|
+
'tail-from': numberArg.optional().describe('tail starts at this elapsed ms (default: last 5000ms)'),
|
|
277
|
+
'ignore-errors': z.boolean().optional().describe('record evaluation errors as sample points instead of aborting'),
|
|
278
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
279
|
+
}),
|
|
280
|
+
});
|
|
281
|
+
const waitStableCmd = (0, command_1.declareCommand)({
|
|
282
|
+
name: 'wait-stable',
|
|
283
|
+
category: 'core',
|
|
284
|
+
description: 'Wait until a JS expression stops changing (tolerance + stable window)',
|
|
285
|
+
args: z.object({
|
|
286
|
+
expression: z.string().describe('JS expression polled until stable'),
|
|
287
|
+
}),
|
|
288
|
+
options: z.object({
|
|
289
|
+
tolerance: numberArg.optional().describe('absolute tolerance for numeric values (default 0)'),
|
|
290
|
+
window: numberArg.optional().describe('stable window in ms (default 1500)'),
|
|
291
|
+
timeout: numberArg.optional().describe('timeout in ms (default 30000)'),
|
|
292
|
+
interval: numberArg.optional().describe('polling interval in ms (default 200)'),
|
|
293
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
294
|
+
}),
|
|
295
|
+
});
|
|
296
|
+
const runCmd = (0, command_1.declareCommand)({
|
|
297
|
+
name: 'run',
|
|
298
|
+
category: 'core',
|
|
299
|
+
description: 'Run a multi-step flow script (default-exports an async function receiving the API object)',
|
|
300
|
+
args: z.object({
|
|
301
|
+
file: z.string().describe('flow script path (.js / .mjs / .cjs)'),
|
|
302
|
+
}),
|
|
303
|
+
options: z.object({
|
|
304
|
+
timeout: numberArg.optional().describe('whole-script timeout in ms (default 300000, 0 = unlimited)'),
|
|
158
305
|
}),
|
|
159
306
|
});
|
|
160
307
|
// ── state management ──
|
|
@@ -179,6 +326,15 @@ const stateSave = (0, command_1.declareCommand)({
|
|
|
179
326
|
name: z.string().optional().describe('state file name (default: session)'),
|
|
180
327
|
}),
|
|
181
328
|
});
|
|
329
|
+
const stateCheck = (0, command_1.declareCommand)({
|
|
330
|
+
name: 'state-check',
|
|
331
|
+
category: 'core',
|
|
332
|
+
description: 'Validate a saved state file: cookies, expiry, and target domain coverage',
|
|
333
|
+
options: z.object({
|
|
334
|
+
state: z.string().optional().describe('state file path (default: ~/.cssprobe-cli/states/session.json)'),
|
|
335
|
+
url: z.string().optional().describe('target URL whose hostname must be covered by the cookies'),
|
|
336
|
+
}),
|
|
337
|
+
});
|
|
182
338
|
// ── config ──
|
|
183
339
|
const configShow = (0, command_1.declareCommand)({
|
|
184
340
|
name: 'config-show',
|
|
@@ -274,13 +430,15 @@ const mcpUninstall = (0, command_1.declareCommand)({
|
|
|
274
430
|
// ── export ──
|
|
275
431
|
const commandsArray = [
|
|
276
432
|
// session management
|
|
277
|
-
open, close, status,
|
|
433
|
+
open, close, status, framesCmd, doctor, scenarioCmd,
|
|
278
434
|
// cssprobe
|
|
279
435
|
inspect, tree, layout, findings, injectCss,
|
|
280
436
|
// browser
|
|
281
437
|
resize, evalCmd, playwrightCmd, screenshot,
|
|
438
|
+
// interaction
|
|
439
|
+
click, hover, typeCmd, waitForCmd, sampleCmd, waitStableCmd, runCmd,
|
|
282
440
|
// state
|
|
283
|
-
stateImport, stateSave,
|
|
441
|
+
stateImport, stateSave, stateCheck,
|
|
284
442
|
// config
|
|
285
443
|
configShow, configSet, configList, configUse, configNew, configPath,
|
|
286
444
|
// skill
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveInputArg = resolveInputArg;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function resolveInputArg(value, source = {}) {
|
|
10
|
+
const exists = source.exists ?? fs_1.default.existsSync;
|
|
11
|
+
const readFile = source.readFile ?? ((file) => fs_1.default.readFileSync(file, 'utf-8'));
|
|
12
|
+
if (value === '-') {
|
|
13
|
+
const readStdin = source.readStdin ?? (() => fs_1.default.readFileSync(0, 'utf-8'));
|
|
14
|
+
return readStdin();
|
|
15
|
+
}
|
|
16
|
+
if (value.startsWith('@')) {
|
|
17
|
+
const file = path_1.default.resolve(value.slice(1));
|
|
18
|
+
if (!exists(file))
|
|
19
|
+
throw new Error(`File not found: ${file}`);
|
|
20
|
+
return readFile(file);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=input.js.map
|