form-tester 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,8 +28,17 @@ Persona IDs: `ung-mann`, `gravid-kvinne`, `eldre-kvinne`, `kronisk-syk-mann`. De
28
28
 
29
29
  ## Recording & Replay
30
30
 
31
- Every test run records all playwright-cli commands to `recording.json` in the output folder. Replay a previous run:
31
+ Every test run records all commands to `recording.json` in the output folder. To ensure ALL commands are recorded (including form filling, clicking, etc.), always use `form-tester exec` instead of `playwright-cli` directly:
32
32
 
33
+ ```bash
34
+ # ALWAYS use this instead of playwright-cli directly:
35
+ form-tester exec fill e1 "value"
36
+ form-tester exec click e3
37
+ form-tester exec screenshot --filename "path.png" --full-page
38
+ form-tester exec close # finalizes and saves the recording
39
+ ```
40
+
41
+ Replay a previous run:
33
42
  ```bash
34
43
  form-tester replay output/form-id/timestamp/recording.json
35
44
  ```
@@ -57,4 +66,12 @@ Use `/people` to rescan the visible person list and get a numbered selection pro
57
66
 
58
67
  ## Playwright CLI
59
68
 
60
- You can also run `playwright-cli` commands directly when needed.
69
+ IMPORTANT: Always use `form-tester exec` instead of `playwright-cli` directly. This ensures all commands are recorded for replay. The syntax is the same — just prefix with `form-tester exec`:
70
+
71
+ ```bash
72
+ form-tester exec snapshot
73
+ form-tester exec fill e1 "value"
74
+ form-tester exec click e3
75
+ form-tester exec screenshot --filename "page.png" --full-page
76
+ form-tester exec close
77
+ ```
package/form-tester.js CHANGED
@@ -6,39 +6,78 @@ const { spawn, execSync } = require("child_process");
6
6
 
7
7
  const CONFIG_PATH = path.join(process.cwd(), "form-tester.config.json");
8
8
  const OUTPUT_BASE = path.resolve(process.cwd(), "output");
9
- const LOCAL_VERSION = "0.6.0";
9
+ const LOCAL_VERSION = "0.7.0";
10
10
  const RECOMMENDED_PERSON = "Uromantisk Direktør";
11
11
 
12
- // Recording state when active, all playwright-cli commands are logged
13
- let activeRecording = null;
12
+ // Recording — persisted to disk so `form-tester exec` can append across processes
13
+ let activeRecordingPath = null;
14
14
 
15
15
  function startRecording(outputDir) {
16
- activeRecording = { commands: [], outputDir, startedAt: new Date().toISOString() };
16
+ const filePath = path.join(outputDir, "recording.json");
17
+ const data = {
18
+ version: LOCAL_VERSION,
19
+ startedAt: new Date().toISOString(),
20
+ completedAt: null,
21
+ commandCount: 0,
22
+ commands: [],
23
+ };
24
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
25
+ activeRecordingPath = filePath;
26
+ return filePath;
17
27
  }
18
28
 
19
29
  function recordCommand(args) {
20
- if (activeRecording) {
21
- activeRecording.commands.push({ args, timestamp: new Date().toISOString() });
30
+ // In-process recording
31
+ if (activeRecordingPath && fs.existsSync(activeRecordingPath)) {
32
+ appendToRecording(activeRecordingPath, args);
33
+ return;
34
+ }
35
+ // Check config for active recording (cross-process via exec)
36
+ try {
37
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
38
+ if (config.activeRecording && fs.existsSync(config.activeRecording)) {
39
+ appendToRecording(config.activeRecording, args);
40
+ }
41
+ } catch (e) {
42
+ // no config or no active recording, skip
22
43
  }
23
44
  }
24
45
 
25
- function saveRecording() {
26
- if (!activeRecording || !activeRecording.commands.length) return null;
27
- const filePath = path.join(activeRecording.outputDir, "recording.json");
28
- const data = {
29
- version: LOCAL_VERSION,
30
- startedAt: activeRecording.startedAt,
31
- completedAt: new Date().toISOString(),
32
- commandCount: activeRecording.commands.length,
33
- commands: activeRecording.commands,
34
- };
35
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
36
- activeRecording = null;
37
- return filePath;
46
+ function appendToRecording(filePath, args) {
47
+ try {
48
+ const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
49
+ data.commands.push({ args, timestamp: new Date().toISOString() });
50
+ data.commandCount = data.commands.length;
51
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
52
+ } catch (e) {
53
+ // recording file corrupted or gone, skip
54
+ }
55
+ }
56
+
57
+ function finalizeRecording(filePath) {
58
+ if (!filePath || !fs.existsSync(filePath)) return null;
59
+ try {
60
+ const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
61
+ data.completedAt = new Date().toISOString();
62
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
63
+ return filePath;
64
+ } catch (e) {
65
+ return null;
66
+ }
38
67
  }
39
68
 
40
- function stopRecording() {
41
- activeRecording = null;
69
+ function saveRecording() {
70
+ const result = finalizeRecording(activeRecordingPath);
71
+ activeRecordingPath = null;
72
+ // Clear active recording from config
73
+ try {
74
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
75
+ delete config.activeRecording;
76
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
77
+ } catch (e) {
78
+ // config not found, skip
79
+ }
80
+ return result;
42
81
  }
43
82
 
44
83
  const PERSONAS = [
@@ -854,8 +893,10 @@ async function handleTest(url, config) {
854
893
  config.lastRunDir = outputDir;
855
894
  saveConfig(config);
856
895
 
857
- // Start recording
858
- startRecording(outputDir);
896
+ // Start recording and persist path to config for `exec`
897
+ const recordingFile = startRecording(outputDir);
898
+ config.activeRecording = recordingFile;
899
+ saveConfig(config);
859
900
 
860
901
  if (personaChoice.type === "preset") {
861
902
  fs.writeFileSync(
@@ -971,8 +1012,10 @@ async function handleTestAuto(url, config, flags) {
971
1012
  config.lastRunDir = outputDir;
972
1013
  saveConfig(config);
973
1014
 
974
- // Start recording
975
- startRecording(outputDir);
1015
+ // Start recording and persist path to config for `exec`
1016
+ const recordingFile = startRecording(outputDir);
1017
+ config.activeRecording = recordingFile;
1018
+ saveConfig(config);
976
1019
 
977
1020
  // Save persona and scenario
978
1021
  if (personaChoice.type === "preset") {
@@ -1213,6 +1256,40 @@ async function main() {
1213
1256
  process.exit(0);
1214
1257
  }
1215
1258
 
1259
+ if (args[0] === "exec") {
1260
+ const pwArgs = args.slice(1);
1261
+ if (!pwArgs.length) {
1262
+ console.error("Usage: form-tester exec <playwright-cli command and args>");
1263
+ process.exit(1);
1264
+ }
1265
+ // Record (reads config.activeRecording) and run playwright-cli directly
1266
+ recordCommand(pwArgs);
1267
+ // Run without going through runPlaywrightCli to avoid double-recording
1268
+ const spec = getPlaywrightCommandSpec();
1269
+ const spawnOpts = { stdio: "inherit" };
1270
+ if (spec.shell) spawnOpts.shell = true;
1271
+ const code = await new Promise((resolve) => {
1272
+ const child = spawn(spec.command, [...spec.args, ...pwArgs], spawnOpts);
1273
+ child.on("error", (err) => { console.error(err.message); resolve(1); });
1274
+ child.on("exit", (c) => resolve(c ?? 0));
1275
+ });
1276
+ // If the command was "close", finalize the recording
1277
+ if (pwArgs[0] === "close") {
1278
+ try {
1279
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
1280
+ if (config.activeRecording) {
1281
+ const rPath = finalizeRecording(config.activeRecording);
1282
+ delete config.activeRecording;
1283
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
1284
+ if (rPath) console.log(`Recording saved: ${rPath}`);
1285
+ }
1286
+ } catch (e) {
1287
+ // no config, skip
1288
+ }
1289
+ }
1290
+ process.exit(code);
1291
+ }
1292
+
1216
1293
  if (args[0] === "replay") {
1217
1294
  const filePath = args[1];
1218
1295
  if (!filePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-tester",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "AI-powered form testing skill for /skjemautfyller forms using Playwright CLI. Works with Claude Code and GitHub Copilot.",
5
5
  "main": "form-tester.js",
6
6
  "bin": {