Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
358 lines
11 KiB
TypeScript
358 lines
11 KiB
TypeScript
/**
|
|
* 登录和认证流程端到端测试
|
|
* 使用 Playwright 进行自动化测试
|
|
*/
|
|
|
|
import { test, expect, Page } from '@playwright/test';
|
|
|
|
// 测试配置
|
|
const TEST_CONFIG = {
|
|
baseURL: 'http://localhost:5173',
|
|
timeout: 30000,
|
|
// 测试账号信息
|
|
testUser: {
|
|
username: 'admin',
|
|
password: '111111',
|
|
},
|
|
// 超级管理员账号
|
|
superAdmin: {
|
|
username: 'super_admin',
|
|
password: 'super123456',
|
|
},
|
|
// 普通用户账号
|
|
normalUser: {
|
|
username: 'test_user',
|
|
password: 'test123456',
|
|
},
|
|
};
|
|
|
|
test.describe('登录和认证流程测试', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// 设置较长的超时时间
|
|
test.setTimeout(TEST_CONFIG.timeout);
|
|
|
|
// 访问登录页面
|
|
await page.goto(TEST_CONFIG.baseURL);
|
|
|
|
// 等待页面加载完成
|
|
await page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
test('应该正确显示登录页面', async ({ page }) => {
|
|
// 检查页面标题
|
|
await expect(page).toHaveTitle(/Telegram Management System|登录/);
|
|
|
|
// 检查登录表单元素是否存在
|
|
await expect(page.locator('[data-testid="username-input"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="password-input"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="login-button"]')).toBeVisible();
|
|
|
|
// 检查记住密码复选框
|
|
await expect(page.locator('[data-testid="remember-me"]')).toBeVisible();
|
|
|
|
// 检查语言切换按钮
|
|
await expect(
|
|
page.locator('[data-testid="language-switcher"]'),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('空用户名密码应该显示验证错误', async ({ page }) => {
|
|
// 直接点击登录按钮
|
|
await page.click('[data-testid="login-button"]');
|
|
|
|
// 等待验证错误消息出现
|
|
await expect(page.locator('.ant-form-item-explain-error')).toContainText(
|
|
'请输入用户名',
|
|
);
|
|
});
|
|
|
|
test('错误的用户名密码应该显示错误信息', async ({ page }) => {
|
|
// 输入错误的用户名和密码
|
|
await page.fill('[data-testid="username-input"]', 'wrong_user');
|
|
await page.fill('[data-testid="password-input"]', 'wrong_password');
|
|
|
|
// 点击登录按钮
|
|
await page.click('[data-testid="login-button"]');
|
|
|
|
// 等待错误消息出现
|
|
await expect(page.locator('.ant-message-error')).toBeVisible();
|
|
await expect(page.locator('.ant-message-error')).toContainText(
|
|
/用户名或密码错误|登录失败/,
|
|
);
|
|
});
|
|
|
|
test('正确的用户名密码应该成功登录', async ({ page }) => {
|
|
await loginWithCredentials(page, TEST_CONFIG.testUser);
|
|
|
|
// 验证登录成功后跳转到首页
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
|
|
// 检查用户信息是否正确显示
|
|
await expect(page.locator('[data-testid="user-avatar"]')).toBeVisible();
|
|
await expect(
|
|
page.locator('[data-testid="username-display"]'),
|
|
).toContainText(TEST_CONFIG.testUser.username);
|
|
});
|
|
|
|
test('记住密码功能应该正常工作', async ({ page }) => {
|
|
// 输入用户名密码
|
|
await page.fill(
|
|
'[data-testid="username-input"]',
|
|
TEST_CONFIG.testUser.username,
|
|
);
|
|
await page.fill(
|
|
'[data-testid="password-input"]',
|
|
TEST_CONFIG.testUser.password,
|
|
);
|
|
|
|
// 勾选记住密码
|
|
await page.check('[data-testid="remember-me"]');
|
|
|
|
// 登录
|
|
await page.click('[data-testid="login-button"]');
|
|
|
|
// 等待登录完成
|
|
await page.waitForURL(/\/dashboard/);
|
|
|
|
// 退出登录
|
|
await page.click('[data-testid="user-dropdown"]');
|
|
await page.click('[data-testid="logout-button"]');
|
|
|
|
// 回到登录页面,检查是否记住了用户名
|
|
await page.waitForURL(/\/login/);
|
|
await expect(page.locator('[data-testid="username-input"]')).toHaveValue(
|
|
TEST_CONFIG.testUser.username,
|
|
);
|
|
});
|
|
|
|
test('语言切换功能应该正常工作', async ({ page }) => {
|
|
// 点击语言切换按钮
|
|
await page.click('[data-testid="language-switcher"]');
|
|
|
|
// 选择英文
|
|
await page.click('[data-testid="lang-en"]');
|
|
|
|
// 检查页面是否切换到英文
|
|
await expect(page.locator('[data-testid="login-title"]')).toContainText(
|
|
/Login|Sign In/,
|
|
);
|
|
|
|
// 切换回中文
|
|
await page.click('[data-testid="language-switcher"]');
|
|
await page.click('[data-testid="lang-zh"]');
|
|
|
|
// 检查页面是否切换回中文
|
|
await expect(page.locator('[data-testid="login-title"]')).toContainText(
|
|
/登录|用户登录/,
|
|
);
|
|
});
|
|
|
|
test('Token过期应该自动跳转到登录页', async ({ page }) => {
|
|
// 先正常登录
|
|
await loginWithCredentials(page, TEST_CONFIG.testUser);
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
|
|
// 模拟Token过期 - 清除localStorage中的token
|
|
await page.evaluate(() => {
|
|
localStorage.removeItem('access_token');
|
|
localStorage.removeItem('refresh_token');
|
|
});
|
|
|
|
// 尝试访问需要认证的页面
|
|
await page.goto(`${TEST_CONFIG.baseURL}/account/list`);
|
|
|
|
// 应该自动跳转到登录页
|
|
await expect(page).toHaveURL(/\/login/);
|
|
|
|
// 应该显示登录过期提示
|
|
await expect(page.locator('.ant-message-warning')).toContainText(
|
|
/登录过期|请重新登录/,
|
|
);
|
|
});
|
|
|
|
test('退出登录应该清除认证状态', async ({ page }) => {
|
|
// 先登录
|
|
await loginWithCredentials(page, TEST_CONFIG.testUser);
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
|
|
// 退出登录
|
|
await page.click('[data-testid="user-dropdown"]');
|
|
await page.click('[data-testid="logout-button"]');
|
|
|
|
// 应该跳转到登录页
|
|
await expect(page).toHaveURL(/\/login/);
|
|
|
|
// 验证Token已被清除
|
|
const accessToken = await page.evaluate(() =>
|
|
localStorage.getItem('access_token'),
|
|
);
|
|
expect(accessToken).toBeNull();
|
|
|
|
// 尝试直接访问受保护页面应该被重定向
|
|
await page.goto(`${TEST_CONFIG.baseURL}/account/list`);
|
|
await expect(page).toHaveURL(/\/login/);
|
|
});
|
|
|
|
test('刷新页面应该保持登录状态', async ({ page }) => {
|
|
// 登录
|
|
await loginWithCredentials(page, TEST_CONFIG.testUser);
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
|
|
// 刷新页面
|
|
await page.reload();
|
|
|
|
// 应该仍然保持登录状态
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
await expect(page.locator('[data-testid="user-avatar"]')).toBeVisible();
|
|
});
|
|
|
|
test('多标签页登录状态应该同步', async ({ context }) => {
|
|
const page1 = await context.newPage();
|
|
const page2 = await context.newPage();
|
|
|
|
// 在第一个标签页登录
|
|
await page1.goto(TEST_CONFIG.baseURL);
|
|
await loginWithCredentials(page1, TEST_CONFIG.testUser);
|
|
await expect(page1).toHaveURL(/\/dashboard/);
|
|
|
|
// 在第二个标签页访问应用
|
|
await page2.goto(TEST_CONFIG.baseURL);
|
|
|
|
// 第二个标签页应该也是登录状态
|
|
await expect(page2).toHaveURL(/\/dashboard/);
|
|
await expect(page2.locator('[data-testid="user-avatar"]')).toBeVisible();
|
|
|
|
// 在第一个标签页退出登录
|
|
await page1.click('[data-testid="user-dropdown"]');
|
|
await page1.click('[data-testid="logout-button"]');
|
|
|
|
// 刷新第二个标签页,应该也被退出登录
|
|
await page2.reload();
|
|
await expect(page2).toHaveURL(/\/login/);
|
|
});
|
|
});
|
|
|
|
test.describe('权限和角色测试', () => {
|
|
test('超级管理员应该能访问所有页面', async ({ page }) => {
|
|
await page.goto(TEST_CONFIG.baseURL);
|
|
await loginWithCredentials(page, TEST_CONFIG.superAdmin);
|
|
|
|
// 检查菜单项是否完整显示
|
|
const menuItems = [
|
|
'[data-testid="menu-dashboard"]',
|
|
'[data-testid="menu-account"]',
|
|
'[data-testid="menu-system"]',
|
|
'[data-testid="menu-logs"]',
|
|
'[data-testid="menu-marketing"]',
|
|
];
|
|
|
|
for (const menuItem of menuItems) {
|
|
await expect(page.locator(menuItem)).toBeVisible();
|
|
}
|
|
|
|
// 测试访问系统管理页面
|
|
await page.click('[data-testid="menu-system"]');
|
|
await page.click('[data-testid="menu-permission-management"]');
|
|
await expect(page).toHaveURL(/\/system\/permission/);
|
|
});
|
|
|
|
test('普通用户应该只能访问授权页面', async ({ page }) => {
|
|
await page.goto(TEST_CONFIG.baseURL);
|
|
await loginWithCredentials(page, TEST_CONFIG.normalUser);
|
|
|
|
// 检查某些管理功能不可见
|
|
await expect(page.locator('[data-testid="menu-system"]')).not.toBeVisible();
|
|
|
|
// 尝试直接访问受限页面应该被拒绝
|
|
await page.goto(`${TEST_CONFIG.baseURL}/system/permission`);
|
|
await expect(page.locator('.ant-result-403')).toBeVisible();
|
|
await expect(page.locator('.ant-result-title')).toContainText('403');
|
|
});
|
|
|
|
test('按钮级权限控制应该正常工作', async ({ page }) => {
|
|
await page.goto(TEST_CONFIG.baseURL);
|
|
await loginWithCredentials(page, TEST_CONFIG.normalUser);
|
|
|
|
// 访问账号列表页面
|
|
await page.goto(`${TEST_CONFIG.baseURL}/account/list`);
|
|
|
|
// 检查删除按钮是否被隐藏(假设普通用户没有删除权限)
|
|
await expect(
|
|
page.locator('[data-testid="delete-button"]'),
|
|
).not.toBeVisible();
|
|
|
|
// 但查看按钮应该可见
|
|
await expect(page.locator('[data-testid="view-button"]')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('国际化功能测试', () => {
|
|
test('语言切换应该影响整个应用', async ({ page }) => {
|
|
await page.goto(TEST_CONFIG.baseURL);
|
|
await loginWithCredentials(page, TEST_CONFIG.testUser);
|
|
|
|
// 切换到英文
|
|
await page.click('[data-testid="language-switcher"]');
|
|
await page.click('[data-testid="lang-en"]');
|
|
|
|
// 检查导航菜单是否切换到英文
|
|
await expect(page.locator('[data-testid="menu-dashboard"]')).toContainText(
|
|
/Dashboard/,
|
|
);
|
|
await expect(page.locator('[data-testid="menu-account"]')).toContainText(
|
|
/Account/,
|
|
);
|
|
|
|
// 切换回中文
|
|
await page.click('[data-testid="language-switcher"]');
|
|
await page.click('[data-testid="lang-zh"]');
|
|
|
|
// 检查是否切换回中文
|
|
await expect(page.locator('[data-testid="menu-dashboard"]')).toContainText(
|
|
/仪表盘|首页/,
|
|
);
|
|
await expect(page.locator('[data-testid="menu-account"]')).toContainText(
|
|
/账号管理/,
|
|
);
|
|
});
|
|
|
|
test('语言偏好应该被持久化', async ({ page }) => {
|
|
await page.goto(TEST_CONFIG.baseURL);
|
|
|
|
// 在登录页切换到英文
|
|
await page.click('[data-testid="language-switcher"]');
|
|
await page.click('[data-testid="lang-en"]');
|
|
|
|
// 登录
|
|
await loginWithCredentials(page, TEST_CONFIG.testUser);
|
|
|
|
// 刷新页面,语言设置应该保持
|
|
await page.reload();
|
|
await expect(page.locator('[data-testid="menu-dashboard"]')).toContainText(
|
|
/Dashboard/,
|
|
);
|
|
|
|
// 退出重新登录,语言设置也应该保持
|
|
await page.click('[data-testid="user-dropdown"]');
|
|
await page.click('[data-testid="logout-button"]');
|
|
|
|
await page.waitForURL(/\/login/);
|
|
await expect(page.locator('[data-testid="login-title"]')).toContainText(
|
|
/Login|Sign In/,
|
|
);
|
|
});
|
|
});
|
|
|
|
// 辅助函数:使用指定凭据登录
|
|
async function loginWithCredentials(
|
|
page: Page,
|
|
credentials: { username: string; password: string },
|
|
) {
|
|
await page.fill('[data-testid="username-input"]', credentials.username);
|
|
await page.fill('[data-testid="password-input"]', credentials.password);
|
|
await page.click('[data-testid="login-button"]');
|
|
|
|
// 等待登录完成(等待跳转到仪表盘页面)
|
|
await page.waitForURL(/\/dashboard/, { timeout: 10000 });
|
|
}
|