form-tester 0.4.2 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +51 -3
  2. package/form-tester.js +46 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,7 +32,9 @@ Edit `form-tester.config.json` and set your `pnr`.
32
32
 
33
33
  ## Usage
34
34
 
35
- ### Non-interactive (recommended for AI agents)
35
+ ### Default: non-interactive (`--auto`)
36
+
37
+ AI agents (Claude Code, Copilot) will use `--auto` mode by default. No prompts, just runs the test:
36
38
 
37
39
  ```bash
38
40
  form-tester test <url> --auto
@@ -43,6 +45,8 @@ Persona IDs: `ung-mann`, `gravid-kvinne`, `eldre-kvinne`, `kronisk-syk-mann`
43
45
 
44
46
  ### Interactive CLI
45
47
 
48
+ For manual use without an AI agent:
49
+
46
50
  ```bash
47
51
  form-tester
48
52
  ```
@@ -51,11 +55,55 @@ Commands: `/setup`, `/update`, `/version`, `/people`, `/test {url}`, `/save {lab
51
55
 
52
56
  ### Claude Code
53
57
 
54
- The skill is automatically detected when you open the project. Use the `/form-tester` skill.
58
+ After `form-tester install`, the skill is automatically detected. Use the `/form-tester` skill or just ask Claude to test a form.
59
+
60
+ If the skill isn't showing up:
61
+ 1. Make sure `.claude/skills/form-tester/` exists in your project (run `form-tester install`)
62
+ 2. Restart Claude Code or start a new conversation
55
63
 
56
64
  ### GitHub Copilot
57
65
 
58
- Copilot reads instructions from `.github/copilot-instructions.md` automatically.
66
+ After `form-tester install`, Copilot reads `.github/copilot-instructions.md` and `.claude/skills/` automatically.
67
+
68
+ If Copilot doesn't recognize the skill:
69
+ 1. Make sure `.claude/skills/form-tester/` exists in your project (run `form-tester install`)
70
+ 2. Run `/skills` in the Copilot CLI to reload skills, or restart the session
71
+
72
+ ## Skip permission prompts
73
+
74
+ By default, AI agents will ask permission for every shell command. To run without interruptions, pre-allow the relevant tools.
75
+
76
+ ### Claude Code
77
+
78
+ Add to your project's `.claude/settings.local.json` (or global `~/.claude/settings.json`):
79
+
80
+ ```json
81
+ {
82
+ "permissions": {
83
+ "allow": [
84
+ "Skill(form-tester)",
85
+ "Bash(form-tester:*)",
86
+ "Bash(playwright-cli:*)"
87
+ ]
88
+ }
89
+ }
90
+ ```
91
+
92
+ Or use the Claude Code CLI:
93
+ ```bash
94
+ claude config add permissions.allow "Skill(form-tester)"
95
+ claude config add permissions.allow "Bash(form-tester:*)"
96
+ claude config add permissions.allow "Bash(playwright-cli:*)"
97
+ ```
98
+
99
+ ### GitHub Copilot
100
+
101
+ In Copilot CLI, use auto-approve mode:
102
+ ```bash
103
+ copilot --auto-approve
104
+ ```
105
+
106
+ Or approve the tool categories when first prompted and select "Always allow".
59
107
 
60
108
  ## Test Output
61
109
 
package/form-tester.js CHANGED
@@ -6,7 +6,7 @@ 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.4.2";
9
+ const LOCAL_VERSION = "0.5.0";
10
10
  const RECOMMENDED_PERSON = "Uromantisk Direktør";
11
11
 
12
12
  const PERSONAS = [
@@ -430,7 +430,7 @@ function printNextSteps(outputDir, dokumenterUrl) {
430
430
  );
431
431
  console.log(`- Or use: /save step`);
432
432
  console.log(
433
- `- Screenshot before submit: playwright-cli screenshot --filename "${path.join(outputDir, "before_submit.png")}"`,
433
+ `- Screenshot before submit: playwright-cli screenshot --filename "${path.join(outputDir, "before_submit.png")}" --full-page`,
434
434
  );
435
435
  console.log("- Submit only when validation errors are cleared.");
436
436
  console.log(
@@ -702,8 +702,8 @@ async function saveArtifacts(config, label) {
702
702
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
703
703
  const base = path.join(config.lastRunDir, `${safeLabel}_${timestamp}`);
704
704
  await runPlaywrightCli(["snapshot", "--filename", `${base}.yml`]);
705
- await runPlaywrightCli(["screenshot", "--filename", `${base}.png`]);
706
- console.log(`Saved: ${base}.yml and ${base}.png`);
705
+ await runPlaywrightCli(["screenshot", "--filename", `${base}.png`, "--full-page"]);
706
+ console.log(`Saved: ${base}.yml and ${base}.png (full-page)`);
707
707
  }
708
708
 
709
709
  async function handleSetup() {
@@ -862,6 +862,7 @@ async function handleTest(url, config) {
862
862
  "screenshot",
863
863
  "--filename",
864
864
  path.join(outputDir, "page_open.png"),
865
+ "--full-page",
865
866
  ]);
866
867
 
867
868
  await promptPersonSelection(config);
@@ -879,6 +880,10 @@ async function handleTest(url, config) {
879
880
  }
880
881
 
881
882
  async function handleTestAuto(url, config, flags) {
883
+ const v = flags.verbosity || "normal";
884
+ const log = (msg) => { if (v !== "silent") console.log(msg); };
885
+ const verbose = (msg) => { if (v === "verbose") console.log(msg); };
886
+
882
887
  // Resolve PNR
883
888
  const pnr = flags.pnr || config.pnr;
884
889
  if (!pnr) {
@@ -888,6 +893,7 @@ async function handleTestAuto(url, config, flags) {
888
893
  const fullUrl = extractPnrFromUrl(url) ? url : setPnrOnUrl(url, pnr);
889
894
  config.pnr = pnr;
890
895
  saveConfig(config);
896
+ verbose(`URL: ${fullUrl}`);
891
897
 
892
898
  // Resolve persona
893
899
  let personaChoice;
@@ -895,14 +901,14 @@ async function handleTestAuto(url, config, flags) {
895
901
  if (personaId) {
896
902
  const found = getPersonaById(personaId);
897
903
  if (found) {
898
- console.log(`Persona: ${found.name} — ${found.description}`);
904
+ log(`Persona: ${found.name} — ${found.description}`);
899
905
  personaChoice = { type: "preset", persona: found };
900
906
  } else {
901
- console.log(`Unknown persona "${personaId}", using Noen.`);
907
+ log(`Unknown persona "${personaId}", using Noen.`);
902
908
  personaChoice = { type: "noen", persona: null };
903
909
  }
904
910
  } else {
905
- console.log("Persona: Noen — nøytrale svar (auto)");
911
+ log("Persona: Noen — nøytrale svar (auto)");
906
912
  personaChoice = { type: "noen", persona: null };
907
913
  }
908
914
 
@@ -910,7 +916,7 @@ async function handleTestAuto(url, config, flags) {
910
916
  const scenarioChoice = flags.scenario
911
917
  ? { type: "custom", description: flags.scenario }
912
918
  : { type: "default", description: "Standard test" };
913
- console.log(`Scenario: ${scenarioChoice.description}`);
919
+ log(`Scenario: ${scenarioChoice.description}`);
914
920
 
915
921
  // Create output directory
916
922
  const formId = sanitizeSegment(extractFormId(fullUrl));
@@ -932,16 +938,18 @@ async function handleTestAuto(url, config, flags) {
932
938
  }
933
939
  fs.writeFileSync(path.join(outputDir, "scenario.json"), JSON.stringify(scenarioChoice, null, 2));
934
940
 
935
- // Open and snapshot
936
- console.log("Opening form with Playwright CLI...");
941
+ // Open and take initial full-page screenshot
942
+ log("Opening form with Playwright CLI...");
937
943
  await runPlaywrightCli(["open", fullUrl]);
938
944
  await runPlaywrightCli(["snapshot", "--filename", path.join(outputDir, "page_open.yml")]);
939
- await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "page_open.png")]);
945
+ await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "page_open.png"), "--full-page"]);
946
+ log("Saved: page_open.yml + page_open.png (full-page)");
940
947
 
941
948
  // Auto-select person (try recommended, then first available)
942
949
  let options = extractPersonsFromSnapshotFile(path.join(outputDir, "page_open.yml"));
943
950
  if (!options.length) {
944
951
  for (let attempt = 0; attempt < 3; attempt++) {
952
+ verbose(`Scanning for person options (attempt ${attempt + 1})...`);
945
953
  await sleep(1500);
946
954
  options = await fetchPersonOptions();
947
955
  if (options.length) break;
@@ -950,17 +958,31 @@ async function handleTestAuto(url, config, flags) {
950
958
  if (options.length) {
951
959
  options = prioritizeRecommended(options, RECOMMENDED_PERSON);
952
960
  const chosen = options[0];
953
- console.log(`Auto-selected person: ${chosen}`);
961
+ log(`Auto-selected person: ${chosen}`);
954
962
  config.lastPerson = chosen;
955
963
  saveConfig(config);
956
964
  }
957
965
 
966
+ // Take form loaded screenshot after person selection
967
+ await runPlaywrightCli(["snapshot", "--filename", path.join(outputDir, "form_loaded.yml")]);
968
+ await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "form_loaded.png"), "--full-page"]);
969
+ log("Saved: form_loaded.yml + form_loaded.png (full-page)");
970
+
958
971
  const dokumenterUrl = resolveDokumenterUrl(config);
972
+
973
+ // Always print output folder (even in silent mode)
959
974
  console.log(`Output folder: ${outputDir}`);
960
- if (dokumenterUrl) {
961
- console.log(`Dokumenter URL: ${dokumenterUrl}`);
975
+
976
+ if (v !== "silent") {
977
+ if (dokumenterUrl) {
978
+ console.log(`Dokumenter URL: ${dokumenterUrl}`);
979
+ }
980
+ console.log("");
981
+ console.log("IMPORTANT: All screenshots MUST use --full-page to capture the entire page.");
982
+ console.log("Example: playwright-cli screenshot --filename \"path/to/file.png\" --full-page");
983
+ console.log("");
984
+ printNextSteps(outputDir, dokumenterUrl || "/dokumenter?pnr={PNR}");
962
985
  }
963
- printNextSteps(outputDir, dokumenterUrl || "/dokumenter?pnr={PNR}");
964
986
  }
965
987
 
966
988
  async function handleCommand(line, config) {
@@ -1120,13 +1142,17 @@ async function main() {
1120
1142
  const config = loadConfig();
1121
1143
  const url = args.find((a) => a.startsWith("http"));
1122
1144
  if (!url) {
1123
- console.error("Usage: form-tester test <url> --auto [--pnr <pnr>] [--persona <id>] [--scenario <text>]");
1145
+ console.error("Usage: form-tester test <url> --auto [--pnr <pnr>] [--persona <id>] [--scenario <text>] [--silent|--verbose]");
1124
1146
  process.exit(1);
1125
1147
  }
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 });
1148
+ const flagVal = (flag) => args.includes(flag) ? args[args.indexOf(flag) + 1] : undefined;
1149
+ const verbosity = args.includes("--silent") ? "silent" : args.includes("--verbose") ? "verbose" : "normal";
1150
+ await handleTestAuto(url, config, {
1151
+ pnr: flagVal("--pnr"),
1152
+ persona: flagVal("--persona"),
1153
+ scenario: flagVal("--scenario"),
1154
+ verbosity,
1155
+ });
1130
1156
  process.exit(0);
1131
1157
  }
1132
1158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-tester",
3
- "version": "0.4.2",
3
+ "version": "0.5.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": {