133 lines
4.7 KiB
TypeScript
133 lines
4.7 KiB
TypeScript
/**
|
||
* ONES 测试计划结果同步脚本
|
||
*
|
||
* 用法:
|
||
* npx ts-node scripts/sync-ones-results.ts --plan <PLAN_UUID> [--dry-run]
|
||
*
|
||
* 流程:
|
||
* 1. 读取 reports/.results.json (自动化执行后的结果)
|
||
* 2. 从 ONES 拉取测试计划用例列表
|
||
* 3. 优先按测试名锚点 [ONES:号(#step)] 精确匹配(支持 step 级回写),
|
||
* 无锚点的回退到用例名 LCS 模糊匹配
|
||
* 4. 反写结果到 ONES (dry-run 仅打印 payload)
|
||
*/
|
||
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
import { TestResult } from '../utils/test-reporter';
|
||
import {
|
||
fetchPlanCases,
|
||
matchResults,
|
||
buildAnchoredPayloads,
|
||
postPayloads,
|
||
OnesUpdatePayload,
|
||
} from '../utils/ones-sync';
|
||
import { execSync } from 'child_process';
|
||
|
||
const ONES_CLI = '/Users/woan/local/bin/ones';
|
||
const RESULTS_FILE = path.resolve(__dirname, '../reports/.results.json');
|
||
|
||
function parseArgs() {
|
||
const args = process.argv.slice(2);
|
||
let planUUID = '';
|
||
let dryRun = false;
|
||
|
||
for (let i = 0; i < args.length; i++) {
|
||
if (args[i] === '--plan' && args[i + 1]) {
|
||
planUUID = args[i + 1];
|
||
i++;
|
||
} else if (args[i] === '--dry-run') {
|
||
dryRun = true;
|
||
}
|
||
}
|
||
|
||
if (!planUUID) {
|
||
console.error('Usage: npx ts-node scripts/sync-ones-results.ts --plan <PLAN_UUID> [--dry-run]');
|
||
process.exit(1);
|
||
}
|
||
|
||
return { planUUID, dryRun };
|
||
}
|
||
|
||
function loadResults(): TestResult[] {
|
||
if (!fs.existsSync(RESULTS_FILE)) {
|
||
console.error(`结果文件不存在: ${RESULTS_FILE}`);
|
||
console.error('请先运行自动化测试以生成结果文件');
|
||
process.exit(1);
|
||
}
|
||
|
||
const data = JSON.parse(fs.readFileSync(RESULTS_FILE, 'utf-8'));
|
||
return data.results || [];
|
||
}
|
||
|
||
function main() {
|
||
const { planUUID, dryRun } = parseArgs();
|
||
|
||
const config = JSON.parse(execSync(`${ONES_CLI} config show`, { encoding: 'utf-8' }));
|
||
const executor = config.user_id;
|
||
|
||
console.log('='.repeat(60));
|
||
console.log(' ONES 测试计划结果同步');
|
||
console.log('='.repeat(60));
|
||
console.log(` 计划UUID: ${planUUID}`);
|
||
console.log(` 模式: ${dryRun ? '预览 (dry-run)' : '实际写入'}`);
|
||
console.log('-'.repeat(60));
|
||
|
||
// 1. 加载自动化结果
|
||
const testResults = loadResults();
|
||
console.log(`\n[1/4] 加载自动化结果: ${testResults.length} 条`);
|
||
const passed = testResults.filter(r => r.status === 'PASS').length;
|
||
const failed = testResults.filter(r => r.status === 'FAIL').length;
|
||
const skipped = testResults.filter(r => r.status === 'SKIP').length;
|
||
console.log(` PASS: ${passed} | FAIL: ${failed} | SKIP: ${skipped}`);
|
||
|
||
// 2. 从 ONES 拉取计划用例
|
||
console.log(`\n[2/4] 从 ONES 拉取测试计划用例 ...`);
|
||
const planCases = fetchPlanCases(planUUID);
|
||
console.log(` 计划共 ${planCases.length} 条用例`);
|
||
|
||
// 3. 匹配:锚点优先(支持 step 级),无锚点回退 LCS
|
||
console.log(`\n[3/4] 匹配 (锚点优先 + LCS 兜底) ...`);
|
||
const { payloads: anchored, unanchored } = buildAnchoredPayloads(planCases, testResults, executor);
|
||
const anchoredUUIDs = new Set(anchored.map(p => p.uuid));
|
||
const stepCount = anchored.reduce((n, p) => n + p.steps.length, 0);
|
||
console.log(` 锚点匹配: ${anchored.length} 用例 (含 ${stepCount} 个 step 级结果)`);
|
||
|
||
// LCS 兜底:仅补充锚点未覆盖的用例
|
||
const lcs = matchResults(planCases, unanchored);
|
||
const lcsPayloads: OnesUpdatePayload[] = [];
|
||
for (const [, { caseUUID, result }] of lcs) {
|
||
if (anchoredUUIDs.has(caseUUID)) continue;
|
||
lcsPayloads.push({ uuid: caseUUID, executor, note: '', result, steps: [] });
|
||
}
|
||
console.log(` LCS 兜底: ${lcsPayloads.length} 用例 (未锚点剩余 ${unanchored.length} 条)`);
|
||
|
||
const payloads = [...anchored, ...lcsPayloads];
|
||
if (payloads.length > 0) {
|
||
console.log('\n 回写预览:');
|
||
for (const p of payloads) {
|
||
const pc = planCases.find(c => c.caseUUID === p.uuid);
|
||
const icon = p.result === 'passed' ? '✓' : p.result === 'failed' ? '✗' : '○';
|
||
const stepInfo = p.steps.length ? ` (${p.steps.length} steps)` : '';
|
||
console.log(` ${icon} [${p.result}] ${pc?.caseName || p.uuid}${stepInfo}`);
|
||
}
|
||
}
|
||
|
||
// 4. 反写
|
||
if (dryRun) {
|
||
console.log(`\n[4/4] DRY-RUN:将更新 ${payloads.length} 条用例,跳过实际写入`);
|
||
console.log(' 完整 payload (供核对 step 字段格式):');
|
||
console.log(JSON.stringify({ cases: payloads }, null, 2));
|
||
} else {
|
||
console.log(`\n[4/4] 反写结果到 ONES ...`);
|
||
const { success, failed: failCount } = postPayloads(planUUID, payloads);
|
||
console.log(` 成功: ${success} | 失败: ${failCount}`);
|
||
}
|
||
|
||
console.log('\n' + '='.repeat(60));
|
||
console.log(' 同步完成');
|
||
console.log('='.repeat(60));
|
||
}
|
||
|
||
main();
|