import { Page, Locator, expect } from '@playwright/test'; export class RouteManagementPage { readonly page: Page; readonly pageTitle: Locator; readonly createRouteButton: Locator; readonly searchInput: Locator; readonly routeTable: Locator; readonly editButtons: Locator; readonly deleteButtons: Locator; readonly statusToggleButtons: Locator; readonly pagination: Locator; readonly vehicleTypeFilter: Locator; constructor(page: Page) { this.page = page; this.pageTitle = page.locator('[data-testid="route-management-title"]'); this.createRouteButton = page.locator('[data-testid="create-route-button"]'); this.searchInput = page.locator('[data-testid="route-search-input"]'); this.routeTable = page.locator('[data-testid="route-table"]'); this.editButtons = page.locator('[data-testid^="edit-route-"]'); this.deleteButtons = page.locator('[data-testid^="delete-route-"]'); this.statusToggleButtons = page.locator('[data-testid^="toggle-route-"]'); this.pagination = page.locator('[data-slot="pagination"]'); this.vehicleTypeFilter = page.locator('[data-testid="route-vehicle-type-filter"]'); } async goto() { // 直接导航到路线管理页面 await this.page.goto('/admin/routes'); // 等待页面完全加载 await this.page.waitForLoadState('domcontentloaded'); // 等待路线管理标题出现 await this.page.waitForSelector('[data-testid="route-management-title"]', { state: 'visible', timeout: 15000 }); // 等待表格数据加载完成 await this.page.waitForSelector('[data-testid="route-table"] tbody tr', { state: 'visible', timeout: 20000 }); await this.expectToBeVisible(); } async expectToBeVisible() { // 等待页面完全加载 await expect(this.pageTitle).toBeVisible({ timeout: 15000 }); await expect(this.createRouteButton).toBeVisible({ timeout: 10000 }); // 等待至少一行路线数据加载完成 await expect(this.routeTable.locator('tbody tr').first()).toBeVisible({ timeout: 20000 }); } async searchRoutes(keyword: string) { await this.searchInput.fill(keyword); // 防抖搜索,等待网络请求完成 await this.page.waitForTimeout(500); // 等待防抖延迟 await this.page.waitForLoadState('networkidle'); } async filterByVehicleType(type: '大巴' | '中巴' | '小车') { await this.vehicleTypeFilter.click(); await this.page.getByRole('option', { name: type }).click(); await this.page.waitForLoadState('networkidle'); } async createRoute(routeData: { name: string; startPoint: string; endPoint: string; vehicleType: 'bus' | 'van' | 'car'; price: number; seatCount: number; departureTime: string; activityId: number; }) { await this.createRouteButton.click(); // 填写路线表单 await this.page.getByLabel('路线名称').fill(routeData.name); await this.page.getByLabel('出发地').fill(routeData.startPoint); await this.page.getByLabel('目的地').fill(routeData.endPoint); // 选择车型 await this.page.getByLabel('车型').click(); const vehicleTypeMap = { 'bus': '大巴', 'van': '中巴', 'car': '小车' }; await this.page.getByRole('option', { name: vehicleTypeMap[routeData.vehicleType] }).click(); // 填写价格和座位数 await this.page.getByLabel('价格').fill(routeData.price.toString()); await this.page.getByLabel('座位数').fill(routeData.seatCount.toString()); // 填写出发时间 await this.page.getByLabel('出发时间').fill(routeData.departureTime); // 选择活动 await this.page.getByLabel('关联活动').click(); await this.page.getByRole('option').first().click(); // 提交表单 - 使用模态框中的创建按钮 await this.page.locator('[role="dialog"]').getByRole('button', { name: '创建路线' }).click(); 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) { return; } // 如果是创建成功,刷新页面 await this.page.waitForTimeout(1000); await this.page.reload(); await this.page.waitForLoadState('networkidle'); await this.expectToBeVisible(); } catch (error) { // 如果没有提示出现,继续执行 console.log('创建操作没有显示提示信息,继续执行'); await this.page.reload(); await this.page.waitForLoadState('networkidle'); await this.expectToBeVisible(); } } async getRouteCount(): Promise { const rows = await this.routeTable.locator('tbody tr').count(); // 如果只有一行且包含"暂无路线数据",则返回0 if (rows === 1) { const firstRow = this.routeTable.locator('tbody tr').first(); const rowText = await firstRow.textContent(); if (rowText && rowText.includes('暂无路线数据')) { return 0; } } return rows; } async getRouteByName(name: string): Promise { const routeRow = this.routeTable.locator('tbody tr').filter({ hasText: name }).first(); return (await routeRow.count()) > 0 ? routeRow : null; } async routeExists(name: string): Promise { const routeRow = this.routeTable.locator('tbody tr').filter({ hasText: name }).first(); return (await routeRow.count()) > 0; } async editRoute(name: string, updates: { name?: string; startPoint?: string; endPoint?: string; vehicleType?: 'bus' | 'van' | 'car'; price?: number; seatCount?: number; departureTime?: string; }) { const routeRow = await this.getRouteByName(name); if (!routeRow) throw new Error(`Route ${name} not found`); // 编辑按钮 const editButton = routeRow.locator('[data-testid^="edit-route-"]'); 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.startPoint) { await this.page.getByLabel('出发地').fill(updates.startPoint); } if (updates.endPoint) { await this.page.getByLabel('目的地').fill(updates.endPoint); } if (updates.vehicleType) { await this.page.getByLabel('车型').click(); const vehicleTypeMap = { 'bus': '大巴', 'van': '中巴', 'car': '小车' }; await this.page.getByRole('option', { name: vehicleTypeMap[updates.vehicleType] }).click(); } if (updates.price) { await this.page.getByLabel('价格').fill(updates.price.toString()); } if (updates.seatCount) { await this.page.getByLabel('座位数').fill(updates.seatCount.toString()); } if (updates.departureTime) { await this.page.getByLabel('出发时间').fill(updates.departureTime); } // 提交更新 await this.page.locator('[role="dialog"]').getByRole('button', { name: '更新路线' }).click(); await this.page.waitForLoadState('networkidle'); // 等待操作完成 await this.page.waitForTimeout(1000); } async deleteRoute(name: string) { const routeRow = await this.getRouteByName(name); if (!routeRow) throw new Error(`Route ${name} not found`); // 删除按钮 const deleteButton = routeRow.locator('[data-testid^="delete-route-"]'); await deleteButton.waitFor({ state: 'visible', timeout: 10000 }); await deleteButton.click(); // 确认删除对话框 await this.page.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.reload(); await this.page.waitForLoadState('networkidle'); await this.expectToBeVisible(); } async toggleRouteStatus(name: string) { const routeRow = await this.getRouteByName(name); if (!routeRow) throw new Error(`Route ${name} not found`); // 状态切换按钮 const statusButton = routeRow.locator('[data-testid^="toggle-route-"]'); await statusButton.waitFor({ state: 'visible', timeout: 10000 }); const currentStatus = await statusButton.textContent(); await statusButton.click(); // 确认状态切换对话框 await this.page.getByRole('button', { name: '确认' }).click(); // 等待操作完成 await this.page.waitForLoadState('networkidle'); await this.page.waitForTimeout(1000); return currentStatus; } async expectRouteExists(name: string) { const exists = await this.routeExists(name); expect(exists).toBe(true); } async expectRouteNotExists(name: string) { const exists = await this.routeExists(name); expect(exists).toBe(false); } async getRouteStatus(name: string): Promise { const routeRow = await this.getRouteByName(name); if (!routeRow) return null; const statusButton = routeRow.locator('[data-testid^="toggle-route-"]'); return await statusButton.textContent(); } }