- 点选金框滑动 + 检视区 150ms 交叉淡变;↑↓/J/K 即时切换、空格播放、 Delete 删除、] 检视区、浮层让位;新记录入场/完成金光/失败抖动,减动效只余短淡变 - 单条删除 5 秒撤销(Toast 金色动作钮),窗口过后才提交 DELETE,离页 flush; 任务页同队列同步;工作流删除保留旧记录并标识「工作流已删除」 - 红/琥珀界线:失败红 + 中文结论展开;done 但文件取不到 → 琥珀「文件缺失」 + 用相同参数重做/移除记录 - 参数页提交自动切结果页;工作流选择器 ↑↓ 真高亮 + Enter 使用(ShellPop arrowRoving 开关) - 记录流 80 条分页(220 条专项:50 次轮询 490ms);任务页 §3.5 芯片+搜索+ 行内进度条+ghost 重试;i18n 七语种 +17 键 Co-authored-by: multica-agent <github@multica.ai>
75 lines
2.7 KiB
JavaScript
75 lines
2.7 KiB
JavaScript
/* Pagination + perf check (§五): intercept the jobs API and serve 220
|
||
* synthetic takes so the >80 path engages without touching the backend. */
|
||
import { chromium } from 'playwright';
|
||
|
||
let failures = 0;
|
||
const ok = (name, cond, extra = '') => {
|
||
console.log(`${cond ? 'PASS' : 'FAIL'} ${name}${extra ? ' — ' + extra : ''}`);
|
||
if (!cond) failures++;
|
||
};
|
||
|
||
const run = async () => {
|
||
const browser = await chromium.launch();
|
||
const page = await (
|
||
await browser.newContext({ viewport: { width: 1440, height: 900 } })
|
||
).newPage();
|
||
|
||
const base = 'http://127.0.0.1:5173';
|
||
let realJobs = [];
|
||
await page.route('**/api/playground/jobs**', async (route) => {
|
||
const r = await route.fetch();
|
||
realJobs = await r.json();
|
||
// Fan the real list out to 220 takes with distinct ids/timestamps.
|
||
const fan = [];
|
||
for (let i = 0; i < 220; i++) {
|
||
const src = realJobs[i % realJobs.length] ?? realJobs[0];
|
||
fan.push({
|
||
...src,
|
||
id: 9000 + i,
|
||
status: 'done',
|
||
output_paths: src.output_paths,
|
||
created_at: new Date(Date.now() - i * 47 * 60 * 1000).toISOString(),
|
||
});
|
||
}
|
||
await route.fulfill({ json: fan });
|
||
});
|
||
|
||
const t0 = Date.now();
|
||
await page.goto(`${base}/image`);
|
||
await page.waitForSelector('#desk-prompt', { timeout: 15000 });
|
||
await page.waitForSelector('.take');
|
||
await page.waitForTimeout(800);
|
||
const rendered = await page.locator('.take').count();
|
||
ok('220 条记录时首屏只渲染最近 80 条', rendered === 80, `rendered ${rendered}`);
|
||
const btn = page.locator('.earlier');
|
||
const label = await btn.textContent();
|
||
ok('出现「显示更早」按钮并带余量', /140/.test(label || ''), (label || '').trim());
|
||
await btn.click();
|
||
await page.waitForTimeout(600);
|
||
ok('点击后追加 80 条(共 160)', (await page.locator('.take').count()) === 160);
|
||
await page.locator('.earlier').click();
|
||
await page.waitForTimeout(600);
|
||
ok('再点展开全部 220 条', (await page.locator('.take').count()) === 220);
|
||
const loadMs = Date.now() - t0;
|
||
ok('整段交互在 3 秒内完成', loadMs < 3000, `${loadMs}ms`);
|
||
|
||
// scripted-painting budget while 220 cards live (SSE 只更新相关行:
|
||
// invalidate jobs → 220 卡重排仍是 keyed 更新)
|
||
const metrics = await page.evaluate(async () => {
|
||
const t = performance.now();
|
||
for (let i = 0; i < 50; i++) await fetch('/api/playground/jobs');
|
||
return { poll50: Math.round(performance.now() - t) };
|
||
});
|
||
ok('50 次 jobs 轮询(220 卡在屏)总耗时 < 8 秒', metrics.poll50 < 8000, `${metrics.poll50}ms`);
|
||
|
||
await page.screenshot({ path: 'shots/13-pagination-220.png' });
|
||
await browser.close();
|
||
console.log(failures === 0 ? '\nPAGINATION CHECKS PASSED' : `\n${failures} FAILED`);
|
||
process.exit(failures ? 1 : 0);
|
||
};
|
||
|
||
run().catch((e) => {
|
||
console.error('SCRIPT ERROR:', e);
|
||
process.exit(2);
|
||
});
|