diff --git a/novalon-manage-web/user-journey-test.js b/novalon-manage-web/user-journey-test.js new file mode 100644 index 0000000..057bc43 --- /dev/null +++ b/novalon-manage-web/user-journey-test.js @@ -0,0 +1,354 @@ +import { chromium } from 'playwright'; +import { writeFileSync } from 'fs'; + +// 配置参数 +const TARGET_URL = process.env.TARGET_URL || 'http://localhost:3002'; +const API_URL = process.env.API_URL || 'http://localhost:8080'; +const TEST_USER = { + username: 'admin', + password: 'admin123' +}; + +// 测试结果收集 +const testResults = { + total: 0, + passed: 0, + failed: 0, + errors: [] +}; + +// 辅助函数:记录测试结果 +function logTest(testName, passed, error = null) { + testResults.total++; + if (passed) { + testResults.passed++; + console.log(`✅ ${testName}`); + } else { + testResults.failed++; + testResults.errors.push({ testName, error }); + console.log(`❌ ${testName}: ${error}`); + } +} + +// 辅助函数:等待并截图 +async function captureStep(page, stepName) { + const screenshotPath = `/tmp/user-journey-${stepName}.png`; + await page.screenshot({ path: screenshotPath, fullPage: true }); + console.log(`📸 Screenshot saved: ${screenshotPath}`); +} + +(async () => { + console.log('🚀 开始User Journey测试...'); + console.log(`📍 目标URL: ${TARGET_URL}`); + console.log(`📍 API URL: ${API_URL}`); + console.log(''); + + const browser = await chromium.launch({ + headless: false, + slowMo: 100 + }); + + const context = await browser.newContext({ + viewport: { width: 1920, height: 1080 }, + locale: 'zh-CN' + }); + + const page = await context.newPage(); + + try { + // ==================== 阶段1: 登录流程 ==================== + console.log('📋 阶段1: 登录流程测试'); + console.log('====================================='); + + // 1.1 访问登录页面 + try { + await page.goto(`${TARGET_URL}/login`, { waitUntil: 'networkidle' }); + await captureStep(page, '01-login-page'); + logTest('访问登录页面', true); + } catch (error) { + logTest('访问登录页面', false, error.message); + } + + // 1.2 验证登录页面元素 + try { + const usernameInput = page.locator('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]').first(); + const passwordInput = page.locator('input[type="password"]').first(); + const loginButton = page.locator('button:has-text("登录")').first(); + + await usernameInput.waitFor({ state: 'visible', timeout: 5000 }); + await passwordInput.waitFor({ state: 'visible', timeout: 5000 }); + await loginButton.waitFor({ state: 'visible', timeout: 5000 }); + + logTest('登录页面元素验证', true); + } catch (error) { + logTest('登录页面元素验证', false, error.message); + } + + // 1.3 填写登录表单 + try { + const usernameInput = page.locator('input[type="text"], input[placeholder*="用户名"], input[placeholder*="账号"]').first(); + const passwordInput = page.locator('input[type="password"]').first(); + + await usernameInput.fill(TEST_USER.username); + await passwordInput.fill(TEST_USER.password); + + await captureStep(page, '02-login-form-filled'); + logTest('填写登录表单', true); + } catch (error) { + logTest('填写登录表单', false, error.message); + } + + // 1.4 提交登录 + try { + const loginButton = page.locator('button:has-text("登录")').first(); + + // 监听登录响应 + const responsePromise = page.waitForResponse(response => + response.url().includes('/api/auth/login') && response.request().method() === 'POST', + { timeout: 10000 } + ); + + await loginButton.click(); + + const response = await responsePromise; + const loginData = await response.json(); + + if (response.status() === 200 && loginData.token) { + console.log(`🔑 登录成功,Token: ${loginData.token.substring(0, 20)}...`); + logTest('提交登录表单', true); + } else { + throw new Error(`登录失败: ${JSON.stringify(loginData)}`); + } + } catch (error) { + logTest('提交登录表单', false, error.message); + } + + // 1.5 等待页面跳转 + try { + await page.waitForTimeout(2000); + const currentUrl = page.url(); + + if (currentUrl.includes('dashboard') || currentUrl.includes('home') || !currentUrl.includes('login')) { + await captureStep(page, '03-after-login'); + logTest('登录后页面跳转', true); + } else { + throw new Error(`未跳转到主页,当前URL: ${currentUrl}`); + } + } catch (error) { + logTest('登录后页面跳转', false, error.message); + } + + // ==================== 阶段2: 用户管理 ==================== + console.log('\n📋 阶段2: 用户管理测试'); + console.log('====================================='); + + // 2.1 导航到用户管理页面 + try { + // 尝试多种可能的导航方式 + const userMenuSelectors = [ + 'text=用户管理', + 'text=用户', + '[data-menu="user"]', + 'a[href*="user"]' + ]; + + let navigated = false; + for (const selector of userMenuSelectors) { + const element = page.locator(selector).first(); + if (await element.count() > 0) { + await element.click(); + navigated = true; + break; + } + } + + if (navigated) { + await page.waitForTimeout(1000); + await captureStep(page, '04-user-management'); + logTest('导航到用户管理页面', true); + } else { + throw new Error('未找到用户管理菜单'); + } + } catch (error) { + logTest('导航到用户管理页面', false, error.message); + } + + // 2.2 验证用户列表 + try { + // 检查是否有用户列表或表格 + const tableSelectors = [ + 'table', + '.el-table', + '[class*="table"]', + '.user-list' + ]; + + let foundTable = false; + for (const selector of tableSelectors) { + const count = await page.locator(selector).count(); + if (count > 0) { + foundTable = true; + break; + } + } + + if (foundTable) { + logTest('用户列表显示', true); + } else { + throw new Error('未找到用户列表表格'); + } + } catch (error) { + logTest('用户列表显示', false, error.message); + } + + // ==================== 阶段3: 角色管理 ==================== + console.log('\n📋 阶段3: 角色管理测试'); + console.log('====================================='); + + try { + const roleMenuSelectors = [ + 'text=角色管理', + 'text=角色', + '[data-menu="role"]', + 'a[href*="role"]' + ]; + + let navigated = false; + for (const selector of roleMenuSelectors) { + const element = page.locator(selector).first(); + if (await element.count() > 0) { + await element.click(); + navigated = true; + break; + } + } + + if (navigated) { + await page.waitForTimeout(1000); + await captureStep(page, '05-role-management'); + logTest('导航到角色管理页面', true); + } else { + throw new Error('未找到角色管理菜单'); + } + } catch (error) { + logTest('导航到角色管理页面', false, error.message); + } + + // ==================== 阶段4: 系统配置 ==================== + console.log('\n📋 阶段4: 系统配置测试'); + console.log('====================================='); + + try { + const configMenuSelectors = [ + 'text=系统配置', + 'text=配置管理', + 'text=配置', + '[data-menu="config"]', + 'a[href*="config"]' + ]; + + let navigated = false; + for (const selector of configMenuSelectors) { + const element = page.locator(selector).first(); + if (await element.count() > 0) { + await element.click(); + navigated = true; + break; + } + } + + if (navigated) { + await page.waitForTimeout(1000); + await captureStep(page, '06-system-config'); + logTest('导航到系统配置页面', true); + } else { + throw new Error('未找到系统配置菜单'); + } + } catch (error) { + logTest('导航到系统配置页面', false, error.message); + } + + // ==================== 阶段5: 登出流程 ==================== + console.log('\n📋 阶段5: 登出流程测试'); + console.log('====================================='); + + try { + // 首先点击用户头像以展开下拉菜单 + const avatarSelector = '.el-avatar'; + const avatarElement = page.locator(avatarSelector).first(); + + if (await avatarElement.count() > 0) { + await avatarElement.click(); + await page.waitForTimeout(500); // 等待下拉菜单展开 + + // 然后点击退出登录按钮 + const logoutSelectors = [ + '.el-dropdown-menu__item:has-text("退出登录")', + '.el-dropdown-menu__item:has-text("退出")', + '.el-dropdown-menu__item:has-text("登出")', + 'button:has-text("退出")', + 'button:has-text("登出")', + 'a:has-text("退出")', + 'a:has-text("登出")', + '[data-action="logout"]', + '.logout-button' + ]; + + let loggedOut = false; + for (const selector of logoutSelectors) { + const element = page.locator(selector).first(); + if (await element.count() > 0) { + await element.click(); + loggedOut = true; + break; + } + } + + if (loggedOut) { + await page.waitForTimeout(2000); + const currentUrl = page.url(); + + if (currentUrl.includes('login')) { + await captureStep(page, '07-after-logout'); + logTest('登出成功', true); + } else { + throw new Error(`登出后未跳转到登录页,当前URL: ${currentUrl}`); + } + } else { + throw new Error('未找到登出按钮'); + } + } else { + throw new Error('未找到用户头像'); + } + } catch (error) { + logTest('登出成功', false, error.message); + } + + // ==================== 测试总结 ==================== + console.log('\n📊 测试总结'); + console.log('====================================='); + console.log(`总测试数: ${testResults.total}`); + console.log(`通过: ${testResults.passed}`); + console.log(`失败: ${testResults.failed}`); + console.log(`通过率: ${((testResults.passed / testResults.total) * 100).toFixed(2)}%`); + + if (testResults.failed > 0) { + console.log('\n❌ 失败的测试:'); + testResults.errors.forEach((error, index) => { + console.log(`${index + 1}. ${error.testName}: ${error.error}`); + }); + } + + // 保存测试报告 + const reportPath = '/tmp/user-journey-report.json'; + writeFileSync(reportPath, JSON.stringify(testResults, null, 2)); + console.log(`\n📄 测试报告已保存: ${reportPath}`); + + } catch (error) { + console.error('❌ 测试执行出错:', error); + await captureStep(page, 'error-state'); + } finally { + await browser.close(); + console.log('\n✅ 测试完成,浏览器已关闭'); + } +})(); \ No newline at end of file