fix: web-probe 输出 DOM 诊断分组 (#584)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -1075,7 +1075,7 @@ const report = {
|
||||
await writeFile(reportJsonPath, JSON.stringify(report, null, 2) + "\n", { mode: 0o600 });
|
||||
await writeFile(reportMdPath, renderMarkdown(report), { mode: 0o600 });
|
||||
const [jsonMeta, mdMeta] = await Promise.all([fileMeta(reportJsonPath), fileMeta(reportMdPath)]);
|
||||
console.log(JSON.stringify({ ok: true, command: "web-probe-observe analyze", stateDir, reportJsonPath, reportJsonSha256: jsonMeta.sha256, reportMdPath, reportMdSha256: mdMeta.sha256, counts: report.counts, sampleMetrics: sampleMetrics.summary, pageProvenance: pageProvenance.summary, pagePerformance: pagePerformance.summary, promptNetwork: promptNetwork.summary, runtimeAlerts: runtimeAlerts.summary, turnTimingRecentUpdateSawtoothJumps: sampleMetrics.turnTimingRecentUpdateSawtoothJumps.slice(0, 20), turnTimingRecentUpdateLargestSteps: sampleMetrics.turnTimingRecentUpdateLargestSteps.slice(0, 20), pagePerformanceSlowApi: pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0).slice(0, 20), pagePerformanceLongLivedStreams: pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream).slice(0, 20), findings: findings.slice(0, 20), valuesRedacted: true }, null, 2));
|
||||
console.log(JSON.stringify({ ok: true, command: "web-probe-observe analyze", stateDir, reportJsonPath, reportJsonSha256: jsonMeta.sha256, reportMdPath, reportMdSha256: mdMeta.sha256, counts: report.counts, sampleMetrics: sampleMetrics.summary, pageProvenance: pageProvenance.summary, pagePerformance: pagePerformance.summary, promptNetwork: promptNetwork.summary, runtimeAlerts: runtimeAlerts.summary, domDiagnosticGroups: runtimeAlerts.domDiagnosticsByText.slice(0, 20), turnTimingRecentUpdateSawtoothJumps: sampleMetrics.turnTimingRecentUpdateSawtoothJumps.slice(0, 20), turnTimingRecentUpdateLargestSteps: sampleMetrics.turnTimingRecentUpdateLargestSteps.slice(0, 20), pagePerformanceSlowApi: pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0).slice(0, 20), pagePerformanceLongLivedStreams: pagePerformance.sameOriginApiByPath.filter((item) => item.isLongLivedStream).slice(0, 20), findings: findings.slice(0, 20), valuesRedacted: true }, null, 2));
|
||||
|
||||
async function readJson(file) {
|
||||
try { return JSON.parse(await readFile(file, "utf8")); } catch { return null; }
|
||||
@@ -1129,7 +1129,7 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN
|
||||
if (recentUpdateSawtoothJumps.length > 0) findings.push({ id: "turn-timing-recent-update-sawtooth-jump", severity: "amber", summary: "最近更新 value jumped faster than sample interval; expected sawtooth increase-or-reset", count: recentUpdateSawtoothJumps.length, samples: recentUpdateSawtoothJumps.slice(0, 20) });
|
||||
if ((runtimeAlerts?.summary?.httpErrorCount ?? 0) > 0) findings.push({ id: "runtime-http-errors", severity: "amber", summary: "natural page requests returned HTTP error status during observation", count: runtimeAlerts.summary.httpErrorCount, groups: runtimeAlerts.networkHttpErrorsByPath.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.requestFailedCount ?? 0) > 0) findings.push({ id: "runtime-requestfailed", severity: "amber", summary: "browser requestfailed events were captured during observation", count: runtimeAlerts.summary.requestFailedCount, groups: runtimeAlerts.networkRequestFailedByPath.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.domDiagnosticSampleCount ?? 0) > 0) findings.push({ id: "runtime-dom-diagnostics", severity: "amber", summary: "diagnostic/error/warning-like text was visible in sampled DOM", count: runtimeAlerts.summary.domDiagnosticSampleCount, samples: runtimeAlerts.domDiagnostics.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.domDiagnosticSampleCount ?? 0) > 0) findings.push({ id: "runtime-dom-diagnostics", severity: "amber", summary: "diagnostic/error/warning-like text was visible in sampled DOM", count: runtimeAlerts.summary.domDiagnosticSampleCount, groupCount: runtimeAlerts.summary.domDiagnosticGroupCount ?? 0, groups: runtimeAlerts.domDiagnosticsByText.slice(0, 12), samples: runtimeAlerts.domDiagnostics.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.executionErrorCount ?? 0) > 0) findings.push({ id: "runtime-execution-errors", severity: "red", summary: "Workbench rendered execution failure/error rows during observation", count: runtimeAlerts.summary.executionErrorCount, groups: runtimeAlerts.runtimeExecutionErrorsByCode.slice(0, 12) });
|
||||
if ((runtimeAlerts?.summary?.consoleAlertCount ?? 0) > 0) findings.push({ id: "runtime-console-alerts", severity: "amber", summary: "browser console warning/error entries were captured during observation", count: runtimeAlerts.summary.consoleAlertCount, groups: runtimeAlerts.consoleAlertsByPath.slice(0, 12) });
|
||||
const slowApi = Array.isArray(pagePerformance?.sameOriginApiByPath) ? pagePerformance.sameOriginApiByPath.filter((item) => item.overFiveSecondCount > 0) : [];
|
||||
@@ -1604,6 +1604,7 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
httpErrorCount: httpErrors.length,
|
||||
requestFailedCount: requestFailed.length,
|
||||
domDiagnosticSampleCount: domDiagnostics.length,
|
||||
domDiagnosticGroupCount: groupDomDiagnostics(domDiagnostics).length,
|
||||
executionErrorCount: executionErrors.length,
|
||||
baselineExecutionErrorCount: baselineExecutionErrors.length,
|
||||
consoleAlertCount: consoleAlerts.length,
|
||||
@@ -1617,6 +1618,7 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
networkHttpErrorsByPath: groupNetworkAlerts(httpErrors),
|
||||
networkRequestFailedByPath: groupNetworkAlerts(requestFailed),
|
||||
domDiagnostics: domDiagnostics.slice(0, 80),
|
||||
domDiagnosticsByText: groupDomDiagnostics(domDiagnostics),
|
||||
runtimeExecutionErrors: executionErrors.slice(0, 120),
|
||||
runtimeExecutionErrorsByCode: groupExecutionErrors(executionErrors),
|
||||
baselineRuntimeExecutionErrors: baselineExecutionErrors.slice(0, 80),
|
||||
@@ -1627,6 +1629,86 @@ function buildRuntimeAlerts(samples, control, network, consoleEvents, errors) {
|
||||
};
|
||||
}
|
||||
|
||||
function groupDomDiagnostics(events) {
|
||||
const groups = new Map();
|
||||
for (const item of events || []) {
|
||||
const preview = String(item?.preview || "").trim();
|
||||
const normalizedPreview = normalizeDiagnosticPreview(preview);
|
||||
const key = [
|
||||
item?.source || "",
|
||||
item?.diagnosticCode || "",
|
||||
normalizedPreview
|
||||
].join("|");
|
||||
const existing = groups.get(key) || {
|
||||
source: item?.source || null,
|
||||
diagnosticCode: item?.diagnosticCode || null,
|
||||
textHash: item?.textHash || null,
|
||||
normalizedPreview,
|
||||
preview,
|
||||
count: 0,
|
||||
firstAt: item?.ts || null,
|
||||
lastAt: item?.ts || null,
|
||||
promptIndexes: new Set(),
|
||||
traceIds: new Set(),
|
||||
sampleSeqs: []
|
||||
};
|
||||
existing.count += 1;
|
||||
existing.firstAt = minIso(existing.firstAt, item?.ts || null);
|
||||
existing.lastAt = maxIso(existing.lastAt, item?.ts || null);
|
||||
if (Number.isFinite(Number(item?.promptIndex))) existing.promptIndexes.add(Number(item.promptIndex));
|
||||
for (const traceId of extractDiagnosticTraceIds(item, preview)) existing.traceIds.add(traceId);
|
||||
if (existing.sampleSeqs.length < 12 && item?.seq !== undefined && item?.seq !== null) existing.sampleSeqs.push(item.seq);
|
||||
groups.set(key, existing);
|
||||
}
|
||||
return Array.from(groups.values())
|
||||
.map((item) => ({
|
||||
source: item.source,
|
||||
diagnosticCode: item.diagnosticCode,
|
||||
textHash: item.textHash,
|
||||
normalizedPreview: item.normalizedPreview,
|
||||
preview: item.preview,
|
||||
count: item.count,
|
||||
firstAt: item.firstAt,
|
||||
lastAt: item.lastAt,
|
||||
promptIndexes: Array.from(item.promptIndexes).sort((a, b) => a - b),
|
||||
traceIds: Array.from(item.traceIds).sort(),
|
||||
sampleSeqs: item.sampleSeqs
|
||||
}))
|
||||
.sort((a, b) => (b.count - a.count) || String(a.firstAt || "").localeCompare(String(b.firstAt || "")));
|
||||
}
|
||||
|
||||
function normalizeDiagnosticPreview(text) {
|
||||
return String(text || "")
|
||||
.replace(/trace_id=[A-Za-z0-9_-]+/gu, "trace_id=:traceId")
|
||||
.replace(/\btrc_[A-Za-z0-9_-]+\b/gu, "trc_:traceId")
|
||||
.replace(/\bses_[A-Za-z0-9_-]+\b/gu, "ses_:sessionId")
|
||||
.replace(/\brun_[A-Za-z0-9_-]+\b/gu, "run_:runId")
|
||||
.replace(/\bcmd_[A-Za-z0-9_-]+\b/gu, "cmd_:commandId")
|
||||
.replace(/\s+/gu, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function extractDiagnosticTraceIds(item, preview) {
|
||||
const ids = new Set();
|
||||
if (item?.traceId) ids.add(String(item.traceId));
|
||||
const text = String(preview || "");
|
||||
for (const match of text.matchAll(/\btrc_[A-Za-z0-9_-]+\b/gu)) ids.add(match[0]);
|
||||
for (const match of text.matchAll(/trace_id=([A-Za-z0-9_-]+)/gu)) ids.add(match[1]);
|
||||
return ids;
|
||||
}
|
||||
|
||||
function minIso(a, b) {
|
||||
if (!a) return b || null;
|
||||
if (!b) return a || null;
|
||||
return Date.parse(a) <= Date.parse(b) ? a : b;
|
||||
}
|
||||
|
||||
function maxIso(a, b) {
|
||||
if (!a) return b || null;
|
||||
if (!b) return a || null;
|
||||
return Date.parse(a) >= Date.parse(b) ? a : b;
|
||||
}
|
||||
|
||||
function sampleExecutionErrorCandidates(sample) {
|
||||
const candidates = [];
|
||||
const add = (source, items) => {
|
||||
|
||||
Reference in New Issue
Block a user