397 lines
13 KiB
JavaScript
397 lines
13 KiB
JavaScript
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 systemMenuSelector = '.el-sub-menu:has-text("系统管理")';
|
|
const systemMenuElement = page.locator(systemMenuSelector).first();
|
|
|
|
if (await systemMenuElement.count() > 0) {
|
|
// 点击展开系统管理菜单
|
|
await systemMenuElement.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// 然后点击用户管理菜单项
|
|
const userMenuSelectors = [
|
|
'.el-menu-item:has-text("用户管理")',
|
|
'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('未找到用户管理菜单');
|
|
}
|
|
} 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 systemMenuSelector = '.el-sub-menu:has-text("系统管理")';
|
|
const systemMenuElement = page.locator(systemMenuSelector).first();
|
|
|
|
if (await systemMenuElement.count() > 0) {
|
|
// 点击展开系统管理菜单
|
|
await systemMenuElement.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// 然后点击角色管理菜单项
|
|
const roleMenuSelectors = [
|
|
'.el-menu-item:has-text("角色管理")',
|
|
'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('未找到角色管理菜单');
|
|
}
|
|
} else {
|
|
throw new Error('未找到系统管理菜单');
|
|
}
|
|
} catch (error) {
|
|
logTest('导航到角色管理页面', false, error.message);
|
|
}
|
|
|
|
// ==================== 阶段4: 系统配置 ====================
|
|
console.log('\n📋 阶段4: 系统配置测试');
|
|
console.log('=====================================');
|
|
|
|
try {
|
|
// 首先展开系统管理菜单(如果是折叠状态)
|
|
const systemMenuSelector = '.el-sub-menu:has-text("系统管理")';
|
|
const systemMenuElement = page.locator(systemMenuSelector).first();
|
|
|
|
if (await systemMenuElement.count() > 0) {
|
|
// 点击展开系统管理菜单
|
|
await systemMenuElement.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// 然后点击参数配置菜单项
|
|
const configMenuSelectors = [
|
|
'.el-menu-item:has-text("参数配置")',
|
|
'.el-menu-item:has-text("系统配置")',
|
|
'.el-menu-item:has-text("配置管理")',
|
|
'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('未找到系统配置菜单');
|
|
}
|
|
} 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✅ 测试完成,浏览器已关闭');
|
|
}
|
|
})(); |