cssprobe-cli 0.4.0 → 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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  `cssprobe-cli` — Runtime CSS probe for layout/scroll/overflow/color/font inspection with session-based browser management. Entrypoint: `bin/cssprobe-cli.js` → `require('../dist/index')`.
6
6
  Build: `npm run build` (= `tsc` + `esbuild` collector IIFE bundle + `esbuild` daemon entry + `tsx scripts/generate-help.ts`).
7
- Test: `npm test` (= `tsx --test tests/*.test.ts` — Node.js built-in test runner, 170 tests).
7
+ Test: `npm test` (= `tsx --test tests/*.test.ts` — Node.js built-in test runner, 173 tests).
8
8
  Dependencies: `playwright-core`, `zod`. Dev: `typescript`, `@types/node`, `tsx`, `esbuild`.
9
9
 
10
10
  ## Architecture
@@ -63,6 +63,7 @@ src/
63
63
  - **Output strategy**: `Output` interface → `TextOutput` (Markdown for inspect, tables for lists) / `JsonOutput` (machine-readable JSON).
64
64
  - **Config priority**: CLI flags → env vars → `~/.cssprobe-clirc` profiles. Switch with `-p <profile>`.
65
65
  - **State management**: `state-import` converts Netscape cookies; `state-save` persists the current session's cookies + localStorage after manual login. Session persists browser state until `close`.
66
+ - **Iframe guidance**: pages may contain hidden helper iframes (`about:blank`); use `frames` to pick the real one and `state: "attached"` for hidden targets. Preview frames can render in 6–8s while analytics keep `networkIdle` busy — use a fixed `wait` plus an in-frame `waitFor` instead of `networkIdle`.
66
67
  - **Browser discovery**: `findBrowserExecutable()` scans the platform Playwright cache (macOS/Linux/Windows, `PLAYWRIGHT_BROWSERS_PATH` override), prefers the revision required by the bundled `playwright-core`, and falls back to the newest install. `doctor` reports expected vs installed revisions.
67
68
  - **Error format**: `Error: <message>` → JSON mode: `{ isError: true, error: "<message>" }` → `process.exit(1)`
68
69
 
@@ -116,14 +117,17 @@ src/
116
117
  - `wait-for --js "<predicate>"` — wait until a JS predicate is truthy
117
118
  - `wait-for --network-idle` — wait until the page is network-idle
118
119
  - `--timeout <ms>` — default 30000; timeout errors name the target
120
+ - `--frame <index|name|url-substring>` — wait inside a frame (selector/js only; `networkIdle` is page-level)
119
121
  - `scenario run <file>` — Run a JSON scenario suite (fresh browser per case, assertions, artifacts, JUnit)
120
122
  - `--state <file>` — Fallback state file for cases without one
121
123
  - `--artifacts <dir>` — Artifact directory (default `~/.cssprobe-cli/scenario-runs/<suite>-<timestamp>`)
122
124
  - `--junit <file>` — Write a JUnit XML report
123
125
  - `--bail` — Stop after the first failing case
124
- - Steps: `goto`, `reload`, `eval`, `sample`, `waitStable`, `click`, `waitFor`, `screenshot`, `assert`
126
+ - Steps: `goto`, `reload`, `wait`, `eval`, `sample`, `waitStable`, `click`, `waitFor`, `screenshot`, `assert`
127
+ - `wait` takes a fixed `ms`; `waitFor` accepts `frame` for in-iframe selector/js waits
125
128
  - `run <file>` — Run a multi-step flow script inside the daemon
126
129
  - Script default-exports an async function receiving the API object: `page`, `context`, `frames()`, `newPage()`, `click`, `hover`, `type`, `waitFor`, `inspect`, `screenshot`, `expect`, `log` (CJS also supported)
130
+ - `waitFor(target, { state, timeout, frame })`; `frame` targets iframe selector/js waits
127
131
  - Uncaught page errors are collected in the envelope as `pageErrors[]`
128
132
  - `--timeout <ms>` — whole-script guard (default 300000, 0 = unlimited); failure exits 1 and keeps the session alive
129
133
  - `screenshot` relative paths resolve against the script's directory; logs collected and returned
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.1] - 2026-10-23
4
+
5
+ ### Added
6
+ - `wait-for --frame` (selector/js), scenario `waitFor.frame`, and flow `waitFor(target, { frame })`
7
+ - Scenario `wait` step for fixed delays before slow-rendering preview frames
8
+
9
+ ### Documentation
10
+ - Iframe/preview guidance: hidden helper frames need `state: "attached"`; prefer in-frame `waitFor` over `networkIdle` when analytics keep the page busy
11
+
3
12
  ## [0.4.0] - 2026-10-23
4
13
 
5
14
  ### Added
package/README.md CHANGED
@@ -202,7 +202,8 @@ Inspection commands accept `--frame <index|name|url-substring>` to target an ifr
202
202
  | `type <selector> <text>` | Fill text (replaces value); `--press Enter` to submit; `--sequential` for real key events |
203
203
  | `wait-for <selector>` | Wait for selector state (`--state visible\|hidden\|attached\|detached`, `--timeout ms`) |
204
204
  | `wait-for --js "<predicate>"` | Wait until a JS predicate is truthy |
205
- | `wait-for --network-idle` | Wait until the page is network-idle |
205
+ | `wait-for --network-idle` | Wait until the page is network-idle (page-level; no `--frame`) |
206
+ | `wait-for ... --frame <spec>` | Wait inside an iframe (selector/js modes) |
206
207
  | `sample "<expr>"` | Sample an expression over time (`--interval`, `--duration`, `--key`, `--frame`); returns timeline + summary |
207
208
  | `wait-stable "<expr>"` | Wait until an expression stops changing (`--tolerance`, `--window`, `--timeout`) |
208
209
  | `run <file>` | Run a multi-step flow script |
@@ -269,7 +270,7 @@ export default async ({ page, context, frames, newPage, click, waitFor, type, in
269
270
  cssprobe-cli run ./flow.js # --timeout ms for the whole script (default 300000, 0 = unlimited)
270
271
  ```
271
272
 
272
- Scripts must `default-export` an async function receiving the API object: `page`, `context`, `frames()`, `newPage()`, `click`, `hover`, `type`, `waitFor`, `inspect`, `screenshot`, `expect`, `log`. Uncaught page errors are collected into the result envelope as `pageErrors`. `screenshot` relative paths resolve against the script's directory; logs (`log()` and `console.*`) are collected and returned with the result. On failure the command exits `1`, prints the collected logs, and keeps the session alive.
273
+ Scripts must `default-export` an async function receiving the API object: `page`, `context`, `frames()`, `newPage()`, `click`, `hover`, `type`, `waitFor`, `inspect`, `screenshot`, `expect`, `log`. `waitFor` accepts `{ state, timeout, frame }`. Uncaught page errors are collected into the result envelope as `pageErrors`. `screenshot` relative paths resolve against the script's directory; logs (`log()` and `console.*`) are collected and returned with the result. On failure the command exits `1`, prints the collected logs, and keeps the session alive.
273
274
 
274
275
  > Scripts are executed by the local daemon with full Node privileges — only run trusted scripts.
275
276
 
@@ -313,7 +314,9 @@ cssprobe-cli scenario run suites/ticket-26571394.json \
313
314
  }
314
315
  ```
315
316
 
316
- Steps: `goto`, `reload`, `eval`, `sample`, `waitStable`, `click`, `waitFor`, `screenshot`, `assert`. `sample` and `eval` save values with `save: "<name>"`; assertions read them with `on: "<name>"` + `path`, or check live `state` (`url`, `title`, `frames`), `url`, `globals`, `absent`. `sample` supports `key`, `tailFrom`, `ignoreErrors`; every run writes `summary.json`, per-case `case.json`, sample series and failure screenshots.
317
+ Steps: `goto`, `reload`, `wait`, `eval`, `sample`, `waitStable`, `click`, `waitFor`, `screenshot`, `assert`. `wait` takes a fixed `ms`; `waitFor` supports `frame` for in-iframe selector/js waits. `sample` and `eval` save values with `save: "<name>"`; assertions read them with `on: "<name>"` + `path`, or check live `state` (`url`, `title`, `frames`), `url`, `globals`, `absent`. `sample` supports `key`, `tailFrom`, `ignoreErrors`; every run writes `summary.json`, per-case `case.json`, sample series and failure screenshots.
318
+
319
+ **Iframe and preview pages.** A page can contain hidden helper iframes (`about:blank`); use `frames` to pick the real one, and `state: "attached"` when waiting for a frame element that is not visible. Preview frames can take 6–8s to render and analytics requests keep `networkIdle` busy — prefer a fixed `wait` step followed by a `waitFor` inside the frame (`frame: "i/preview"`) instead of `networkIdle`.
317
320
 
318
321
  ## Confidence Model
319
322
 
@@ -1,4 +1,4 @@
1
- import type { Page } from 'playwright-core';
1
+ import type { Frame, Page } from 'playwright-core';
2
2
  export interface ElementDescriptor {
3
3
  tag: string;
4
4
  classes: string[];
@@ -46,4 +46,4 @@ export declare function resolveWaitTarget(selector: string | undefined, opts: {
46
46
  state?: string;
47
47
  }): WaitTarget;
48
48
  export declare function normalizePredicateExpression(expression: string): string;
49
- export declare function waitForCondition(page: Page, target: WaitTarget, timeout: number): Promise<number>;
49
+ export declare function waitForCondition(host: Page | Frame, target: WaitTarget, timeout: number): Promise<number>;
@@ -78,17 +78,21 @@ function normalizePredicateExpression(expression) {
78
78
  return trimmed;
79
79
  return `(${trimmed})()`;
80
80
  }
81
- async function waitForCondition(page, target, timeout) {
81
+ async function waitForCondition(host, target, timeout) {
82
+ const isPage = typeof host.setViewportSize === 'function';
83
+ if (target.kind === 'networkIdle' && !isPage) {
84
+ throw new Error('networkIdle waits are page-level; remove the frame target');
85
+ }
82
86
  const started = Date.now();
83
87
  switch (target.kind) {
84
88
  case 'selector':
85
- await page.waitForSelector(target.selector, { state: target.state, timeout });
89
+ await host.waitForSelector(target.selector, { state: target.state, timeout });
86
90
  break;
87
91
  case 'js':
88
- await page.waitForFunction(normalizePredicateExpression(target.expression), null, { timeout });
92
+ await host.waitForFunction(normalizePredicateExpression(target.expression), null, { timeout });
89
93
  break;
90
94
  case 'networkIdle':
91
- await page.waitForLoadState('networkidle', { timeout });
95
+ await host.waitForLoadState('networkidle', { timeout });
92
96
  break;
93
97
  }
94
98
  return Date.now() - started;
@@ -259,6 +259,7 @@ const waitForCmd = (0, command_1.declareCommand)({
259
259
  state: z.enum(['visible', 'hidden', 'attached', 'detached']).optional().describe('selector state to wait for (default visible)'),
260
260
  js: z.string().optional().describe('JS predicate, e.g. "() => document.querySelector(\'.x\')?.offsetHeight > 100"'),
261
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'),
262
263
  timeout: numberArg.optional().describe('timeout in ms (default 30000)'),
263
264
  }),
264
265
  });
@@ -278,9 +278,11 @@ async function executeCommand(launcher, args) {
278
278
  networkIdle: args['network-idle'],
279
279
  state: args.state,
280
280
  });
281
+ const page = requirePage(launcher);
282
+ const waitHost = args.frame ? (0, frames_1.resolveFrame)(page, args.frame) : page;
281
283
  let elapsedMs;
282
284
  try {
283
- elapsedMs = await (0, interactions_1.waitForCondition)(requirePage(launcher), target, timeout);
285
+ elapsedMs = await (0, interactions_1.waitForCondition)(waitHost, target, timeout);
284
286
  }
285
287
  catch (e) {
286
288
  throw new Error(`wait-for failed for ${(0, interactions_1.describeWaitTarget)(target)} within ${timeout}ms: ${e.message}`);
@@ -19,6 +19,7 @@ export interface FlowEnvelope {
19
19
  export interface WaitForOptions {
20
20
  state?: WaitState;
21
21
  timeout?: number;
22
+ frame?: string;
22
23
  }
23
24
  export interface WaitOutcome {
24
25
  waitedFor: 'selector' | 'js' | 'networkidle';
@@ -66,20 +66,23 @@ function requirePage(launcher) {
66
66
  return page;
67
67
  }
68
68
  async function flowWaitFor(page, target, opts) {
69
+ const host = opts?.frame ? (0, frames_1.resolveFrame)(page, opts.frame) : page;
69
70
  if (typeof target === 'string') {
70
71
  const state = opts?.state ?? 'visible';
71
72
  const timeout = opts?.timeout ?? 30_000;
72
- const elapsedMs = await (0, interactions_1.waitForCondition)(page, { kind: 'selector', selector: target, state }, timeout);
73
+ const elapsedMs = await (0, interactions_1.waitForCondition)(host, { kind: 'selector', selector: target, state }, timeout);
73
74
  return { waitedFor: 'selector', state, elapsedMs };
74
75
  }
75
76
  if ('js' in target) {
76
77
  const timeout = target.timeout ?? 30_000;
77
- const elapsedMs = await (0, interactions_1.waitForCondition)(page, { kind: 'js', expression: target.js }, timeout);
78
+ const elapsedMs = await (0, interactions_1.waitForCondition)(host, { kind: 'js', expression: target.js }, timeout);
78
79
  return { waitedFor: 'js', elapsedMs };
79
80
  }
80
81
  if (target.networkIdle) {
82
+ if (opts?.frame)
83
+ throw new Error('networkIdle waits are page-level; remove opts.frame');
81
84
  const timeout = target.timeout ?? 30_000;
82
- const elapsedMs = await (0, interactions_1.waitForCondition)(page, { kind: 'networkIdle' }, timeout);
85
+ const elapsedMs = await (0, interactions_1.waitForCondition)(host, { kind: 'networkIdle' }, timeout);
83
86
  return { waitedFor: 'networkidle', elapsedMs };
84
87
  }
85
88
  throw new Error('waitFor requires a selector string, { js }, or { networkIdle: true }');
package/dist/help.json CHANGED
@@ -175,11 +175,12 @@
175
175
  ]
176
176
  },
177
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",
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 --frame wait inside a frame: index, name, or url substring\n --timeout timeout in ms (default 30000)\n\nGlobal options:\n --json output as JSON\n --profile, -p <name> use named profile",
179
179
  "flags": {
180
180
  "state": "string",
181
181
  "js": "string",
182
182
  "network-idle": "boolean",
183
+ "frame": "string",
183
184
  "timeout": "string"
184
185
  },
185
186
  "args": [
@@ -207,20 +207,27 @@ async function executeStep(step, context) {
207
207
  }
208
208
  case 'waitFor': {
209
209
  const timeout = step.timeout ?? 30000;
210
+ const frameSpec = step.frame ?? context.frameSpec;
210
211
  if (step.networkIdle) {
212
+ if (frameSpec)
213
+ throw new Error('networkIdle waits are page-level; remove frame');
211
214
  await (0, interactions_1.waitForCondition)(page, { kind: 'networkIdle' }, timeout);
212
215
  }
213
216
  else if (step.js) {
214
- await (0, interactions_1.waitForCondition)(page, { kind: 'js', expression: step.js }, timeout);
217
+ await (0, interactions_1.waitForCondition)(frameSpec ? (0, frames_1.resolveFrame)(page, frameSpec) : page, { kind: 'js', expression: step.js }, timeout);
215
218
  }
216
219
  else if (step.selector) {
217
- await (0, interactions_1.waitForCondition)(page, { kind: 'selector', selector: step.selector, state: step.state ?? 'visible' }, timeout);
220
+ await (0, interactions_1.waitForCondition)(frameSpec ? (0, frames_1.resolveFrame)(page, frameSpec) : page, { kind: 'selector', selector: step.selector, state: step.state ?? 'visible' }, timeout);
218
221
  }
219
222
  else {
220
223
  throw new Error('waitFor requires selector, js, or networkIdle');
221
224
  }
222
225
  return;
223
226
  }
227
+ case 'wait': {
228
+ await new Promise(resolve => setTimeout(resolve, step.ms));
229
+ return;
230
+ }
224
231
  case 'screenshot': {
225
232
  const buffer = await page.screenshot({ fullPage: step.fullPage });
226
233
  const out = path_1.default.resolve(caseDir, step.path ?? `step-${stepIndex + 1}-screenshot.png`);
@@ -177,11 +177,13 @@ export declare const stepSchema: z.ZodDiscriminatedUnion<"do", [z.ZodObject<{
177
177
  js: z.ZodOptional<z.ZodString>;
178
178
  networkIdle: z.ZodOptional<z.ZodBoolean>;
179
179
  state: z.ZodOptional<z.ZodEnum<["visible", "hidden", "attached", "detached"]>>;
180
+ frame: z.ZodOptional<z.ZodString>;
180
181
  timeout: z.ZodOptional<z.ZodNumber>;
181
182
  }, "strip", z.ZodTypeAny, {
182
183
  do: "waitFor";
183
184
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
184
185
  selector?: string | undefined;
186
+ frame?: string | undefined;
185
187
  timeout?: number | undefined;
186
188
  js?: string | undefined;
187
189
  networkIdle?: boolean | undefined;
@@ -189,9 +191,19 @@ export declare const stepSchema: z.ZodDiscriminatedUnion<"do", [z.ZodObject<{
189
191
  do: "waitFor";
190
192
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
191
193
  selector?: string | undefined;
194
+ frame?: string | undefined;
192
195
  timeout?: number | undefined;
193
196
  js?: string | undefined;
194
197
  networkIdle?: boolean | undefined;
198
+ }>, z.ZodObject<{
199
+ do: z.ZodLiteral<"wait">;
200
+ ms: z.ZodNumber;
201
+ }, "strip", z.ZodTypeAny, {
202
+ do: "wait";
203
+ ms: number;
204
+ }, {
205
+ do: "wait";
206
+ ms: number;
195
207
  }>, z.ZodObject<{
196
208
  do: z.ZodLiteral<"screenshot">;
197
209
  path: z.ZodOptional<z.ZodString>;
@@ -387,11 +399,13 @@ export declare const caseSchema: z.ZodObject<{
387
399
  js: z.ZodOptional<z.ZodString>;
388
400
  networkIdle: z.ZodOptional<z.ZodBoolean>;
389
401
  state: z.ZodOptional<z.ZodEnum<["visible", "hidden", "attached", "detached"]>>;
402
+ frame: z.ZodOptional<z.ZodString>;
390
403
  timeout: z.ZodOptional<z.ZodNumber>;
391
404
  }, "strip", z.ZodTypeAny, {
392
405
  do: "waitFor";
393
406
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
394
407
  selector?: string | undefined;
408
+ frame?: string | undefined;
395
409
  timeout?: number | undefined;
396
410
  js?: string | undefined;
397
411
  networkIdle?: boolean | undefined;
@@ -399,9 +413,19 @@ export declare const caseSchema: z.ZodObject<{
399
413
  do: "waitFor";
400
414
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
401
415
  selector?: string | undefined;
416
+ frame?: string | undefined;
402
417
  timeout?: number | undefined;
403
418
  js?: string | undefined;
404
419
  networkIdle?: boolean | undefined;
420
+ }>, z.ZodObject<{
421
+ do: z.ZodLiteral<"wait">;
422
+ ms: z.ZodNumber;
423
+ }, "strip", z.ZodTypeAny, {
424
+ do: "wait";
425
+ ms: number;
426
+ }, {
427
+ do: "wait";
428
+ ms: number;
405
429
  }>, z.ZodObject<{
406
430
  do: z.ZodLiteral<"screenshot">;
407
431
  path: z.ZodOptional<z.ZodString>;
@@ -516,9 +540,13 @@ export declare const caseSchema: z.ZodObject<{
516
540
  do: "waitFor";
517
541
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
518
542
  selector?: string | undefined;
543
+ frame?: string | undefined;
519
544
  timeout?: number | undefined;
520
545
  js?: string | undefined;
521
546
  networkIdle?: boolean | undefined;
547
+ } | {
548
+ do: "wait";
549
+ ms: number;
522
550
  } | {
523
551
  do: "screenshot";
524
552
  path?: string | undefined;
@@ -588,9 +616,13 @@ export declare const caseSchema: z.ZodObject<{
588
616
  do: "waitFor";
589
617
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
590
618
  selector?: string | undefined;
619
+ frame?: string | undefined;
591
620
  timeout?: number | undefined;
592
621
  js?: string | undefined;
593
622
  networkIdle?: boolean | undefined;
623
+ } | {
624
+ do: "wait";
625
+ ms: number;
594
626
  } | {
595
627
  do: "screenshot";
596
628
  path?: string | undefined;
@@ -775,11 +807,13 @@ export declare const suiteSchema: z.ZodObject<{
775
807
  js: z.ZodOptional<z.ZodString>;
776
808
  networkIdle: z.ZodOptional<z.ZodBoolean>;
777
809
  state: z.ZodOptional<z.ZodEnum<["visible", "hidden", "attached", "detached"]>>;
810
+ frame: z.ZodOptional<z.ZodString>;
778
811
  timeout: z.ZodOptional<z.ZodNumber>;
779
812
  }, "strip", z.ZodTypeAny, {
780
813
  do: "waitFor";
781
814
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
782
815
  selector?: string | undefined;
816
+ frame?: string | undefined;
783
817
  timeout?: number | undefined;
784
818
  js?: string | undefined;
785
819
  networkIdle?: boolean | undefined;
@@ -787,9 +821,19 @@ export declare const suiteSchema: z.ZodObject<{
787
821
  do: "waitFor";
788
822
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
789
823
  selector?: string | undefined;
824
+ frame?: string | undefined;
790
825
  timeout?: number | undefined;
791
826
  js?: string | undefined;
792
827
  networkIdle?: boolean | undefined;
828
+ }>, z.ZodObject<{
829
+ do: z.ZodLiteral<"wait">;
830
+ ms: z.ZodNumber;
831
+ }, "strip", z.ZodTypeAny, {
832
+ do: "wait";
833
+ ms: number;
834
+ }, {
835
+ do: "wait";
836
+ ms: number;
793
837
  }>, z.ZodObject<{
794
838
  do: z.ZodLiteral<"screenshot">;
795
839
  path: z.ZodOptional<z.ZodString>;
@@ -904,9 +948,13 @@ export declare const suiteSchema: z.ZodObject<{
904
948
  do: "waitFor";
905
949
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
906
950
  selector?: string | undefined;
951
+ frame?: string | undefined;
907
952
  timeout?: number | undefined;
908
953
  js?: string | undefined;
909
954
  networkIdle?: boolean | undefined;
955
+ } | {
956
+ do: "wait";
957
+ ms: number;
910
958
  } | {
911
959
  do: "screenshot";
912
960
  path?: string | undefined;
@@ -976,9 +1024,13 @@ export declare const suiteSchema: z.ZodObject<{
976
1024
  do: "waitFor";
977
1025
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
978
1026
  selector?: string | undefined;
1027
+ frame?: string | undefined;
979
1028
  timeout?: number | undefined;
980
1029
  js?: string | undefined;
981
1030
  networkIdle?: boolean | undefined;
1031
+ } | {
1032
+ do: "wait";
1033
+ ms: number;
982
1034
  } | {
983
1035
  do: "screenshot";
984
1036
  path?: string | undefined;
@@ -1051,9 +1103,13 @@ export declare const suiteSchema: z.ZodObject<{
1051
1103
  do: "waitFor";
1052
1104
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
1053
1105
  selector?: string | undefined;
1106
+ frame?: string | undefined;
1054
1107
  timeout?: number | undefined;
1055
1108
  js?: string | undefined;
1056
1109
  networkIdle?: boolean | undefined;
1110
+ } | {
1111
+ do: "wait";
1112
+ ms: number;
1057
1113
  } | {
1058
1114
  do: "screenshot";
1059
1115
  path?: string | undefined;
@@ -1135,9 +1191,13 @@ export declare const suiteSchema: z.ZodObject<{
1135
1191
  do: "waitFor";
1136
1192
  state?: "visible" | "hidden" | "attached" | "detached" | undefined;
1137
1193
  selector?: string | undefined;
1194
+ frame?: string | undefined;
1138
1195
  timeout?: number | undefined;
1139
1196
  js?: string | undefined;
1140
1197
  networkIdle?: boolean | undefined;
1198
+ } | {
1199
+ do: "wait";
1200
+ ms: number;
1141
1201
  } | {
1142
1202
  do: "screenshot";
1143
1203
  path?: string | undefined;
@@ -94,8 +94,13 @@ const waitForStep = z.object({
94
94
  js: z.string().optional(),
95
95
  networkIdle: z.boolean().optional(),
96
96
  state: z.enum(['visible', 'hidden', 'attached', 'detached']).optional(),
97
+ frame: z.string().optional(),
97
98
  timeout: z.number().optional(),
98
99
  });
100
+ const waitStep = z.object({
101
+ do: z.literal('wait'),
102
+ ms: z.number().positive(),
103
+ });
99
104
  const screenshotStep = z.object({
100
105
  do: z.literal('screenshot'),
101
106
  path: z.string().optional(),
@@ -119,6 +124,7 @@ exports.stepSchema = z.discriminatedUnion('do', [
119
124
  waitStableStep,
120
125
  clickStep,
121
126
  waitForStep,
127
+ waitStep,
122
128
  screenshotStep,
123
129
  assertStep,
124
130
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cssprobe-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "mcpName": "io.github.mack-peng/cssprobe-cli",
5
5
  "description": "Runtime CSS probe — inspect layout, colors, backgrounds, fonts and more in a live browser. AI agents and shell automation.",
6
6
  "keywords": [