首次提交: OnebotCatalog 项目代码与文档(含 NX 按需生成服务二期、后台、一键启动)

This commit is contained in:
wangruiguo
2026-09-03 17:55:45 +08:00
commit fafa86d3a6
241 changed files with 78656 additions and 0 deletions

90
tools/e2e-gen.js Normal file
View File

@@ -0,0 +1,90 @@
// 无头 E2E: 非标档位在线生成 (二期) — 按钮升级/立即命中下载/排队轮询下载
// 前置: 站点 config.js 的 api 已指向本地 GenServer (匿名模式); GenServer 已启动
// 用法: node e2e-gen.js <siteUrl> <jobsDir> <outputDir>
const fs = require('fs');
const path = require('path');
let chromium;
try { chromium = require('playwright-core').chromium; }
catch (e) { chromium = require('C:/Users/ruigu/AppData/Local/Temp/nds3dtest/node_modules/playwright-core').chromium; }
const edge = 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe';
(async () => {
const url = process.argv[2] || 'http://localhost:8080/';
const jobsDir = process.argv[3];
const outputDir = process.argv[4];
const browser = await chromium.launch({ executablePath: edge, headless: true });
const context = await browser.newContext({ acceptDownloads: true, viewport: { width: 1600, height: 900 } });
const page = await context.newPage();
const errs = [];
page.on('pageerror', e => errs.push(e.message));
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2500);
let pass = 0, fail = 0;
const out = [];
const check = (name, ok) => { out.push((ok ? 'PASS ' : 'FAIL ') + name); if (ok) pass++; else fail++; };
// KC 系列
const names = await page.$$eval('.tree .series', els => els.map(e => e.textContent.trim()));
await (await page.$$('.tree .series'))[names.findIndex(n => n.indexOf('KC') === 0)].click();
await page.waitForTimeout(500);
const setSel = async (code, val) => { await page.selectOption('#sel-' + code, val); await page.waitForTimeout(250); };
const setStroke = async (v) => { await page.fill('#sel-stroke', String(v)); await page.dispatchEvent('#sel-stroke', 'change'); await page.waitForTimeout(400); };
const btn = async () => page.$eval('#btnDownload', el => ({ text: el.textContent, disabled: el.disabled, hidden: el.classList.contains('hidden') }));
// 1) 非标 87 (输出目录已预置) → 按钮=在线生成数模; 点击 → immediate → 触发下载
await setSel('type', '00'); await setSel('bore', '32'); await setStroke(87);
await setSel('magnet', '__EMPTY__'); await setSel('mount', '__EMPTY__'); await page.waitForTimeout(400);
var b = await btn();
check('非标按钮 = 在线生成数模 且可用 (实际: ' + b.text + (b.disabled ? '/disabled' : '/enabled') + ')',
b.text.indexOf('在线生成') >= 0 && !b.disabled);
var dlEvent = null;
page.waitForEvent('download', { timeout: 10000 }).then(d => { dlEvent = d; }).catch(() => { });
await page.click('#btnDownload');
await page.waitForTimeout(2500);
check('立即命中 → 触发全格式 zip 下载', dlEvent !== null);
var st = await page.$eval('#status', el => el.textContent);
check('状态含 就绪/下载 (实际: ' + st.trim() + ')', st.indexOf('就绪') >= 0 || st.indexOf('下载') >= 0);
// 2) 非标 88 (无输出) → 点击 → 排队/生成中 → 外部模拟 worker 完成 → 触发下载
await setStroke(88); await page.waitForTimeout(400);
b = await btn();
check('非标 88 按钮可用', b.text.indexOf('在线生成') >= 0 && !b.disabled);
dlEvent = null;
page.waitForEvent('download', { timeout: 30000 }).then(d => { dlEvent = d; }).catch(() => { });
await page.click('#btnDownload');
await page.waitForTimeout(4000);
b = await btn();
check('点击后进入生成/排队态 (实际: ' + b.text + ')', b.text.indexOf('生成') >= 0 || b.text.indexOf('队列') >= 0);
// 模拟 worker: 认领 .json → .running → 造输出 → 写 .done
const jobs = fs.readdirSync(jobsDir).filter(f => f.startsWith('job_') && f.endsWith('.json'));
if (jobs.length) {
const jf = path.join(jobsDir, jobs[0]);
const rf = jf.replace(/\.json$/, '.running');
fs.renameSync(jf, rf);
const o = path.join(outputDir, 'KC', 'KC0032-88');
fs.mkdirSync(o, { recursive: true });
['KC0032-88.stp', 'KC0032-88.x_t', 'KC0032-88.igs', 'KC0032-88.stl'].forEach(f => fs.writeFileSync(path.join(o, f), 'FAKE ' + f));
fs.writeFileSync(jf.replace(/\.json$/, '.done'), JSON.stringify({
ok: true, error: '',
items: [{ series: 'KC', code: 'KC0032-88', ok: true, hit: false, files: ['KC0032-88.stp', 'KC0032-88.x_t', 'KC0032-88.igs', 'KC0032-88.stl'] }]
}));
fs.unlinkSync(rf);
check('模拟 worker 完成写入', true);
} else {
check('任务已入队 (jobs 目录可见)', jobs.length > 0);
}
await page.waitForTimeout(12000); // 轮询间隔 3s, 等状态刷新并触发下载
check('排队轮询 → 完成后触发下载', dlEvent !== null);
// 3) 标准档位回归: stroke=10 → 按钮回 下载 STEP 可用
await setStroke(10); await page.waitForTimeout(400);
b = await btn();
check('标准档位按钮回 下载 STEP (实际: ' + b.text + ')', b.text.indexOf('下载 STEP') >= 0 && !b.disabled && !b.hidden);
console.log(out.join('\n'));
console.log('结果: ' + pass + ' 通过, ' + fail + ' 失败 | 页面错误: ' + (errs.length ? errs.join(' | ') : '无'));
await browser.close();
process.exit(fail ? 1 : 0);
})().catch(e => { console.error('FAIL:', e.message); process.exit(1); });