@questpie/probe 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/{browser-DoCXU5Bs.js → browser-C0KoBYfo.js} +17 -0
  2. package/dist/{check-1Y6cPMkN.js → check-DzHesF9c.js} +2 -2
  3. package/dist/cli.js +34 -15
  4. package/dist/{compose-1CYahcRa.js → compose-BuBOYqop.js} +3 -3
  5. package/dist/{logs-BIt5sCky.js → logs-LWtXR5o6.js} +1 -1
  6. package/dist/{process-manager-CITbaM9X.js → process-manager-CUmXxN1Y.js} +1 -1
  7. package/dist/process-manager-D_k19e8-.js +4 -0
  8. package/dist/{ps-UFZx0jy0.js → ps-E1hgvd2u.js} +2 -2
  9. package/dist/{record-C4SmoPsT.js → record-DE4BA6jl.js} +3 -56
  10. package/dist/recorder-FojdM3gr.js +81 -0
  11. package/dist/{recordings-Cb31alos.js → recordings-CmVHyt3A.js} +2 -2
  12. package/dist/{replay-Dg9PHNrg.js → replay-DTB0C9DC.js} +1 -1
  13. package/dist/{restart-CEzd2Mgn.js → restart-CCnfVg7M.js} +2 -2
  14. package/dist/{start-BJnzhZp9.js → start-DAj1ldz_.js} +2 -2
  15. package/dist/{stop-C0xDWnnB.js → stop-Y3DscZCW.js} +2 -2
  16. package/package.json +1 -1
  17. package/skills/qprobe/SKILL.md +227 -93
  18. package/skills/qprobe/references/assertions.md +168 -0
  19. package/skills/qprobe/references/check.md +61 -0
  20. package/skills/qprobe/references/logs.md +116 -0
  21. package/skills/qprobe/references/recording.md +42 -0
  22. package/dist/process-manager-BVSQm3gK.js +0 -4
  23. /package/dist/{codegen-BH3cUNuf.js → codegen-XzGmnbb5.js} +0 -0
  24. /package/dist/{health-B36ufFzJ.js → health-Derc4CP4.js} +0 -0
  25. /package/dist/{http-BZouO1Cj.js → http-7baasp3-.js} +0 -0
  26. /package/dist/{init-BjTfn_-A.js → init-B91b6oc2.js} +0 -0
  27. /package/dist/{reporter-CqWc26OP.js → reporter-B-dIKeqZ.js} +0 -0
  28. /package/dist/{state-DRTSIt_r.js → state-B0f4kwdz.js} +0 -0
@@ -2,9 +2,13 @@ import { parseDuration } from "./duration-DUrbfMLK.js";
2
2
  import { loadProbeConfig, resolveBaseUrl } from "./config-BUEMgFYN.js";
3
3
  import { AgentBrowserDriver } from "./agent-browser-Cxuu-Zz0.js";
4
4
  import { error, info, json, log, success } from "./output-CHUjdVDf.js";
5
+ import { recordAction } from "./recorder-FojdM3gr.js";
5
6
  import { defineCommand } from "citty";
6
7
 
7
8
  //#region src/commands/browser.ts
9
+ function record(command$1, ...args) {
10
+ recordAction(`browser ${command$1}`, args);
11
+ }
8
12
  async function getDriver(args) {
9
13
  const config = await loadProbeConfig();
10
14
  const driverName = args.driver ?? config.browser?.driver ?? "agent-browser";
@@ -45,6 +49,7 @@ const open = defineCommand({
45
49
  async run({ args }) {
46
50
  const driver = await getDriver(args);
47
51
  await driver.open(args.url);
52
+ record("open", args.url);
48
53
  success(`Opened ${args.url}`);
49
54
  }
50
55
  });
@@ -61,6 +66,7 @@ const back = defineCommand({
61
66
  async run({ args }) {
62
67
  const driver = await getDriver(args);
63
68
  await driver.back();
69
+ record("back");
64
70
  success("Navigated back");
65
71
  }
66
72
  });
@@ -77,6 +83,7 @@ const forward = defineCommand({
77
83
  async run({ args }) {
78
84
  const driver = await getDriver(args);
79
85
  await driver.forward();
86
+ record("forward");
80
87
  success("Navigated forward");
81
88
  }
82
89
  });
@@ -93,6 +100,7 @@ const reload = defineCommand({
93
100
  async run({ args }) {
94
101
  const driver = await getDriver(args);
95
102
  await driver.reload();
103
+ record("reload");
96
104
  success("Reloaded");
97
105
  }
98
106
  });
@@ -206,6 +214,7 @@ const click = defineCommand({
206
214
  async run({ args }) {
207
215
  const driver = await getDriver(args);
208
216
  await driver.click(args.ref);
217
+ record("click", args.ref);
209
218
  success(`Clicked ${args.ref}`);
210
219
  }
211
220
  });
@@ -226,6 +235,7 @@ const dblclick = defineCommand({
226
235
  async run({ args }) {
227
236
  const driver = await getDriver(args);
228
237
  await driver.dblclick(args.ref);
238
+ record("dblclick", args.ref);
229
239
  success(`Double-clicked ${args.ref}`);
230
240
  }
231
241
  });
@@ -252,6 +262,7 @@ const fill = defineCommand({
252
262
  async run({ args }) {
253
263
  const driver = await getDriver(args);
254
264
  await driver.fill(args.ref, args.value);
265
+ record("fill", args.ref, args.value);
255
266
  success(`Filled ${args.ref}`);
256
267
  }
257
268
  });
@@ -276,6 +287,7 @@ const selectCmd = defineCommand({
276
287
  async run({ args }) {
277
288
  const driver = await getDriver(args);
278
289
  await driver.select(args.ref, args.value);
290
+ record("select", args.ref, args.value);
279
291
  success(`Selected "${args.value}" in ${args.ref}`);
280
292
  }
281
293
  });
@@ -296,6 +308,7 @@ const checkCmd = defineCommand({
296
308
  async run({ args }) {
297
309
  const driver = await getDriver(args);
298
310
  await driver.check(args.ref);
311
+ record("check", args.ref);
299
312
  success(`Checked ${args.ref}`);
300
313
  }
301
314
  });
@@ -316,6 +329,7 @@ const uncheckCmd = defineCommand({
316
329
  async run({ args }) {
317
330
  const driver = await getDriver(args);
318
331
  await driver.uncheck(args.ref);
332
+ record("uncheck", args.ref);
319
333
  success(`Unchecked ${args.ref}`);
320
334
  }
321
335
  });
@@ -337,6 +351,7 @@ const press = defineCommand({
337
351
  async run({ args }) {
338
352
  const driver = await getDriver(args);
339
353
  await driver.press(args.key);
354
+ record("press", args.key);
340
355
  success(`Pressed ${args.key}`);
341
356
  }
342
357
  });
@@ -358,6 +373,7 @@ const typeCmd = defineCommand({
358
373
  async run({ args }) {
359
374
  const driver = await getDriver(args);
360
375
  await driver.type(args.text);
376
+ record("type", args.text);
361
377
  success("Typed text");
362
378
  }
363
379
  });
@@ -487,6 +503,7 @@ const screenshot = defineCommand({
487
503
  full: args.full,
488
504
  selector: args.selector
489
505
  });
506
+ record("screenshot", path);
490
507
  success(`Screenshot saved: ${path}`);
491
508
  }
492
509
  });
@@ -1,8 +1,8 @@
1
1
  import "./duration-DUrbfMLK.js";
2
2
  import { loadProbeConfig, resolveBaseUrl } from "./config-BUEMgFYN.js";
3
3
  import { error, info, success, warn } from "./output-CHUjdVDf.js";
4
- import "./state-DRTSIt_r.js";
5
- import { listProcesses } from "./process-manager-CITbaM9X.js";
4
+ import "./state-B0f4kwdz.js";
5
+ import { listProcesses } from "./process-manager-CUmXxN1Y.js";
6
6
  import { defineCommand } from "citty";
7
7
  import { ofetch } from "ofetch";
8
8
 
package/dist/cli.js CHANGED
@@ -4,6 +4,23 @@ import { defineCommand, runMain, showUsage } from "citty";
4
4
  import { consola } from "consola";
5
5
 
6
6
  //#region src/cli.ts
7
+ const subCommandNames = new Set([
8
+ "start",
9
+ "stop",
10
+ "restart",
11
+ "ps",
12
+ "health",
13
+ "compose",
14
+ "logs",
15
+ "http",
16
+ "check",
17
+ "browser",
18
+ "record",
19
+ "replay",
20
+ "recordings",
21
+ "assert",
22
+ "init"
23
+ ]);
7
24
  const main = defineCommand({
8
25
  meta: {
9
26
  name: "qprobe",
@@ -11,23 +28,25 @@ const main = defineCommand({
11
28
  description: "Dev testing CLI for AI coding agents"
12
29
  },
13
30
  subCommands: {
14
- start: () => import("./start-BJnzhZp9.js").then((m) => m.default),
15
- stop: () => import("./stop-C0xDWnnB.js").then((m) => m.default),
16
- restart: () => import("./restart-CEzd2Mgn.js").then((m) => m.default),
17
- ps: () => import("./ps-UFZx0jy0.js").then((m) => m.default),
18
- health: () => import("./health-B36ufFzJ.js").then((m) => m.default),
19
- compose: () => import("./compose-1CYahcRa.js").then((m) => m.default),
20
- logs: () => import("./logs-BIt5sCky.js").then((m) => m.default),
21
- http: () => import("./http-BZouO1Cj.js").then((m) => m.default),
22
- check: () => import("./check-1Y6cPMkN.js").then((m) => m.default),
23
- browser: () => import("./browser-DoCXU5Bs.js").then((m) => m.default),
24
- record: () => import("./record-C4SmoPsT.js").then((m) => m.default),
25
- replay: () => import("./replay-Dg9PHNrg.js").then((m) => m.default),
26
- recordings: () => import("./recordings-Cb31alos.js").then((m) => m.default),
31
+ start: () => import("./start-DAj1ldz_.js").then((m) => m.default),
32
+ stop: () => import("./stop-Y3DscZCW.js").then((m) => m.default),
33
+ restart: () => import("./restart-CCnfVg7M.js").then((m) => m.default),
34
+ ps: () => import("./ps-E1hgvd2u.js").then((m) => m.default),
35
+ health: () => import("./health-Derc4CP4.js").then((m) => m.default),
36
+ compose: () => import("./compose-BuBOYqop.js").then((m) => m.default),
37
+ logs: () => import("./logs-LWtXR5o6.js").then((m) => m.default),
38
+ http: () => import("./http-7baasp3-.js").then((m) => m.default),
39
+ check: () => import("./check-DzHesF9c.js").then((m) => m.default),
40
+ browser: () => import("./browser-C0KoBYfo.js").then((m) => m.default),
41
+ record: () => import("./record-DE4BA6jl.js").then((m) => m.default),
42
+ replay: () => import("./replay-DTB0C9DC.js").then((m) => m.default),
43
+ recordings: () => import("./recordings-CmVHyt3A.js").then((m) => m.default),
27
44
  assert: () => import("./assert-BLP5_JwC.js").then((m) => m.default),
28
- init: () => import("./init-BjTfn_-A.js").then((m) => m.default)
45
+ init: () => import("./init-B91b6oc2.js").then((m) => m.default)
29
46
  },
30
- async run({ cmd }) {
47
+ async run({ cmd, rawArgs }) {
48
+ const firstArg = rawArgs[0];
49
+ if (firstArg && subCommandNames.has(firstArg)) return;
31
50
  const hasConfig = existsSync("qprobe.config.ts") || existsSync("qprobe.config.js") || existsSync("qprobe.config.mjs");
32
51
  if (hasConfig) {
33
52
  await showUsage(cmd);
@@ -1,8 +1,8 @@
1
1
  import "./duration-DUrbfMLK.js";
2
2
  import { loadProbeConfig } from "./config-BUEMgFYN.js";
3
3
  import { error, info, json, success, table } from "./output-CHUjdVDf.js";
4
- import "./state-DRTSIt_r.js";
5
- import { listProcesses, startProcess, stopProcess } from "./process-manager-CITbaM9X.js";
4
+ import "./state-B0f4kwdz.js";
5
+ import { listProcesses, startProcess, stopProcess } from "./process-manager-CUmXxN1Y.js";
6
6
  import { defineCommand } from "citty";
7
7
  import { ofetch } from "ofetch";
8
8
 
@@ -163,7 +163,7 @@ const restart = defineCommand({
163
163
  error(`Unknown service: "${args.name}"`);
164
164
  process.exit(1);
165
165
  }
166
- const { stopProcess: stopProcess$1, startProcess: startProcess$1 } = await import("./process-manager-BVSQm3gK.js");
166
+ const { stopProcess: stopProcess$1, startProcess: startProcess$1 } = await import("./process-manager-D_k19e8-.js");
167
167
  try {
168
168
  await stopProcess$1(args.name);
169
169
  } catch {}
@@ -1,6 +1,6 @@
1
1
  import { parseDuration } from "./duration-DUrbfMLK.js";
2
2
  import { error, info, json, log } from "./output-CHUjdVDf.js";
3
- import { getLogPath, listProcessNames } from "./state-DRTSIt_r.js";
3
+ import { getLogPath, listProcessNames } from "./state-B0f4kwdz.js";
4
4
  import { defineCommand } from "citty";
5
5
  import { consola } from "consola";
6
6
  import { readFile, stat } from "node:fs/promises";
@@ -1,4 +1,4 @@
1
- import { ensureLogsDir, getLogPath, listProcessNames, readPid, readState, removePid, removeState, savePid, saveState } from "./state-DRTSIt_r.js";
1
+ import { ensureLogsDir, getLogPath, listProcessNames, readPid, readState, removePid, removeState, savePid, saveState } from "./state-B0f4kwdz.js";
2
2
  import { openSync } from "node:fs";
3
3
  import { appendFile } from "node:fs/promises";
4
4
  import { spawn } from "node:child_process";
@@ -0,0 +1,4 @@
1
+ import "./state-B0f4kwdz.js";
2
+ import { getProcessState, listProcesses, startProcess, stopAll, stopProcess } from "./process-manager-CUmXxN1Y.js";
3
+
4
+ export { startProcess, stopProcess };
@@ -1,6 +1,6 @@
1
1
  import { info, json, table } from "./output-CHUjdVDf.js";
2
- import "./state-DRTSIt_r.js";
3
- import { listProcesses } from "./process-manager-CITbaM9X.js";
2
+ import "./state-B0f4kwdz.js";
3
+ import { listProcesses } from "./process-manager-CUmXxN1Y.js";
4
4
  import { defineCommand } from "citty";
5
5
 
6
6
  //#region src/commands/ps.ts
@@ -1,65 +1,12 @@
1
1
  import "./duration-DUrbfMLK.js";
2
2
  import { loadProbeConfig } from "./config-BUEMgFYN.js";
3
3
  import { error, info, success } from "./output-CHUjdVDf.js";
4
- import { generatePlaywrightTest } from "./codegen-BH3cUNuf.js";
4
+ import { cancelRecording, getActiveRecording, startRecording, stopRecording } from "./recorder-FojdM3gr.js";
5
+ import { generatePlaywrightTest } from "./codegen-XzGmnbb5.js";
5
6
  import { defineCommand } from "citty";
6
- import { mkdir, readFile, writeFile } from "node:fs/promises";
7
+ import { mkdir, writeFile } from "node:fs/promises";
7
8
  import { join } from "node:path";
8
9
 
9
- //#region src/testing/recorder.ts
10
- const STATE_FILE = "tmp/qprobe/state/recording.json";
11
- let activeRecording = null;
12
- async function startRecording(name) {
13
- if (activeRecording) throw new Error(`Already recording "${activeRecording.name}". Stop it first.`);
14
- const config = await loadProbeConfig();
15
- activeRecording = {
16
- name,
17
- startedAt: new Date().toISOString(),
18
- baseUrl: config.http?.baseUrl ?? config.browser?.baseUrl,
19
- actions: []
20
- };
21
- await mkdir("tmp/qprobe/state", { recursive: true });
22
- await writeFile(STATE_FILE, JSON.stringify(activeRecording, null, 2), "utf-8");
23
- }
24
- async function stopRecording() {
25
- const recording = activeRecording ?? await loadActiveRecording();
26
- if (!recording) throw new Error("No active recording");
27
- recording.finishedAt = new Date().toISOString();
28
- const config = await loadProbeConfig();
29
- const testsDir = config.tests?.dir ?? "tests/qprobe";
30
- const recordingsDir = join(testsDir, "recordings");
31
- await mkdir(recordingsDir, { recursive: true });
32
- const jsonPath = join(recordingsDir, `${recording.name}.json`);
33
- await writeFile(jsonPath, JSON.stringify(recording, null, 2), "utf-8");
34
- activeRecording = null;
35
- try {
36
- const { rm: rm$1 } = await import("node:fs/promises");
37
- await rm$1(STATE_FILE);
38
- } catch {}
39
- return recording;
40
- }
41
- async function cancelRecording() {
42
- activeRecording = null;
43
- try {
44
- const { rm: rm$1 } = await import("node:fs/promises");
45
- await rm$1(STATE_FILE);
46
- } catch {}
47
- }
48
- async function loadActiveRecording() {
49
- try {
50
- const content = await readFile(STATE_FILE, "utf-8");
51
- activeRecording = JSON.parse(content);
52
- return activeRecording;
53
- } catch {
54
- return null;
55
- }
56
- }
57
- async function getActiveRecording() {
58
- if (activeRecording) return activeRecording;
59
- return loadActiveRecording();
60
- }
61
-
62
- //#endregion
63
10
  //#region src/commands/record.ts
64
11
  const start = defineCommand({
65
12
  meta: {
@@ -0,0 +1,81 @@
1
+ import { loadProbeConfig } from "./config-BUEMgFYN.js";
2
+ import { readFileSync } from "node:fs";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+
6
+ //#region src/testing/recorder.ts
7
+ const STATE_FILE = "tmp/qprobe/state/recording.json";
8
+ let activeRecording = null;
9
+ async function startRecording(name) {
10
+ const existing = activeRecording ?? await loadActiveRecording();
11
+ if (existing) {
12
+ const startedAt = new Date(existing.startedAt);
13
+ const elapsed = Date.now() - startedAt.getTime();
14
+ const mins = Math.floor(elapsed / 6e4);
15
+ const age = mins > 0 ? ` (started ${mins}m ago)` : "";
16
+ throw new Error(`Already recording "${existing.name}"${age}. Run "qprobe record stop" or "qprobe record cancel" first.`);
17
+ }
18
+ const config = await loadProbeConfig();
19
+ activeRecording = {
20
+ name,
21
+ startedAt: new Date().toISOString(),
22
+ baseUrl: config.http?.baseUrl ?? config.browser?.baseUrl,
23
+ actions: []
24
+ };
25
+ await mkdir("tmp/qprobe/state", { recursive: true });
26
+ await writeFile(STATE_FILE, JSON.stringify(activeRecording, null, 2), "utf-8");
27
+ }
28
+ function recordAction(command, args) {
29
+ if (!activeRecording) try {
30
+ const content = readFileSync(STATE_FILE, "utf-8");
31
+ activeRecording = JSON.parse(content);
32
+ } catch {
33
+ return;
34
+ }
35
+ activeRecording.actions.push({
36
+ command,
37
+ args,
38
+ timestamp: new Date().toISOString()
39
+ });
40
+ writeFile(STATE_FILE, JSON.stringify(activeRecording, null, 2), "utf-8");
41
+ }
42
+ async function stopRecording() {
43
+ const recording = activeRecording ?? await loadActiveRecording();
44
+ if (!recording) throw new Error("No active recording");
45
+ recording.finishedAt = new Date().toISOString();
46
+ const config = await loadProbeConfig();
47
+ const testsDir = config.tests?.dir ?? "tests/qprobe";
48
+ const recordingsDir = join(testsDir, "recordings");
49
+ await mkdir(recordingsDir, { recursive: true });
50
+ const jsonPath = join(recordingsDir, `${recording.name}.json`);
51
+ await writeFile(jsonPath, JSON.stringify(recording, null, 2), "utf-8");
52
+ activeRecording = null;
53
+ try {
54
+ const { rm: rm$1 } = await import("node:fs/promises");
55
+ await rm$1(STATE_FILE);
56
+ } catch {}
57
+ return recording;
58
+ }
59
+ async function cancelRecording() {
60
+ activeRecording = null;
61
+ try {
62
+ const { rm: rm$1 } = await import("node:fs/promises");
63
+ await rm$1(STATE_FILE);
64
+ } catch {}
65
+ }
66
+ async function loadActiveRecording() {
67
+ try {
68
+ const content = await readFile(STATE_FILE, "utf-8");
69
+ activeRecording = JSON.parse(content);
70
+ return activeRecording;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ async function getActiveRecording() {
76
+ if (activeRecording) return activeRecording;
77
+ return loadActiveRecording();
78
+ }
79
+
80
+ //#endregion
81
+ export { cancelRecording, getActiveRecording, recordAction, startRecording, stopRecording };
@@ -1,8 +1,8 @@
1
1
  import "./duration-DUrbfMLK.js";
2
2
  import { loadProbeConfig } from "./config-BUEMgFYN.js";
3
3
  import { error, info, json, log, success } from "./output-CHUjdVDf.js";
4
- import { generatePlaywrightTest } from "./codegen-BH3cUNuf.js";
5
- import { formatRecordingsList } from "./reporter-CqWc26OP.js";
4
+ import { generatePlaywrightTest } from "./codegen-XzGmnbb5.js";
5
+ import { formatRecordingsList } from "./reporter-B-dIKeqZ.js";
6
6
  import { defineCommand } from "citty";
7
7
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
8
8
  import { join } from "node:path";
@@ -1,7 +1,7 @@
1
1
  import "./duration-DUrbfMLK.js";
2
2
  import { loadProbeConfig } from "./config-BUEMgFYN.js";
3
3
  import { error, log } from "./output-CHUjdVDf.js";
4
- import { formatReplayResult } from "./reporter-CqWc26OP.js";
4
+ import { formatReplayResult } from "./reporter-B-dIKeqZ.js";
5
5
  import { defineCommand } from "citty";
6
6
  import { mkdir, writeFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
@@ -1,6 +1,6 @@
1
1
  import { error, success } from "./output-CHUjdVDf.js";
2
- import "./state-DRTSIt_r.js";
3
- import { getProcessState, startProcess, stopProcess } from "./process-manager-CITbaM9X.js";
2
+ import "./state-B0f4kwdz.js";
3
+ import { getProcessState, startProcess, stopProcess } from "./process-manager-CUmXxN1Y.js";
4
4
  import { defineCommand } from "citty";
5
5
 
6
6
  //#region src/commands/restart.ts
@@ -1,7 +1,7 @@
1
1
  import { parseDuration } from "./duration-DUrbfMLK.js";
2
2
  import { error, success } from "./output-CHUjdVDf.js";
3
- import "./state-DRTSIt_r.js";
4
- import { startProcess } from "./process-manager-CITbaM9X.js";
3
+ import "./state-B0f4kwdz.js";
4
+ import { startProcess } from "./process-manager-CUmXxN1Y.js";
5
5
  import { defineCommand } from "citty";
6
6
 
7
7
  //#region src/commands/start.ts
@@ -1,6 +1,6 @@
1
1
  import { error, success } from "./output-CHUjdVDf.js";
2
- import "./state-DRTSIt_r.js";
3
- import { stopAll, stopProcess } from "./process-manager-CITbaM9X.js";
2
+ import "./state-B0f4kwdz.js";
3
+ import { stopAll, stopProcess } from "./process-manager-CUmXxN1Y.js";
4
4
  import { defineCommand } from "citty";
5
5
 
6
6
  //#region src/commands/stop.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questpie/probe",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Dev testing CLI for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -5,100 +5,234 @@ description: "QUESTPIE Probe — dev testing CLI for AI coding agents. Orchestra
5
5
 
6
6
  # QUESTPIE Probe
7
7
 
8
- Dev testing CLI. Manages dev servers, reads logs, controls browsers, sends HTTP requests, records and replays tests.
8
+ Dev testing CLI for AI coding agents. Start servers, read logs, test APIs, control browsers, record and replay tests.
9
9
 
10
10
  **Install CLI:** `bun add -g @questpie/probe`
11
11
  **Install skill:** `bunx skills add questpie/probe`
12
+ **Docs:** https://probe.questpie.com/docs
12
13
 
13
- ## Core Principle
14
-
15
- > Read logs before opening a browser. 90% of debugging needs zero visual context.
16
-
17
- ## Quick Start
18
-
19
- ```bash
20
- qprobe compose up # start all services from config
21
- qprobe check http://localhost:3000 # health + console + network summary
22
- qprobe logs server --grep "ERROR" # check process logs
23
- qprobe http GET /api/health # test API endpoint
24
- qprobe browser open http://localhost:3000 # open browser (only if needed)
25
- qprobe browser snapshot -i # see interactive elements
26
- ```
27
-
28
- ## Command Groups
29
-
30
- | Command | What it does | When to use |
31
- |---------|-------------|-------------|
32
- | `qprobe start` | Start a process with ready detection | Need to run dev server, DB, worker |
33
- | `qprobe stop` | Stop a running process | Cleanup, restart |
34
- | `qprobe ps` | List running processes | Check what's running |
35
- | `qprobe health` | Poll URL until healthy | Wait for server to be ready |
36
- | `qprobe compose` | Multi-service orchestration | Start full stack (DB+server+admin) |
37
- | `qprobe logs` | Read/filter/follow process logs | Debug server errors |
38
- | `qprobe http` | Send HTTP requests | Test API endpoints |
39
- | `qprobe check` | All-in-one health summary | Quick status overview |
40
- | `qprobe browser` | Browser automation | Visual testing, form filling |
41
- | `qprobe record` | Record a test session | Capture a flow for regression |
42
- | `qprobe replay` | Replay recorded tests | Run regression (zero AI tokens) |
43
- | `qprobe assert` | Assert conditions | Verify state in CI or scripts |
44
-
45
- ## Recommended Workflow
46
-
47
- 1. **Start stack** → `qprobe compose up`
48
- 2. **Read logs** → `qprobe logs --all --grep ERROR` (no browser yet!)
49
- 3. **Test API** `qprobe http GET /api/health` (still no browser!)
50
- 4. **Quick check** → `qprobe check` (summarizes everything)
51
- 5. **Open browser** → `qprobe browser open /login` (only if needed)
52
- 6. **Record test** `qprobe record start "login-flow"`
53
- 7. **Replay free** `qprobe replay --all` (pure Playwright, zero tokens)
54
-
55
- ## Sub-Skills
56
-
57
- For detailed reference on each command group, read the corresponding reference file:
58
-
59
- - **Process management** (start, stop, ps, health, restart) → read `references/process.md`
60
- - **Compose** (multi-service orchestration, config file) → read `references/compose.md`
61
- - **HTTP requests** (API testing, auth, assertions) → read `references/http.md`
62
- - **Browser control** (snapshot, click, fill, console, network) → read `references/browser.md`
63
- - **Recording & replay** (test capture, Playwright codegen) → read `references/recording.md`
64
-
65
- ## Config File
66
-
67
- Create `qprobe.config.ts` in project root (optional CLI works without it):
68
-
69
- ```typescript
70
- import { defineConfig } from '@questpie/probe'
71
-
72
- export default defineConfig({
73
- services: {
74
- db: {
75
- cmd: 'docker compose up postgres',
76
- ready: 'ready to accept connections',
77
- },
78
- server: {
79
- cmd: 'bun dev',
80
- ready: 'ready on http://localhost:3000',
81
- port: 3000,
82
- health: '/api/health',
83
- depends: ['db'],
84
- },
85
- },
86
- browser: {
87
- driver: 'agent-browser',
88
- baseUrl: 'http://localhost:3000',
89
- },
90
- http: {
91
- baseUrl: 'http://localhost:3000',
92
- },
93
- })
94
- ```
95
-
96
- ## Tips for Agents
97
-
98
- - **Always check logs before opening browser** — most bugs are visible in `qprobe logs`
99
- - **Use `qprobe check`** for a one-command overview of server health
100
- - **Use `qprobe http`** instead of browser for API testing — much cheaper on tokens
101
- - **Snapshot with `-i`** (interactive only) to save tokens skip structural/decorative elements
102
- - **Snapshot with `--diff`** after actions to see only what changed
103
- - **Record flows you want to verify later** — replay costs zero AI tokens
104
- - **Grep is your friend** — `qprobe logs server --grep "ERROR"` is faster than reading everything
14
+ ## Core Principles
15
+
16
+ 1. **Logs before browser** — 90% of bugs are visible in logs and HTTP responses. Open a browser only when you need visual verification.
17
+ 2. **Record → replay free** — record a flow once, replay it forever with pure Playwright. Zero AI tokens on regression.
18
+ 3. **Token-efficient** — use `snapshot -i` (interactive only), `--diff` (changes only), `--grep` (filtered logs). Read only what you need.
19
+
20
+ ---
21
+
22
+ ## Testing Workflow
23
+
24
+ Follow this order every time you test an app. Do NOT skip to browser.
25
+
26
+ ### Step 1 Start the app
27
+ ```bash
28
+ # Option A: from config
29
+ qprobe compose up
30
+
31
+ # Option B: manually
32
+ qprobe start server "bun dev" --ready "ready on" --port 3000
33
+ ```
34
+
35
+ ### Step 2 Check health (no browser yet)
36
+ ```bash
37
+ qprobe ps # is it running?
38
+ qprobe health http://localhost:3000/api/health # does it respond?
39
+ qprobe check http://localhost:3000 # all-in-one summary
40
+ ```
41
+
42
+ ### Step 3 Read logs (no browser yet)
43
+ ```bash
44
+ qprobe logs server # recent logs
45
+ qprobe logs server --grep "ERROR" # find errors
46
+ qprobe logs server --level error # only ERROR level
47
+ qprobe logs --all --grep "ERROR" # errors from all services
48
+ ```
49
+
50
+ ### Step 4 Test API (no browser yet)
51
+ ```bash
52
+ qprobe http GET /api/health --status 200
53
+ qprobe http GET /api/users --status 200
54
+ qprobe http POST /api/users -d '{"name":"Test"}' --status 201
55
+ ```
56
+
57
+ ### Step 5 — Open browser (only if needed)
58
+ ```bash
59
+ qprobe browser open http://localhost:3000
60
+ qprobe browser snapshot -i -c # see interactive elements
61
+ qprobe browser errors # JS errors?
62
+ qprobe browser network --failed # failed requests?
63
+ qprobe browser console --level error # console errors?
64
+ ```
65
+
66
+ ### Step 6 — Interact and verify
67
+ ```bash
68
+ qprobe browser click @e3 # click element from snapshot
69
+ qprobe browser fill @e2 "hello" # fill input
70
+ qprobe browser press Enter # submit
71
+ qprobe browser snapshot --diff # what changed?
72
+ qprobe browser screenshot # visual proof
73
+ ```
74
+
75
+ ### Step 7 — Assert
76
+ ```bash
77
+ qprobe assert status 200 /api/health # API is healthy
78
+ qprobe assert no-errors # no JS errors in browser
79
+ qprobe assert no-network-errors # no 4xx/5xx in network
80
+ qprobe assert text "Dashboard" # page shows expected text
81
+ qprobe assert title "My App" # page title matches
82
+ ```
83
+
84
+ ### Step 8 — Record for regression
85
+ ```bash
86
+ qprobe record start "my-flow"
87
+ # ... repeat steps 5-7 ...
88
+ qprobe record stop # saves JSON + generates .spec.ts
89
+ qprobe replay "my-flow" # run Playwright test (zero tokens)
90
+ qprobe replay --all # run all recorded tests
91
+ ```
92
+
93
+ ### Step 9 — Cleanup
94
+ ```bash
95
+ qprobe stop --all # or: qprobe compose down
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Debugging Workflow
101
+
102
+ When something doesn't work don't jump to browser. Triage in this order:
103
+
104
+ ### 1. Is it running?
105
+ ```bash
106
+ qprobe ps
107
+ qprobe check
108
+ ```
109
+
110
+ ### 2. Check logs for errors
111
+ ```bash
112
+ qprobe logs server --level error
113
+ qprobe logs --all --grep "Error|fail|crash" --since 10m
114
+ ```
115
+
116
+ ### 3. Test API directly
117
+ ```bash
118
+ qprobe http GET /api/health --status 200
119
+ qprobe http GET /api/<endpoint> -v # verbose: see full request/response
120
+ ```
121
+
122
+ ### 4. Browser only if UI bug
123
+ ```bash
124
+ qprobe browser open <url>
125
+ qprobe browser console --level error # JS errors?
126
+ qprobe browser network --failed # failed requests?
127
+ qprobe browser snapshot -i # what does the page show?
128
+ ```
129
+
130
+ ### 5. Fix → restart → verify → record
131
+ ```bash
132
+ qprobe restart server
133
+ qprobe logs server --level error --since 1m # should be empty now
134
+ qprobe record start "fix-<issue>"
135
+ # (re-run the verification steps that failed before)
136
+ qprobe assert no-errors
137
+ qprobe record stop
138
+ qprobe replay "fix-<issue>" # MUST pass
139
+ ```
140
+
141
+ ### Exit Codes
142
+
143
+ | Code | Meaning | What to do |
144
+ |------|---------|------------|
145
+ | 0 | Success | Continue |
146
+ | 1 | Assertion/command error | Read the output message |
147
+ | 2 | Process timeout | Ready pattern not matched — check logs, increase `--timeout` |
148
+ | 3 | Health check failed | Server not responding — check if process is running |
149
+ | 4 | Config error | Check `qprobe.config.ts` syntax |
150
+ | 5 | Browser driver missing | Install: `bun add -g agent-browser` |
151
+ | 6 | Replay test failure | Regression — check what changed in code |
152
+
153
+ ---
154
+
155
+ ## Shipping a Feature
156
+
157
+ A feature is **NOT done** until its replay passes.
158
+
159
+ ### 1. Plan what to test
160
+
161
+ Before writing any test, identify:
162
+ - What are the **critical user flows** for this feature?
163
+ - What are the **edge cases**? (empty state, validation errors, permissions)
164
+ - What **API endpoints** does it touch?
165
+
166
+ If unclear — **ask the user** for their expected flows and scenarios.
167
+
168
+ ### 2. Verify with logs + API first
169
+ ```bash
170
+ qprobe logs server --level error # no errors after change
171
+ qprobe http GET /api/<endpoint> --status 200 # API works
172
+ ```
173
+
174
+ ### 3. Record each user flow
175
+ ```bash
176
+ # For each critical flow:
177
+ qprobe record start "<feature>-<flow-name>"
178
+ qprobe browser open <start-url>
179
+ # (interact: fill, click, navigate)
180
+ qprobe assert no-errors
181
+ qprobe assert no-network-errors
182
+ qprobe record stop
183
+ ```
184
+
185
+ ### 4. Replay ALL — must pass
186
+ ```bash
187
+ qprobe replay --all
188
+ # If any replay fails → fix and re-record that flow
189
+ ```
190
+
191
+ ### 5. Feature is shippable when:
192
+ - All **existing replays** still pass (no regressions)
193
+ - All **new feature flows** are recorded and pass
194
+ - **No JS errors**, no network errors
195
+
196
+ ---
197
+
198
+ ## Command Reference
199
+
200
+ | Command | Purpose | When to use |
201
+ |---------|---------|-------------|
202
+ | `qprobe start` | Start process with ready detection | Dev server, DB, worker |
203
+ | `qprobe stop` | Graceful stop (SIGTERM → SIGKILL) | Cleanup |
204
+ | `qprobe restart` | Stop + start with saved config | After code changes |
205
+ | `qprobe ps` | List running processes | Check what's up |
206
+ | `qprobe health` | Poll URL until healthy | Wait for server |
207
+ | `qprobe compose` | Multi-service orchestration | Start full stack |
208
+ | `qprobe logs` | Read/filter/follow logs | Debug errors |
209
+ | `qprobe http` | HTTP requests with assertions | Test API |
210
+ | `qprobe check` | All-in-one health summary | Quick overview |
211
+ | `qprobe browser` | Browser automation (27 subcommands) | Visual testing |
212
+ | `qprobe record` | Record browser actions | Capture test flow |
213
+ | `qprobe replay` | Run recorded Playwright tests | Regression testing |
214
+ | `qprobe assert` | Assert conditions (exit 1 on fail) | Verify state |
215
+ | `qprobe init` | Scaffold qprobe.config.ts | Project setup |
216
+
217
+ ## Detailed References
218
+
219
+ Read the reference file for the command group you need:
220
+
221
+ - **Process management** (start, stop, ps, health, restart) → `references/process.md`
222
+ - **Logs** (read, filter, grep, follow, merge) → `references/logs.md`
223
+ - **HTTP requests & API testing** → `references/http.md`
224
+ - **Quick health check** (one-shot status + services) → `references/check.md`
225
+ - **Browser control** (snapshot, click, fill, console, network) → `references/browser.md`
226
+ - **Service orchestration** (compose up/down, dependencies) → `references/compose.md`
227
+ - **Recording & replay** (test capture, Playwright codegen) → `references/recording.md`
228
+ - **Assertions** (text, element, URL, status, errors) → `references/assertions.md`
229
+ - **UX testing methods** (heuristics, task completion, a11y) → `references/ux.md`
230
+
231
+ ## Tips
232
+
233
+ - `qprobe logs server --grep "ERROR"` is faster than reading all logs
234
+ - `qprobe http` is cheaper than `qprobe browser` for API testing
235
+ - `snapshot -i` saves tokens — shows only interactive elements
236
+ - `snapshot --diff` after actions shows only what changed
237
+ - `qprobe check` gives a one-command status overview
238
+ - Record flows early — replay is free forever
@@ -0,0 +1,168 @@
1
+ # Assertions Reference
2
+
3
+ Assert conditions against browser state or HTTP endpoints. Exit code 0 on pass, exit code 1 on fail.
4
+
5
+ ## Assertion Types
6
+
7
+ ### text / no-text
8
+
9
+ ```bash
10
+ qprobe assert text "Dashboard" # page body contains text
11
+ qprobe assert no-text "Error" # page body does NOT contain text
12
+ ```
13
+
14
+ - **Case-sensitive** — `"Dashboard"` will NOT match `"dashboard"`
15
+ - Uses `includes()` on full page text content
16
+ - Requires open browser session
17
+
18
+ Output:
19
+ ```
20
+ ✅ Page contains "Dashboard"
21
+ ❌ Page does not contain "Dashboard" # exit 1
22
+ ```
23
+
24
+ ### element
25
+
26
+ ```bash
27
+ qprobe assert element @e3 # ref from snapshot exists
28
+ qprobe assert element "#user-avatar" # CSS selector exists
29
+ ```
30
+
31
+ - Checks the interactive snapshot (`-i -c`) for the ref or selector string
32
+ - Accepts `@e` refs (from most recent snapshot) and CSS selectors
33
+
34
+ Output:
35
+ ```
36
+ ✅ Element "@e3" exists
37
+ ❌ Element "@e3" not found # exit 1
38
+ ```
39
+
40
+ ### url
41
+
42
+ ```bash
43
+ qprobe assert url "/dashboard" # URL contains /dashboard
44
+ qprobe assert url "^https://" # URL starts with https
45
+ qprobe assert url "/users/\\d+" # regex match
46
+ ```
47
+
48
+ - Pattern is a **JavaScript RegExp** (not glob, not exact match)
49
+ - Matches against full current URL
50
+
51
+ Output:
52
+ ```
53
+ ✅ URL matches "/dashboard" (http://localhost:3000/dashboard)
54
+ ❌ URL "http://localhost:3000/login" does not match "/dashboard" # exit 1
55
+ ```
56
+
57
+ ### title
58
+
59
+ ```bash
60
+ qprobe assert title "Dashboard"
61
+ ```
62
+
63
+ - **Case-sensitive** `includes()` on page title
64
+
65
+ Output:
66
+ ```
67
+ ✅ Title contains "Dashboard" (Dashboard - My App)
68
+ ❌ Title "Login - My App" does not contain "Dashboard" # exit 1
69
+ ```
70
+
71
+ ### status
72
+
73
+ ```bash
74
+ qprobe assert status 200 /api/health # TWO arguments: code + path
75
+ qprobe assert status 404 /api/nonexistent
76
+ qprobe assert status 201 /api/users
77
+ ```
78
+
79
+ - **Two positional arguments**: expected status code, then URL path
80
+ - Path resolves against baseUrl (same resolution as `qprobe http`)
81
+ - Makes an HTTP request and checks the response status
82
+
83
+ Output:
84
+ ```
85
+ ✅ http://localhost:3000/api/health returned 200
86
+ ❌ http://localhost:3000/api/health returned 500, expected 200 # exit 1
87
+ ```
88
+
89
+ ### no-errors
90
+
91
+ ```bash
92
+ qprobe assert no-errors
93
+ ```
94
+
95
+ - Checks browser console for uncaught JS exceptions
96
+ - Requires open browser session
97
+
98
+ Output:
99
+ ```
100
+ ✅ No JS errors
101
+
102
+ ❌ Found 2 JS error(s): # exit 1
103
+ TypeError: Cannot read properties of undefined (reading 'map')
104
+ ReferenceError: user is not defined
105
+ ```
106
+
107
+ ### no-network-errors
108
+
109
+ ```bash
110
+ qprobe assert no-network-errors
111
+ ```
112
+
113
+ - Checks network tab for 4xx/5xx HTTP responses
114
+ - Requires open browser session
115
+
116
+ Output:
117
+ ```
118
+ ✅ No network errors
119
+
120
+ ❌ Found 1 network error(s): # exit 1
121
+ POST 500 /api/users
122
+ ```
123
+
124
+ ## Common Patterns
125
+
126
+ ### After form submission
127
+ ```bash
128
+ qprobe browser click @e5 # submit
129
+ qprobe browser wait --network idle
130
+ qprobe assert no-errors
131
+ qprobe assert no-network-errors
132
+ qprobe assert text "Saved successfully"
133
+ ```
134
+
135
+ ### After page navigation
136
+ ```bash
137
+ qprobe browser click @e3
138
+ qprobe assert url "/dashboard"
139
+ qprobe assert title "Dashboard"
140
+ qprobe assert no-errors
141
+ ```
142
+
143
+ ### Full health check (API + browser)
144
+ ```bash
145
+ qprobe assert status 200 /api/health
146
+ qprobe assert status 200 /api/users
147
+ qprobe assert no-errors
148
+ qprobe assert no-network-errors
149
+ ```
150
+
151
+ ### CI/script chaining
152
+ ```bash
153
+ # All assertions exit 1 on failure — chain with &&
154
+ qprobe assert status 200 /api/health && \
155
+ qprobe assert no-errors && \
156
+ qprobe assert text "Welcome" && \
157
+ echo "All checks passed"
158
+ ```
159
+
160
+ ## Tips
161
+
162
+ - `text` / `no-text` are **case-sensitive** — use exact case from the page
163
+ - `url` uses **regex** — escape dots in literal URLs: `"localhost\\.com"`
164
+ - `element` checks the interactive snapshot string, not raw DOM
165
+ - `status` needs **TWO args** — the expected code AND the URL path
166
+ - `no-errors` and `no-network-errors` **require an open browser session**
167
+ - Chain assertions with `&&` for scripts and CI
168
+ - All assertions exit code 1 on failure — useful for conditional logic
@@ -0,0 +1,61 @@
1
+ # Check Reference
2
+
3
+ Quick one-shot health check. Single HTTP request + timing + running services summary.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ qprobe check [url]
9
+ ```
10
+
11
+ No flags. URL defaults to baseUrl from config (same resolution as `qprobe http`).
12
+
13
+ ## What It Does
14
+
15
+ 1. Makes a single HTTP request to the URL
16
+ 2. Reports HTTP status code + response time in milliseconds
17
+ 3. Lists all running managed processes
18
+
19
+ ## Output
20
+
21
+ ```bash
22
+ qprobe check http://localhost:3000
23
+ # ✅ HTTP 200 OK (45ms)
24
+ # ℹ 3 service(s) running (db, server, worker)
25
+ ```
26
+
27
+ ```bash
28
+ qprobe check http://localhost:3000
29
+ # ❌ HTTP 500 Internal Server Error (120ms)
30
+ # ℹ 2 service(s) running (db, server)
31
+ ```
32
+
33
+ ```bash
34
+ qprobe check http://localhost:3000
35
+ # ❌ Connection failed: ECONNREFUSED
36
+ # ⚠ No managed services running
37
+ ```
38
+
39
+ ```bash
40
+ # Without URL — uses config baseUrl
41
+ qprobe check
42
+ # ✅ HTTP 200 OK (32ms)
43
+ # ℹ 1 service(s) running (server)
44
+ ```
45
+
46
+ ## check vs health
47
+
48
+ | | `qprobe check` | `qprobe health` |
49
+ |--|----------------|-----------------|
50
+ | Requests | Single | Polling until success |
51
+ | Timeout | None | `--timeout` (default 30s) |
52
+ | Retries | No | Yes (`--interval`) |
53
+ | Service list | Yes | No |
54
+ | Use case | "Is it up right now?" | "Wait until it's up" |
55
+
56
+ ## When to Use
57
+
58
+ - **First command in debugging** — quick sanity check before digging deeper
59
+ - **After `qprobe compose up`** — verify the stack came up
60
+ - **After `qprobe restart`** — verify server is responding
61
+ - Simpler than `qprobe health` when you don't need polling/retries
@@ -0,0 +1,116 @@
1
+ # Logs Reference
2
+
3
+ Timestamped, level-parsed logs for every managed process.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ qprobe logs <name> [flags] # single process
9
+ qprobe logs --all [flags] # all processes, merged by timestamp
10
+ qprobe logs --unified [flags] # all processes + browser log
11
+ ```
12
+
13
+ ## Flags
14
+
15
+ | Flag | Short | Default | Description |
16
+ |------|-------|---------|-------------|
17
+ | `--follow` | `-f` | `false` | Stream new entries in real time (tail -f). Blocks until Ctrl+C |
18
+ | `--lines` | `-n` | `50` | Number of recent lines to show |
19
+ | `--grep` | | — | Case-insensitive regex filter on message text |
20
+ | `--level` | | — | Exact level filter: `error`, `warn`, `info`, `debug` |
21
+ | `--since` | | — | Time range: `30s`, `5m`, `1h` — only entries newer than this |
22
+ | `--all` | | `false` | Merge logs from all processes, sorted by timestamp |
23
+ | `--unified` | | `false` | All processes + browser console, interleaved |
24
+ | `--json` | | `false` | Output as JSON array of LogEntry objects |
25
+
26
+ ## Output Format
27
+
28
+ Single process:
29
+ ```
30
+ 2026-03-25T20:30:15.247Z INFO Server ready on http://localhost:3000
31
+ 2026-03-25T20:30:16.103Z ERROR Connection refused to database
32
+ ```
33
+
34
+ With `--all` or `--unified` (includes source name):
35
+ ```
36
+ [server] 2026-03-25T20:30:15.247Z INFO Server ready on http://localhost:3000
37
+ [db] 2026-03-25T20:30:14.892Z INFO PostgreSQL ready to accept connections
38
+ [server] 2026-03-25T20:30:16.103Z ERROR Connection refused to database
39
+ ```
40
+
41
+ Colors in follow mode: ERROR = red, WARN = yellow, others = default.
42
+
43
+ JSON output (`--json`):
44
+ ```json
45
+ [
46
+ {
47
+ "timestamp": "2026-03-25T20:30:16.103Z",
48
+ "level": "ERROR",
49
+ "message": "Connection refused to database",
50
+ "source": "server"
51
+ }
52
+ ]
53
+ ```
54
+
55
+ ## Examples
56
+
57
+ ```bash
58
+ # Last 50 lines from server (default)
59
+ qprobe logs server
60
+
61
+ # Only errors
62
+ qprobe logs server --level error
63
+
64
+ # Regex search across message text (case-insensitive)
65
+ qprobe logs server --grep "connection|timeout|refused"
66
+
67
+ # Errors from the last 5 minutes
68
+ qprobe logs server --level error --since 5m
69
+
70
+ # Stream new errors in real time
71
+ qprobe logs server -f --level error
72
+ # (blocks — Ctrl+C to stop)
73
+
74
+ # Check all services for a pattern
75
+ qprobe logs --all --grep "ECONNREFUSED"
76
+
77
+ # Last 200 lines as JSON (pipe to jq)
78
+ qprobe logs server --json --lines 200
79
+ ```
80
+
81
+ ## Common Patterns
82
+
83
+ ### Find why the server crashed
84
+ ```bash
85
+ qprobe ps # is it still running?
86
+ qprobe logs server --level error # recent errors
87
+ qprobe logs server --grep "stack|trace|fatal" # stack traces
88
+ ```
89
+
90
+ ### Watch for errors during testing
91
+ ```bash
92
+ # In one terminal / before testing:
93
+ qprobe logs server -f --level error
94
+ # Run browser commands in another terminal — errors appear in real time
95
+ ```
96
+
97
+ ### Check all services after compose up
98
+ ```bash
99
+ qprobe compose up
100
+ qprobe logs --all --level error --since 1m # any errors during startup?
101
+ ```
102
+
103
+ ### Machine-readable logs for analysis
104
+ ```bash
105
+ qprobe logs server --json --since 1h | jq '.[] | select(.level == "ERROR")'
106
+ ```
107
+
108
+ ## Tips
109
+
110
+ - **Always check logs BEFORE opening a browser** — most bugs are visible here
111
+ - `--grep` is a regex — use `"fail|error|crash"` for broad search
112
+ - `--level error` is faster than `--grep "error"` (exact field match vs string search)
113
+ - `--since 5m` narrows results to recent issues — useful after code changes
114
+ - Follow mode (`-f`) blocks the terminal — use it for real-time monitoring during testing
115
+ - Log files are stored in `tmp/qprobe/logs/<name>.log`
116
+ - Level detection is automatic: lines containing "error" → ERROR, "warn" → WARN, etc.
@@ -184,6 +184,47 @@ tests/qprobe/
184
184
  └── playwright.config.ts # auto-generated
185
185
  ```
186
186
 
187
+ ## Edge Cases
188
+
189
+ ### Stale recording from a previous session
190
+ If a recording was started but the CLI process was interrupted (Ctrl+C, crash, terminal closed), the state file persists on disk.
191
+
192
+ Next `qprobe record start` will detect it:
193
+ ```
194
+ Already recording "old-name" (started 45m ago).
195
+ Run "qprobe record stop" or "qprobe record cancel" first.
196
+ ```
197
+
198
+ Fix: `qprobe record cancel`
199
+
200
+ ### Starting when one is already active
201
+ ```
202
+ Already recording "login-flow".
203
+ Run "qprobe record stop" or "qprobe record cancel" first.
204
+ ```
205
+
206
+ Always cancel or stop before starting a new recording.
207
+
208
+ ### Error during recording
209
+ If a browser command fails during recording, the recording **continues**. The failed action is still captured. Stop or cancel when ready.
210
+
211
+ ### Empty recording
212
+ If you stop a recording with zero actions, the JSON and `.spec.ts` files are still generated — but the test will be empty and pass trivially.
213
+
214
+ ### Recording is transparent
215
+ When no recording is active, all browser and HTTP commands work identically. Recording state is completely transparent — commands behave the same whether recording is on or off.
216
+
217
+ ### Corrupted state file
218
+ If the state file is corrupted and `record cancel` doesn't work:
219
+ ```bash
220
+ rm tmp/qprobe/state/recording.json
221
+ ```
222
+
223
+ ### File locations
224
+ - Active recording state: `tmp/qprobe/state/recording.json`
225
+ - Saved recordings: `tests/qprobe/recordings/<name>.json`
226
+ - Generated tests: `tests/qprobe/recordings/<name>.spec.ts`
227
+
187
228
  ## Tips
188
229
 
189
230
  - **Record important flows early** — login, CRUD, critical paths
@@ -192,3 +233,4 @@ tests/qprobe/
192
233
  - **Export recordings** to share with team or run in CI
193
234
  - **Edit generated .spec.ts** files to add custom assertions
194
235
  - **Use `--retries 2`** for network-dependent tests that may be flaky
236
+ - **If `record start` fails** — check for stale recording with `qprobe record cancel`
@@ -1,4 +0,0 @@
1
- import "./state-DRTSIt_r.js";
2
- import { getProcessState, listProcesses, startProcess, stopAll, stopProcess } from "./process-manager-CITbaM9X.js";
3
-
4
- export { startProcess, stopProcess };
File without changes
File without changes
File without changes
File without changes