verify-resize-and-org.mjs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. #!/usr/bin/env node
  2. /**
  3. * 缩放跨端提示 + 管理员用户管理 端到端验证(2026-11-10)
  4. *
  5. * 验证点:
  6. * ① 桌面端把窗口拖窄到 768 以下 → 弹「切换到手机端」提示(不静默跳转)
  7. * ② 连续改宽度只弹一次(防抖)
  8. * ③ 管理员账号能看到「成员管理」入口与页面
  9. */
  10. import { chromium } from 'playwright';
  11. import { mkdir } from 'node:fs/promises';
  12. import { dirname, resolve } from 'node:path';
  13. import { fileURLToPath } from 'node:url';
  14. const here = dirname(fileURLToPath(import.meta.url));
  15. const outDir = resolve(here, '..', 'test-results', 'resize-org');
  16. const BASE = process.env.BASE_URL || 'http://127.0.0.1:4300';
  17. await mkdir(outDir, { recursive: true });
  18. const browser = await chromium.launch({ channel: 'chrome' });
  19. const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
  20. const page = await ctx.newPage();
  21. const errors = [];
  22. page.on('pageerror', (e) => errors.push(String(e)));
  23. const results = [];
  24. // ---------- ① 缩放提示 ----------
  25. await page.goto(`${BASE}/desktop/chat`, { waitUntil: 'domcontentloaded' });
  26. await page.waitForTimeout(8000);
  27. const hintBefore = await page.locator('.narrow-desktop').count();
  28. results.push(['桌面宽屏:不提示', hintBefore === 0, `hint=${hintBefore}`]);
  29. // 连续改宽度(模拟拖拽)→ 防抖后只应出现一个提示
  30. for (const w of [1100, 1000, 900, 850, 820, 790, 760, 700, 640, 600]) {
  31. await page.setViewportSize({ width: w, height: 900 });
  32. await page.waitForTimeout(40);
  33. }
  34. await page.waitForTimeout(800);
  35. const hintAfter = await page.locator('.narrow-desktop').count();
  36. await page.screenshot({ path: `${outDir}/1-narrow-hint.png` });
  37. results.push(['拖窄到 768 以下 → 弹提示', hintAfter === 1, `hint=${hintAfter}`]);
  38. const jumped = page.url().includes('/mobile');
  39. results.push(['提示时**不**静默跳端', !jumped, page.url()]);
  40. // 点「我会切」→ 真的跳移动端
  41. const goBtn = page.locator('.narrow-btn', { hasText: '切换到手机端' });
  42. if (await goBtn.count()) {
  43. await goBtn.first().click();
  44. await page.waitForTimeout(2500);
  45. results.push(['点切换 → 进入移动端', page.url().includes('/mobile'), page.url()]);
  46. } else {
  47. results.push(['点切换 → 进入移动端', false, '按钮不存在']);
  48. }
  49. // ---------- ② 管理员用户管理 ----------
  50. await page.setViewportSize({ width: 1440, height: 900 });
  51. await page.goto(`${BASE}/desktop/admin/users`, { waitUntil: 'domcontentloaded' });
  52. await page.waitForTimeout(6000);
  53. await page.screenshot({ path: `${outDir}/2-admin-users.png` });
  54. const url = page.url();
  55. const reached = url.includes('/admin/users');
  56. const blocked = url.includes('/desktop/chat') && !reached;
  57. results.push(['管理员可进入成员管理', reached, url]);
  58. if (blocked) results.push(['非管理员被挡回首页', true, url]);
  59. const hasTable = await page.locator('.table, .rows, ui-empty, .empty').count();
  60. results.push(['页面有内容/三态(不是白屏)', hasTable > 0, `nodes=${hasTable}`]);
  61. // 导航入口:一级菜单只显示 4 项,成员管理在「更多」里
  62. await page.goto(`${BASE}/desktop/chat`, { waitUntil: 'domcontentloaded' });
  63. await page.waitForTimeout(5000);
  64. let navAdmin = await page.locator('.route', { hasText: '成员管理' }).count();
  65. if (navAdmin === 0) {
  66. const more = page.locator('.route-more');
  67. if (await more.count()) {
  68. await more.click();
  69. await page.waitForTimeout(600);
  70. navAdmin = await page.locator('.more-item', { hasText: '成员管理' }).count();
  71. }
  72. }
  73. results.push(['侧边导航有「成员管理」入口', navAdmin > 0, `matches=${navAdmin}`]);
  74. // ---------- 汇总 ----------
  75. let pass = true;
  76. console.log('\n=== 结果 ===');
  77. for (const [name, ok, detail] of results) {
  78. if (!ok) pass = false;
  79. console.log(`${ok ? '✓' : '✗'} ${name} (${detail})`);
  80. }
  81. if (errors.length) {
  82. pass = false;
  83. console.log('页面错误:', errors.slice(0, 3).join(' | '));
  84. }
  85. console.log(`\n${pass ? '✓ 全部通过' : '✗ 存在失败项'}`);
  86. await browser.close();
  87. process.exit(pass ? 0 : 1);