Files
photo-live/docs/designs/ui06-implementation/capture-before.cjs
T

187 lines
6.3 KiB
JavaScript
Raw Normal View History

/**
* 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);
});