form-tester 0.5.1 → 0.6.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.
@@ -26,6 +26,14 @@ form-tester
26
26
 
27
27
  Persona IDs: `ung-mann`, `gravid-kvinne`, `eldre-kvinne`, `kronisk-syk-mann`. Defaults to "noen" (neutral answers) if omitted.
28
28
 
29
+ ## Recording & Replay
30
+
31
+ Every test run records all playwright-cli commands to `recording.json` in the output folder. Replay a previous run:
32
+
33
+ ```bash
34
+ form-tester replay output/form-id/timestamp/recording.json
35
+ ```
36
+
29
37
  ## Commands
30
38
 
31
39
  ```bash
package/form-tester.js CHANGED
@@ -6,9 +6,41 @@ 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.5.1";
9
+ const LOCAL_VERSION = "0.6.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;
14
+
15
+ function startRecording(outputDir) {
16
+ activeRecording = { commands: [], outputDir, startedAt: new Date().toISOString() };
17
+ }
18
+
19
+ function recordCommand(args) {
20
+ if (activeRecording) {
21
+ activeRecording.commands.push({ args, timestamp: new Date().toISOString() });
22
+ }
23
+ }
24
+
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;
38
+ }
39
+
40
+ function stopRecording() {
41
+ activeRecording = null;
42
+ }
43
+
12
44
  const PERSONAS = [
13
45
  {
14
46
  id: "ung-mann",
@@ -286,6 +318,7 @@ function runCommand(command, args, options = {}) {
286
318
  }
287
319
 
288
320
  function runPlaywrightCli(args) {
321
+ recordCommand(args);
289
322
  return new Promise((resolve) => {
290
323
  const spec = getPlaywrightCommandSpec();
291
324
  const spawnOpts = { stdio: "inherit" };
@@ -821,6 +854,9 @@ async function handleTest(url, config) {
821
854
  config.lastRunDir = outputDir;
822
855
  saveConfig(config);
823
856
 
857
+ // Start recording
858
+ startRecording(outputDir);
859
+
824
860
  if (personaChoice.type === "preset") {
825
861
  fs.writeFileSync(
826
862
  path.join(outputDir, "persona.json"),
@@ -877,6 +913,13 @@ async function handleTest(url, config) {
877
913
  dokumenterUrl ||
878
914
  "/dokumenter?pnr={PNR}",
879
915
  );
916
+
917
+ // Save recording
918
+ const recordingPath = saveRecording();
919
+ if (recordingPath) {
920
+ console.log(`Recording saved: ${recordingPath}`);
921
+ console.log(`Replay with: form-tester replay "${recordingPath}"`);
922
+ }
880
923
  }
881
924
 
882
925
  async function handleTestAuto(url, config, flags) {
@@ -928,6 +971,9 @@ async function handleTestAuto(url, config, flags) {
928
971
  config.lastRunDir = outputDir;
929
972
  saveConfig(config);
930
973
 
974
+ // Start recording
975
+ startRecording(outputDir);
976
+
931
977
  // Save persona and scenario
932
978
  if (personaChoice.type === "preset") {
933
979
  fs.writeFileSync(path.join(outputDir, "persona.json"), JSON.stringify(personaChoice.persona, null, 2));
@@ -983,6 +1029,35 @@ async function handleTestAuto(url, config, flags) {
983
1029
  console.log("");
984
1030
  printNextSteps(outputDir, dokumenterUrl || "/dokumenter?pnr={PNR}");
985
1031
  }
1032
+
1033
+ // Save recording
1034
+ const recordingPath = saveRecording();
1035
+ if (recordingPath) {
1036
+ log(`Recording saved: ${recordingPath}`);
1037
+ log(`Replay with: form-tester replay "${recordingPath}"`);
1038
+ }
1039
+ }
1040
+
1041
+ async function handleReplay(filePath) {
1042
+ if (!fs.existsSync(filePath)) {
1043
+ console.error(`Recording not found: ${filePath}`);
1044
+ process.exit(1);
1045
+ }
1046
+ const recording = JSON.parse(fs.readFileSync(filePath, "utf8"));
1047
+ console.log(`Replaying ${recording.commandCount} commands from ${recording.startedAt}`);
1048
+ console.log("");
1049
+
1050
+ for (let i = 0; i < recording.commands.length; i++) {
1051
+ const cmd = recording.commands[i];
1052
+ console.log(`[${i + 1}/${recording.commandCount}] playwright-cli ${cmd.args.join(" ")}`);
1053
+ const code = await runPlaywrightCli(cmd.args);
1054
+ if (code !== 0) {
1055
+ console.error(`Command failed with exit code ${code}. Stopping replay.`);
1056
+ process.exit(1);
1057
+ }
1058
+ }
1059
+
1060
+ console.log("\nReplay complete.");
986
1061
  }
987
1062
 
988
1063
  async function handleCommand(line, config) {
@@ -1138,6 +1213,16 @@ async function main() {
1138
1213
  process.exit(0);
1139
1214
  }
1140
1215
 
1216
+ if (args[0] === "replay") {
1217
+ const filePath = args[1];
1218
+ if (!filePath) {
1219
+ console.error("Usage: form-tester replay <recording.json>");
1220
+ process.exit(1);
1221
+ }
1222
+ await handleReplay(path.resolve(filePath));
1223
+ process.exit(0);
1224
+ }
1225
+
1141
1226
  if (args[0] === "test" && args.includes("--auto")) {
1142
1227
  const config = loadConfig();
1143
1228
  const url = args.find((a) => a.startsWith("http"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-tester",
3
- "version": "0.5.1",
3
+ "version": "0.6.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": {