| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- import assert from 'node:assert/strict';
- import { readFile } from 'node:fs/promises';
- import { fileURLToPath, pathToFileURL } from 'node:url';
- import { chromium } from 'playwright';
- // 页面目录独立来源于批准线稿,检查全部页面在两个宽度下的可见内容和文档几何。
- const groups = [
- ['customer', 'customer01', ['home', 'categories', 'product', 'cart', 'profile', 'messages', 'address', 'favorites', 'settings', 'chat', 'login', 'orders', 'order', 'policy', 'apply', 'proposal', 'ticket', 'memory', 'identity']],
- ['merchant', 'admin01', ['login', 'overview', 'access', 'channels', 'rules', 'knowledge', 'release', 'billing', 'products', 'orders', 'after-sales', 'customers', 'marketing', 'service', 'notifications', 'notification-detail']],
- ['staff', 'staff01', ['queue', 'conversation', 'draft', 'ticket', 'customers', 'reference', 'quick-replies', 'statistics', 'notifications', 'notification-detail']],
- ['supervisor', 'supervisor01', ['approvals', 'review', 'history', 'team', 'statistics', 'notifications', 'notification-detail']],
- ['platform', 'platform-admin', ['login', 'dashboard', 'tenants', 'capacity', 'deliveries', 'health', 'audit', 'applications', 'models', 'permissions', 'settings']],
- ];
- const pathFor = (role, name) => role === 'staff' ? `merchant/service/${name}` : role === 'supervisor' ? `merchant/approval/${name}` : `${role}/${name}`;
- export async function checkLayouts(page, baseUrl, accounts) {
- const platformAccount = JSON.parse(await readFile(new URL('../../.work/platform-accounts.json', import.meta.url), 'utf8'));
- const results = [], failures = [];
- const origin = baseUrl.replace(/\/$/, '');
- const visit = async (role, name) => { await page.goto(`${origin}/#/${pathFor(role, name)}`); };
- const checkPage = async (role, name, width) => {
- await visit(role, name);
- const container = page.locator(role === 'customer' ? '.mobile' : name === 'login' ? '.login-panel' : '.admin-content');
- await container.waitFor({ state: 'visible', timeout: 15000 });
- await page.locator('.loading').waitFor({ state: 'hidden' });
- assert.equal(await page.getByRole('heading', { name: '页面不可访问', exact: true }).count(), 0, `${role}/${name} 可访问`);
- assert.ok((await container.innerText()).trim().length > 10, `${role}/${name} 显示页面内容`);
- // 仅测量渲染后的 DOM,不读取 Vue 数据、浏览器存储或组件对象。
- const geometry = await page.locator('html').evaluate(element => ({
- width: element.clientWidth,
- scroll: element.scrollWidth,
- overflowing: [...document.querySelectorAll('body *')].filter(node => {
- const box = node.getBoundingClientRect();
- return box.width > 0 && box.right > element.clientWidth + 1 && !node.closest('.table-wrap');
- }).slice(0, 8).map(node => ({ tag: node.tagName, class: node.className, text: node.textContent.slice(0, 70), right: node.getBoundingClientRect().right })),
- }));
- const nameText = `${width}px ${role}/${name}`;
- if (geometry.scroll > geometry.width + 1) failures.push({ name: nameText, ...geometry });
- results.push({ name: nameText, passed: geometry.scroll <= geometry.width + 1 });
- };
- const signIn = async (role, username) => {
- if (role === 'customer') {
- await visit('customer', 'login');
- await page.getByLabel('账户', { exact: true }).fill(username);
- await page.getByLabel('密码', { exact: true }).fill(accounts.password);
- await page.getByRole('checkbox').check();
- await page.getByRole('button', { name: '登录并继续', exact: true }).click();
- await page.waitForURL(url => !url.hash.endsWith('/login'));
- await visit('customer', 'profile');
- await page.getByRole('heading', { name: username, exact: true }).waitFor();
- return;
- }
- const product = role === 'platform' ? 'platform' : 'merchant';
- await visit(product, 'login');
- if (product === 'merchant') await page.getByLabel('商家标识', { exact: true }).fill(accounts.tenant_id);
- await page.getByLabel('员工账户', { exact: true }).fill(username);
- await page.getByLabel('密码', { exact: true }).fill(product === 'platform' ? platformAccount.password : accounts.password);
- await page.getByRole('button', { name: '登录', exact: true }).click();
- await page.locator('.admin-content').waitFor();
- };
- const logout = async () => {
- await page.getByRole('button', { name: '退出登录', exact: true }).click();
- await page.getByRole('dialog').getByRole('button', { name: '确认操作', exact: true }).click();
- await page.locator('.login-panel').waitFor();
- };
- assert.equal(groups.reduce((sum, [, , pages]) => sum + pages.length, 0), 63);
- for (const [role, username, pages] of groups) {
- await signIn(role, username);
- for (const width of [1440, 390]) {
- await page.setViewportSize({ width, height: 1000 });
- for (const name of pages) await checkPage(role, name, width);
- }
- if (role !== 'customer') {
- await visit(role, pages.find(name => name !== 'login'));
- await logout();
- }
- }
- if (failures.length) throw new Error(`页面文档宽度超出视口:${JSON.stringify(failures)}`);
- return results;
- }
- if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
- const accounts = JSON.parse(await readFile(fileURLToPath(new URL('../../.work/frontend-accounts.json', import.meta.url)), 'utf8'));
- const browser = await chromium.launch({ headless: true, executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE || 'C:/Users/Administrator/AppData/Local/ms-playwright/chromium-1228/chrome-win64/chrome.exe' });
- try {
- const page = await browser.newPage();
- page.on('pageerror', error => { console.error(error.message); });
- const results = await checkLayouts(page, process.env.FRONTEND_URL || 'http://127.0.0.1:18762', accounts);
- console.log(JSON.stringify({ passed: results.length, pages: 63, widths: [1440, 390] }));
- } finally { await browser.close(); }
- }
|