form-tester 0.11.4 → 0.12.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/form-tester.js +136 -133
- package/package.json +1 -1
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.
|
|
10
|
+
const LOCAL_VERSION = "0.12.0";
|
|
11
11
|
const RECOMMENDED_PERSON = "Uromantisk Direktør";
|
|
12
12
|
|
|
13
13
|
// Recording — persisted to disk so `form-tester exec` can append across processes
|
|
@@ -139,17 +139,39 @@ function formatIssue(issue) {
|
|
|
139
139
|
return `[${time}] [${issue.category}]${formId}${url}\n ${issue.message}`;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
// Helper: find a person button ref inside the person picker region of a snapshot
|
|
143
|
+
function findPersonRefInSnapshot(snapshotText, personName) {
|
|
144
|
+
const lines = snapshotText.split(/\r?\n/);
|
|
145
|
+
let inRegion = false;
|
|
146
|
+
let firstRef = null;
|
|
147
|
+
for (const line of lines) {
|
|
148
|
+
if (line.includes('region "Hvem vil du bruke Helsenorge')) inRegion = true;
|
|
149
|
+
if (!inRegion) continue;
|
|
150
|
+
const btnMatch = line.match(/button "([^"]*)" \[ref=(e\d+)\]/);
|
|
151
|
+
if (btnMatch) {
|
|
152
|
+
if (!firstRef) firstRef = btnMatch[2];
|
|
153
|
+
if (btnMatch[1].includes(personName)) return btnMatch[2];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return firstRef; // fallback: first button in region
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Helper: detect what page we're on from a snapshot
|
|
160
|
+
function detectPage(snapshotText) {
|
|
161
|
+
if (!snapshotText) return "unknown";
|
|
162
|
+
if (snapshotText.includes("Hvem vil du bruke Helsenorge")) return "person-picker";
|
|
163
|
+
if (snapshotText.includes("cookie") || snapshotText.includes("informasjonskapsler")) return "cookies";
|
|
164
|
+
if (snapshotText.includes('main "Dokumenter"') || snapshotText.includes("Se detaljer")) return "document-list";
|
|
165
|
+
if (snapshotText.includes("Åpne dokumentet")) return "document-detail";
|
|
166
|
+
if (/href.*\/pdf\//i.test(snapshotText) || /blob:/i.test(snapshotText) || /\.pdf/i.test(snapshotText)) return "document-pdf";
|
|
167
|
+
if (snapshotText.length > 2000) return "document-html"; // substantial content, likely the doc
|
|
168
|
+
return "unknown";
|
|
169
|
+
}
|
|
170
|
+
|
|
142
171
|
async function handleDocuments(config, flags = {}) {
|
|
143
172
|
const v = flags.verbosity || "normal";
|
|
144
173
|
const log = (msg) => { if (v !== "silent") console.log(msg); };
|
|
145
174
|
|
|
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
175
|
const outputDir = config.lastRunDir;
|
|
154
176
|
if (!outputDir) {
|
|
155
177
|
console.error("No output folder. Run a test first.");
|
|
@@ -157,150 +179,131 @@ async function handleDocuments(config, flags = {}) {
|
|
|
157
179
|
}
|
|
158
180
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
159
181
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
182
|
+
const personName = config.lastPerson || config.person || RECOMMENDED_PERSON;
|
|
183
|
+
const maxSteps = 8; // safety limit
|
|
184
|
+
|
|
185
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
186
|
+
// Take snapshot to detect current page state
|
|
187
|
+
const snapPath = path.join(outputDir, `doc_step_${step}.yml`);
|
|
188
|
+
await runPlaywrightCli(["snapshot", "--filename", snapPath]);
|
|
189
|
+
const snapText = fs.existsSync(snapPath) ? fs.readFileSync(snapPath, "utf8") : "";
|
|
190
|
+
const page = detectPage(snapText);
|
|
191
|
+
log(`Step ${step + 1}: detected page = ${page}`);
|
|
192
|
+
|
|
193
|
+
if (page === "cookies") {
|
|
194
|
+
log("Dismissing cookies...");
|
|
195
|
+
await handleCookies();
|
|
196
|
+
await sleep(1000);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (page === "person-picker") {
|
|
201
|
+
log("Selecting person...");
|
|
202
|
+
const ref = findPersonRefInSnapshot(snapText, personName);
|
|
203
|
+
if (ref) {
|
|
204
|
+
await runPlaywrightCli(["click", ref]);
|
|
205
|
+
log(`Clicked person button ref=${ref}`);
|
|
206
|
+
} else {
|
|
207
|
+
log("No person button found in snapshot.");
|
|
208
|
+
logIssue("person-selection", "No person button found in Dokumenter snapshot");
|
|
188
209
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
210
|
+
await sleep(3000);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (page === "document-list") {
|
|
215
|
+
// Save the document list
|
|
216
|
+
fs.copyFileSync(snapPath, path.join(outputDir, "dokumenter.yml"));
|
|
217
|
+
await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "dokumenter.png"), "--full-page"]);
|
|
218
|
+
log("Saved: dokumenter.yml + dokumenter.png");
|
|
219
|
+
|
|
220
|
+
// Click "Se detaljer" on first document — find it in the snapshot
|
|
221
|
+
const lines = snapText.split(/\r?\n/);
|
|
222
|
+
let detailRef = null;
|
|
223
|
+
for (const line of lines) {
|
|
224
|
+
const match = line.match(/button "Se detaljer" \[ref=(e\d+)\]/);
|
|
225
|
+
if (match) { detailRef = match[1]; break; }
|
|
203
226
|
}
|
|
204
|
-
if (
|
|
205
|
-
await runPlaywrightCli(["click",
|
|
206
|
-
log(
|
|
227
|
+
if (detailRef) {
|
|
228
|
+
await runPlaywrightCli(["click", detailRef]);
|
|
229
|
+
log(`Clicked 'Se detaljer' (ref=${detailRef})`);
|
|
207
230
|
} else {
|
|
208
|
-
log("Could not find
|
|
209
|
-
|
|
231
|
+
log("Could not find 'Se detaljer' in snapshot.");
|
|
232
|
+
logIssue("documents", "No 'Se detaljer' button found in document list");
|
|
210
233
|
}
|
|
211
234
|
await sleep(2000);
|
|
235
|
+
continue;
|
|
212
236
|
}
|
|
213
|
-
try { fs.unlinkSync(checkSnapshot); } catch (e) {}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Step 3: Take snapshot of document list
|
|
217
|
-
const docListSnapshot = path.join(outputDir, "dokumenter.yml");
|
|
218
|
-
await runPlaywrightCli(["snapshot", "--filename", docListSnapshot]);
|
|
219
|
-
await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "dokumenter.png"), "--full-page"]);
|
|
220
|
-
log("Saved: dokumenter.yml + dokumenter.png");
|
|
221
|
-
|
|
222
|
-
// Step 4: Click first document's "Se detaljer"
|
|
223
|
-
log("Looking for first document...");
|
|
224
|
-
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"; }']);
|
|
225
|
-
if (clickResult.stdout.includes("not-found")) {
|
|
226
|
-
logIssue("documents", "Could not find 'Se detaljer' link on Dokumenter page", { url: dokumenterUrl });
|
|
227
|
-
log("Could not find 'Se detaljer'. Check dokumenter.yml for the page structure.");
|
|
228
|
-
// Still continue — agent can handle manually
|
|
229
|
-
} else {
|
|
230
|
-
log("Clicked 'Se detaljer' on first document.");
|
|
231
|
-
await sleep(2000);
|
|
232
|
-
}
|
|
233
237
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (/href.*\/pdf\//i.test(snapshotText) || /blob:/i.test(snapshotText) || /\.pdf/i.test(snapshotText)) {
|
|
252
|
-
format = "pdf";
|
|
253
|
-
} else if (snapshotText.length > 500) {
|
|
254
|
-
// Has substantial content — likely HTML
|
|
255
|
-
format = "html";
|
|
238
|
+
if (page === "document-detail") {
|
|
239
|
+
// Click "Åpne dokumentet"
|
|
240
|
+
const lines = snapText.split(/\r?\n/);
|
|
241
|
+
let openRef = null;
|
|
242
|
+
for (const line of lines) {
|
|
243
|
+
const match = line.match(/(?:button|link) "([^"]*Åpne dokumentet[^"]*)" \[ref=(e\d+)\]/);
|
|
244
|
+
if (match) { openRef = match[2]; break; }
|
|
245
|
+
}
|
|
246
|
+
if (openRef) {
|
|
247
|
+
await runPlaywrightCli(["click", openRef]);
|
|
248
|
+
log(`Clicked 'Åpne dokumentet' (ref=${openRef})`);
|
|
249
|
+
} else {
|
|
250
|
+
log("Could not find 'Åpne dokumentet' in snapshot.");
|
|
251
|
+
logIssue("documents", "No 'Åpne dokumentet' button found in document detail");
|
|
252
|
+
}
|
|
253
|
+
await sleep(3000);
|
|
254
|
+
continue;
|
|
256
255
|
}
|
|
257
|
-
}
|
|
258
|
-
log(`Detected document format: ${format}`);
|
|
259
256
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
logIssue("pdf-download", "PDF download failed", { url: dokumenterUrl, outputDir });
|
|
267
|
-
log("PDF download failed. Check document.yml for available links.");
|
|
268
|
-
} else {
|
|
269
|
-
const pdfPath = path.join(outputDir, "document.pdf");
|
|
270
|
-
if (fs.existsSync(pdfPath)) {
|
|
271
|
-
log(`PDF saved: ${pdfPath}`);
|
|
257
|
+
if (page === "document-pdf") {
|
|
258
|
+
log("PDF document detected. Downloading...");
|
|
259
|
+
fs.copyFileSync(snapPath, path.join(outputDir, "document.yml"));
|
|
260
|
+
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'); }`]);
|
|
261
|
+
if (dlCode === 0 && fs.existsSync(path.join(outputDir, "document.pdf"))) {
|
|
262
|
+
log(`PDF saved: ${path.join(outputDir, "document.pdf")}`);
|
|
272
263
|
} else {
|
|
273
|
-
logIssue("pdf-download", "PDF download
|
|
274
|
-
log("PDF download
|
|
264
|
+
logIssue("pdf-download", "PDF download failed", { outputDir });
|
|
265
|
+
log("PDF download failed.");
|
|
275
266
|
}
|
|
267
|
+
log(`\nDocument verification complete. Format: pdf`);
|
|
268
|
+
log(`Output: ${outputDir}`);
|
|
269
|
+
return 0;
|
|
276
270
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
logIssue("html-capture", "Full-page screenshot failed (possible PDF misdetected as HTML)", { outputDir });
|
|
283
|
-
log("Screenshot failed — document might be PDF. Trying download...");
|
|
284
|
-
format = "pdf";
|
|
285
|
-
} else {
|
|
271
|
+
|
|
272
|
+
if (page === "document-html") {
|
|
273
|
+
log("HTML document detected. Capturing...");
|
|
274
|
+
fs.copyFileSync(snapPath, path.join(outputDir, "document.yml"));
|
|
275
|
+
await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "document_screenshot.png"), "--full-page"]);
|
|
286
276
|
log("Saved: document_screenshot.png (full-page)");
|
|
277
|
+
const htmlResult = await runPlaywrightCliCapture(["eval", "document.documentElement.outerHTML"]);
|
|
278
|
+
if (htmlResult.code === 0 && htmlResult.stdout) {
|
|
279
|
+
fs.writeFileSync(path.join(outputDir, "document.html"), htmlResult.stdout.replace(/^### Result\s*/i, ""));
|
|
280
|
+
log("Saved: document.html");
|
|
281
|
+
}
|
|
282
|
+
log(`\nDocument verification complete. Format: html`);
|
|
283
|
+
log(`Output: ${outputDir}`);
|
|
284
|
+
return 0;
|
|
287
285
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
286
|
+
|
|
287
|
+
// Unknown page — if this is step 0, try navigating to Dokumenter
|
|
288
|
+
if (step === 0) {
|
|
289
|
+
const dokumenterUrl = resolveDokumenterUrl(config);
|
|
290
|
+
if (dokumenterUrl) {
|
|
291
|
+
log(`Unknown page state. Navigating to Dokumenter: ${dokumenterUrl}`);
|
|
292
|
+
await runPlaywrightCli(["goto", dokumenterUrl]);
|
|
293
|
+
await sleep(2000);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
294
296
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
log(
|
|
297
|
+
|
|
298
|
+
// Still unknown after navigation — give up
|
|
299
|
+
log(`Could not determine page state. Taking screenshot for manual review.`);
|
|
298
300
|
await runPlaywrightCli(["screenshot", "--filename", path.join(outputDir, "document_screenshot.png"), "--full-page"]);
|
|
301
|
+
logIssue("documents", `Stuck on unknown page at step ${step + 1}`, { outputDir });
|
|
302
|
+
break;
|
|
299
303
|
}
|
|
300
304
|
|
|
301
|
-
log(`\nDocument verification complete. Format: ${format}`);
|
|
302
305
|
log(`Output: ${outputDir}`);
|
|
303
|
-
return
|
|
306
|
+
return 1;
|
|
304
307
|
}
|
|
305
308
|
|
|
306
309
|
// --- Standardized commands: cookies, select-person, validate ---
|
package/package.json
CHANGED