回归通过率此前不可信: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 实测通过
83 lines
4.0 KiB
JavaScript
83 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* run-regression.mjs — headless 回归 runner(CI / 本地均可)
|
||
*
|
||
* 前置:仓库根目录起 dev-server(node site/dev-server.js)
|
||
* 依赖:npm i playwright(或全局已装)
|
||
* 运行:node tools/run-regression.mjs
|
||
* 产出:tests/report.json(首页「测试通过率」数据源)+ tests/report-junit.xml
|
||
*/
|
||
import { writeFileSync } from 'fs';
|
||
|
||
const BASE = process.env.REG_BASE || 'http://127.0.0.1:3311';
|
||
const CONC = 6;
|
||
|
||
const { chromium } = await import('playwright');
|
||
const browser = await chromium.launch();
|
||
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
||
|
||
const data = await (await fetch(`${BASE}/site/data.json`)).json();
|
||
const slugs = data.components.map(c => c.slug);
|
||
const results = [];
|
||
|
||
async function runOne(browser, slug) {
|
||
const p = await browser.newPage({ viewport: { width: 1200, height: 800 } });
|
||
try {
|
||
await p.goto(`${BASE}/tests/${slug}.html`, { waitUntil: 'load', timeout: 15000 });
|
||
/* 断言在 iframe 演示页加载后自动执行;等汇总区出现结果 */
|
||
await p.waitForFunction(() => document.getElementById('sum-total')?.textContent !== '总数 0', { timeout: 8000 })
|
||
.catch(() => {});
|
||
const btn = p.locator('#run-asserts');
|
||
if (await btn.count()) { await btn.click(); await p.waitForTimeout(150); }
|
||
const r = await p.evaluate(() => {
|
||
const num = s => parseInt(String(s || '0').replace(/\D/g, '')) || 0;
|
||
const t = id => { const n = document.getElementById(id); return n ? n.textContent : '0'; };
|
||
const failures = [...document.querySelectorAll('#result-list .t-row')]
|
||
.filter(li => li.querySelector('.t-pill')?.classList.contains('fail'))
|
||
.map(li => li.querySelector('.id')?.textContent || '?')
|
||
.slice(0, 8);
|
||
return { total: num(t('sum-total')), pass: num(t('sum-pass')), fail: num(t('sum-fail')), skip: num(t('sum-skip')), failures };
|
||
});
|
||
results.push({ slug, ...r });
|
||
} catch (e) {
|
||
results.push({ slug, total: 0, pass: 0, fail: 1, skip: 0, failures: ['page-error'] });
|
||
} finally {
|
||
await p.close();
|
||
}
|
||
}
|
||
|
||
for (let i = 0; i < slugs.length; i += CONC) {
|
||
await Promise.all(slugs.slice(i, i + CONC).map(s => runOne(browser, s)));
|
||
}
|
||
await browser.close();
|
||
|
||
const totalAssert = results.reduce((s, r) => s + r.total, 0);
|
||
const passAssert = results.reduce((s, r) => s + r.pass, 0);
|
||
const failAssert = results.reduce((s, r) => s + r.fail, 0);
|
||
const skipAssert = results.reduce((s, r) => s + r.skip, 0);
|
||
/* 通过率分母排除 N/A(静态组件无交互面等),与 tests/_collect.html 同口径 */
|
||
const denom = passAssert + failAssert;
|
||
const pagesAllPass = results.filter(r => r.fail === 0 && r.total > 0).length;
|
||
const report = {
|
||
generated: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||
version: data.meta.version,
|
||
pages: results.length,
|
||
pagesAllPass,
|
||
passRate: denom ? Math.round(passAssert / denom * 1000) / 10 : 0,
|
||
assertions: { total: totalAssert, pass: passAssert, fail: failAssert, skip: skipAssert, na: totalAssert - denom },
|
||
failedPages: results.filter(r => r.fail > 0).map(r => ({ slug: r.slug, fail: r.fail, failures: r.failures }))
|
||
};
|
||
writeFileSync('tests/report.json', JSON.stringify(report, null, 2) + '\n');
|
||
|
||
/* JUnit XML(供 CI 展示) */
|
||
const esc = s => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
||
const cases = results.map(r =>
|
||
` <testcase name="tests/${r.slug}" time="0">` +
|
||
(r.fail > 0 ? `<failure message="${esc((r.failures || []).join('; '))}">${r.fail} failed</failure>` : '') +
|
||
`</testcase>`).join('\n');
|
||
writeFileSync('tests/report-junit.xml',
|
||
`<?xml version="1.0" encoding="UTF-8"?>\n<testsuite name="aurora-admin tests" tests="${results.length}" failures="${results.filter(r => r.fail > 0).length}">\n${cases}\n</testsuite>\n`);
|
||
|
||
console.log(`passRate ${report.passRate}% | pages ${results.length} (all-pass ${pagesAllPass}) | assertions ${passAssert}/${totalAssert}`);
|
||
console.log('written: tests/report.json, tests/report-junit.xml');
|