f5dec95a83
refactor: 重构页面导航和滚动逻辑,提升用户体验 test: 更新测试配置和用例,增加覆盖率和稳定性 perf: 优化性能指标和阈值,适应开发环境需求 ci: 添加Lighthouse CI工作流,集成性能测试 docs: 更新API文档和健康检查端点 fix: 修复登录页面和表单提交问题 style: 调整响应式布局和可访问性改进 chore: 更新依赖项和脚本配置
248 lines
7.8 KiB
TypeScript
248 lines
7.8 KiB
TypeScript
import { Page, Locator } from '@playwright/test';
|
|
import { BasePage } from './BasePage';
|
|
|
|
export class AdminLoginPage extends BasePage {
|
|
readonly emailInput: Locator;
|
|
readonly passwordInput: Locator;
|
|
readonly loginButton: Locator;
|
|
readonly errorMessage: Locator;
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.emailInput = page.locator('#email, input[type="email"]');
|
|
this.passwordInput = page.locator('#password, input[type="password"]');
|
|
this.loginButton = page.getByRole('button', { name: /登录|login/i });
|
|
this.errorMessage = page.locator('[role="alert"], .text-red-700');
|
|
}
|
|
|
|
async goto() {
|
|
await this.navigate('/admin/login');
|
|
await this.page.waitForLoadState('domcontentloaded', { timeout: 30000 });
|
|
await this.page.waitForTimeout(1000);
|
|
await this.emailInput.waitFor({ state: 'visible', timeout: 20000 });
|
|
}
|
|
|
|
async login(email: string, password: string) {
|
|
await this.emailInput.fill(email);
|
|
await this.passwordInput.fill(password);
|
|
await this.loginButton.click();
|
|
}
|
|
|
|
async expectLoginSuccess() {
|
|
await this.page.waitForURL(/\/admin(?!\/login)/);
|
|
}
|
|
|
|
async expectLoginError() {
|
|
await this.errorMessage.waitFor({ state: 'visible' });
|
|
}
|
|
}
|
|
|
|
export class AdminDashboardPage extends BasePage {
|
|
readonly sidebar: Locator;
|
|
readonly navigationItems: Locator;
|
|
readonly contentMenuItem: Locator;
|
|
readonly settingsMenuItem: Locator;
|
|
readonly usersMenuItem: Locator;
|
|
readonly logsMenuItem: Locator;
|
|
readonly logoutButton: Locator;
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.sidebar = page.locator('aside, [role="navigation"]');
|
|
this.navigationItems = this.sidebar.locator('nav a, nav button');
|
|
this.contentMenuItem = this.sidebar.getByRole('link', { name: /内容管理/i });
|
|
this.settingsMenuItem = this.sidebar.getByRole('link', { name: /配置中心|设置/i });
|
|
this.usersMenuItem = this.sidebar.getByRole('link', { name: /用户管理/i });
|
|
this.logsMenuItem = this.sidebar.getByRole('link', { name: /审计日志|日志/i });
|
|
this.logoutButton = this.sidebar.getByRole('button', { name: /登出|退出|logout/i });
|
|
}
|
|
|
|
async goto() {
|
|
await this.navigate('/admin');
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async navigateToContent() {
|
|
await this.contentMenuItem.click();
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async navigateToSettings() {
|
|
await this.settingsMenuItem.click();
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async navigateToUsers() {
|
|
await this.usersMenuItem.click();
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async navigateToLogs() {
|
|
await this.logsMenuItem.click();
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async logout() {
|
|
await this.logoutButton.click();
|
|
}
|
|
}
|
|
|
|
export class AdminContentPage extends BasePage {
|
|
readonly createButton: Locator;
|
|
readonly contentList: Locator;
|
|
readonly searchInput: Locator;
|
|
readonly filterButtons: Locator;
|
|
readonly editButtons: Locator;
|
|
readonly deleteButtons: Locator;
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.createButton = page.getByRole('button', { name: /创建|新建|create/i });
|
|
this.contentList = page.locator('table tbody tr').or(page.locator('[data-testid="content-item"]'));
|
|
this.searchInput = page.locator('input[type="search"], input[placeholder*="搜索"]');
|
|
this.filterButtons = page.locator('button[role="tab"], select');
|
|
this.editButtons = page.getByRole('button', { name: /编辑|edit/i });
|
|
this.deleteButtons = page.getByRole('button', { name: /删除|delete/i });
|
|
}
|
|
|
|
async goto() {
|
|
await this.navigate('/admin/content');
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async createContent(data: {
|
|
type: string;
|
|
title: string;
|
|
slug: string;
|
|
content?: string;
|
|
}) {
|
|
await this.createButton.click();
|
|
|
|
await this.page.locator('select[name="type"]').selectOption(data.type);
|
|
await this.page.locator('input[name="title"]').fill(data.title);
|
|
await this.page.locator('input[name="slug"]').fill(data.slug);
|
|
|
|
if (data.content) {
|
|
await this.page.locator('textarea[name="content"], .ProseMirror').fill(data.content);
|
|
}
|
|
|
|
await this.page.getByRole('button', { name: /保存|submit/i }).click();
|
|
}
|
|
|
|
async searchContent(query: string) {
|
|
await this.searchInput.fill(query);
|
|
await this.page.keyboard.press('Enter');
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async editContent(index: number) {
|
|
const editButton = this.editButtons.nth(index);
|
|
await editButton.click();
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async deleteContent(index: number) {
|
|
const deleteButton = this.deleteButtons.nth(index);
|
|
await deleteButton.click();
|
|
|
|
const confirmButton = this.page.getByRole('button', { name: /确认|确定|confirm/i });
|
|
if (await confirmButton.isVisible()) {
|
|
await confirmButton.click();
|
|
}
|
|
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
}
|
|
|
|
export class AdminUsersPage extends BasePage {
|
|
readonly createButton: Locator;
|
|
readonly usersList: Locator;
|
|
readonly searchInput: Locator;
|
|
readonly editButtons: Locator;
|
|
readonly deleteButtons: Locator;
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.createButton = page.getByRole('button', { name: /创建|新建|create/i });
|
|
this.usersList = page.locator('table tbody tr, [role="listitem"]');
|
|
this.searchInput = page.locator('input[type="search"], input[placeholder*="搜索"]');
|
|
this.editButtons = page.getByRole('button', { name: /编辑|edit/i });
|
|
this.deleteButtons = page.getByRole('button', { name: /删除|delete/i });
|
|
}
|
|
|
|
async goto() {
|
|
await this.navigate('/admin/users');
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async createUser(data: {
|
|
email: string;
|
|
name: string;
|
|
password: string;
|
|
role: string;
|
|
}) {
|
|
await this.createButton.click();
|
|
|
|
await this.page.locator('input[name="email"]').fill(data.email);
|
|
await this.page.locator('input[name="name"]').fill(data.name);
|
|
await this.page.locator('input[name="password"]').fill(data.password);
|
|
await this.page.locator('select[name="role"]').selectOption(data.role);
|
|
|
|
await this.page.getByRole('button', { name: /保存|submit/i }).click();
|
|
}
|
|
|
|
async deleteUser(index: number) {
|
|
const deleteButton = this.deleteButtons.nth(index);
|
|
await deleteButton.click();
|
|
|
|
const confirmButton = this.page.getByRole('button', { name: /确认|确定|confirm/i });
|
|
if (await confirmButton.isVisible()) {
|
|
await confirmButton.click();
|
|
}
|
|
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
}
|
|
|
|
export class AdminLogsPage extends BasePage {
|
|
readonly logsList: Locator;
|
|
readonly actionFilter: Locator;
|
|
readonly resourceTypeFilter: Locator;
|
|
readonly refreshButton: Locator;
|
|
readonly pagination: Locator;
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.logsList = page.locator('table tbody tr, [role="listitem"]');
|
|
this.actionFilter = page.locator('select[name="action"], select').first();
|
|
this.resourceTypeFilter = page.locator('select[name="resourceType"], select').nth(1);
|
|
this.refreshButton = page.getByRole('button', { name: /刷新|refresh/i });
|
|
this.pagination = page.locator('[role="navigation"], .pagination');
|
|
}
|
|
|
|
async goto() {
|
|
await this.navigate('/admin/logs');
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async filterByAction(action: string) {
|
|
await this.actionFilter.selectOption(action);
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async filterByResourceType(type: string) {
|
|
await this.resourceTypeFilter.selectOption(type);
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async refresh() {
|
|
await this.refreshButton.click();
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async goToPage(pageNumber: number) {
|
|
await this.pagination.getByRole('button', { name: String(pageNumber) }).click();
|
|
await this.waitForLoadState('networkidle');
|
|
}
|
|
}
|