cssprobe-cli 0.3.1 → 0.4.1
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 +39 -7
- package/CHANGELOG.md +30 -0
- package/README.md +58 -5
- package/dist/browser/frames.d.ts +15 -0
- package/dist/browser/frames.js +60 -0
- package/dist/browser/interactions.d.ts +3 -2
- package/dist/browser/interactions.js +16 -4
- 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 +93 -5
- package/dist/cli/input.d.ts +6 -0
- package/dist/cli/input.js +24 -0
- package/dist/cli/program.js +152 -1
- package/dist/cli/session-ops.d.ts +8 -0
- package/dist/cli/session-ops.js +24 -1
- 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 +70 -20
- package/dist/daemon/expect.d.ts +8 -0
- package/dist/daemon/expect.js +47 -0
- package/dist/daemon/flowRunner.d.ts +13 -1
- package/dist/daemon/flowRunner.js +28 -3
- package/dist/engine/evalSerializer.js +2 -2
- package/dist/help.json +96 -17
- 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 +251 -0
- package/dist/scenario/schema.d.ts +1270 -0
- package/dist/scenario/schema.js +151 -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,7 @@ 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})"'),
|
|
158
205
|
}),
|
|
159
206
|
});
|
|
160
207
|
// ── interaction ──
|
|
@@ -212,9 +259,41 @@ const waitForCmd = (0, command_1.declareCommand)({
|
|
|
212
259
|
state: z.enum(['visible', 'hidden', 'attached', 'detached']).optional().describe('selector state to wait for (default visible)'),
|
|
213
260
|
js: z.string().optional().describe('JS predicate, e.g. "() => document.querySelector(\'.x\')?.offsetHeight > 100"'),
|
|
214
261
|
'network-idle': z.boolean().optional().describe('wait until the page is network-idle'),
|
|
262
|
+
frame: z.string().optional().describe('wait inside a frame: index, name, or URL substring'),
|
|
215
263
|
timeout: numberArg.optional().describe('timeout in ms (default 30000)'),
|
|
216
264
|
}),
|
|
217
265
|
});
|
|
266
|
+
const sampleCmd = (0, command_1.declareCommand)({
|
|
267
|
+
name: 'sample',
|
|
268
|
+
category: 'core',
|
|
269
|
+
description: 'Sample a JS expression over time and return a timeline with summary stats',
|
|
270
|
+
args: z.object({
|
|
271
|
+
expression: z.string().describe('JS expression sampled on an interval'),
|
|
272
|
+
}),
|
|
273
|
+
options: z.object({
|
|
274
|
+
interval: numberArg.optional().describe('sampling interval in ms (default 250)'),
|
|
275
|
+
duration: numberArg.optional().describe('total sampling duration in ms (default 5000)'),
|
|
276
|
+
key: z.string().optional().describe('dot path for numeric extraction, e.g. "top" or "box.y"'),
|
|
277
|
+
'tail-from': numberArg.optional().describe('tail starts at this elapsed ms (default: last 5000ms)'),
|
|
278
|
+
'ignore-errors': z.boolean().optional().describe('record evaluation errors as sample points instead of aborting'),
|
|
279
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
const waitStableCmd = (0, command_1.declareCommand)({
|
|
283
|
+
name: 'wait-stable',
|
|
284
|
+
category: 'core',
|
|
285
|
+
description: 'Wait until a JS expression stops changing (tolerance + stable window)',
|
|
286
|
+
args: z.object({
|
|
287
|
+
expression: z.string().describe('JS expression polled until stable'),
|
|
288
|
+
}),
|
|
289
|
+
options: z.object({
|
|
290
|
+
tolerance: numberArg.optional().describe('absolute tolerance for numeric values (default 0)'),
|
|
291
|
+
window: numberArg.optional().describe('stable window in ms (default 1500)'),
|
|
292
|
+
timeout: numberArg.optional().describe('timeout in ms (default 30000)'),
|
|
293
|
+
interval: numberArg.optional().describe('polling interval in ms (default 200)'),
|
|
294
|
+
frame: z.string().optional().describe('target frame: index, name, or URL substring (default: main frame)'),
|
|
295
|
+
}),
|
|
296
|
+
});
|
|
218
297
|
const runCmd = (0, command_1.declareCommand)({
|
|
219
298
|
name: 'run',
|
|
220
299
|
category: 'core',
|
|
@@ -248,6 +327,15 @@ const stateSave = (0, command_1.declareCommand)({
|
|
|
248
327
|
name: z.string().optional().describe('state file name (default: session)'),
|
|
249
328
|
}),
|
|
250
329
|
});
|
|
330
|
+
const stateCheck = (0, command_1.declareCommand)({
|
|
331
|
+
name: 'state-check',
|
|
332
|
+
category: 'core',
|
|
333
|
+
description: 'Validate a saved state file: cookies, expiry, and target domain coverage',
|
|
334
|
+
options: z.object({
|
|
335
|
+
state: z.string().optional().describe('state file path (default: ~/.cssprobe-cli/states/session.json)'),
|
|
336
|
+
url: z.string().optional().describe('target URL whose hostname must be covered by the cookies'),
|
|
337
|
+
}),
|
|
338
|
+
});
|
|
251
339
|
// ── config ──
|
|
252
340
|
const configShow = (0, command_1.declareCommand)({
|
|
253
341
|
name: 'config-show',
|
|
@@ -343,15 +431,15 @@ const mcpUninstall = (0, command_1.declareCommand)({
|
|
|
343
431
|
// ── export ──
|
|
344
432
|
const commandsArray = [
|
|
345
433
|
// session management
|
|
346
|
-
open, close, status,
|
|
434
|
+
open, close, status, framesCmd, doctor, scenarioCmd,
|
|
347
435
|
// cssprobe
|
|
348
436
|
inspect, tree, layout, findings, injectCss,
|
|
349
437
|
// browser
|
|
350
438
|
resize, evalCmd, playwrightCmd, screenshot,
|
|
351
439
|
// interaction
|
|
352
|
-
click, hover, typeCmd, waitForCmd, runCmd,
|
|
440
|
+
click, hover, typeCmd, waitForCmd, sampleCmd, waitStableCmd, runCmd,
|
|
353
441
|
// state
|
|
354
|
-
stateImport, stateSave,
|
|
442
|
+
stateImport, stateSave, stateCheck,
|
|
355
443
|
// config
|
|
356
444
|
configShow, configSet, configList, configUse, configNew, configPath,
|
|
357
445
|
// 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
|