form-tester 0.9.1 → 0.10.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.
@@ -52,113 +52,123 @@ Notes:
52
52
  - The CLI opens the form with Playwright CLI, saves an initial snapshot + screenshot, and prints next-step commands.
53
53
  - Use `/save {label}` to capture additional snapshots + screenshots into the same output folder.
54
54
  - Use `/update` after changing the skill to update Playwright CLI + skills, then reload the skill in Copilot CLI with `/skills` (reload) or `/restart`.
55
- - Use `/people` to rescan the visible person list and get a numbered selection prompt.
56
- - Use the next-step checklist printed by the CLI (cookies, person selection, validation fix, Dokumenter verification, save HTML/PDF).
57
- - If the error modal appears on save or submit ("Det skjedde en feil under innsending av skjema. Prøv igjen senere."), open DevTools -> Network before retrying. Then try resubmitting once. If it persists, find the failed request and capture the Correlation ID header in test_results.txt.
58
55
  - Use `--help` or `-h` to print the command list without starting the prompt.
59
56
  - 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
57
  - Replay a previous run: `form-tester replay output/form-id/timestamp/recording.json`
61
58
  - 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
59
  `form-tester issue <category> "<description of what happened>"`
63
60
  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
61
  - View logged issues: `form-tester issues`
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`
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).
62
+ - IMPORTANT: All screenshots MUST use `--full-page`. Take a full-page screenshot EVERY TIME the page changes.
63
+
64
+ Test flow (step by step):
70
65
 
71
- Test flow (when /test is triggered):
72
66
  IMPORTANT: Each prompt below MUST be asked as a separate message to the user. Wait for the user's response before proceeding to the next step. Do NOT combine multiple prompts into one message.
73
67
 
74
68
  1. Prompt for PNR (if not in URL). Wait for response.
75
69
  2. Prompt for persona (1-6 selection). Show the numbered list and wait for the user's response.
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.
77
- 4. Only after receiving answers to all prompts: open browser, fill form, submit, verify.
70
+ 3. Prompt for test scenario in a NEW separate message. Wait for the user's response.
71
+ 4. Only after receiving answers to all prompts, proceed with the steps below.
72
+
73
+ Step 1 — Open and setup:
74
+ ```
75
+ form-tester test <url> --auto --pnr <pnr> --persona <id> --scenario "<text>"
76
+ ```
77
+
78
+ Step 2 — Dismiss cookies:
79
+ ```
80
+ form-tester cookies
81
+ ```
82
+ This automatically finds and clicks the cookie banner. No-op if no banner is present.
78
83
 
79
- Form filling strategy:
80
- Before filling any fields, take a snapshot and study the FULL form structure:
84
+ Step 3 — Select person:
85
+ ```
86
+ form-tester select-person
87
+ ```
88
+ This scans for available persons, selects the recommended one ("Uromantisk Direktør"), clicks it, and takes a screenshot. To select a specific person:
89
+ ```
90
+ form-tester select-person "Navn Navnesen"
91
+ ```
92
+
93
+ Step 4 — Study the form structure:
94
+ Take a snapshot and study the FULL form before filling anything:
81
95
  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.
96
+ 2. Expand ALL collapsed sections FIRST by clicking their header buttons.
97
+ 3. Take a new snapshot after expanding.
98
+ 4. Identify ALL required fields across all sections.
99
+
100
+ Step 5 — Fill the form:
101
+ Fill fields section by section, top to bottom. Use `form-tester exec` for all commands.
85
102
 
86
103
  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:
104
+ Do NOT use `fill` + `Enter` — the value won't commit. Instead:
89
105
  ```
90
106
  form-tester exec fill <ref> "search text"
91
107
  ```
92
- Then wait for the dropdown to appear and take a snapshot to find the suggestion element:
108
+ Wait for the dropdown, take a snapshot to find the suggestion, then click it:
93
109
  ```
94
110
  form-tester exec snapshot
111
+ form-tester exec click <suggestion-ref>
95
112
  ```
96
- Then click the correct suggestion from the dropdown list. If no dropdown appears, try:
113
+ Or use run-code:
97
114
  ```
98
115
  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
116
  ```
100
117
 
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.
118
+ Step 6 Submit:
119
+ Take a screenshot before submitting, then click the submit button.
111
120
 
112
- Post-submit verification:
121
+ Step 7 — Handle validation errors:
122
+ IMPORTANT: If validation errors appear after clicking submit, the form DID NOT SUBMIT. The errors are blocking submission. You must fix them first.
123
+
124
+ Run the validate command to find and scroll to each error:
125
+ ```
126
+ form-tester validate
127
+ ```
128
+ This will:
129
+ 1. Take a snapshot and parse all validation errors
130
+ 2. Click each error link to scroll to the unfilled field
131
+ 3. Take a snapshot at each field so you can see what needs to be filled
132
+ 4. Print structured output with field IDs and error messages
133
+
134
+ After `form-tester validate`, fix each field it found, then run `form-tester validate` again to confirm errors are resolved. Only then resubmit.
135
+
136
+ CRITICAL RULES:
137
+ - MAXIMUM 3 submit attempts. After 3, STOP. Log remaining errors with `form-tester issue validation "..."` and note them in test_results.txt.
138
+ - Do NOT re-fill fields that are already filled. Only fix fields listed in validation errors.
139
+ - Do NOT use JavaScript `dispatchEvent` hacks or `element.evaluate()` to set values — these bypass React's state. Always use Playwright's `fill`, `click`, `select` commands.
140
+ - If the same errors persist after fixing, the field might be inside a collapsed accordion section. Expand it first.
141
+
142
+ Step 8 — Post-submit verification:
113
143
  After a successful submission, read the modal text carefully:
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.
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.
144
+ - If it says the form is stored in Dokumenter (e.g. "lagret i Dokumenter"), proceed with Dokumenter verification.
145
+ - If the modal does NOT mention Dokumenter, skip Dokumenter verification. Record this in test_results.txt.
116
146
 
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:
147
+ Step 9 — Dokumenter verification (only when modal confirms storage):
119
148
  ```
120
149
  form-tester documents
121
150
  ```
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:
131
- 1. Navigate to `/dokumenter?pnr={PNR}` and select the same person used during form fill.
132
- 2. The document list loads sorted newest first. The first entry should match the form title.
133
- 3. Click "Se detaljer" on the first document, then click "Åpne dokumentet".
134
- 4. After clicking "Åpne dokumentet", determine the document format:
135
-
136
- How to detect format:
137
- - Take a snapshot: `form-tester exec snapshot`
138
- - If snapshot shows a link with href containing `/pdf/` or `blob:` → PDF
139
- - If `--full-page` screenshot times out → PDF (do NOT retry, switch to download)
140
- - If snapshot shows rendered HTML content (headings, paragraphs, form data) → HTML
141
-
142
- PDF documents — DOWNLOAD, do NOT screenshot:
143
- IMPORTANT: `run-code` does NOT have access to `require` or `fs`. Do NOT use `require('fs')`. Use Playwright's download API instead.
144
-
145
- Find the PDF download link in the snapshot (look for `a[href*="/pdf/"]` or "Last ned" link), then download using the Playwright download event:
151
+ This handles the full flow: navigate to Dokumenter, find latest doc, detect format, download PDF or screenshot HTML.
152
+
153
+ If `form-tester documents` fails (logged as issues), fall back to manual steps:
154
+ 1. Navigate to `/dokumenter?pnr={PNR}` and select the same person.
155
+ 2. Click "Se detaljer" on the first document, then "Åpne dokumentet".
156
+ 3. Detect format from snapshot:
157
+ - `a[href*="/pdf/"]` or `blob:` PDF
158
+ - `--full-page` screenshot times out → PDF
159
+ - Rendered HTML content HTML
160
+
161
+ PDF download:
146
162
  ```
147
163
  form-tester exec run-code "async page => { const link = page.locator('a[href*=\"/pdf/\"]').first(); const [download] = await Promise.all([ page.waitForEvent('download'), link.click() ]); await download.saveAs('$OUTPUT_DIR/document.pdf'); }"
148
164
  ```
149
- If there is no direct PDF link but a "Last ned" button:
150
- ```
151
- form-tester exec run-code "async page => { const [download] = await Promise.all([ page.waitForEvent('download'), page.getByRole('link', { name: 'Last ned' }).click() ]); await download.saveAs('$OUTPUT_DIR/document.pdf'); }"
152
- ```
153
- Verify the download: check that document.pdf exists in the output folder.
154
165
 
155
- HTML documents SCREENSHOT full page:
166
+ HTML — screenshot + save:
156
167
  ```
157
168
  form-tester exec screenshot --filename "$OUTPUT_DIR/document_screenshot.png" --full-page
169
+ form-tester exec eval "document.documentElement.outerHTML"
158
170
  ```
159
- Also save raw HTML: `form-tester exec eval "document.documentElement.outerHTML"` → save to document.html.
160
-
161
- XML/other: Note type in test_results.txt, skip capture.
162
171
 
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).
172
+ Step 10 Finalize:
173
+ - Write test_results.txt with status, data used, and notes.
174
+ - Close browser: `form-tester exec close`
@@ -47,21 +47,31 @@ Replay a previous run:
47
47
  form-tester replay output/form-id/timestamp/recording.json
48
48
  ```
49
49
 
50
- ## Document verification
50
+ ## Standardized commands
51
+
52
+ Use these instead of manual steps — they handle retries, edge cases, and error logging automatically:
51
53
 
52
- After form submission, use the standardized documents command:
53
54
  ```bash
54
- form-tester documents # auto-navigates, detects PDF/HTML, captures
55
+ form-tester cookies # dismiss cookie banner (tries known selectors)
56
+ form-tester select-person # select recommended person ("Uromantisk Direktør")
57
+ form-tester select-person "Name" # select specific person by name
58
+ form-tester validate # parse validation errors, scroll to each field, show context
59
+ form-tester documents # navigate to Dokumenter, detect PDF/HTML, capture
60
+ form-tester issue <category> "<text>" # log an issue for skill improvement
61
+ form-tester issues # view recent issues
55
62
  ```
56
- This handles the full flow: navigate to Dokumenter, find latest doc, detect format, download PDF or screenshot HTML.
63
+
64
+ ### Typical test flow:
65
+ 1. `form-tester test <url> --auto` — open form
66
+ 2. `form-tester cookies` — dismiss cookies
67
+ 3. `form-tester select-person` — select person
68
+ 4. Fill the form with `form-tester exec fill/click/select`
69
+ 5. Submit, then `form-tester validate` — find and fix any validation errors
70
+ 6. `form-tester documents` — verify document after successful submission
71
+ 7. `form-tester exec close` — finalize recording
57
72
 
58
73
  ## Issue logging
59
74
 
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
75
  Categories: `person-selection`, `navigation`, `form-fill`, `submission`, `documents`, `pdf-download`, `html-capture`, `screenshot`, `snapshot`, `validation`, `modal`, `timeout`, `other`
66
76
 
67
77
  ## Interactive commands
@@ -10,96 +10,105 @@ npm install -g form-tester
10
10
  form-tester install
11
11
  ```
12
12
 
13
- ## Running the CLI
13
+ ## Running a test
14
14
 
15
+ When the user gives you a form URL to test, execute ALL steps below in sequence WITHOUT stopping to ask. Do not ask "want me to continue?" — just do the entire flow.
16
+
17
+ ### Step 1 — Start the test
15
18
  ```bash
16
- # AI mode (default) — no prompts:
17
19
  form-tester test <url> --auto
20
+ ```
21
+ Or with options:
22
+ ```bash
18
23
  form-tester test <url> --auto --pnr 12345 --persona ung-mann --scenario "test validation"
19
-
20
- # Human mode — user chooses persona and scenario:
21
- form-tester test <url> --human # lists personas
22
- form-tester test <url> --human --persona ung-mann --scenario "test X" # run with choices
23
-
24
- # Full interactive CLI:
25
- form-tester
26
24
  ```
27
25
 
28
- Persona IDs: `ung-mann`, `gravid-kvinne`, `eldre-kvinne`, `kronisk-syk-mann`. Defaults to "noen" if omitted.
29
-
30
- When the user asks for `--human` mode:
31
- 1. Run `form-tester test <url> --human` to get persona list.
32
- 2. Ask the user to pick a persona and scenario.
33
- 3. Re-run: `form-tester test <url> --human --persona <id> --scenario "<text>"` (use `""` for standard test).
34
- Otherwise default to `--auto`.
35
-
36
- ## Commands
37
-
38
- | Command | Description |
39
- |---------|-------------|
40
- | `/setup` | Initial setup |
41
- | `/update` | Update Playwright CLI + skills |
42
- | `/version` | Show version |
43
- | `/people` | Rescan visible person list |
44
- | `/test {url}` | Test a form URL |
45
- | `/save {label}` | Save snapshot + screenshot |
46
- | `/clear` | Clear session |
47
- | `/quit` | Exit CLI |
48
-
49
- ## Playwright CLI (use via form-tester exec)
26
+ ### Step 2 Dismiss cookies
27
+ ```bash
28
+ form-tester cookies
29
+ ```
50
30
 
51
- IMPORTANT: Always use `form-tester exec` instead of `playwright-cli` directly. This records all commands for replay.
31
+ ### Step 3 Select person
32
+ ```bash
33
+ form-tester select-person
34
+ ```
35
+ To select a specific person: `form-tester select-person "Name"`
52
36
 
37
+ ### Step 4 — Study the form
38
+ Take a snapshot and identify ALL sections and required fields:
53
39
  ```bash
54
- form-tester exec open https://example.com
55
40
  form-tester exec snapshot
56
- form-tester exec fill e1 "value"
57
- form-tester exec click e3
58
- form-tester exec screenshot --filename=page.png --full-page
59
- form-tester exec close # finalizes recording
60
41
  ```
42
+ Look for collapsed/accordion sections (buttons with arrow icons). Expand ALL of them by clicking their header buttons before filling anything.
61
43
 
62
- Replay a previous run:
44
+ ### Step 5 — Fill the form
45
+ Use `form-tester exec` for ALL commands (this records them for replay):
63
46
  ```bash
64
- form-tester replay output/form-id/timestamp/recording.json
47
+ form-tester exec fill <ref> "value"
48
+ form-tester exec click <ref>
49
+ form-tester exec select <ref> "option text"
50
+ form-tester exec screenshot --filename "path.png" --full-page
65
51
  ```
66
52
 
67
- ## Test Flow
53
+ For autosuggest/search fields: fill the text, wait for dropdown, then click the suggestion:
54
+ ```bash
55
+ form-tester exec fill <ref> "search text"
56
+ form-tester exec snapshot # find the suggestion element
57
+ form-tester exec click <suggestion-ref>
58
+ ```
68
59
 
69
- When `/test` is triggered:
60
+ ### Step 6 — Submit
61
+ Take a screenshot, then click the submit button.
70
62
 
71
- 1. Prompt for PNR if not in URL. Wait for response.
72
- 2. Prompt for persona (numbered selection). Wait for response.
73
- 3. Prompt for test scenario in a separate message. Wait for response.
74
- 4. Only after all prompts answered: open browser, fill form, submit, verify.
63
+ ### Step 7 Handle validation errors
64
+ IMPORTANT: If validation errors appear after submit, the form DID NOT SUBMIT. Run:
65
+ ```bash
66
+ form-tester validate
67
+ ```
68
+ This parses all validation errors, clicks each error link to scroll to the field, and shows what needs to be filled. Fix each field, then run `form-tester validate` again to confirm. Only then resubmit.
75
69
 
76
- ## Important Notes
70
+ RULES:
71
+ - Maximum 3 submit attempts. After 3, STOP and write results.
72
+ - Do NOT re-fill fields that are already filled.
73
+ - Do NOT use JavaScript `dispatchEvent` or `element.evaluate()` to set values.
74
+ - Always use Playwright's `fill`, `click`, `select` commands.
77
75
 
78
- - Provide a full /skjemautfyller URL. If `pnr` is missing, the CLI will prompt.
79
- - All screenshots MUST use `--full-page` to capture the entire page.
80
- - Take a full-page screenshot EVERY TIME the page changes: after clicking action buttons (Neste, Forrige, Send inn), after step/page transitions, after validation errors, after modals, and after submission.
81
- - Use `/save {label}` to capture additional snapshots into the output folder.
82
- - If an error modal appears on submit, open DevTools -> Network, retry once, and capture the Correlation ID header.
76
+ ### Step 8 Post-submit verification
77
+ After successful submission, read the modal text:
78
+ - If it mentions Dokumenter storage ("lagret i Dokumenter") run document verification
79
+ - If it does NOT mention Dokumenter skip, note in test_results.txt
83
80
 
84
- ## Post-Submit Verification
81
+ ### Step 9 — Document verification
82
+ ```bash
83
+ form-tester documents
84
+ ```
85
+ This handles everything: navigate to Dokumenter, find latest doc, detect PDF vs HTML, download or screenshot.
85
86
 
86
- After submission, read the modal text:
87
- - If it mentions Dokumenter storage -> navigate to `/dokumenter?pnr={PNR}`, verify the document appears.
88
- - If it does NOT mention Dokumenter -> skip verification, note in test_results.txt.
87
+ ### Step 10 Finalize
88
+ - Write test_results.txt with status, data used, and notes
89
+ - Close browser: `form-tester exec close`
89
90
 
90
- Document capture — detect format via `form-tester exec snapshot`:
91
+ ## Issue logging
91
92
 
92
- **PDF documents** (link with href containing `/pdf/`, or screenshot times out):
93
- Do NOT screenshot PDFs. Do NOT use `require('fs')` in run-code (it doesn't exist there).
94
- Download using Playwright's download event:
95
- ```
96
- form-tester exec run-code "async page => { const link = page.locator('a[href*=\"/pdf/\"]').first(); const [download] = await Promise.all([ page.waitForEvent('download'), link.click() ]); await download.saveAs('$OUTPUT_DIR/document.pdf'); }"
97
- ```
98
- Or if there's a "Last ned" button:
99
- ```
100
- form-tester exec run-code "async page => { const [download] = await Promise.all([ page.waitForEvent('download'), page.getByRole('link', { name: 'Last ned' }).click() ]); await download.saveAs('$OUTPUT_DIR/document.pdf'); }"
93
+ When something unexpected happens, log it:
94
+ ```bash
95
+ form-tester issue <category> "<description>"
101
96
  ```
97
+ Categories: `person-selection`, `navigation`, `form-fill`, `submission`, `documents`, `pdf-download`, `html-capture`, `screenshot`, `snapshot`, `validation`, `modal`, `timeout`, `other`
98
+
99
+ View recent issues: `form-tester issues`
100
+
101
+ ## Important rules
102
+
103
+ - ALWAYS use `form-tester exec` instead of `playwright-cli` directly
104
+ - ALL screenshots MUST use `--full-page`
105
+ - Take a screenshot EVERY TIME the page changes
106
+ - Do NOT stop to ask the user between steps — execute the full flow
107
+ - Persona IDs: `ung-mann`, `gravid-kvinne`, `eldre-kvinne`, `kronisk-syk-mann`, `noen`
102
108
 
103
- **HTML documents**: `form-tester exec screenshot --filename "..." --full-page`. Also save raw HTML.
109
+ ## Human mode
104
110
 
105
- **XML/other**: note the type in test_results.txt and skip capture.
111
+ When user asks for `--human` mode:
112
+ 1. Run `form-tester test <url> --human` to get persona list
113
+ 2. Ask user to pick persona and scenario
114
+ 3. Re-run: `form-tester test <url> --human --persona <id> --scenario "<text>"`
package/form-tester.js CHANGED
@@ -7,7 +7,7 @@ const { spawn, execSync } = require("child_process");
7
7
  const CONFIG_PATH = path.join(process.cwd(), "form-tester.config.json");
8
8
  const OUTPUT_BASE = path.resolve(process.cwd(), "output");
9
9
  const ISSUES_PATH = path.join(OUTPUT_BASE, "issues.jsonl");
10
- const LOCAL_VERSION = "0.9.1";
10
+ const LOCAL_VERSION = "0.10.1";
11
11
  const RECOMMENDED_PERSON = "Uromantisk Direktør";
12
12
 
13
13
  // Recording — persisted to disk so `form-tester exec` can append across processes
@@ -257,6 +257,268 @@ async function handleDocuments(config, flags = {}) {
257
257
  return 0;
258
258
  }
259
259
 
260
+ // --- Standardized commands: cookies, select-person, validate ---
261
+
262
+ async function handleCookies() {
263
+ // Try known cookie banner selectors
264
+ const script = `() => {
265
+ const selectors = [
266
+ '[data-testid="reject-all-cookies"]',
267
+ '[data-testid="accept-all-cookies"]',
268
+ 'button[id*="cookie" i]',
269
+ 'button[class*="cookie" i]',
270
+ '[data-testid*="cookie" i]',
271
+ 'button:has-text("Avvis alle")',
272
+ 'button:has-text("Reject")',
273
+ 'button:has-text("Aksepter")',
274
+ ];
275
+ for (const sel of selectors) {
276
+ const el = document.querySelector(sel);
277
+ if (el && el.offsetParent !== null) {
278
+ el.click();
279
+ return { found: true, selector: sel, text: (el.textContent || "").trim().substring(0, 60) };
280
+ }
281
+ }
282
+ return { found: false };
283
+ }`;
284
+ const result = await runPlaywrightCliCapture(["eval", script]);
285
+ const output = result.stdout.replace(/^### Result\s*/i, "").trim();
286
+ try {
287
+ const parsed = JSON.parse(output);
288
+ if (parsed.found) {
289
+ console.log(`Cookie banner dismissed: "${parsed.text}" (${parsed.selector})`);
290
+ return 0;
291
+ }
292
+ } catch (e) {
293
+ // parse failed, check raw output
294
+ }
295
+ console.log("No cookie banner found.");
296
+ return 0;
297
+ }
298
+
299
+ async function handleSelectPerson(config, targetName) {
300
+ const v = "normal";
301
+ const log = (msg) => console.log(msg);
302
+
303
+ // Step 1: Try to find person list from current page snapshot
304
+ const tmpSnapshot = path.join(
305
+ config.lastRunDir || OUTPUT_BASE,
306
+ `_person_scan_${Date.now()}.yml`,
307
+ );
308
+ await runPlaywrightCli(["snapshot", "--filename", tmpSnapshot]);
309
+
310
+ let options = extractPersonsFromSnapshotFile(tmpSnapshot);
311
+
312
+ // Step 2: If no options from snapshot, try JS-based detection
313
+ if (!options.length) {
314
+ for (let attempt = 0; attempt < 3; attempt++) {
315
+ log(`Scanning for person options (attempt ${attempt + 1})...`);
316
+ await sleep(1500);
317
+ options = await fetchPersonOptions();
318
+ if (options.length) break;
319
+ }
320
+ }
321
+
322
+ if (!options.length) {
323
+ console.error("No person options found on page.");
324
+ logIssue("person-selection", "No person options found by select-person command");
325
+ // Clean up temp file
326
+ try { fs.unlinkSync(tmpSnapshot); } catch (e) {}
327
+ return 1;
328
+ }
329
+
330
+ options = prioritizeRecommended(options, RECOMMENDED_PERSON);
331
+
332
+ // Step 3: Determine which person to select
333
+ let chosen;
334
+ if (targetName) {
335
+ const target = targetName.toLowerCase();
336
+ chosen = options.find((o) => o.toLowerCase().includes(target));
337
+ if (!chosen) {
338
+ log(`Person "${targetName}" not found. Available: ${options.join(", ")}`);
339
+ chosen = options[0];
340
+ log(`Falling back to: ${chosen}`);
341
+ }
342
+ } else {
343
+ chosen = options[0]; // recommended or first
344
+ }
345
+
346
+ // Step 4: Click the person button
347
+ log(`Selecting person: ${chosen}`);
348
+ const clickScript = `() => {
349
+ const buttons = Array.from(document.querySelectorAll('button, [role="button"], [role="option"], a'));
350
+ const normalize = (s) => (s || "").replace(/\\s+/g, " ").trim();
351
+ const target = ${JSON.stringify(chosen)};
352
+ for (const btn of buttons) {
353
+ const text = normalize(btn.textContent);
354
+ if (text.includes(target) || text === target) {
355
+ btn.click();
356
+ return { clicked: true, text: text.substring(0, 80) };
357
+ }
358
+ }
359
+ return { clicked: false, available: buttons.slice(0, 10).map(b => normalize(b.textContent).substring(0, 60)).filter(Boolean) };
360
+ }`;
361
+ const clickResult = await runPlaywrightCliCapture(["eval", clickScript]);
362
+ const clickOutput = clickResult.stdout.replace(/^### Result\s*/i, "").trim();
363
+ try {
364
+ const parsed = JSON.parse(clickOutput);
365
+ if (parsed.clicked) {
366
+ log(`Person selected: ${parsed.text}`);
367
+ config.lastPerson = chosen;
368
+ saveConfig(config);
369
+ await sleep(1000);
370
+ // Take screenshot after selection
371
+ if (config.lastRunDir) {
372
+ await runPlaywrightCli(["screenshot", "--filename", path.join(config.lastRunDir, "person_selected.png"), "--full-page"]);
373
+ }
374
+ } else {
375
+ log("Could not click person button. Available buttons:");
376
+ (parsed.available || []).forEach((b) => log(` - ${b}`));
377
+ logIssue("person-selection", `Could not click "${chosen}". Buttons found but none matched.`);
378
+ }
379
+ } catch (e) {
380
+ log(`Person selection result: ${clickOutput}`);
381
+ }
382
+
383
+ // Clean up temp file
384
+ try { fs.unlinkSync(tmpSnapshot); } catch (e) {}
385
+ return 0;
386
+ }
387
+
388
+ function parseValidationErrors(snapshotText) {
389
+ const lines = snapshotText.split(/\r?\n/);
390
+ const errors = [];
391
+ // Look for the validation status block: status "Sjekk at følgende er riktig utfylt:"
392
+ let inValidation = false;
393
+ let validationIndent = null;
394
+
395
+ for (const line of lines) {
396
+ const indentMatch = line.match(/^(\s*)/);
397
+ const indent = indentMatch ? indentMatch[1].length : 0;
398
+
399
+ if (!inValidation) {
400
+ if (line.includes('status "Sjekk at følgende er riktig utfylt:"') || line.includes('status "Sjekk at f')) {
401
+ inValidation = true;
402
+ validationIndent = indent;
403
+ }
404
+ continue;
405
+ }
406
+
407
+ // Exit validation block when we hit same or lower indent that's not part of the list
408
+ if (indent <= validationIndent && !line.includes("list") && !line.includes("listitem") && !line.includes("link") && line.trim().startsWith("-")) {
409
+ break;
410
+ }
411
+
412
+ // Parse error links: link "error text" [ref=eXXX] ... /url: "#fieldId"
413
+ const linkMatch = line.match(/link "([^"]+)" \[ref=(e\d+)\]/);
414
+ if (linkMatch) {
415
+ const errorText = linkMatch[1];
416
+ const ref = linkMatch[2];
417
+ // Look for the URL on the next line or in the same block
418
+ errors.push({ text: errorText, ref, fieldId: null });
419
+ }
420
+
421
+ // Parse URL for the most recent error
422
+ const urlMatch = line.match(/\/url:\s*"#([^"]+)"/);
423
+ if (urlMatch && errors.length > 0 && !errors[errors.length - 1].fieldId) {
424
+ errors[errors.length - 1].fieldId = urlMatch[1];
425
+ }
426
+ }
427
+
428
+ return errors;
429
+ }
430
+
431
+ async function handleValidate(config) {
432
+ const outputDir = config.lastRunDir;
433
+ if (!outputDir) {
434
+ console.error("No output folder. Run a test first.");
435
+ return 1;
436
+ }
437
+ fs.mkdirSync(outputDir, { recursive: true });
438
+
439
+ // Step 1: Take a fresh snapshot
440
+ const snapshotPath = path.join(outputDir, `validate_${Date.now()}.yml`);
441
+ await runPlaywrightCli(["snapshot", "--filename", snapshotPath]);
442
+
443
+ if (!fs.existsSync(snapshotPath)) {
444
+ console.error("Failed to take snapshot.");
445
+ return 1;
446
+ }
447
+
448
+ const snapshotText = fs.readFileSync(snapshotPath, "utf8");
449
+ const errors = parseValidationErrors(snapshotText);
450
+
451
+ if (!errors.length) {
452
+ console.log("No validation errors found. Form is ready to submit.");
453
+ return 0;
454
+ }
455
+
456
+ console.log(`\n${errors.length} validation error(s) found:\n`);
457
+
458
+ for (let i = 0; i < errors.length; i++) {
459
+ const err = errors[i];
460
+ const fieldId = err.fieldId ? decodeURIComponent(err.fieldId) : "unknown";
461
+ console.log(` ${i + 1}. [${fieldId}] "${err.text}" (ref=${err.ref})`);
462
+ }
463
+
464
+ // Step 2: Click each error link to scroll to it and take a snapshot
465
+ console.log("\nScrolling to each error and taking snapshots...\n");
466
+
467
+ for (let i = 0; i < errors.length; i++) {
468
+ const err = errors[i];
469
+ const fieldId = err.fieldId ? decodeURIComponent(err.fieldId) : null;
470
+
471
+ // Click the error link to scroll to the field
472
+ const clickCode = await runPlaywrightCli(["click", err.ref]);
473
+ if (clickCode !== 0) {
474
+ console.log(` ${i + 1}. Could not click error link ${err.ref}`);
475
+ continue;
476
+ }
477
+
478
+ await sleep(500);
479
+
480
+ // Take a snapshot focused on the field area
481
+ const fieldSnapshotPath = path.join(outputDir, `validation_error_${i + 1}.yml`);
482
+ await runPlaywrightCli(["snapshot", "--filename", fieldSnapshotPath]);
483
+
484
+ // Read the snapshot to find the field context
485
+ if (fs.existsSync(fieldSnapshotPath)) {
486
+ const fieldSnapshot = fs.readFileSync(fieldSnapshotPath, "utf8");
487
+
488
+ // Find the field in the snapshot by its ID
489
+ let fieldContext = "";
490
+ if (fieldId) {
491
+ const fieldLines = fieldSnapshot.split(/\r?\n/);
492
+ for (let j = 0; j < fieldLines.length; j++) {
493
+ // Look for elements with aria-invalid or the field ID
494
+ if (fieldLines[j].includes(`[aria-invalid`) || fieldLines[j].includes(fieldId.replace(/\./g, "%2E"))) {
495
+ // Grab surrounding context (5 lines before, 10 after)
496
+ const start = Math.max(0, j - 5);
497
+ const end = Math.min(fieldLines.length, j + 10);
498
+ fieldContext = fieldLines.slice(start, end).join("\n");
499
+ break;
500
+ }
501
+ }
502
+ }
503
+
504
+ if (fieldContext) {
505
+ console.log(` ${i + 1}. [${fieldId}] "${err.text}"`);
506
+ console.log(` Field context from snapshot:`);
507
+ console.log(fieldContext.split("\n").map((l) => ` ${l}`).join("\n"));
508
+ console.log("");
509
+ } else {
510
+ console.log(` ${i + 1}. [${fieldId}] "${err.text}" — scrolled to field, see ${fieldSnapshotPath}`);
511
+ }
512
+ }
513
+ }
514
+
515
+ // Step 3: Scroll back to top
516
+ await runPlaywrightCliCapture(["eval", "window.scrollTo(0, 0)"]);
517
+
518
+ console.log(`\nFix these ${errors.length} field(s), then run 'form-tester validate' again before submitting.`);
519
+ return errors.length;
520
+ }
521
+
260
522
  const PERSONAS = [
261
523
  {
262
524
  id: "ung-mann",
@@ -650,9 +912,12 @@ function printHelp() {
650
912
  " form-tester test <url> --human Interactive test with prompts",
651
913
  " form-tester exec <command> [args] Run playwright-cli command (recorded)",
652
914
  " 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",
915
+ " form-tester cookies Dismiss cookie banner",
916
+ " form-tester select-person [name] Select person (recommended or by name)",
917
+ " form-tester validate Parse validation errors, scroll to each field",
918
+ " form-tester documents Verify document in Dokumenter (auto PDF/HTML)",
919
+ " form-tester issue <category> <message> Log an issue for skill improvement",
920
+ " form-tester issues [limit] Show recent logged issues",
656
921
  "",
657
922
  "Interactive commands:",
658
923
  " /test {url} Open form URL and start test",
@@ -1543,6 +1808,24 @@ async function main() {
1543
1808
  process.exit(code);
1544
1809
  }
1545
1810
 
1811
+ if (args[0] === "cookies") {
1812
+ const code = await handleCookies();
1813
+ process.exit(code);
1814
+ }
1815
+
1816
+ if (args[0] === "select-person") {
1817
+ const config = loadConfig();
1818
+ const targetName = args.slice(1).join(" ") || null;
1819
+ const code = await handleSelectPerson(config, targetName);
1820
+ process.exit(code);
1821
+ }
1822
+
1823
+ if (args[0] === "validate") {
1824
+ const config = loadConfig();
1825
+ const code = await handleValidate(config);
1826
+ process.exit(code);
1827
+ }
1828
+
1546
1829
  if (args[0] === "test" && args.includes("--human")) {
1547
1830
  const config = loadConfig();
1548
1831
  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.9.1",
3
+ "version": "0.10.1",
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": {