| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- /* 验证脚本:逐路由截图 + 断言(控制台 0 error / 无横向溢出 / 配色与暗色能切换)
- 用法:node verify.mjs (产物在 ./shots/) */
- import { chromium } from 'playwright';
- import fs from 'fs';
- import path from 'path';
- const dir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'));
- const out = path.join(dir, 'shots');
- fs.mkdirSync(out, { recursive: true });
- const ROUTES = [
- ['01-messages', '#/messages'],
- ['04-contacts', '#/contacts'], ['05-agent', '#/agent/a-nova'], ['06-projects', '#/projects'],
- ['07-project-detail', '#/projects/pr-1'], ['08-tasks', '#/tasks'], ['09-task-blocked', '#/tasks/t-1'],
- ['10-deliverables', '#/deliverables'], ['11-events', '#/events'], ['12-workbench', '#/workbench'],
- ['13-mine', '#/mine'],
- ];
- const browser = await chromium.launch({ channel: 'msedge' });
- const page = await browser.newPage({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2 });
- const errors = [];
- page.on('pageerror', e => errors.push('pageerror: ' + e.message));
- page.on('console', m => { if (m.type() === 'error') errors.push('console: ' + m.text()); });
- const url = 'file:///' + path.join(dir, 'index.html').replace(/\\/g, '/');
- await page.goto(url + '#/messages', { waitUntil: 'load' });
- await page.waitForTimeout(400);
- const report = { routes: [], overflow: [], palettes: {} };
- const wx = {};
- // ---- 登录流程:未登录先落到 #/login,手机号 → 验证码 → 同意 → 登录 ----
- await page.evaluate(() => localStorage.clear());
- await page.goto(url, { waitUntil: 'load' });
- await page.waitForTimeout(300);
- wx.loginRedirect = await page.evaluate(() => location.hash);
- await page.screenshot({ path: path.join(out, '00-login-phone.png') });
- wx.loginBtnDisabledBeforePhone = await page.locator('[data-act="login-next"][data-step="code"]').isDisabled();
- await page.locator('[data-act="login-phone"]').fill('13800000000');
- await page.waitForTimeout(150);
- wx.loginPhoneValid = await page.locator('[data-act="login-next"][data-step="code"]').isEnabled();
- await page.locator('[data-act="login-next"][data-step="code"]').click();
- await page.waitForTimeout(200);
- await page.screenshot({ path: path.join(out, '00-login-code.png') });
- await page.locator('[data-act="login-code"]').fill('123456');
- await page.waitForTimeout(150);
- wx.loginCodeValid = await page.locator('[data-act="login-submit"]').isEnabled();
- await page.locator('[data-act="login-agree"]').click();
- await page.waitForTimeout(150);
- await page.locator('[data-act="login-submit"]').click();
- await page.waitForTimeout(400);
- wx.afterLogin = await page.evaluate(() => location.hash);
- // ---- 底部输入坞不随内容滚动 ----
- await page.evaluate(() => { location.hash = '#/chat/s-1'; });
- await page.waitForTimeout(250);
- const posA = await page.locator('.dock').boundingBox();
- await page.evaluate(() => { const b = document.querySelector('.page-body'); b.scrollTop = b.scrollHeight; });
- await page.waitForTimeout(200);
- const posB = await page.locator('.dock').boundingBox();
- wx.dockFixedDuringScroll = !!posA && !!posB && Math.abs(posA.y - posB.y) < 1;
- wx.dockInsideViewport = await page.evaluate(() => {
- const el = document.querySelector('.dock');
- if (!el) return true; // 该页没有输入坞
- return Math.round(el.getBoundingClientRect().bottom) <= window.innerHeight;
- });
- // 有 TabBar 的页面:输入坞底边不得压住 TabBar
- await page.evaluate(() => { location.hash = '#/messages'; });
- await page.waitForTimeout(250);
- wx.homeDockAboveTabbar = await page.evaluate(() => {
- const d = document.querySelector('.dock');
- const t = document.querySelector('.tabbar');
- if (!d || !t) return true;
- return getComputedStyle(t).display === 'flex' && d.getBoundingClientRect().bottom <= t.getBoundingClientRect().top + 1;
- });
- for (const [name, hash] of ROUTES) {
- await page.evaluate(h => { location.hash = h; }, hash);
- await page.waitForTimeout(180);
- await page.screenshot({ path: path.join(out, name + '.png') });
- const info = await page.evaluate(() => ({
- hasView: !!document.querySelector('.view.active .page'),
- hOverflow: document.documentElement.scrollWidth > window.innerWidth + 1,
- title: (document.querySelector('.navbar .title') || {}).textContent?.trim() || '',
- }));
- if (info.hOverflow) report.overflow.push(name);
- report.routes.push([name, info.hasView, info.title]);
- }
- // 抽屉 + 会话内成果抽屉 + 预览层
- await page.evaluate(() => { location.hash = '#/messages'; });
- await page.waitForTimeout(150);
- await page.locator('[data-act="drawer"]').click();
- await page.waitForTimeout(350);
- await page.screenshot({ path: path.join(out, '14-drawer.png') });
- const drawerOk = await page.locator('.drawer.show').count();
- await page.evaluate(() => { document.querySelectorAll('#layers > div').forEach(w => w.remove()); document.querySelectorAll('#layers > .preview').forEach(x => x.remove()); });
- await page.waitForTimeout(300);
- await page.evaluate(() => { location.hash = '#/chat/s-2'; });
- await page.waitForTimeout(150);
- await page.locator('[data-act="sheet-chat"]').click();
- await page.waitForTimeout(350);
- await page.screenshot({ path: path.join(out, '15-chat-sheet.png') });
- await page.locator('[data-tab="deliver"]').click();
- await page.waitForTimeout(150);
- await page.locator('[data-sheet-body] .row').first().click();
- await page.waitForTimeout(300);
- await page.screenshot({ path: path.join(out, '16-preview.png') });
- const previewOk = await page.locator('.preview').count();
- await page.locator('.preview [data-act="close"]').click();
- await page.waitForTimeout(300);
- // 配色 × 明暗(先回首页,避免在预览层级上操作)
- await page.evaluate(() => { location.hash = '#/messages'; document.querySelectorAll('#layers > *').forEach(x => x.remove()); });
- await page.waitForTimeout(300);
- for (const p of ['platinum', 'ice', 'clay', 'slate']) {
- for (const dark of [false, true]) {
- await page.evaluate(([p, dark]) => {
- document.documentElement.dataset.palette = p;
- document.documentElement.classList.toggle('dark', dark);
- }, [p, dark]);
- const bg = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
- report.palettes[p + (dark ? '-dark' : '-light')] = bg;
- }
- await page.evaluate(p => {
- document.documentElement.dataset.palette = p;
- document.documentElement.classList.remove('dark');
- location.hash = '#/messages';
- }, p);
- await page.waitForTimeout(150);
- await page.screenshot({ path: path.join(out, `17-palette-${p}.png`) });
- }
- // 用页面自身入口切一次,验证持久化路径
- await page.evaluate(() => { location.hash = '#/mine'; });
- await page.waitForTimeout(150);
- await page.locator('[data-act="palette"][data-key="ice"]').click();
- await page.waitForTimeout(150);
- report.paletteAfterClick = await page.evaluate(() => document.documentElement.dataset.palette);
- await page.locator('[data-act="toggle-dark"]').click();
- await page.waitForTimeout(150);
- report.darkAfterClick = await page.evaluate(() => document.documentElement.classList.contains('dark'));
- await page.evaluate(() => { localStorage.clear(); });
- // 设置页截图(深色 + ice)
- await page.evaluate(() => { document.documentElement.dataset.palette = 'ice'; document.documentElement.classList.add('dark'); location.hash = '#/mine'; });
- await page.waitForTimeout(200);
- await page.screenshot({ path: path.join(out, '18-mine-dark-ice.png') });
- const distinct = new Set(Object.values(report.palettes)).size;
- console.log(JSON.stringify({
- ...wx,
- routes: report.routes,
- overflow: report.overflow,
- drawerOk, previewOk,
- paletteColors: report.palettes,
- distinctPaletteColors: distinct,
- paletteAfterClick: report.paletteAfterClick,
- darkAfterClick: report.darkAfterClick,
- errors,
- }, null, 1));
- await browser.close();
- process.exit(errors.length || report.overflow.length || distinct < 8 ? 1 : 0);
|