Add script to summarize log by keyword

Change-Id: Ida060dd5654a56d9271fb55b85ceaab463159128
diff --git a/summarize-log.js b/summarize-log.js
new file mode 100644
index 0000000..0218309
--- /dev/null
+++ b/summarize-log.js
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+// Summarizes KorAP.e2e.log into one line per test run matching a keyword.
+//
+// Usage: node summarize-log.js <keyword> [logfile]
+//   keyword  substring to match against the run's target URL (e.g. "dnb")
+//   logfile  path to the log file (default: ./KorAP.e2e.log)
+
+const fs = require('fs');
+const path = require('path');
+
+const keyword = process.argv[2];
+const logFile = process.argv[3] || path.join(__dirname, 'KorAP.e2e.log');
+
+if (!keyword) {
+    console.error('Usage: node summarize-log.js <keyword> [logfile]');
+    process.exit(1);
+}
+
+const lines = fs.readFileSync(logFile, 'utf8').split('\n');
+
+const RUN_START_RE = /^\s*Running KorAP UI end-to-end tests on (\S+)/;
+const TIMESTAMP_RE = /^Run started (\S+) on/;
+const CHECK_RE = /^\s*(✔|✘|-)\s+(.+?)(?:\s*\(\d+m?s\))?$/;
+const SUMMARY_RE = /^\s*(\d+) (passing|pending|failing)(?:\s*\(\S+\))?/;
+
+const runs = [];
+let current = null;
+
+for (const line of lines) {
+    const startMatch = line.match(RUN_START_RE);
+    if (startMatch) {
+        current = { url: startMatch[1], timestamp: null, checks: [], summary: {} };
+        runs.push(current);
+        continue;
+    }
+    if (!current) continue;
+
+    const tsMatch = line.match(TIMESTAMP_RE);
+    if (tsMatch) {
+        current.timestamp = tsMatch[1];
+        continue;
+    }
+
+    const checkMatch = line.match(CHECK_RE);
+    if (checkMatch) {
+        const [, mark, name] = checkMatch;
+        const status = mark === '✔' ? 'pass' : mark === '✘' ? 'fail' : 'skip';
+        current.checks.push({ status, name: name.trim() });
+        continue;
+    }
+
+    const summaryMatch = line.match(SUMMARY_RE);
+    if (summaryMatch) {
+        current.summary[summaryMatch[2]] = parseInt(summaryMatch[1], 10);
+    }
+}
+
+const matching = runs.filter(r => r.url.toLowerCase().includes(keyword.toLowerCase()));
+
+if (matching.length === 0) {
+    console.error(`No runs found matching "${keyword}" in ${logFile}`);
+    process.exit(1);
+}
+
+for (const run of matching) {
+    const date = run.timestamp ? run.timestamp.replace('T', ' ').replace(/\.\d+Z$/, '') : 'unknown time';
+    const [datePart, timePart] = date.split(' ');
+
+    const failed = run.checks.filter(c => c.status === 'fail');
+    const passed = run.summary.passing ?? run.checks.filter(c => c.status === 'pass').length;
+    const skipped = run.summary.pending ?? run.checks.filter(c => c.status === 'skip').length;
+
+    const mark = failed.length > 0 ? '✗' : '✓';
+    const info = failed.length > 0
+        ? `${passed} passing, ${failed.length} failing: ${failed.map(c => c.name).join('; ')}`
+        : `${passed} passing, ${skipped} skipped`;
+
+    console.log(`${datePart}  ${timePart}  ${info}  ${mark}`);
+}