批2(QA harness·B覆盖总览面板+本体枚举器): 义务层=tools/extract_capability_list.py(README能力清单四列表按旅程段解析,7段103行,用例指针剥反引号); 执行层=tools/coverage_receipts.py六源回执明细化(冒烟/单元集成/系统/探针/渲染/静态各一.js,名×结果×计数粒度,日志缺失=not_run亮灰不冒充绿)+run_all_gates.sh末尾挂点(观测件非判定件,解析失败打红字不翻闸判)+probe_manifest.js(面板file://无法列目录,按现存登记文件落清单); 机器义务双源之本体源=tools/coverage_enum.py(两扫描器+比对:.tres行集/schema词表字段/tscn按钮/gui_input自绘类/屏模态清单×extract_checks断言文料,865实体/缺口212); 呈现层=tools/coverage_report.html(四节:六层闸况/能力行×六层灯按旅程段/控件下钻交互源+渲染golden展示源/本体对账缺口×探针命中降级探针承载;状态灯四态+数据时间戳陈旧可见+完备性边界页眉;JSONP直载file://即开); 豁免=tools/coverage_exemptions.js人工维护母版; 登记件扩seen面(场景树全量BaseButton+db布局表hotspot_key公共面枚举,seen-hit=存在但零真输入覆盖缺口数据源)。读数: 两探针窄跑绿(screen_flow 19命中/116存在,ui_click 27命中/91存在——停手钮/树购买重建代等真缺口在册,弃图刷新确认链走视图方法属衔接条目余项); 六源明细化对真实闸日志抽核(unit82套件/系统69检查/探针10段/静态13条/golden逐屏L1-L3); 面板浏览器真实入口四节渲染验过; 全量档闸【验收通过】无新增红(exit=0,回执commit 07d4977f,六源明细化在闸内实跑=挂点真路径验证)
This commit is contained in:
1 parent
07d4977f41
commit
bb9ce8a117
7 files changed
+745
No files matched your search
@@ -0,0 +1,226 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>QA 覆盖总览面板——Dungeon Sweeper</title>
|
||||
<style>
|
||||
:root { --ok:#3a7d44; --bad:#a33; --off:#9aa; --na:#d5d5d0; --ex:#b8860b; --ink:#2c2a26; --paper:#f4f1ea; }
|
||||
body { font-family: "Microsoft YaHei", sans-serif; margin: 0; background: var(--paper); color: var(--ink); }
|
||||
header { padding: 14px 20px 10px; border-bottom: 3px solid var(--ink); background: #fff; }
|
||||
h1 { font-size: 19px; margin: 0 0 4px; }
|
||||
h2 { font-size: 16px; margin: 22px 0 6px; border-left: 5px solid var(--ink); padding-left: 8px; }
|
||||
.note { font-size: 12px; color: #555; margin: 3px 0; }
|
||||
.warn { color: var(--bad); font-weight: bold; }
|
||||
main { padding: 12px 20px 40px; }
|
||||
table { border-collapse: collapse; width: 100%; background: #fff; font-size: 12.5px; }
|
||||
th, td { border: 1px solid #cfc9bd; padding: 4px 7px; text-align: left; vertical-align: top; }
|
||||
th { background: #ebe6da; }
|
||||
.lamp { display: inline-block; width: 13px; height: 13px; border-radius: 50%; margin-right: 3px; vertical-align: -2px; }
|
||||
.ok { background: var(--ok); } .bad { background: var(--bad); }
|
||||
.off { background: var(--off); } .na { background: var(--na); } .ex { background: #fff; border: 3px double var(--ex); }
|
||||
.chip { display: inline-block; padding: 1px 8px; border-radius: 9px; color: #fff; font-size: 12px; }
|
||||
details { margin: 4px 0 10px; } summary { cursor: pointer; font-size: 13px; }
|
||||
.gaplist { font-size: 12px; color: #444; line-height: 1.7; }
|
||||
.seg { margin-bottom: 18px; }
|
||||
code { background: #eceade; padding: 0 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>QA 覆盖总览面板 <span style="font-weight:normal;font-size:13px">(义务 × 执行合并视图——只读)</span></h1>
|
||||
<div class="note">状态灯:绿=该层最近跑批过 红=跑批挂 灰=该层无承载/未跑 双圈=豁免(<code>tools/coverage_exemptions.js</code> 人工维护)。</div>
|
||||
<div class="note" id="staleness"></div>
|
||||
<div class="note"><b>完备性边界</b>:控件级视图只对「被探针/C 打开过的屏」负责;未打开过的屏由动线断言+C 探索兜底。
|
||||
本体对账为文本 grep 关联——有假阳假阴(断言测了实体但文本没带 id=假缺口,人工核销;文本带 id 但断言弱=假覆盖),缺口须人工复核后再立事项。</div>
|
||||
<div class="note">数据源:随最近一次闸批落盘于 <code>game/tests/.gates/coverage/</code>;刷新本页即读最新。</div>
|
||||
</header>
|
||||
<main>
|
||||
<h2>一、六层闸况</h2>
|
||||
<div id="gateSummary"></div>
|
||||
|
||||
<h2>二、能力清单 × 六层状态灯(按旅程段)</h2>
|
||||
<div class="note">灯的判据:用例文件落哪层就钉哪层的回执(单元=逐套件 PASS/FAIL;探针/系统/冒烟/渲染/静态=层判据);该层无对应承载=灰。</div>
|
||||
<div id="capabilityMatrix"></div>
|
||||
|
||||
<h2>三、控件级交互覆盖下钻(探针真输入登记)</h2>
|
||||
<div class="note">交互源=探针真输入命中登记(push_input 管线命中);「存在但零命中」=控件缺口——hitbox 错位/遮挡/驱动空洞在此暴露。展示源=渲染闸 golden 逐屏登记。</div>
|
||||
<div id="controlDrill"></div>
|
||||
|
||||
<h2>四、本体对账(游戏内容 ↔ 测试验证)</h2>
|
||||
<div class="note">缺口=本体存在、断言文料零关联;若该 id 出现在探针命中登记则降级为「探针承载」。首见批次=本工具生成时间,逐批对比看新面孔。</div>
|
||||
<div id="ontologyGaps"></div>
|
||||
</main>
|
||||
<script src="./coverage_exemptions.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/capability_list.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/gate_smoke.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/gate_unit.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/gate_system.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/gate_probe.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/gate_render.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/gate_static.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/probe_manifest.js"></script>
|
||||
<script src="../game/tests/.gates/coverage/ontology_gaps.js"></script>
|
||||
<script>
|
||||
/* 探针登记文件由 manifest 同步补挂(document.write 在解析期阻塞加载,渲染段前数据已就位) */
|
||||
(function () {
|
||||
const probes = ((window.__QA_COVERAGE__ || {}).probe_manifest || {}).probes || [];
|
||||
for (const p of probes) {
|
||||
document.write('<script src="../game/tests/.gates/coverage/' + encodeURIComponent(p) + '"><\/script>');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
"use strict";
|
||||
const C = window.__QA_COVERAGE__ || {};
|
||||
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":">",'"':"""}[c])); }
|
||||
function lamp(cls, title) { return `<span class="lamp ${cls}" title="${esc(title)}"></span>`; }
|
||||
function chip(txt, cls) { return `<span class="chip" style="background:${cls}">${esc(txt)}</span>`; }
|
||||
const VCOLOR = {"绿":"var(--ok)","红":"var(--bad)","灰":"var(--off)","not_run":"var(--off)"};
|
||||
|
||||
// ── 数据新鲜度(陈旧可见——防静默旧数据) ──
|
||||
(function () {
|
||||
const stamps = [];
|
||||
const cl = (C.capability_list || {}).meta; if (cl) stamps.push(`义务层 ${cl.generated_at}`);
|
||||
const rc = C.gate_receipts || {};
|
||||
for (const k in rc) { const m = (rc[k].receipt || {}).meta; if (m) stamps.push(`${m.label} ${m.generated_at}`); }
|
||||
const on = (C.ontology || {}).meta; if (on) stamps.push(`本体 ${on.generated_at}`);
|
||||
const pm = (C.probe_manifest || {}).meta; if (pm) stamps.push(`探针登记 ${pm.generated_at}`);
|
||||
document.getElementById("staleness").innerHTML = "数据时间:" + stamps.map(esc).join(" · ");
|
||||
})();
|
||||
|
||||
// ── 一、六层闸况 ──
|
||||
(function () {
|
||||
const rc = C.gate_receipts || {};
|
||||
const ORDER = ["smoke", "unit", "system", "probe", "render", "static"];
|
||||
const LABEL = {smoke:"冒烟测试", unit:"单元与集成测试", system:"系统测试", probe:"场景探针", render:"渲染闸", static:"静态扫描"};
|
||||
let html = "<table><tr><th>层</th><th>判定</th><th>明细</th></tr>";
|
||||
for (const k of ORDER) {
|
||||
const r = (rc[k] || {}).receipt;
|
||||
if (!r) { html += `<tr><td>${LABEL[k]}</td><td>${chip("缺数据", "var(--off)")}</td><td>—</td></tr>`; continue; }
|
||||
const v = r.result === "not_run" ? "未跑" : r.result;
|
||||
const color = VCOLOR[r.result] || "var(--off)";
|
||||
let detail = `共 ${r.items ? r.items.length : 0} 项`;
|
||||
if (r.checks != null) detail = `${r.checks} 项检查 / ${r.fails} 失败`;
|
||||
if (r.suites_total != null) detail = `${r.suites_total} 套件`;
|
||||
if (r.probes_total != null) detail = `${r.probes_total} 探针`;
|
||||
html += `<tr><td>${LABEL[k]}</td><td>${chip(v, color)}</td><td>${esc(detail)} <span class="note">源=${esc(r.meta.source_log)} @ ${esc(r.meta.generated_at)}</span></td></tr>`;
|
||||
}
|
||||
html += "</table><details><summary>单元与集成逐套件明细</summary><div class='gaplist'>";
|
||||
const unit = ((rc.unit || {}).receipt || {}).items || [];
|
||||
for (const it of unit) {
|
||||
html += `${lamp(it.result === "PASS" ? "ok" : "bad", it.result)} ${esc(it.name.replace("res://tests/", ""))}${it.exit ? "(exit=" + esc(it.exit) + ")" : ""}<br>`;
|
||||
}
|
||||
html += "</div></details><details><summary>场景探针逐段明细</summary><div class='gaplist'>";
|
||||
const pr = ((rc.probe || {}).receipt || {}).items || [];
|
||||
for (const it of pr) {
|
||||
html += `${lamp(it.result === "PASS" ? "ok" : (it.result === "FAIL" ? "bad" : "off"), it.result)} ${esc(it.name)}${it.segment ? "(" + esc(it.segment) + ")" : ""}<br>`;
|
||||
}
|
||||
html += "</div></details><details><summary>静态扫描逐条明细</summary><div class='gaplist'>";
|
||||
const st = ((rc.static || {}).receipt || {}).items || [];
|
||||
for (const it of st) {
|
||||
html += `${lamp(it.result === "ok" ? "ok" : "bad", it.result)} ${esc(it.name)}<br>`;
|
||||
}
|
||||
html += "</div></details>";
|
||||
document.getElementById("gateSummary").innerHTML = html;
|
||||
})();
|
||||
|
||||
// ── 二、能力清单 × 六层灯 ──
|
||||
(function () {
|
||||
const cl = C.capability_list;
|
||||
if (!cl || !cl.segments) { document.getElementById("capabilityMatrix").innerHTML = "<p class='warn'>义务层缺数据——先跑 tools/extract_capability_list.py</p>"; return; }
|
||||
const rc = C.gate_receipts || {};
|
||||
const ex = ((C.exemptions || {}).abilities || []).map(s => String(s));
|
||||
function layerOf(caseFile) {
|
||||
if (/system_test/.test(caseFile)) return "system";
|
||||
if (/probe\//.test(caseFile) || /_probe\.gd/.test(caseFile)) return "probe";
|
||||
if (/golden|render/.test(caseFile)) return "render";
|
||||
return "unit";
|
||||
}
|
||||
function rowLight(row) {
|
||||
if (ex.includes(row.ability)) return lamp("ex", "豁免(coverage_exemptions.js 在册)");
|
||||
if (!row.cases || !row.cases.length) return lamp("off", "无代表用例指针");
|
||||
let worst = null; const seenLayers = new Set();
|
||||
for (const c of row.cases) {
|
||||
const layer = layerOf(c);
|
||||
seenLayers.add(layer);
|
||||
const r = (rc[layer] || {}).receipt;
|
||||
if (!r || r.result === "not_run") { worst = worst || "na"; continue; }
|
||||
if (r.result === "红") return lamp("bad", `${layer} 层跑批挂`);
|
||||
if (layer === "unit") {
|
||||
const items = r.items || [];
|
||||
const hit = items.filter(it => c.indexOf(it.name.replace("res://tests/", "")) >= 0 || it.name.indexOf(c.split(":")[0].replace(".gd", "")) >= 0);
|
||||
if (!hit.length) { worst = worst || "off"; continue; }
|
||||
if (hit.some(it => it.result !== "PASS")) return lamp("bad", "代表套件红:" + c);
|
||||
worst = worst || "ok";
|
||||
} else {
|
||||
worst = worst || (r.result === "绿" ? "ok" : "off");
|
||||
}
|
||||
}
|
||||
if (worst === "ok") return lamp("ok", "承载层绿");
|
||||
if (worst === "na") return lamp("na", "承载层本批未跑");
|
||||
return lamp("off", "该层无对应承载(灰)");
|
||||
}
|
||||
let html = "";
|
||||
for (const seg of cl.segments) {
|
||||
if (!seg.rows.length) continue;
|
||||
html += `<div class="seg"><h3 style="font-size:14px;margin:8px 0 4px">${esc(seg.segment)}(${seg.rows.length} 行)</h3>`;
|
||||
html += "<table><tr><th style='width:32%'>能力</th><th>系统</th><th style='width:14%'>灯</th><th style='width:26%'>代表用例</th></tr>";
|
||||
for (const row of seg.rows) {
|
||||
html += `<tr><td>${esc(row.ability)}</td><td>${esc(row.system)}</td><td>${rowLight(row)}</td><td>${row.cases.map(esc).join("<br>")}</td></tr>`;
|
||||
}
|
||||
html += "</table></div>";
|
||||
}
|
||||
document.getElementById("capabilityMatrix").innerHTML = html;
|
||||
})();
|
||||
|
||||
// ── 三、控件级下钻 ──
|
||||
(function () {
|
||||
const pm = C.probe_manifest || {probes: []};
|
||||
const ex = ((C.exemptions || {}).controls || []).map(s => String(s));
|
||||
const rd = ((C.gate_receipts || {}).render || {}).receipt || {};
|
||||
const renderScreens = [];
|
||||
for (const it of (rd.items || [])) if (it.screens instanceof Array) renderScreens.push(...it.screens);
|
||||
let html = "";
|
||||
if (renderScreens.length) {
|
||||
html += `<p class="note"><b>渲染展示登记(golden 逐屏)</b>:${renderScreens.map(esc).join("、")}</p>`;
|
||||
}
|
||||
for (const pf of pm.probes) {
|
||||
const name = pf.replace(/^probe_hits_/, "").replace(/\.js$/, "");
|
||||
// 同名重复 <script> 无法动态注入——manifest 落盘时同步内联一份数据入口由面板经 window 取
|
||||
const data = (window.__QA_COVERAGE__.probe_real_input || {})[name];
|
||||
if (!data) { html += `<p class="warn">${esc(name)}:登记文件未加载(检查 script 标签与 manifest)</p>`; continue; }
|
||||
const hits = data.hits || {};
|
||||
const seen = data.seen || [];
|
||||
const gaps = seen.filter(k => !(k in hits) && !ex.some(e => k.indexOf(e) >= 0));
|
||||
html += `<details open><summary><b>${esc(name)}</b>:命中 ${Object.keys(hits).length} 控件 / 存在 ${seen.length} 控件 / 零命中缺口 ${gaps.length}(@ ${esc(data.meta.generated_at)})</summary><div class='gaplist'>`;
|
||||
for (const g of gaps) html += `${lamp("off", "存在但零真输入命中")} ${esc(g)}<br>`;
|
||||
html += "<b>已命中:</b><br>";
|
||||
for (const k of Object.keys(hits).sort()) html += `${lamp("ok", "真输入命中 " + hits[k] + " 次")} ${esc(k)}×${hits[k]}<br>`;
|
||||
html += "</div></details>";
|
||||
}
|
||||
document.getElementById("controlDrill").innerHTML = html || "<p class='warn'>探针登记缺数据——先跑探针与 tools/coverage_receipts.py</p>";
|
||||
})();
|
||||
|
||||
// ── 四、本体对账 ──
|
||||
(function () {
|
||||
const on = C.ontology;
|
||||
if (!on || !on.categories) { document.getElementById("ontologyGaps").innerHTML = "<p class='warn'>本体数据缺——先跑 tools/coverage_enum.py</p>"; return; }
|
||||
const ex = ((C.exemptions || {}).ontology || []).map(s => String(s));
|
||||
const hitTexts = [];
|
||||
const pri = (C.probe_real_input || {});
|
||||
for (const k in pri) for (const hk of Object.keys(pri[k].hits || {})) hitTexts.push(hk);
|
||||
let html = `<p class="note">实体 ${esc(on.meta.entities_total)} · 断言文料 ${esc(on.meta.corpus_rows)} 行 · 首见批次 ${esc(on.meta.generated_at)}</p>`;
|
||||
for (const cat of on.categories) {
|
||||
const shown = cat.gaps.filter(g => !ex.includes(g.id));
|
||||
const probed = shown.filter(g => hitTexts.some(h => h.indexOf(g.id) >= 0));
|
||||
const pure = shown.filter(g => !probed.includes(g));
|
||||
html += `<details><summary><b>${esc(cat.category)}</b>:${cat.total} 实体 / 关联 ${cat.matched} / 缺口 ${shown.length}(其中探针承载 ${probed.length})</summary><div class='gaplist'>`;
|
||||
for (const g of probed) html += `${lamp("ok", "探针真输入命中承载")} ${esc(g.id)} <span class="note">(断言文料零关联——交互覆盖由探针承载)</span><br>`;
|
||||
for (const g of pure) html += `${lamp("off", "验证零关联缺口——人工复核后再立事项")} ${esc(g.id)}<br>`;
|
||||
html += "</div></details>";
|
||||
}
|
||||
document.getElementById("ontologyGaps").innerHTML = html;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user