#!/usr/bin/env node /** * 成员管理全链路验证(2026-11-10) * * 云函数已部署,这里验证真实数据流: * ① 列表加载出真实成员 * ② 搜索能过滤 * ③ 分页条工作 * ④ 编辑弹窗能打开并回填 * ⑤ 删除确认弹窗能打开(**不实际执行删除**) */ 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', 'admin-users-e2e'); 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 page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); const errors = []; page.on('pageerror', (e) => errors.push(String(e))); const results = []; await page.goto(`${BASE}/desktop/admin/users`, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(7000); await page.screenshot({ path: `${outDir}/1-list.png` }); // ① 真实数据 const rows = await page.locator('.tbody .tr').count(); const isEmpty = await page.locator('ui-empty', { hasText: '加载失败' }).count(); results.push(['列表加载出真实成员', rows > 0 && isEmpty === 0, `rows=${rows} error=${isEmpty}`]); const totalText = await page.locator('.total').innerText().catch(() => ''); results.push(['显示总人数', /共\s*\d+\s*人/.test(totalText), totalText]); // ② 搜索 const firstMobile = await page.locator('.tbody .tr .mono').first().innerText().catch(() => ''); if (firstMobile && firstMobile !== '—') { await page.locator('input.search').fill(firstMobile); await page.locator('ui-button', { hasText: '搜索' }).first().click(); await page.waitForTimeout(2500); const afterSearch = await page.locator('.tbody .tr').count(); await page.screenshot({ path: `${outDir}/2-search.png` }); results.push(['搜索能过滤', afterSearch >= 1 && afterSearch <= rows, `before=${rows} after=${afterSearch}`]); // 还原 await page.locator('input.search').fill(''); await page.locator('ui-button', { hasText: '搜索' }).first().click(); await page.waitForTimeout(2000); } else { results.push(['搜索能过滤', false, '取不到手机号列']); } // ③ 分页 const pagerCount = await page.locator('.pager ui-pagination').count(); results.push(['有分页条', pagerCount > 0, `pager=${pagerCount}`]); // ④ 编辑弹窗 const editBtn = page.locator('ui-button', { hasText: '编辑' }).first(); if (await editBtn.count()) { await editBtn.click(); await page.waitForTimeout(900); const dialogTitle = await page.locator('ui-modal', { hasText: '编辑成员' }).count(); const nickInput = await page.locator('ui-modal input').first().inputValue().catch(() => ''); await page.screenshot({ path: `${outDir}/3-edit-modal.png` }); results.push(['编辑弹窗打开并回填', dialogTitle > 0, `title=${dialogTitle} nick=${nickInput}`]); // 关闭 await page.locator('ui-modal ui-button', { hasText: '取消' }).first().click().catch(() => {}); await page.waitForTimeout(600); } else { results.push(['编辑弹窗打开并回填', false, '没有编辑按钮']); } // ⑤ 删除二次确认(只打开,不确认) const delBtn = page.locator('ui-button', { hasText: '移除' }).first(); if (await delBtn.count()) { await delBtn.click(); await page.waitForTimeout(900); const confirm = await page.locator('ui-modal', { hasText: '确认移除' }).count(); await page.screenshot({ path: `${outDir}/4-remove-confirm.png` }); results.push(['移除走二次确认', confirm > 0, `confirm=${confirm}`]); await page.locator('ui-modal ui-button', { hasText: '取消' }).first().click().catch(() => {}); } else { results.push(['移除走二次确认', false, '没有移除按钮']); } // 汇总 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);