#!/usr/bin/env node /** * 缩放跨端提示 + 管理员用户管理 端到端验证(2026-11-10) * * 验证点: * ① 桌面端把窗口拖窄到 768 以下 → 弹「切换到手机端」提示(不静默跳转) * ② 连续改宽度只弹一次(防抖) * ③ 管理员账号能看到「成员管理」入口与页面 */ import { chromium } from 'playwright'; import { mkdir } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const outDir = resolve(here, '..', 'test-results', 'resize-org'); const BASE = process.env.BASE_URL || 'http://127.0.0.1:4300'; await mkdir(outDir, { recursive: true }); const browser = await chromium.launch({ channel: 'chrome' }); const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); const page = await ctx.newPage(); const errors = []; page.on('pageerror', (e) => errors.push(String(e))); const results = []; // ---------- ① 缩放提示 ---------- await page.goto(`${BASE}/desktop/chat`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(8000); const hintBefore = await page.locator('.narrow-desktop').count(); results.push(['桌面宽屏:不提示', hintBefore === 0, `hint=${hintBefore}`]); // 连续改宽度(模拟拖拽)→ 防抖后只应出现一个提示 for (const w of [1100, 1000, 900, 850, 820, 790, 760, 700, 640, 600]) { await page.setViewportSize({ width: w, height: 900 }); await page.waitForTimeout(40); } await page.waitForTimeout(800); const hintAfter = await page.locator('.narrow-desktop').count(); await page.screenshot({ path: `${outDir}/1-narrow-hint.png` }); results.push(['拖窄到 768 以下 → 弹提示', hintAfter === 1, `hint=${hintAfter}`]); const jumped = page.url().includes('/mobile'); results.push(['提示时**不**静默跳端', !jumped, page.url()]); // 点「我会切」→ 真的跳移动端 const goBtn = page.locator('.narrow-btn', { hasText: '切换到手机端' }); if (await goBtn.count()) { await goBtn.first().click(); await page.waitForTimeout(2500); results.push(['点切换 → 进入移动端', page.url().includes('/mobile'), page.url()]); } else { results.push(['点切换 → 进入移动端', false, '按钮不存在']); } // ---------- ② 管理员用户管理 ---------- await page.setViewportSize({ width: 1440, height: 900 }); await page.goto(`${BASE}/desktop/admin/users`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(6000); await page.screenshot({ path: `${outDir}/2-admin-users.png` }); const url = page.url(); const reached = url.includes('/admin/users'); const blocked = url.includes('/desktop/chat') && !reached; results.push(['管理员可进入成员管理', reached, url]); if (blocked) results.push(['非管理员被挡回首页', true, url]); const hasTable = await page.locator('.table, .rows, ui-empty, .empty').count(); results.push(['页面有内容/三态(不是白屏)', hasTable > 0, `nodes=${hasTable}`]); // 导航入口:一级菜单只显示 4 项,成员管理在「更多」里 await page.goto(`${BASE}/desktop/chat`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(5000); let navAdmin = await page.locator('.route', { hasText: '成员管理' }).count(); if (navAdmin === 0) { const more = page.locator('.route-more'); if (await more.count()) { await more.click(); await page.waitForTimeout(600); navAdmin = await page.locator('.more-item', { hasText: '成员管理' }).count(); } } results.push(['侧边导航有「成员管理」入口', navAdmin > 0, `matches=${navAdmin}`]); // ---------- 汇总 ---------- let pass = true; console.log('\n=== 结果 ==='); for (const [name, ok, detail] of results) { if (!ok) pass = false; console.log(`${ok ? '✓' : '✗'} ${name} (${detail})`); } if (errors.length) { pass = false; console.log('页面错误:', errors.slice(0, 3).join(' | ')); } console.log(`\n${pass ? '✓ 全部通过' : '✗ 存在失败项'}`); await browser.close(); process.exit(pass ? 0 : 1);