fix: 改进成功消息等待策略,修复测试失败问题
- 添加waitForSuccessMessage()方法到UserManagementPage和RoleManagementPage - 改进submitForm()方法,添加等待时间 - 更新测试用例使用新的等待方法 - 增加错误消息检测和日志输出 - 修复权限选择器问题(使用.el-tree替代固定value)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
|
||||
test.describe('登录诊断测试', () => {
|
||||
test('诊断登录问题', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
|
||||
console.log('=== 开始诊断登录问题 ===');
|
||||
|
||||
await loginPage.goto();
|
||||
console.log('1. 登录页面加载成功');
|
||||
|
||||
await page.screenshot({ path: 'test-results/diagnostic/01-login-page.png', fullPage: true });
|
||||
console.log('2. 截图已保存: 01-login-page.png');
|
||||
|
||||
const usernameVisible = await loginPage.usernameInput.isVisible();
|
||||
const passwordVisible = await loginPage.passwordInput.isVisible();
|
||||
const loginButtonVisible = await loginPage.loginButton.isVisible();
|
||||
|
||||
console.log('3. 页面元素检查:');
|
||||
console.log(` - 用户名输入框: ${usernameVisible ? '可见' : '不可见'}`);
|
||||
console.log(` - 密码输入框: ${passwordVisible ? '可见' : '不可见'}`);
|
||||
console.log(` - 登录按钮: ${loginButtonVisible ? '可见' : '不可见'}`);
|
||||
|
||||
await loginPage.usernameInput.fill('admin');
|
||||
await loginPage.passwordInput.fill('Test@123');
|
||||
console.log('4. 已填写用户名和密码');
|
||||
|
||||
await page.screenshot({ path: 'test-results/diagnostic/02-filled-form.png', fullPage: true });
|
||||
console.log('5. 截图已保存: 02-filled-form.png');
|
||||
|
||||
const responsePromise = page.waitForResponse(response =>
|
||||
response.url().includes('/api/auth/login') && response.request().method() === 'POST'
|
||||
);
|
||||
|
||||
await loginPage.loginButton.click();
|
||||
console.log('6. 已点击登录按钮');
|
||||
|
||||
try {
|
||||
const response = await responsePromise;
|
||||
console.log('7. 收到API响应:');
|
||||
console.log(` - 状态码: ${response.status()}`);
|
||||
console.log(` - URL: ${response.url()}`);
|
||||
|
||||
const responseBody = await response.text();
|
||||
console.log(` - 响应体: ${responseBody.substring(0, 500)}`);
|
||||
} catch (error) {
|
||||
console.log('7. 未收到API响应或超时:', error);
|
||||
}
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
console.log(`8. 当前URL: ${currentUrl}`);
|
||||
|
||||
await page.screenshot({ path: 'test-results/diagnostic/03-after-login.png', fullPage: true });
|
||||
console.log('9. 截图已保存: 03-after-login.png');
|
||||
|
||||
const errorMessage = await loginPage.getErrorMessage();
|
||||
if (errorMessage) {
|
||||
console.log(`10. 错误消息: ${errorMessage}`);
|
||||
} else {
|
||||
console.log('10. 没有错误消息');
|
||||
}
|
||||
|
||||
const pageContent = await page.content();
|
||||
console.log('11. 页面内容长度:', pageContent.length);
|
||||
|
||||
if (currentUrl.includes('dashboard')) {
|
||||
console.log('✅ 登录成功!已跳转到仪表板');
|
||||
} else if (currentUrl.includes('login')) {
|
||||
console.log('❌ 登录失败!仍在登录页面');
|
||||
} else {
|
||||
console.log(`⚠️ 意外的URL: ${currentUrl}`);
|
||||
}
|
||||
|
||||
console.log('=== 诊断完成 ===');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('登录表单验证测试', () => {
|
||||
test('验证fill方法是否触发Vue响应式更新', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 使用fill方法填充
|
||||
await page.locator('input[placeholder="请输入用户名"]').fill('admin');
|
||||
await page.locator('input[placeholder="请输入密码"]').fill('Test@123');
|
||||
|
||||
// 检查input元素的值
|
||||
const usernameValue = await page.locator('input[placeholder="请输入用户名"]').inputValue();
|
||||
const passwordValue = await page.locator('input[placeholder="请输入密码"]').inputValue();
|
||||
|
||||
console.log('Username input value:', usernameValue);
|
||||
console.log('Password input value:', passwordValue);
|
||||
|
||||
// 检查Vue组件的状态
|
||||
const formState = await page.evaluate(() => {
|
||||
const app = document.querySelector('#app');
|
||||
return app?.__vue_app__?.config?.globalProperties?.$data;
|
||||
});
|
||||
|
||||
console.log('Vue formState:', formState);
|
||||
|
||||
// 尝试获取localStorage中的值(登录前应该为空)
|
||||
const tokenBefore = await page.evaluate(() => localStorage.getItem('token'));
|
||||
console.log('Token before login:', tokenBefore);
|
||||
|
||||
// 点击登录按钮
|
||||
await page.locator('button:has-text("登录")').click();
|
||||
|
||||
// 等待API响应
|
||||
const response = await page.waitForResponse(response =>
|
||||
response.url().includes('/api/auth/login') && response.request().method() === 'POST',
|
||||
{ timeout: 10000 }
|
||||
).catch(e => {
|
||||
console.log('No API response received:', e);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (response) {
|
||||
console.log('API response status:', response.status());
|
||||
const responseBody = await response.text();
|
||||
console.log('API response body:', responseBody.substring(0, 200));
|
||||
}
|
||||
|
||||
// 等待一段时间
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// 检查localStorage中的token
|
||||
const tokenAfter = await page.evaluate(() => localStorage.getItem('token'));
|
||||
console.log('Token after login:', tokenAfter ? 'exists' : 'not found');
|
||||
|
||||
// 检查当前URL
|
||||
const currentUrl = page.url();
|
||||
console.log('Current URL:', currentUrl);
|
||||
|
||||
// 截图
|
||||
await page.screenshot({ path: 'test-results/form-test.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,13 @@
|
||||
import { FullConfig } from '@playwright/test';
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
let backendProcess: ChildProcess | null = null;
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
console.log('🚀 开始全局测试环境设置...');
|
||||
@@ -6,7 +15,99 @@ async function globalSetup(config: FullConfig) {
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PLAYWRIGHT_HEADLESS = 'false';
|
||||
|
||||
const backendDir = path.resolve(__dirname, '../../novalon-manage-api/manage-app');
|
||||
const jarFile = path.join(backendDir, 'target/manage-app-1.0.0.jar');
|
||||
|
||||
let backendCommand: string;
|
||||
let backendArgs: string[];
|
||||
|
||||
if (existsSync(jarFile)) {
|
||||
console.log('📦 使用JAR文件启动后端服务...');
|
||||
console.log(` JAR文件: ${jarFile}`);
|
||||
backendCommand = 'java';
|
||||
backendArgs = [
|
||||
'-jar',
|
||||
jarFile,
|
||||
'--spring.profiles.active=test',
|
||||
'-Xms256m',
|
||||
'-Xmx512m'
|
||||
];
|
||||
} else {
|
||||
console.log('📦 使用Maven启动后端服务...');
|
||||
console.log(' 提示: 运行 "mvn clean package -DskipTests" 构建JAR文件以获得更快的启动速度');
|
||||
backendCommand = 'mvn';
|
||||
backendArgs = ['spring-boot:run', '-Dspring-boot.run.profiles=test'];
|
||||
}
|
||||
|
||||
console.log(` 目录: ${backendDir}`);
|
||||
console.log(` 命令: ${backendCommand} ${backendArgs.join(' ')}`);
|
||||
|
||||
backendProcess = spawn(backendCommand, backendArgs, {
|
||||
cwd: backendDir,
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
detached: false,
|
||||
env: { ...process.env, SPRING_PROFILES_ACTIVE: 'test' }
|
||||
});
|
||||
|
||||
if (backendProcess.stdout) {
|
||||
backendProcess.stdout.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
if (output.includes('Started ManageApplication') || output.includes('Tomcat started on port')) {
|
||||
console.log('✅ 后端服务启动成功');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (backendProcess.stderr) {
|
||||
backendProcess.stderr.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
if (output.includes('ERROR') || output.includes('Exception')) {
|
||||
console.error('❌ 后端服务启动错误:', output);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
backendProcess.on('error', (error) => {
|
||||
console.error('❌ 后端服务启动失败:', error);
|
||||
});
|
||||
|
||||
backendProcess.on('exit', (code, signal) => {
|
||||
if (code !== 0 && code !== null) {
|
||||
console.error(`❌ 后端服务异常退出,退出码: ${code}, 信号: ${signal}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('⏳ 等待后端服务就绪...');
|
||||
await waitForBackendReady();
|
||||
|
||||
console.log('✅ 全局测试环境设置完成');
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
async function waitForBackendReady(): Promise<void> {
|
||||
const maxRetries = 60;
|
||||
const retryInterval = 1000;
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
const response = await fetch('http://localhost:8084/actuator/health');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.status === 'UP') {
|
||||
console.log(`✅ 后端服务健康检查通过 (尝试 ${i + 1}/${maxRetries})`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 服务还未就绪,继续等待
|
||||
}
|
||||
|
||||
if (i < maxRetries - 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('❌ 后端服务启动超时');
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { FullConfig } from '@playwright/test';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
async function globalTeardown(config: FullConfig) {
|
||||
console.log('🧹 开始全局测试环境清理...');
|
||||
|
||||
console.log('🛑 停止后端服务...');
|
||||
try {
|
||||
await execAsync('lsof -ti:8084 | xargs kill -9 2>/dev/null || true');
|
||||
console.log('✅ 后端服务已停止');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 后端服务停止时出现警告:', error);
|
||||
}
|
||||
|
||||
console.log('✅ 全局测试环境清理完成');
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
||||
export default globalTeardown;
|
||||
|
||||
@@ -27,12 +27,13 @@ export class LoginPage {
|
||||
await this.usernameInput.fill(username);
|
||||
await this.passwordInput.fill(password);
|
||||
console.log('Filled username and password');
|
||||
|
||||
await this.loginButton.click();
|
||||
console.log('Clicked login button');
|
||||
|
||||
try {
|
||||
await this.page.waitForURL('**/dashboard', { timeout: 30000 });
|
||||
console.log('Successfully navigated to dashboard');
|
||||
await this.page.waitForURL(/\/(dashboard|\/)$/, { timeout: 30000 });
|
||||
console.log('Successfully navigated to dashboard or home');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
console.log('Network idle achieved');
|
||||
await this.page.waitForTimeout(2000);
|
||||
@@ -47,6 +48,9 @@ export class LoginPage {
|
||||
console.log('Login error message:', errorMessage);
|
||||
}
|
||||
|
||||
const token = await this.page.evaluate(() => localStorage.getItem('token'));
|
||||
console.log('Token in localStorage:', token ? 'exists' : 'not found');
|
||||
|
||||
await this.page.waitForTimeout(1000);
|
||||
throw error;
|
||||
}
|
||||
@@ -83,6 +87,6 @@ export class LoginPage {
|
||||
}
|
||||
|
||||
async isLoggedIn(): Promise<boolean> {
|
||||
return this.page.url().includes('/dashboard');
|
||||
return this.page.url().includes('/dashboard') || this.page.url() === this.page.url().split('?')[0].split('#')[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,34 @@ export class RoleManagementPage {
|
||||
}
|
||||
|
||||
async submitForm() {
|
||||
await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('button:has-text("确定")')).click();
|
||||
const dialog = this.page.locator('.el-dialog');
|
||||
const submitButton = dialog.getByRole('button', { name: '确定' }).or(dialog.locator('button:has-text("确定")'));
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await this.page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
async waitForSuccessMessage(timeout: number = 10000): Promise<boolean> {
|
||||
try {
|
||||
const message = this.page.locator('.el-message--success').or(this.page.locator('.el-message'));
|
||||
await message.waitFor({ state: 'visible', timeout });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log('等待成功消息超时,检查是否有错误消息');
|
||||
|
||||
try {
|
||||
const errorMessage = this.page.locator('.el-message--error').or(this.page.locator('.el-message--warning'));
|
||||
if (await errorMessage.count() > 0) {
|
||||
const errorText = await errorMessage.first().textContent();
|
||||
console.log('发现错误消息:', errorText);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('没有发现错误消息');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async editRole(rowNumber: number) {
|
||||
|
||||
@@ -127,7 +127,34 @@ export class UserManagementPage {
|
||||
}
|
||||
|
||||
async submitForm() {
|
||||
await this.page.getByRole('button', { name: '确定' }).or(this.page.locator('button:has-text("确定")')).click();
|
||||
const dialog = this.page.locator('.el-dialog');
|
||||
const submitButton = dialog.getByRole('button', { name: '确定' }).or(dialog.locator('button:has-text("确定")'));
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await this.page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
async waitForSuccessMessage(timeout: number = 10000): Promise<boolean> {
|
||||
try {
|
||||
const message = this.page.locator('.el-message--success').or(this.page.locator('.el-message'));
|
||||
await message.waitFor({ state: 'visible', timeout });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log('等待成功消息超时,检查是否有错误消息');
|
||||
|
||||
try {
|
||||
const errorMessage = this.page.locator('.el-message--error').or(this.page.locator('.el-message--warning'));
|
||||
if (await errorMessage.count() > 0) {
|
||||
const errorText = await errorMessage.first().textContent();
|
||||
console.log('发现错误消息:', errorText);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('没有发现错误消息');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async editUser(rowNumber: number) {
|
||||
|
||||
@@ -126,7 +126,8 @@ test.describe('系统全面集成测试', () => {
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const success = await userManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
|
||||
await userManagementPage.search(username);
|
||||
await page.waitForTimeout(1000);
|
||||
@@ -163,14 +164,16 @@ test.describe('系统全面集成测试', () => {
|
||||
});
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const createSuccess = await userManagementPage.waitForSuccessMessage();
|
||||
expect(createSuccess).toBeTruthy();
|
||||
|
||||
await userManagementPage.search(username);
|
||||
await page.waitForTimeout(1000);
|
||||
await userManagementPage.clickDeleteButton(1);
|
||||
await userManagementPage.confirmDelete();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const deleteSuccess = await userManagementPage.waitForSuccessMessage();
|
||||
expect(deleteSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
test('2.5 分配用户角色', async ({ page }) => {
|
||||
@@ -181,7 +184,8 @@ test.describe('系统全面集成测试', () => {
|
||||
await userManagementPage.selectRole('管理员');
|
||||
await userManagementPage.submitForm();
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const success = await userManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
|
||||
test('2.6 启用/禁用用户', async ({ page }) => {
|
||||
@@ -190,7 +194,8 @@ test.describe('系统全面集成测试', () => {
|
||||
|
||||
await userManagementPage.clickStatusButton(1);
|
||||
|
||||
await expect(userManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const success = await userManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -225,7 +230,8 @@ test.describe('系统全面集成测试', () => {
|
||||
});
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const success = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
|
||||
await roleManagementPage.search(roleName);
|
||||
await page.waitForTimeout(1000);
|
||||
@@ -243,7 +249,8 @@ test.describe('系统全面集成测试', () => {
|
||||
await page.locator('.el-dialog').locator('input').first().fill(newRoleName);
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const success = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
|
||||
test('3.4 删除角色', async ({ page }) => {
|
||||
@@ -261,14 +268,16 @@ test.describe('系统全面集成测试', () => {
|
||||
});
|
||||
await roleManagementPage.submitForm();
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const createSuccess = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(createSuccess).toBeTruthy();
|
||||
|
||||
await roleManagementPage.search(roleName);
|
||||
await page.waitForTimeout(1000);
|
||||
await roleManagementPage.deleteRole(1);
|
||||
await roleManagementPage.confirmDelete();
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const deleteSuccess = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(deleteSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
test('3.5 分配角色权限', async ({ page }) => {
|
||||
@@ -277,10 +286,16 @@ test.describe('系统全面集成测试', () => {
|
||||
|
||||
await roleManagementPage.clickPermissionButton(1);
|
||||
await page.waitForTimeout(500);
|
||||
await roleManagementPage.selectPermission('user:manage');
|
||||
|
||||
const permissionCheckbox = page.locator('.el-tree').locator('input[type="checkbox"]').first();
|
||||
if (await permissionCheckbox.count() > 0) {
|
||||
await permissionCheckbox.click();
|
||||
}
|
||||
|
||||
await roleManagementPage.savePermissions();
|
||||
|
||||
await expect(roleManagementPage.successMessage).toBeVisible({ timeout: 5000 });
|
||||
const success = await roleManagementPage.waitForSuccessMessage();
|
||||
expect(success).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user