verify-admin-users-e2e.mjs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. #!/usr/bin/env node
  2. /**
  3. * 成员管理全链路验证(2026-11-10)
  4. *
  5. * 云函数已部署,这里验证真实数据流:
  6. * ① 列表加载出真实成员
  7. * ② 搜索能过滤
  8. * ③ 分页条工作
  9. * ④ 编辑弹窗能打开并回填
  10. * ⑤ 删除确认弹窗能打开(**不实际执行删除**)
  11. */
  12. import { chromium } from 'playwright';
  13. import { mkdir } from 'node:fs/promises';
  14. import { dirname, resolve } from 'node:path';
  15. import { fileURLToPath } from 'node:url';
  16. const here = dirname(fileURLToPath(import.meta.url));
  17. const outDir = resolve(here, '..', 'test-results', 'admin-users-e2e');
  18. const BASE = process.env.BASE_URL || 'http://127.0.0.1:4300';
  19. await mkdir(outDir, { recursive: true });
  20. const browser = await chromium.launch({ channel: 'chrome' });
  21. const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
  22. const errors = [];
  23. page.on('pageerror', (e) => errors.push(String(e)));
  24. const results = [];
  25. await page.goto(`${BASE}/desktop/admin/users`, { waitUntil: 'domcontentloaded' });
  26. await page.waitForTimeout(7000);
  27. await page.screenshot({ path: `${outDir}/1-list.png` });
  28. // ① 真实数据
  29. const rows = await page.locator('.tbody .tr').count();
  30. const isEmpty = await page.locator('ui-empty', { hasText: '加载失败' }).count();
  31. results.push(['列表加载出真实成员', rows > 0 && isEmpty === 0, `rows=${rows} error=${isEmpty}`]);
  32. const totalText = await page.locator('.total').innerText().catch(() => '');
  33. results.push(['显示总人数', /共\s*\d+\s*人/.test(totalText), totalText]);
  34. // ② 搜索
  35. const firstMobile = await page.locator('.tbody .tr .mono').first().innerText().catch(() => '');
  36. if (firstMobile && firstMobile !== '—') {
  37. await page.locator('input.search').fill(firstMobile);
  38. await page.locator('ui-button', { hasText: '搜索' }).first().click();
  39. await page.waitForTimeout(2500);
  40. const afterSearch = await page.locator('.tbody .tr').count();
  41. await page.screenshot({ path: `${outDir}/2-search.png` });
  42. results.push(['搜索能过滤', afterSearch >= 1 && afterSearch <= rows, `before=${rows} after=${afterSearch}`]);
  43. // 还原
  44. await page.locator('input.search').fill('');
  45. await page.locator('ui-button', { hasText: '搜索' }).first().click();
  46. await page.waitForTimeout(2000);
  47. } else {
  48. results.push(['搜索能过滤', false, '取不到手机号列']);
  49. }
  50. // ③ 分页
  51. const pagerCount = await page.locator('.pager ui-pagination').count();
  52. results.push(['有分页条', pagerCount > 0, `pager=${pagerCount}`]);
  53. // ④ 编辑弹窗
  54. const editBtn = page.locator('ui-button', { hasText: '编辑' }).first();
  55. if (await editBtn.count()) {
  56. await editBtn.click();
  57. await page.waitForTimeout(900);
  58. const dialogTitle = await page.locator('ui-modal', { hasText: '编辑成员' }).count();
  59. const nickInput = await page.locator('ui-modal input').first().inputValue().catch(() => '');
  60. await page.screenshot({ path: `${outDir}/3-edit-modal.png` });
  61. results.push(['编辑弹窗打开并回填', dialogTitle > 0, `title=${dialogTitle} nick=${nickInput}`]);
  62. // 关闭
  63. await page.locator('ui-modal ui-button', { hasText: '取消' }).first().click().catch(() => {});
  64. await page.waitForTimeout(600);
  65. } else {
  66. results.push(['编辑弹窗打开并回填', false, '没有编辑按钮']);
  67. }
  68. // ⑤ 删除二次确认(只打开,不确认)
  69. const delBtn = page.locator('ui-button', { hasText: '移除' }).first();
  70. if (await delBtn.count()) {
  71. await delBtn.click();
  72. await page.waitForTimeout(900);
  73. const confirm = await page.locator('ui-modal', { hasText: '确认移除' }).count();
  74. await page.screenshot({ path: `${outDir}/4-remove-confirm.png` });
  75. results.push(['移除走二次确认', confirm > 0, `confirm=${confirm}`]);
  76. await page.locator('ui-modal ui-button', { hasText: '取消' }).first().click().catch(() => {});
  77. } else {
  78. results.push(['移除走二次确认', false, '没有移除按钮']);
  79. }
  80. // 汇总
  81. let pass = true;
  82. console.log('\n=== 结果 ===');
  83. for (const [name, ok, detail] of results) {
  84. if (!ok) pass = false;
  85. console.log(`${ok ? '✓' : '✗'} ${name} (${detail})`);
  86. }
  87. if (errors.length) {
  88. pass = false;
  89. console.log('页面错误:', errors.slice(0, 3).join(' | '));
  90. }
  91. console.log(`\n${pass ? '✓ 全部通过' : '✗ 存在失败项'}`);
  92. await browser.close();
  93. process.exit(pass ? 0 : 1);