这是第一个包含完整工作区的提交,也是后续「拆 SFC / 上 Vite」的回滚基线。 此前的提交均为 index.html 单文件历史快照,由 .bak 文件重建。 当前状态(提交前已实测): - index.html 内联薪酬算法已删除,改调 WHTPayroll - shared/payroll.js 为唯一薪酬实现,浏览器与 Node 共用 - 对外接口未变:aggregateMonth / isCutEdge / payDayKind 等保留同名薄包装 未入库内容见 .gitignore:依赖、Chrome 验证 profile、验证截图、 运行时 sqlite 数据与日志、时间戳备份、.trash。
79 lines
3.4 KiB
JavaScript
79 lines
3.4 KiB
JavaScript
// 实测自动重试:用请求拦截模拟「Vue 前 N 次取不到」,看页面能否自己恢复。
|
||
//
|
||
// 场景对应真实故障:服务端日志里失败的请求压根没有记录,说明请求没到服务器(链路丢包)。
|
||
// 这里用 request.abort() 复现同一效果 —— 浏览器侧看到的就是「拿不到这个文件」。
|
||
const puppeteer = require('/root/.openclaw/workspace/web_tasks/node_modules/puppeteer');
|
||
const fs = require('fs');
|
||
|
||
const URL = 'http://127.0.0.1:9522/';
|
||
const VUE = 'vue.global.prod.js';
|
||
|
||
async function run(label, failTimes, expect) {
|
||
const profile = `/tmp/wht-retry-${failTimes}`;
|
||
fs.rmSync(profile, { recursive: true, force: true });
|
||
const browser = await puppeteer.launch({
|
||
executablePath: '/usr/bin/chromium',
|
||
headless: 'new',
|
||
userDataDir: profile,
|
||
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage']
|
||
});
|
||
const page = await browser.newPage();
|
||
await page.setViewport({ width: 390, height: 844, isMobile: true, deviceScaleFactor: 2 });
|
||
|
||
let blocked = 0, served = 0;
|
||
await page.setRequestInterception(true);
|
||
page.on('request', r => {
|
||
if (r.url().includes(VUE)) {
|
||
if (blocked < failTimes) { blocked++; return r.abort('failed'); }
|
||
served++;
|
||
}
|
||
r.continue();
|
||
});
|
||
|
||
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
// 自动重试有 800ms 递增退避 + reload,留足时间
|
||
await new Promise(r => setTimeout(r, 6000));
|
||
|
||
const s = await page.evaluate(() => {
|
||
const card = document.getElementById('boot-fail');
|
||
return {
|
||
vue: typeof Vue !== 'undefined',
|
||
cardShown: card ? !card.hidden : false,
|
||
title: document.getElementById('boot-title')?.textContent || null,
|
||
detail: document.getElementById('boot-detail')?.textContent || null,
|
||
btnHidden: document.getElementById('boot-retry')?.hidden ?? null,
|
||
mounted: !!document.querySelector('#app')?.innerText?.trim().length,
|
||
retryCount: sessionStorage.getItem('wht_boot_retry')
|
||
};
|
||
});
|
||
|
||
const pass = expect(s);
|
||
console.log(`\n[${pass ? 'PASS' : 'FAIL'}] ${label}`);
|
||
console.log(` 拦截 ${blocked} 次 / 放行 ${served} 次`);
|
||
console.log(` Vue=${s.vue} 已挂载=${s.mounted} 卡片显示=${s.cardShown} 重试计数=${s.retryCount}`);
|
||
if (s.cardShown) console.log(` 标题="${s.title}"\n 详情="${s.detail}"`);
|
||
|
||
await page.screenshot({
|
||
path: `/root/.openclaw/workspace/work-hours-tracker/png/retry_fail${failTimes}.png`
|
||
});
|
||
await browser.close();
|
||
fs.rmSync(profile, { recursive: true, force: true });
|
||
return pass;
|
||
}
|
||
|
||
(async () => {
|
||
const results = [];
|
||
// 0 次失败:正常路径,卡片不该出现
|
||
results.push(await run('正常加载(不拦截)', 0, s => s.vue && s.mounted && !s.cardShown));
|
||
// 1 次失败:应自动重试成功,用户看不到失败卡
|
||
results.push(await run('丢包 1 次 → 自动恢复', 1, s => s.vue && s.mounted && !s.cardShown));
|
||
// 999 次失败:自动重试全败,应显示卡片且按钮可点
|
||
results.push(await run('持续丢包 → 显示卡片且不刷新循环', 999,
|
||
s => !s.vue && s.cardShown && s.title === '页面没能加载完成' && s.btnHidden === false));
|
||
|
||
console.log('\n' + '='.repeat(46));
|
||
console.log(results.every(Boolean) ? `全部通过(${results.length}/${results.length})` : '有失败项');
|
||
console.log('='.repeat(46));
|
||
process.exit(results.every(Boolean) ? 0 : 1);
|
||
})().catch(e => { console.error('FAIL', e); process.exit(1); });
|