form-tester 0.8.3 → 0.9.1
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.
|
@@ -58,6 +58,13 @@ Notes:
|
|
|
58
58
|
- Use `--help` or `-h` to print the command list without starting the prompt.
|
|
59
59
|
- IMPORTANT: Always use `form-tester exec` instead of `playwright-cli` directly. This records all commands for replay. Same syntax: `form-tester exec fill e1 "value"`, `form-tester exec click e3`, `form-tester exec close` (finalizes recording).
|
|
60
60
|
- Replay a previous run: `form-tester replay output/form-id/timestamp/recording.json`
|
|
61
|
+
- IMPORTANT: When something unexpected happens during a test — wrong page state, unexpected modal, failed command, element not found, timeout, wrong document format — ALWAYS log an issue:
|
|
62
|
+
`form-tester issue <category> "<description of what happened>"`
|
|
63
|
+
Categories: person-selection, navigation, form-fill, submission, documents, pdf-download, html-capture, screenshot, snapshot, validation, modal, timeout, other
|
|
64
|
+
Example: `form-tester issue modal "Submit showed error modal instead of success: Det skjedde en feil"`
|
|
65
|
+
Example: `form-tester issue person-selection "Person list showed 0 options, had to retry manually"`
|
|
66
|
+
These logs help us improve the skill to handle more scenarios automatically.
|
|
67
|
+
- View logged issues: `form-tester issues`
|
|
61
68
|
- IMPORTANT: All screenshots taken during a test run MUST use `--full-page` to capture the entire page, not just the viewport. This applies to every screenshot command: `form-tester exec screenshot --filename "..." --full-page`
|
|
62
69
|
- IMPORTANT: Take a full-page screenshot EVERY TIME the page changes. This includes: after clicking any action button (Neste, Forrige, Send inn, etc.), after a step/page transition, after form validation errors appear, after modals open, and after submission. Name screenshots descriptively (e.g., step1_filled.png, step2_before_submit.png, submit_result.png).
|
|
63
70
|
|
|
@@ -69,12 +76,58 @@ IMPORTANT: Each prompt below MUST be asked as a separate message to the user. Wa
|
|
|
69
76
|
3. Prompt for test scenario in a NEW separate message. Ask: "Any specific test scenario? (describe what to test, or Enter for standard clean test)". Wait for the user's response. If the user says nothing specific or "default" or just presses Enter, use standard test. The scenario is saved to scenario.json in the output directory.
|
|
70
77
|
4. Only after receiving answers to all prompts: open browser, fill form, submit, verify.
|
|
71
78
|
|
|
79
|
+
Form filling strategy:
|
|
80
|
+
Before filling any fields, take a snapshot and study the FULL form structure:
|
|
81
|
+
1. Identify ALL sections, including collapsed/accordion sections (buttons with arrow icons).
|
|
82
|
+
2. Expand ALL collapsed sections FIRST by clicking their header buttons. Take a new snapshot after expanding.
|
|
83
|
+
3. Identify ALL required fields across all sections before starting to fill.
|
|
84
|
+
4. Fill fields section by section, top to bottom.
|
|
85
|
+
|
|
86
|
+
Autosuggest / search fields (e.g. "Søk opp et legemiddel"):
|
|
87
|
+
These fields show a dropdown with suggestions as you type. Do NOT use `fill` + `Enter` — the value won't commit.
|
|
88
|
+
Instead:
|
|
89
|
+
```
|
|
90
|
+
form-tester exec fill <ref> "search text"
|
|
91
|
+
```
|
|
92
|
+
Then wait for the dropdown to appear and take a snapshot to find the suggestion element:
|
|
93
|
+
```
|
|
94
|
+
form-tester exec snapshot
|
|
95
|
+
```
|
|
96
|
+
Then click the correct suggestion from the dropdown list. If no dropdown appears, try:
|
|
97
|
+
```
|
|
98
|
+
form-tester exec run-code "async page => { const input = page.locator('#fieldId'); await input.fill('search text'); await page.waitForTimeout(1000); const option = page.locator('[role=\"option\"]').first(); await option.click(); }"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Handling validation errors after submit:
|
|
102
|
+
CRITICAL RULES — follow these exactly:
|
|
103
|
+
1. MAXIMUM 3 submit attempts. If validation errors persist after 3 attempts, STOP. Log the remaining errors with `form-tester issue validation "..."` and note them in test_results.txt. Do NOT keep retrying.
|
|
104
|
+
2. After each failed submit, take a snapshot and READ the validation error list carefully.
|
|
105
|
+
3. Each validation error is a clickable link with an href like `#fieldId`. Click the error link to scroll to and focus the unfilled field. This is the ONLY reliable way to find the field.
|
|
106
|
+
4. After clicking the error link, take a snapshot to see the field in context and fill it.
|
|
107
|
+
5. Do NOT re-fill fields that are already filled. Only fix the fields listed in the validation errors.
|
|
108
|
+
6. Do NOT use JavaScript `dispatchEvent` hacks or `element.evaluate()` to set values — these bypass React's state and the form won't register the value. Always use Playwright's `fill`, `click`, `select` commands.
|
|
109
|
+
7. Before resubmitting, verify that the number of validation errors has decreased. If the same errors persist after you tried to fix them, the approach isn't working — try a different strategy (e.g., expand a collapsed section, use a different selector).
|
|
110
|
+
8. Some forms have accordion/collapsible sections. Validation errors inside collapsed sections cannot be filled until the section is expanded. Look for buttons near the error's field ID in the snapshot and click to expand.
|
|
111
|
+
|
|
72
112
|
Post-submit verification:
|
|
73
113
|
After a successful submission, read the modal text carefully:
|
|
74
114
|
- If it says the form is stored in Dokumenter (e.g. "En kopi er også lagret i Dokumenter" or "Skjemaet er fullført og lagret i Dokumenter"), proceed with Dokumenter verification below.
|
|
75
115
|
- If the modal does NOT mention Dokumenter, or says the form will not be stored/you will not get a response, skip Dokumenter verification entirely. Record this in test_results.txt.
|
|
76
116
|
|
|
77
117
|
Dokumenter verification (only when modal confirms storage):
|
|
118
|
+
Use the standardized documents command — it handles navigation, format detection, PDF download, and HTML capture automatically:
|
|
119
|
+
```
|
|
120
|
+
form-tester documents
|
|
121
|
+
```
|
|
122
|
+
This will:
|
|
123
|
+
1. Navigate to `/dokumenter?pnr={PNR}`
|
|
124
|
+
2. Click "Se detaljer" on the first document
|
|
125
|
+
3. Click "Åpne dokumentet"
|
|
126
|
+
4. Auto-detect PDF vs HTML format
|
|
127
|
+
5. Download PDF or capture HTML screenshot + raw HTML
|
|
128
|
+
6. Log issues automatically if any step fails
|
|
129
|
+
|
|
130
|
+
If `form-tester documents` doesn't find the right elements (logged as issues), fall back to manual steps:
|
|
78
131
|
1. Navigate to `/dokumenter?pnr={PNR}` and select the same person used during form fill.
|
|
79
132
|
2. The document list loads sorted newest first. The first entry should match the form title.
|
|
80
133
|
3. Click "Se detaljer" on the first document, then click "Åpne dokumentet".
|
|
@@ -107,4 +160,5 @@ Dokumenter verification (only when modal confirms storage):
|
|
|
107
160
|
|
|
108
161
|
XML/other: Note type in test_results.txt, skip capture.
|
|
109
162
|
|
|
110
|
-
5.
|
|
163
|
+
5. Log any issues encountered: `form-tester issue documents "description of what went wrong"`
|
|
164
|
+
6. Include the document verification results in test_results.txt (document title, whether it matched the form h1, document type: HTML/PDF/XML).
|
|
@@ -47,7 +47,24 @@ Replay a previous run:
|
|
|
47
47
|
form-tester replay output/form-id/timestamp/recording.json
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Document verification
|
|
51
|
+
|
|
52
|
+
After form submission, use the standardized documents command:
|
|
53
|
+
```bash
|
|
54
|
+
form-tester documents # auto-navigates, detects PDF/HTML, captures
|
|
55
|
+
```
|
|
56
|
+
This handles the full flow: navigate to Dokumenter, find latest doc, detect format, download PDF or screenshot HTML.
|
|
57
|
+
|
|
58
|
+
## Issue logging
|
|
59
|
+
|
|
60
|
+
When something unexpected happens during a test, log it for skill improvement:
|
|
61
|
+
```bash
|
|
62
|
+
form-tester issue <category> "<description>"
|
|
63
|
+
form-tester issues # view recent issues
|
|
64
|
+
```
|
|
65
|
+
Categories: `person-selection`, `navigation`, `form-fill`, `submission`, `documents`, `pdf-download`, `html-capture`, `screenshot`, `snapshot`, `validation`, `modal`, `timeout`, `other`
|
|
66
|
+
|
|
67
|
+
## Interactive commands
|
|
51
68
|
|
|
52
69
|
```bash
|
|
53
70
|
/setup
|
package/form-tester.js
CHANGED
|
@@ -6,7 +6,8 @@ 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
|
|
9
|
+
const ISSUES_PATH = path.join(OUTPUT_BASE, "issues.jsonl");
|
|
10
|
+
const LOCAL_VERSION = "0.9.1";
|
|
10
11
|
const RECOMMENDED_PERSON = "Uromantisk Direktør";
|
|
11
12
|
|
|
12
13
|
// Recording — persisted to disk so `form-tester exec` can append across processes
|
|
@@ -80,6 +81,182 @@ function saveRecording() {
|
|
|
80
81
|
return result;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
// Issue logging — captures problems during test runs for skill improvement
|
|
85
|
+
const ISSUE_CATEGORIES = [
|
|
86
|
+
"person-selection",
|
|
87
|
+
"navigation",
|
|
88
|
+
"form-fill",
|
|
89
|
+
"submission",
|
|
90
|
+
"documents",
|
|
91
|
+
"pdf-download",
|
|
92
|
+
"html-capture",
|
|
93
|
+
"screenshot",
|
|
94
|
+
"snapshot",
|
|
95
|
+
"validation",
|
|
96
|
+
"modal",
|
|
97
|
+
"timeout",
|
|
98
|
+
"other",
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
function logIssue(category, message, context = {}) {
|
|
102
|
+
const entry = {
|
|
103
|
+
timestamp: new Date().toISOString(),
|
|
104
|
+
version: LOCAL_VERSION,
|
|
105
|
+
category,
|
|
106
|
+
message,
|
|
107
|
+
...context,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Append to global issues log
|
|
111
|
+
fs.mkdirSync(path.dirname(ISSUES_PATH), { recursive: true });
|
|
112
|
+
fs.appendFileSync(ISSUES_PATH, JSON.stringify(entry) + "\n");
|
|
113
|
+
|
|
114
|
+
// Also append to current run dir if available
|
|
115
|
+
try {
|
|
116
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
117
|
+
if (config.lastRunDir && fs.existsSync(config.lastRunDir)) {
|
|
118
|
+
const runIssuesPath = path.join(config.lastRunDir, "issues.jsonl");
|
|
119
|
+
fs.appendFileSync(runIssuesPath, JSON.stringify(entry) + "\n");
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// no config or run dir, global log is enough
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function listIssues(limit = 20) {
|
|
129
|
+
if (!fs.existsSync(ISSUES_PATH)) return [];
|
|
130
|
+
const lines = fs.readFileSync(ISSUES_PATH, "utf8").trim().split("\n").filter(Boolean);
|
|
131
|
+
const issues = lines.map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
132
|
+
return issues.slice(-limit);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatIssue(issue) {
|
|
136
|
+
const time = issue.timestamp ? issue.timestamp.replace("T", " ").replace(/\.\d+Z$/, "") : "?";
|
|
137
|
+
const url = issue.url ? ` | ${issue.url}` : "";
|
|
138
|
+
const formId = issue.formId ? ` | ${issue.formId}` : "";
|
|
139
|
+
return `[${time}] [${issue.category}]${formId}${url}\n ${issue.message}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function handleDocuments(config, flags = {}) {
|
|
143
|
+
const v = flags.verbosity || "normal";
|
|
144
|
+
const log = (msg) => { if (v !== "silent") console.log(msg); };
|
|
145
|
+
|
|
146
|
+
const dokumenterUrl = resolveDokumenterUrl(config);
|
|
147
|
+
if (!dokumenterUrl) {
|
|
148
|
+
console.error("No Dokumenter URL available. Set pnr in config or URL.");
|
|
149
|
+
logIssue("documents", "No Dokumenter URL — missing PNR", { url: config.lastTestUrl });
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const outputDir = config.lastRunDir;
|
|
154
|
+
if (!outputDir) {
|
|
155
|
+
console.error("No output folder. Run a test first.");
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
159
|
+
|
|
160
|
+
// Step 1: Navigate to Dokumenter
|
|
161
|
+
log(`Navigating to Dokumenter: ${dokumenterUrl}`);
|
|
162
|
+
let code = await runPlaywrightCli(["goto", dokumenterUrl]);
|
|
163
|
+
if (code !== 0) {
|
|
164
|
+
logIssue("documents", `Failed to navigate to Dokumenter (exit ${code})`, { url: dokumenterUrl });
|
|
165
|
+
console.error("Failed to navigate to Dokumenter.");
|
|
166
|
+
return code;
|
|
167
|
+
}
|
|
168
|
+
await sleep(2000);
|
|
169
|
+
|
|
170
|
+
// Step 2: Take snapshot of document list
|
|
171
|
+
const docListSnapshot = path.join(outputDir, "dokumenter.yml");
|
|
172
|
+
await runPlaywrightCli(["snapshot", "--filename", docListSnapshot]);
|
|
173
|
+
await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "dokumenter.png"), "--full-page"]);
|
|
174
|
+
log("Saved: dokumenter.yml + dokumenter.png");
|
|
175
|
+
|
|
176
|
+
// Step 3: Click first document's "Se detaljer"
|
|
177
|
+
log("Looking for first document...");
|
|
178
|
+
const clickResult = await runPlaywrightCliCapture(["eval", '() => { const link = document.querySelector("a[href*=\\"detaljer\\"], button:has-text(\\"Se detaljer\\"), a:has-text(\\"Se detaljer\\")"); if (link) { link.click(); return "clicked"; } return "not-found"; }']);
|
|
179
|
+
if (clickResult.stdout.includes("not-found")) {
|
|
180
|
+
logIssue("documents", "Could not find 'Se detaljer' link on Dokumenter page", { url: dokumenterUrl });
|
|
181
|
+
log("Could not find 'Se detaljer'. Check dokumenter.yml for the page structure.");
|
|
182
|
+
// Still continue — agent can handle manually
|
|
183
|
+
} else {
|
|
184
|
+
log("Clicked 'Se detaljer' on first document.");
|
|
185
|
+
await sleep(2000);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Step 4: Click "Åpne dokumentet"
|
|
189
|
+
const openResult = await runPlaywrightCliCapture(["eval", '() => { const link = document.querySelector("a:has-text(\\"Åpne dokumentet\\"), button:has-text(\\"Åpne dokumentet\\")"); if (link) { link.click(); return "clicked"; } return "not-found"; }']);
|
|
190
|
+
if (openResult.stdout.includes("not-found")) {
|
|
191
|
+
logIssue("documents", "Could not find 'Åpne dokumentet' link", { url: dokumenterUrl });
|
|
192
|
+
log("Could not find 'Åpne dokumentet'. Take a snapshot to inspect the page.");
|
|
193
|
+
} else {
|
|
194
|
+
log("Clicked 'Åpne dokumentet'.");
|
|
195
|
+
await sleep(3000);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Step 5: Detect format (PDF vs HTML)
|
|
199
|
+
const docSnapshot = path.join(outputDir, "document.yml");
|
|
200
|
+
await runPlaywrightCli(["snapshot", "--filename", docSnapshot]);
|
|
201
|
+
|
|
202
|
+
let format = "unknown";
|
|
203
|
+
if (fs.existsSync(docSnapshot)) {
|
|
204
|
+
const snapshotText = fs.readFileSync(docSnapshot, "utf8");
|
|
205
|
+
if (/href.*\/pdf\//i.test(snapshotText) || /blob:/i.test(snapshotText) || /\.pdf/i.test(snapshotText)) {
|
|
206
|
+
format = "pdf";
|
|
207
|
+
} else if (snapshotText.length > 500) {
|
|
208
|
+
// Has substantial content — likely HTML
|
|
209
|
+
format = "html";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
log(`Detected document format: ${format}`);
|
|
213
|
+
|
|
214
|
+
// Step 6: Capture based on format
|
|
215
|
+
if (format === "pdf") {
|
|
216
|
+
log("Attempting PDF download...");
|
|
217
|
+
// Try direct PDF link first
|
|
218
|
+
const dlCode = await runPlaywrightCli(["run-code", `async page => { const link = page.locator('a[href*="/pdf/"]').first(); const count = await link.count(); if (count > 0) { const [download] = await Promise.all([ page.waitForEvent('download'), link.click() ]); await download.saveAs('${outputDir.replace(/\\/g, "/")}/document.pdf'); return; } const lastNed = page.getByRole('link', { name: 'Last ned' }); const lastNedCount = await lastNed.count(); if (lastNedCount > 0) { const [download] = await Promise.all([ page.waitForEvent('download'), lastNed.click() ]); await download.saveAs('${outputDir.replace(/\\/g, "/")}/document.pdf'); return; } throw new Error('No PDF link found'); }`]);
|
|
219
|
+
if (dlCode !== 0) {
|
|
220
|
+
logIssue("pdf-download", "PDF download failed", { url: dokumenterUrl, outputDir });
|
|
221
|
+
log("PDF download failed. Check document.yml for available links.");
|
|
222
|
+
} else {
|
|
223
|
+
const pdfPath = path.join(outputDir, "document.pdf");
|
|
224
|
+
if (fs.existsSync(pdfPath)) {
|
|
225
|
+
log(`PDF saved: ${pdfPath}`);
|
|
226
|
+
} else {
|
|
227
|
+
logIssue("pdf-download", "PDF download command succeeded but file not found", { outputDir });
|
|
228
|
+
log("PDF download command ran but file not found.");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} else if (format === "html") {
|
|
232
|
+
log("Capturing HTML document...");
|
|
233
|
+
// Screenshot
|
|
234
|
+
const ssCode = await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "document_screenshot.png"), "--full-page"]);
|
|
235
|
+
if (ssCode !== 0) {
|
|
236
|
+
logIssue("html-capture", "Full-page screenshot failed (possible PDF misdetected as HTML)", { outputDir });
|
|
237
|
+
log("Screenshot failed — document might be PDF. Trying download...");
|
|
238
|
+
format = "pdf";
|
|
239
|
+
} else {
|
|
240
|
+
log("Saved: document_screenshot.png (full-page)");
|
|
241
|
+
}
|
|
242
|
+
// Save raw HTML
|
|
243
|
+
const htmlResult = await runPlaywrightCliCapture(["eval", "document.documentElement.outerHTML"]);
|
|
244
|
+
if (htmlResult.code === 0 && htmlResult.stdout) {
|
|
245
|
+
const htmlContent = htmlResult.stdout.replace(/^### Result\s*/i, "");
|
|
246
|
+
fs.writeFileSync(path.join(outputDir, "document.html"), htmlContent);
|
|
247
|
+
log("Saved: document.html");
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
logIssue("documents", `Unknown document format — could not detect PDF or HTML`, { outputDir });
|
|
251
|
+
log("Could not determine format. Taking snapshot and screenshot for manual review.");
|
|
252
|
+
await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "document_screenshot.png"), "--full-page"]);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
log(`\nDocument verification complete. Format: ${format}`);
|
|
256
|
+
log(`Output: ${outputDir}`);
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
83
260
|
const PERSONAS = [
|
|
84
261
|
{
|
|
85
262
|
id: "ung-mann",
|
|
@@ -473,6 +650,9 @@ function printHelp() {
|
|
|
473
650
|
" form-tester test <url> --human Interactive test with prompts",
|
|
474
651
|
" form-tester exec <command> [args] Run playwright-cli command (recorded)",
|
|
475
652
|
" form-tester replay <recording.json> Replay a recorded test run",
|
|
653
|
+
" form-tester documents Verify document in Dokumenter (auto PDF/HTML)",
|
|
654
|
+
" form-tester issue <category> <message> Log an issue for skill improvement",
|
|
655
|
+
" form-tester issues [limit] Show recent logged issues",
|
|
476
656
|
"",
|
|
477
657
|
"Interactive commands:",
|
|
478
658
|
" /test {url} Open form URL and start test",
|
|
@@ -717,6 +897,7 @@ async function fetchPersonOptions() {
|
|
|
717
897
|
console.log(
|
|
718
898
|
`Failed to read person list from browser (${combinedOutput || "unknown error"}).`,
|
|
719
899
|
);
|
|
900
|
+
logIssue("person-selection", `Failed to read person list: ${combinedOutput || "unknown error"}`);
|
|
720
901
|
return [];
|
|
721
902
|
}
|
|
722
903
|
return parsePersonList(result.stdout);
|
|
@@ -749,6 +930,7 @@ async function promptPersonSelection(config) {
|
|
|
749
930
|
console.log(
|
|
750
931
|
"No person options detected. Open the picker and run /people to retry.",
|
|
751
932
|
);
|
|
933
|
+
logIssue("person-selection", "No person options detected after 3 attempts");
|
|
752
934
|
return;
|
|
753
935
|
}
|
|
754
936
|
console.log("Select a person:");
|
|
@@ -1100,6 +1282,7 @@ async function handleReplay(filePath) {
|
|
|
1100
1282
|
console.log(`[${i + 1}/${recording.commandCount}] playwright-cli ${cmd.args.join(" ")}`);
|
|
1101
1283
|
const code = await runPlaywrightCli(cmd.args);
|
|
1102
1284
|
if (code !== 0) {
|
|
1285
|
+
logIssue("other", `Replay command failed: playwright-cli ${cmd.args.join(" ")} (exit ${code})`);
|
|
1103
1286
|
console.error(`Command failed with exit code ${code}. Stopping replay.`);
|
|
1104
1287
|
process.exit(1);
|
|
1105
1288
|
}
|
|
@@ -1317,6 +1500,49 @@ async function main() {
|
|
|
1317
1500
|
process.exit(0);
|
|
1318
1501
|
}
|
|
1319
1502
|
|
|
1503
|
+
if (args[0] === "issue") {
|
|
1504
|
+
const category = args[1];
|
|
1505
|
+
const message = args.slice(2).join(" ");
|
|
1506
|
+
if (!category || !message) {
|
|
1507
|
+
console.error("Usage: form-tester issue <category> <message>");
|
|
1508
|
+
console.error(`\nCategories: ${ISSUE_CATEGORIES.join(", ")}`);
|
|
1509
|
+
process.exit(1);
|
|
1510
|
+
}
|
|
1511
|
+
if (!ISSUE_CATEGORIES.includes(category)) {
|
|
1512
|
+
console.error(`Unknown category "${category}". Valid: ${ISSUE_CATEGORIES.join(", ")}`);
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
const config = loadConfig();
|
|
1516
|
+
const entry = logIssue(category, message, {
|
|
1517
|
+
url: config.lastTestUrl || undefined,
|
|
1518
|
+
formId: config.lastTestUrl ? extractFormId(config.lastTestUrl) : undefined,
|
|
1519
|
+
outputDir: config.lastRunDir || undefined,
|
|
1520
|
+
});
|
|
1521
|
+
console.log(`Issue logged: [${entry.category}] ${entry.message}`);
|
|
1522
|
+
process.exit(0);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
if (args[0] === "issues") {
|
|
1526
|
+
const limit = Number.parseInt(args[1], 10) || 20;
|
|
1527
|
+
const issues = listIssues(limit);
|
|
1528
|
+
if (!issues.length) {
|
|
1529
|
+
console.log("No issues logged yet.");
|
|
1530
|
+
process.exit(0);
|
|
1531
|
+
}
|
|
1532
|
+
console.log(`Last ${issues.length} issue(s):\n`);
|
|
1533
|
+
for (const issue of issues) {
|
|
1534
|
+
console.log(formatIssue(issue));
|
|
1535
|
+
}
|
|
1536
|
+
process.exit(0);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
if (args[0] === "documents") {
|
|
1540
|
+
const config = loadConfig();
|
|
1541
|
+
const verbosity = args.includes("--silent") ? "silent" : args.includes("--verbose") ? "verbose" : "normal";
|
|
1542
|
+
const code = await handleDocuments(config, { verbosity });
|
|
1543
|
+
process.exit(code);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1320
1546
|
if (args[0] === "test" && args.includes("--human")) {
|
|
1321
1547
|
const config = loadConfig();
|
|
1322
1548
|
const url = args.find((a) => a.startsWith("http"));
|
|
@@ -1414,5 +1640,8 @@ module.exports = {
|
|
|
1414
1640
|
startRecording,
|
|
1415
1641
|
appendToRecording,
|
|
1416
1642
|
finalizeRecording,
|
|
1643
|
+
logIssue,
|
|
1644
|
+
listIssues,
|
|
1645
|
+
ISSUE_CATEGORIES,
|
|
1417
1646
|
};
|
|
1418
1647
|
|
package/package.json
CHANGED