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.
- package/README.md +51 -3
- package/form-tester.js +46 -20
- 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
|
-
###
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
904
|
+
log(`Persona: ${found.name} — ${found.description}`);
|
|
899
905
|
personaChoice = { type: "preset", persona: found };
|
|
900
906
|
} else {
|
|
901
|
-
|
|
907
|
+
log(`Unknown persona "${personaId}", using Noen.`);
|
|
902
908
|
personaChoice = { type: "noen", persona: null };
|
|
903
909
|
}
|
|
904
910
|
} else {
|
|
905
|
-
|
|
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
|
-
|
|
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
|
|
936
|
-
|
|
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
|
-
|
|
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
|
-
|
|
961
|
-
|
|
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
|
|
1127
|
-
const
|
|
1128
|
-
|
|
1129
|
-
|
|
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