form-tester 0.3.4 → 0.4.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.
@@ -10,9 +10,15 @@ allowed-tools: Bash(powershell:*), Bash(playwright-cli:*), Bash(npx form-tester:
10
10
 
11
11
  ```bash
12
12
  npx form-tester install # one-time setup
13
- npx form-tester # run the CLI
13
+ npx form-tester # interactive CLI
14
+
15
+ # Non-interactive mode (no prompts, best for AI agents):
16
+ npx form-tester test <url> --auto
17
+ npx form-tester test <url> --auto --pnr 12345 --persona ung-mann --scenario "test validation"
14
18
  ```
15
19
 
20
+ Persona IDs: `ung-mann`, `gravid-kvinne`, `eldre-kvinne`, `kronisk-syk-mann`. Defaults to "noen" (neutral answers) if omitted.
21
+
16
22
  ## Commands
17
23
 
18
24
  ```bash
package/form-tester.js CHANGED
@@ -6,7 +6,7 @@ const { spawn, execSync } = require("child_process");
6
6
 
7
7
  const CONFIG_PATH = path.join(__dirname, "form-tester.config.json");
8
8
  const OUTPUT_BASE = path.resolve(__dirname, "output");
9
- const LOCAL_VERSION = "0.3.4";
9
+ const LOCAL_VERSION = "0.4.0";
10
10
  const RECOMMENDED_PERSON = "Uromantisk Direktør";
11
11
 
12
12
  const PERSONAS = [
@@ -272,7 +272,11 @@ function isPlaywrightCliAvailable() {
272
272
 
273
273
  function runCommand(command, args, options = {}) {
274
274
  return new Promise((resolve) => {
275
- const child = spawn(command, args, { stdio: "inherit", ...options });
275
+ const child = spawn(command, args, {
276
+ stdio: "inherit",
277
+ shell: process.platform === "win32",
278
+ ...options,
279
+ });
276
280
  child.on("error", (err) => {
277
281
  console.error(`Failed to launch ${command}: ${err.message}`);
278
282
  resolve(1);
@@ -284,9 +288,9 @@ function runCommand(command, args, options = {}) {
284
288
  function runPlaywrightCli(args) {
285
289
  return new Promise((resolve) => {
286
290
  const spec = getPlaywrightCommandSpec();
287
- const child = spawn(spec.command, [...spec.args, ...args], {
288
- stdio: "inherit",
289
- });
291
+ const spawnOpts = { stdio: "inherit" };
292
+ if (spec.shell) spawnOpts.shell = true;
293
+ const child = spawn(spec.command, [...spec.args, ...args], spawnOpts);
290
294
  child.on("error", (err) => {
291
295
  console.error(`Failed to launch ${spec.command}: ${err.message}`);
292
296
  resolve(1);
@@ -298,10 +302,12 @@ function runPlaywrightCli(args) {
298
302
  function runPlaywrightCliCapture(args) {
299
303
  return new Promise((resolve) => {
300
304
  const spec = getPlaywrightCommandSpec();
305
+ const captureOpts = { stdio: ["ignore", "pipe", "pipe"] };
306
+ if (spec.shell) captureOpts.shell = true;
301
307
  const child = spawn(
302
308
  spec.command,
303
309
  [...spec.args, ...args],
304
- { stdio: ["ignore", "pipe", "pipe"] },
310
+ captureOpts,
305
311
  );
306
312
  let stdout = "";
307
313
  let stderr = "";
@@ -328,6 +334,7 @@ function getPlaywrightCommandSpec() {
328
334
  const command = getPlaywrightCommand();
329
335
  const lower = command.toLowerCase();
330
336
  if (process.platform === "win32" && (lower.endsWith(".cmd") || lower.endsWith(".ps1"))) {
337
+ // Try to resolve the actual .js entry point to avoid shell/spawn issues
331
338
  const nodeDir = path.dirname(command);
332
339
  const cliPath = path.join(
333
340
  nodeDir,
@@ -337,16 +344,12 @@ function getPlaywrightCommandSpec() {
337
344
  "playwright-cli.js",
338
345
  );
339
346
  if (fs.existsSync(cliPath)) {
340
- return { command: process.execPath, args: [cliPath] };
347
+ return { command: process.execPath, args: [cliPath], shell: false };
341
348
  }
349
+ // Fallback: run via node process.execPath with the .cmd/.ps1 content
350
+ return { command, args: [], shell: true };
342
351
  }
343
- if (lower.endsWith(".ps1")) {
344
- return {
345
- command: "powershell",
346
- args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command],
347
- };
348
- }
349
- return { command, args: [] };
352
+ return { command, args: [], shell: false };
350
353
  }
351
354
 
352
355
  function findRepoRoot(startDir) {
@@ -875,6 +878,91 @@ async function handleTest(url, config) {
875
878
  );
876
879
  }
877
880
 
881
+ async function handleTestAuto(url, config, flags) {
882
+ // Resolve PNR
883
+ const pnr = flags.pnr || config.pnr;
884
+ if (!pnr) {
885
+ console.error("No PNR available. Pass --pnr <value> or set it in form-tester.config.json");
886
+ process.exit(1);
887
+ }
888
+ const fullUrl = extractPnrFromUrl(url) ? url : setPnrOnUrl(url, pnr);
889
+ config.pnr = pnr;
890
+ saveConfig(config);
891
+
892
+ // Resolve persona
893
+ let personaChoice;
894
+ const personaId = flags.persona;
895
+ if (personaId) {
896
+ const found = getPersonaById(personaId);
897
+ if (found) {
898
+ console.log(`Persona: ${found.name} — ${found.description}`);
899
+ personaChoice = { type: "preset", persona: found };
900
+ } else {
901
+ console.log(`Unknown persona "${personaId}", using Noen.`);
902
+ personaChoice = { type: "noen", persona: null };
903
+ }
904
+ } else {
905
+ console.log("Persona: Noen — nøytrale svar (auto)");
906
+ personaChoice = { type: "noen", persona: null };
907
+ }
908
+
909
+ // Resolve scenario
910
+ const scenarioChoice = flags.scenario
911
+ ? { type: "custom", description: flags.scenario }
912
+ : { type: "default", description: "Standard test" };
913
+ console.log(`Scenario: ${scenarioChoice.description}`);
914
+
915
+ // Create output directory
916
+ const formId = sanitizeSegment(extractFormId(fullUrl));
917
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
918
+ const outputDir = path.join(OUTPUT_BASE, formId, timestamp);
919
+ fs.mkdirSync(outputDir, { recursive: true });
920
+
921
+ config.lastTestUrl = fullUrl;
922
+ config.lastRunDir = outputDir;
923
+ saveConfig(config);
924
+
925
+ // Save persona and scenario
926
+ if (personaChoice.type === "preset") {
927
+ fs.writeFileSync(path.join(outputDir, "persona.json"), JSON.stringify(personaChoice.persona, null, 2));
928
+ } else {
929
+ fs.writeFileSync(path.join(outputDir, "persona.json"), JSON.stringify(
930
+ { id: "noen", name: "Noen", description: "Nøytrale svar", traits: {} }, null, 2,
931
+ ));
932
+ }
933
+ fs.writeFileSync(path.join(outputDir, "scenario.json"), JSON.stringify(scenarioChoice, null, 2));
934
+
935
+ // Open and snapshot
936
+ console.log("Opening form with Playwright CLI...");
937
+ await runPlaywrightCli(["open", fullUrl]);
938
+ await runPlaywrightCli(["snapshot", "--filename", path.join(outputDir, "page_open.yml")]);
939
+ await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "page_open.png")]);
940
+
941
+ // Auto-select person (try recommended, then first available)
942
+ let options = extractPersonsFromSnapshotFile(path.join(outputDir, "page_open.yml"));
943
+ if (!options.length) {
944
+ for (let attempt = 0; attempt < 3; attempt++) {
945
+ await sleep(1500);
946
+ options = await fetchPersonOptions();
947
+ if (options.length) break;
948
+ }
949
+ }
950
+ if (options.length) {
951
+ options = prioritizeRecommended(options, RECOMMENDED_PERSON);
952
+ const chosen = options[0];
953
+ console.log(`Auto-selected person: ${chosen}`);
954
+ config.lastPerson = chosen;
955
+ saveConfig(config);
956
+ }
957
+
958
+ const dokumenterUrl = resolveDokumenterUrl(config);
959
+ console.log(`Output folder: ${outputDir}`);
960
+ if (dokumenterUrl) {
961
+ console.log(`Dokumenter URL: ${dokumenterUrl}`);
962
+ }
963
+ printNextSteps(outputDir, dokumenterUrl || "/dokumenter?pnr={PNR}");
964
+ }
965
+
878
966
  async function handleCommand(line, config) {
879
967
  const trimmed = line.trim();
880
968
  if (!trimmed) return;
@@ -1028,6 +1116,20 @@ async function main() {
1028
1116
  process.exit(0);
1029
1117
  }
1030
1118
 
1119
+ if (args[0] === "test" && args.includes("--auto")) {
1120
+ const config = loadConfig();
1121
+ const url = args.find((a) => a.startsWith("http"));
1122
+ if (!url) {
1123
+ console.error("Usage: form-tester test <url> --auto [--pnr <pnr>] [--persona <id>] [--scenario <text>]");
1124
+ process.exit(1);
1125
+ }
1126
+ const pnrFlag = args[args.indexOf("--pnr") + 1];
1127
+ const personaFlag = args[args.indexOf("--persona") + 1];
1128
+ const scenarioFlag = args[args.indexOf("--scenario") + 1];
1129
+ await handleTestAuto(url, config, { pnr: pnrFlag, persona: personaFlag, scenario: scenarioFlag });
1130
+ process.exit(0);
1131
+ }
1132
+
1031
1133
  const config = loadConfig();
1032
1134
  await handleVersionMismatch(config);
1033
1135
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-tester",
3
- "version": "0.3.4",
3
+ "version": "0.4.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": {