| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- import { Page, Locator, expect } from '@playwright/test';
- export class ActivityManagementPage {
- readonly page: Page;
- readonly pageTitle: Locator;
- readonly createActivityButton: Locator;
- readonly searchInput: Locator;
- readonly searchButton: Locator;
- readonly activityTable: Locator;
- readonly editButtons: Locator;
- readonly deleteButtons: Locator;
- readonly statusToggleButtons: Locator;
- readonly pagination: Locator;
- readonly typeFilter: Locator;
- constructor(page: Page) {
- this.page = page;
- this.pageTitle = page.locator('[data-testid="activity-management-title"]');
- this.createActivityButton = page.locator('[data-testid="create-activity-button"]');
- this.searchInput = page.locator('[data-testid="activity-search-input"]');
- this.searchButton = page.getByRole('button', { name: '搜索' });
- this.activityTable = page.locator('[data-testid="activity-table"]');
- this.editButtons = page.locator('[data-testid^="edit-activity-"]');
- this.deleteButtons = page.locator('[data-testid^="delete-activity-"]');
- this.statusToggleButtons = page.locator('[data-testid^="toggle-activity-"]');
- this.pagination = page.locator('[data-slot="pagination"]');
- this.typeFilter = page.locator('[data-testid="activity-type-filter"]');
- }
- async goto() {
- // 直接导航到活动管理页面
- await this.page.goto('/admin/activities');
- // 等待页面完全加载
- await this.page.waitForLoadState('domcontentloaded');
- // 等待活动管理标题出现
- await this.page.waitForSelector('h1:has-text("活动管理")', { state: 'visible', timeout: 15000 });
- // 等待表格数据加载完成
- await this.page.waitForSelector('table tbody tr', { state: 'visible', timeout: 20000 });
- await this.expectToBeVisible();
- }
- async expectToBeVisible() {
- // 等待页面完全加载
- await expect(this.pageTitle).toBeVisible({ timeout: 15000 });
- await expect(this.createActivityButton).toBeVisible({ timeout: 10000 });
- // 等待至少一行活动数据加载完成
- await expect(this.activityTable.locator('tbody tr').first()).toBeVisible({ timeout: 20000 });
- }
- async searchActivities(keyword: string) {
- await this.searchInput.fill(keyword);
- // 等待防抖搜索完成(300ms + 网络请求时间)
- await this.page.waitForTimeout(500);
- await this.page.waitForLoadState('networkidle');
- }
- async filterByType(type: '去程' | '返程') {
- await this.typeFilter.click();
- await this.page.getByRole('option', { name: type }).click();
- await this.page.waitForLoadState('networkidle');
- }
- async createActivity(activityData: {
- name: string;
- description?: string;
- type: '去程' | '返程';
- startDate: string;
- endDate: string;
- }) {
- await this.createActivityButton.click();
- // 填写活动表单
- await this.page.getByLabel('活动名称').fill(activityData.name);
- if (activityData.description) {
- await this.page.getByLabel('活动描述').fill(activityData.description);
- }
- // 选择活动类型
- await this.page.getByLabel('活动类型').click();
- await this.page.getByRole('option', { name: activityData.type }).click();
- // 填写开始日期和结束日期
- await this.page.getByLabel('开始日期').fill(activityData.startDate);
- await this.page.getByLabel('结束日期').fill(activityData.endDate);
- // 提交表单 - 使用模态框中的创建按钮
- await this.page.locator('[role="dialog"]').getByRole('button', { name: '创建活动' }).click();
- // 等待模态框关闭
- await this.page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 10000 });
- await this.page.waitForLoadState('networkidle');
- // 等待活动创建结果提示
- try {
- await Promise.race([
- this.page.waitForSelector('text=创建成功', { timeout: 10000 }),
- this.page.waitForSelector('text=创建失败', { timeout: 10000 })
- ]);
- // 检查是否有错误提示
- const errorVisible = await this.page.locator('text=创建失败').isVisible().catch(() => false);
- if (errorVisible) {
- console.log('创建活动失败:前端显示创建失败提示');
- return;
- }
- // 如果是创建成功,刷新页面
- await this.page.waitForTimeout(2000);
- await this.page.reload();
- await this.page.waitForLoadState('networkidle');
- await this.expectToBeVisible();
- } catch (error) {
- // 如果没有提示出现,继续执行
- console.log('创建操作没有显示提示信息,继续执行');
- await this.page.waitForTimeout(2000);
- await this.page.waitForLoadState('networkidle');
- }
- }
- async getActivityCount(): Promise<number> {
- const rows = await this.activityTable.locator('tbody tr').count();
- // 如果只有一行且显示"暂无活动数据",则返回0
- if (rows === 1) {
- const firstRowText = await this.activityTable.locator('tbody tr').first().textContent();
- if (firstRowText?.includes('暂无活动数据')) {
- return 0;
- }
- }
- return rows;
- }
- async getActivityByName(name: string): Promise<Locator | null> {
- const activityRow = this.activityTable.locator('tbody tr').filter({ hasText: name }).first();
- return (await activityRow.count()) > 0 ? activityRow : null;
- }
- async activityExists(name: string): Promise<boolean> {
- const activityRow = this.activityTable.locator('tbody tr').filter({ hasText: name }).first();
- return (await activityRow.count()) > 0;
- }
- async editActivity(name: string, updates: {
- name?: string;
- description?: string;
- type?: '去程活动' | '返程活动';
- startDate?: string;
- endDate?: string;
- }) {
- const activityRow = await this.getActivityByName(name);
- if (!activityRow) throw new Error(`Activity ${name} not found`);
- // 使用data-testid定位编辑按钮
- const editButton = activityRow.locator('[data-testid^="edit-activity-"]');
- await editButton.waitFor({ state: 'visible', timeout: 10000 });
- await editButton.click();
- // 等待编辑模态框出现
- await this.page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 10000 });
- // 更新字段
- if (updates.name) {
- await this.page.getByLabel('活动名称').fill(updates.name);
- }
- if (updates.description) {
- await this.page.getByLabel('活动描述').fill(updates.description);
- }
- if (updates.type) {
- await this.page.getByLabel('活动类型').click();
- await this.page.getByRole('option', { name: updates.type }).click();
- }
- if (updates.startDate) {
- await this.page.getByLabel('开始日期').fill(updates.startDate);
- }
- if (updates.endDate) {
- await this.page.getByLabel('结束日期').fill(updates.endDate);
- }
- // 提交更新
- await this.page.locator('[role="dialog"]').getByRole('button', { name: '更新活动' }).click();
- await this.page.waitForLoadState('networkidle');
- // 等待操作完成
- await this.page.waitForTimeout(1000);
- }
- async deleteActivity(name: string) {
- const activityRow = await this.getActivityByName(name);
- if (!activityRow) throw new Error(`Activity ${name} not found`);
- // 使用data-testid定位删除按钮
- const deleteButton = activityRow.locator('[data-testid^="delete-activity-"]');
- await deleteButton.waitFor({ state: 'visible', timeout: 10000 });
- await deleteButton.click();
- // 等待删除确认对话框出现 - 使用data-testid
- await this.page.waitForSelector('[data-testid="delete-confirm-dialog"]', { state: 'visible', timeout: 10000 });
- // 确认删除 - 点击删除按钮
- await this.page.locator('[data-testid="delete-confirm-dialog"]').getByRole('button', { name: '删除' }).click();
- // 等待删除操作完成
- try {
- await Promise.race([
- this.page.waitForSelector('text=删除成功', { timeout: 10000 }),
- this.page.waitForSelector('text=删除失败', { timeout: 10000 })
- ]);
- const errorVisible = await this.page.locator('text=删除失败').isVisible().catch(() => false);
- if (errorVisible) {
- throw new Error('删除操作失败:前端显示删除失败提示');
- }
- } catch (error) {
- console.log('删除操作没有显示提示信息,继续执行');
- }
- // 等待页面状态稳定,不需要强制刷新
- await this.page.waitForLoadState('networkidle');
- }
- async toggleActivityStatus(name: string) {
- const activityRow = await this.getActivityByName(name);
- if (!activityRow) throw new Error(`Activity ${name} not found`);
- // 使用data-testid定位状态切换按钮
- const statusButton = activityRow.locator('[data-testid^="toggle-activity-"]');
- await statusButton.waitFor({ state: 'visible', timeout: 10000 });
- // 获取当前状态(从状态单元格获取,不是按钮文本)
- const currentStatus = await this.getActivityStatus(name);
- await statusButton.click();
- // 等待状态切换确认对话框出现 - 使用data-testid
- await this.page.waitForSelector('[data-testid="status-confirm-dialog"]', { state: 'visible', timeout: 10000 });
- // 确认状态切换 - 点击禁用或启用按钮
- // 注意:按钮文本是当前要执行的操作,不是当前状态
- const actionText = currentStatus === '启用' ? '禁用' : '启用';
- await this.page.locator('[data-testid="status-confirm-dialog"]').getByRole('button', { name: actionText }).click();
- // 等待操作完成
- await this.page.waitForLoadState('networkidle');
- await this.page.waitForTimeout(1000);
- return currentStatus;
- }
- async expectActivityExists(name: string) {
- const exists = await this.activityExists(name);
- expect(exists).toBe(true);
- }
- async expectActivityNotExists(name: string) {
- const exists = await this.activityExists(name);
- expect(exists).toBe(false);
- }
- async getActivityStatus(name: string): Promise<string | null> {
- const activityRow = await this.getActivityByName(name);
- if (!activityRow) return null;
- // 状态文本在第五列(索引4)
- const statusCell = activityRow.locator('td').nth(4);
- return await statusCell.textContent();
- }
- }
|