/** * UI06 B0:管理后台「改动前」截图脚本。 * 目标:测试服务器上当前部署的后台(= HEAD 的 admin 产物),1440×1000 全页截图, * 命名与 admin-preview 示例编号对齐,存入同目录 before/。 * 用法:ADMIN_PW=<密码> node capture-before.cjs [--out before] [--base http://219.157.255.213:32005] * 凭据不写入本文件;仅浏览器会话内存。 */ const fs = require('fs'); const path = require('path'); const { chromium } = require( require.resolve('@playwright/test', { paths: [path.resolve(__dirname, '../../../apps/admin')] }) ); const args = process.argv.slice(2); const argOf = (name, def) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : def; }; const BASE = argOf('--base', 'http://219.157.255.213:32005'); const OUT = path.join(__dirname, argOf('--out', 'before')); const ACCOUNT = process.env.ADMIN_ACCOUNT || 'admin'; const PASSWORD = process.env.ADMIN_PW; if (!PASSWORD) { console.error('missing ADMIN_PW env'); process.exit(1); } const notes = []; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); (async () => { fs.mkdirSync(OUT, { recursive: true }); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); const errors = []; page.on('pageerror', (e) => errors.push(e.message)); const shot = async (name) => { await sleep(1200); // 等数据渲染;无动画依赖 await page.screenshot({ path: path.join(OUT, name + '.png'), fullPage: true }); }; const gotoHash = async (hash) => { await page.goto(`${BASE}/admin/#${hash}`); await page.waitForLoadState('networkidle').catch(() => {}); await sleep(800); }; // 01 登录页(未登录态) await gotoHash('/auth/login'); await shot('01-login'); // 登录 await page.fill('input[autocomplete="username"]', ACCOUNT); await page.fill('input[autocomplete="current-password"]', PASSWORD); await page.click('.auth-right-wrap .el-button--primary'); await page.waitForURL((u) => !u.href.includes('/auth/login'), { timeout: 15000 }); await sleep(1200); // 02 相册列表 await gotoHash('/albums/manage'); await shot('02-albums'); // 进入第一个相册详情(03–07、15、16 的宿主)——UI06 起默认卡片视图(点封面),表格模式保留 album-detail-btn const detailBtn = page.locator('[data-test="album-detail-btn"]').first(); const coverBtn = page.locator('[data-test^="album-cover-"]').first(); const entry = (await detailBtn.count()) ? detailBtn : coverBtn; if (await entry.count()) { await entry.click(); await page.waitForURL('**/albums/detail/**'); await sleep(1200); await shot('03-photos'); // photos 为默认页签(UI06 起默认照片网格) // 04–07 各页签 for (const [name, tab] of [ ['04-members', 'members'], ['05-categories', 'categories'], ['06-watermark', 'watermark'], ['07-share', 'share'], ]) { const tabEl = page.locator(`#tab-${tab}`); if (await tabEl.count()) { await tabEl.click(); await sleep(900); await shot(name); } else { notes.push(`${name}: tab #tab-${tab} not found`); } } // 09 编辑对话框(详情页入口) const editBtn = page.locator('[data-test="album-detail-edit"]').first(); if (await editBtn.count()) { // 回到 photos 页签,避免页签状态干扰 const photosTab = page.locator('#tab-photos'); if (await photosTab.count()) await photosTab.click().catch(() => {}); await sleep(400); await editBtn.click(); await sleep(900); await shot('09-edit'); await page.keyboard.press('Escape'); await sleep(400); } else { notes.push('09-edit: album-detail-edit not found'); } // 15 品牌资产(现状:详情卡内 banner 上传区,未独立成页签)——整页含该区 await shot('15-branding'); notes.push('15-branding: before 状态 banner 位于详情卡内,未独立页签'); // 16 照片大图(现状:表格视图,无大图预览) await shot('16-photo-viewer'); notes.push('16-photo-viewer: before 状态无大图预览,以照片表格代替'); } else { notes.push('03-07/15/16: 无相册数据,未截详情系'); } // 08 新建对话框(列表页入口) await gotoHash('/albums/manage'); const createBtn = page.locator('[data-test="album-create-btn"]').first(); if (await createBtn.count()) { await createBtn.click(); await sleep(900); await shot('08-create'); await page.keyboard.press('Escape'); await sleep(300); } else { notes.push('08-create: album-create-btn not found'); } // 10–12 治理页 await gotoHash('/system/users'); await shot('10-users'); await gotoHash('/system/user-sync'); await shot('11-user-sync'); // 20 同步批次失败明细(现状:点历史行打开抽屉;无失败批次则记录) const failRow = page.locator('[data-test^="sync-run"]').first(); if (await failRow.count()) { await failRow.click().catch(() => {}); await sleep(900); await shot('20-sync-detail'); } else { await shot('20-sync-detail'); notes.push('20-sync-detail: 未找到可点历史行,截页面原状'); } await gotoHash('/system/usage'); await shot('12-usage'); // 13 展示位列表 + 14 配置对话框 await gotoHash('/system/embed-slots'); await shot('13-embed-slots'); const slotEdit = page.locator('[data-test="slot-edit-btn"]').first(); if (await slotEdit.count()) { await slotEdit.click(); await sleep(900); await shot('14-embed-editor'); await page.keyboard.press('Escape'); await sleep(300); } else { notes.push('14-embed-editor: slot-edit-btn not found'); } // 17–19 异常页 await gotoHash('/403'); await shot('17-403'); await gotoHash('/ui06-nonexistent'); await shot('18-404'); await gotoHash('/500'); await shot('19-500'); const result = { date: new Date().toISOString(), base: BASE, viewport: '1440x1000', errors, notes, }; fs.writeFileSync( path.join(OUT, 'capture.json'), JSON.stringify(result, null, 2) ); console.log(JSON.stringify(result, null, 2)); await browser.close(); if (errors.length) process.exitCode = 1; })().catch((e) => { console.error(e); process.exit(1); });