Files
aurora-admin/tests/_collect.html
T
aurora-admin 6fec4ba6f8 v1.3.0: 修复验收链路(iframe 真实渲染+跨帧断言)、基座焦点环、契约第二批 15 个
回归通过率此前不可信:run-tests.ps1 抽取演示页 body 时剥离全部 <script>,
而 79 个 H5 演示页的 DOM 全由内联脚本渲染,且测试页从未引用 frameworks/<组件>.css
—— 断言跑在空 DOM + 无组件样式上,v1.2.0 的 75.1% 属结构性误报。

Fixed — 验收链路
- 测试页改为 iframe 加载真实演示页(_template.html),_runtime.js 跨帧在真实
  渲染结果上断言:帧内 getComputedStyle/styleSheets/outerHTML,用例自动标注 +
  帧内描边,结果 postMessage 上报
- 标注改为容器下钻(.row/.demo-block → 首个含 ≥2 有效子元素的容器,最多 6 层)
- 新增 N/A 语义:静态组件键盘/ARIA 记 skip 而非失败;对比度与 token 检查限定
  在被测组件区,页头文档文字不计
- 通过率分母改为 pass/(pass+fail),tools/run-regression.mjs 与收集器同口径

Added — 设计系统
- 基座 :focus-visible 统一焦点环(colors_and_type.css)。--au-color-focus-ring
  此前只定义未消费,79 个组件样式仅 20 个自带 focus 规则 —— 一次性消除 66 页失败
- tests/_collect.html:零依赖浏览器批量回归收集器(并发 iframe + 汇总)
- tests/_index_template.html:测试总览页模板外置,读数来自 data.json

Added — 契约第二批(展示类 15 个)
- tree/collapse/calendar/carousel/imagepreview/qrcode/countdown/watermark/
  cardlist/treetable/timelinelist/steplist/chartpanel/dashboardcard/employeecard
- 逐份从规格原文(组件2/4/7/9.txt)提炼,含 dims/variants/anatomy/structure/
  usageHints/doNotInvent/unknowns;契约化 21 → 36/79

Changed
- run-tests.ps1 重写:删除失效的快照抽取(占位符替换早已不匹配模板),改生成
  iframe 版测试页;输出 WriteAllText 无 BOM;保持 ASCII-only,中文移入 UTF-8 模板
- 回归基线:960 断言 / 794 通过 / 85.1% / 11 页全通过 / N/A 27
- CHANGELOG/TESTING/改造清单同步;测试总览页与首页通过率联动

验证:浏览器全量 79 页收集器跑通两次,基线一致;详情页契约章节
(含 doNotInvent/unknowns)、chip、首页「通过率 85.1%」、changelog v1.3.0 实测通过
2026-09-10 21:19:09 +08:00

182 lines
7.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>批量回归收集器 — Aurora Admin</title>
<link rel="stylesheet" href="../.design_library/aurora-admin/colors_and_type.css">
<link rel="stylesheet" href="../site/style.css">
<style>
body { margin: 0; font-family: var(--au-font-family); color: var(--au-color-text-body); background: var(--au-color-page-bg); }
.shell { max-width: 1240px; margin: 0 auto; padding: 24px; }
.shell h1 { margin: 0 0 4px; font-size: 24px; color: var(--au-color-text-title); }
.shell h1 a { font-size: 13px; margin-left: 8px; color: var(--au-color-brand); }
.shell .desc { margin: 0 0 16px; color: var(--au-color-text-secondary); font-size: 14px; }
.summary { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.pill { height: 28px; padding: 0 12px; border-radius: var(--au-radius-base); background: var(--au-color-card-bg); border: 1px solid var(--au-color-border); display: inline-flex; align-items: center; font-family: var(--au-font-family-num); font-size: 13px; color: var(--au-color-text-body); }
.pill strong { margin-right: 4px; color: var(--au-color-brand); }
.pill.pass { background: rgba(82,196,26,.12); color: var(--au-color-success); border-color: transparent; }
.pill.fail { background: rgba(245,34,45,.12); color: var(--au-color-error); border-color: transparent; }
#frames { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
#frames iframe { width: 100%; height: 320px; border: 1px solid var(--au-color-border); border-radius: var(--au-radius-base); background: #fff; }
pre#out { background: var(--au-color-card-bg); border: 1px solid var(--au-color-border); border-radius: var(--au-radius-base); padding: 12px; font-size: 12px; max-height: 320px; overflow: auto; font-family: var(--au-font-family-num); }
</style>
</head>
<body>
<div class="shell">
<h1>批量回归收集器 <a href="index.html">← 测试总览</a></h1>
<p class="desc">顺序/并发把全部测试页装入同源 iframe,收集 <code>_runtime.js</code> 在帧内跑出的断言汇总,生成与 <code>tools/run-regression.mjs</code> 同结构的报告对象(<code>window.__aaCollectResult</code>)。零 npm 依赖。</p>
<div class="summary">
<span class="pill">进度 <strong id="prog">0 / 0</strong></span>
<span class="pill" id="rate">通过率 —</span>
<span class="pill" id="state">启动中…</span>
</div>
<div id="frames"></div>
<pre id="out">…</pre>
</div>
<script>
(function () {
'use strict';
var CONC = 4;
var TIMEOUT = 8000;
var bySlug = {};
var slugs = [];
var done = 0;
var queue = [];
var busy = 0;
var finished = false;
var host = document.getElementById('frames');
var progEl = document.getElementById('prog');
var rateEl = document.getElementById('rate');
var stateEl = document.getElementById('state');
var outEl = document.getElementById('out');
function setState(t, kind) {
stateEl.textContent = t;
stateEl.className = 'pill' + (kind ? ' ' + kind : '');
}
window.addEventListener('message', function (e) {
var d = e.data;
if (!d || d.type !== 'aa-test-result' || !d.result) return;
var slug = d.result.slug;
if (bySlug[slug]) return;
bySlug[slug] = d.result;
done++;
update();
});
function update() {
progEl.textContent = done + ' / ' + slugs.length;
var tot = 0, pass = 0, fail = 0;
Object.keys(bySlug).forEach(function (k) {
var r = bySlug[k];
tot += r.total; pass += r.pass; fail += r.fail;
});
var denom = pass + fail;
rateEl.textContent = '通过率 ' + (denom ? (Math.round(pass / denom * 1000) / 10) + '%' : '—') + '(' + pass + '/' + denom + ',N/A ' + (tot - denom) + ')';
rateEl.className = 'pill ' + (fail ? 'fail' : 'pass');
}
function loadInto(frame, slug, cb) {
var settled = false;
busy++;
setState('运行中:' + slug);
var timer = setTimeout(finish, TIMEOUT);
function onMsg(e) {
var d = e.data;
if (d && d.type === 'aa-test-result' && d.result && d.result.slug === slug) finish();
}
function finish() {
if (settled) return;
settled = true;
clearTimeout(timer);
window.removeEventListener('message', onMsg);
if (!bySlug[slug]) {
bySlug[slug] = { slug: slug, total: 0, pass: 0, fail: 1, skip: 0, failures: ['timeout'] };
done++;
}
try { frame.removeAttribute('src'); } catch (e) {}
busy--;
update();
cb();
}
window.addEventListener('message', onMsg);
frame.setAttribute('src', './' + slug + '.html');
}
function pump() {
if (finished) return;
if (!queue.length && busy === 0) return finalize();
for (var i = 0; i < CONC && queue.length; i++) {
var frame = document.getElementById('f' + i);
if (frame.getAttribute('data-busy') === '1') continue;
frame.setAttribute('data-busy', '1');
(function (fr) {
var slug = queue.shift();
loadInto(fr, slug, function () {
fr.setAttribute('data-busy', '0');
pump();
});
})(frame);
}
}
function finalize() {
finished = true;
var results = slugs.map(function (s) { return bySlug[s] || { slug: s, total: 0, pass: 0, fail: 1, skip: 0, failures: ['missing'] }; });
var totalAssert = results.reduce(function (s, r) { return s + r.total; }, 0);
var passAssert = results.reduce(function (s, r) { return s + r.pass; }, 0);
var failAssert = results.reduce(function (s, r) { return s + r.fail; }, 0);
var skipAssert = results.reduce(function (s, r) { return s + r.skip; }, 0);
var denom = passAssert + failAssert;
var kinds = {};
results.forEach(function (r) {
(r.failures || []).forEach(function (f) { kinds[f] = (kinds[f] || 0) + 1; });
});
var report = {
generated: new Date().toISOString().slice(0, 19).replace('T', ' '),
version: window.__aaVersion || '',
pages: results.length,
pagesAllPass: results.filter(function (r) { return r.fail === 0 && r.total > 0; }).length,
passRate: denom ? Math.round(passAssert / denom * 1000) / 10 : 0,
assertions: { total: totalAssert, pass: passAssert, fail: failAssert, skip: skipAssert, na: totalAssert - denom },
failureKinds: kinds,
failedPages: results.filter(function (r) { return r.fail > 0; })
.map(function (r) { return { slug: r.slug, fail: r.fail, failures: r.failures || [] }; })
};
window.__aaCollectResult = report;
setState('完成', failAssert ? 'fail' : 'pass');
outEl.textContent = JSON.stringify(report, null, 2);
try { document.title = 'DONE ' + report.passRate + '%'; } catch (e) {}
}
for (var i = 0; i < CONC; i++) {
var f = document.createElement('iframe');
f.id = 'f' + i;
f.setAttribute('title', 'collector frame ' + i);
host.appendChild(f);
}
fetch('../site/data.json', { cache: 'no-store' })
.then(function (r) { return r.json(); })
.then(function (d) {
window.__aaVersion = (d.meta && d.meta.version) || '';
slugs = (d.components || []).map(function (c) { return c.slug; });
queue = slugs.slice();
progEl.textContent = '0 / ' + slugs.length;
pump();
})
.catch(function (e) {
setState('读取 site/data.json 失败', 'fail');
outEl.textContent = '需要先跑 build-site.ps1 并通过 dev-server 访问:' + e;
});
})();
</script>
</body>
</html>