Explorar el Código

✅ test(travel-flow): 添加出行流程E2E测试

- 创建HomePage、ActivitySelectPage和ScheduleListPage页面模型
- 实现完整出行查询流程测试,包括选择出行方式、地区、地点和日期
- 添加省市区三级联动功能测试
- 验证地点搜索功能正常工作
- 测试路线类型动态判断逻辑
- 添加边界场景:无数据时显示友好提示
- 验证并发查询正确处理
yourname hace 3 meses
padre
commit
de33ae3f49

+ 54 - 0
tests/e2e/travel-flow/pages/ActivitySelectPage.ts

@@ -0,0 +1,54 @@
+import { Locator, Page } from '@playwright/test'
+
+export class ActivitySelectPage {
+  readonly page: Page
+
+  // 页面元素定位器
+  readonly title: Locator
+  readonly routeInfo: Locator
+  readonly departureActivitiesTab: Locator
+  readonly returnActivitiesTab: Locator
+  readonly activityCards: Locator
+  readonly noActivitiesText: Locator
+
+  constructor(page: Page) {
+    this.page = page
+
+    // 初始化定位器
+    this.title = page.locator('text=选择观看活动')
+    this.routeInfo = page.locator('text=北京市 → 上海市')
+    this.departureActivitiesTab = page.locator('text=去程活动')
+    this.returnActivitiesTab = page.locator('text=返程活动')
+    this.activityCards = page.locator('text=上海音乐节')
+    this.noActivitiesText = page.locator('text=暂无相关活动')
+  }
+
+  // 页面操作方法
+  async waitForPageLoad() {
+    await this.page.waitForURL(/\/pages\/select-activity\/ActivitySelectPage/)
+  }
+
+  async selectActivity(activityName: string) {
+    await this.page.click(`text=${activityName}`)
+  }
+
+  async switchToDepartureActivities() {
+    await this.departureActivitiesTab.click()
+  }
+
+  async switchToReturnActivities() {
+    await this.returnActivitiesTab.click()
+  }
+
+  async getActivityCount(): Promise<number> {
+    return await this.activityCards.count()
+  }
+
+  async hasActivities(): Promise<boolean> {
+    return await this.activityCards.first().isVisible()
+  }
+
+  async getRouteInfo(): Promise<string> {
+    return await this.routeInfo.textContent() || ''
+  }
+}

+ 115 - 0
tests/e2e/travel-flow/pages/HomePage.ts

@@ -0,0 +1,115 @@
+import { Locator, Page } from '@playwright/test'
+
+export class HomePage {
+  readonly page: Page
+
+  // 页面元素定位器
+  readonly title: Locator
+  readonly subtitle: Locator
+  readonly vehicleTypeButtons: Locator
+  readonly departureAreaSelector: Locator
+  readonly destinationAreaSelector: Locator
+  readonly departureLocationSearch: Locator
+  readonly destinationLocationSearch: Locator
+  readonly datePicker: Locator
+  readonly searchButton: Locator
+  readonly selectedLocationText: Locator
+  readonly provinceList: Locator
+  readonly cityList: Locator
+  readonly districtList: Locator
+  readonly locationSearchResults: Locator
+  readonly noResultsText: Locator
+
+  constructor(page: Page) {
+    this.page = page
+
+    // 初始化定位器
+    this.title = page.locator('text=便捷出行')
+    this.subtitle = page.locator('text=专业出行服务,安全舒适')
+    this.vehicleTypeButtons = page.locator('text=大巴拼车, text=商务车, text=包车')
+    this.departureAreaSelector = page.locator('text=请选择出发地区')
+    this.destinationAreaSelector = page.locator('text=请选择目的地区')
+    this.departureLocationSearch = page.locator('input[placeholder="搜索出发地点"]')
+    this.destinationLocationSearch = page.locator('input[placeholder="搜索目的地点"]')
+    this.datePicker = page.locator('input[type="date"]')
+    this.searchButton = page.locator('text=查询路线')
+    this.selectedLocationText = page.locator('text=已选择:')
+    this.provinceList = page.locator('text=北京市, text=广东省, text=上海市')
+    this.cityList = page.locator('text=广州市, text=深圳市')
+    this.districtList = page.locator('text=天河区, text=越秀区')
+    this.locationSearchResults = page.locator('text=北京首都国际机场, text=北京南站, text=上海虹桥机场')
+    this.noResultsText = page.locator('text=未找到相关地点')
+  }
+
+  // 页面操作方法
+  async goto() {
+    await this.page.goto('/mini/')
+  }
+
+  async selectVehicleType(type: '大巴拼车' | '商务车' | '包车') {
+    await this.page.click(`text=${type}`)
+  }
+
+  async selectDepartureArea(province: string, city?: string, district?: string) {
+    await this.departureAreaSelector.click()
+    await this.page.click(`text=${province}`)
+
+    if (city) {
+      await this.page.click('text=请选择城市')
+      await this.page.click(`text=${city}`)
+    }
+
+    if (district) {
+      await this.page.click('text=请选择区县')
+      await this.page.click(`text=${district}`)
+    }
+  }
+
+  async selectDestinationArea(province: string, city?: string, district?: string) {
+    await this.destinationAreaSelector.click()
+    await this.page.click(`text=${province}`)
+
+    if (city) {
+      await this.page.click('text=请选择城市')
+      await this.page.click(`text=${city}`)
+    }
+
+    if (district) {
+      await this.page.click('text=请选择区县')
+      await this.page.click(`text=${district}`)
+    }
+  }
+
+  async searchDepartureLocation(keyword: string) {
+    await this.departureLocationSearch.fill(keyword)
+    await this.page.waitForTimeout(500) // 等待防抖
+  }
+
+  async searchDestinationLocation(keyword: string) {
+    await this.destinationLocationSearch.fill(keyword)
+    await this.page.waitForTimeout(500) // 等待防抖
+  }
+
+  async selectLocation(locationName: string) {
+    await this.page.click(`text=${locationName}`)
+  }
+
+  async setDate(date: string) {
+    await this.datePicker.fill(date)
+  }
+
+  async searchRoutes() {
+    await this.searchButton.click()
+  }
+
+  async getSelectedLocationText(): Promise<string> {
+    return await this.selectedLocationText.textContent() || ''
+  }
+
+  async isVehicleTypeSelected(type: string): Promise<boolean> {
+    const button = this.page.locator(`text=${type}`)
+    const parent = button.locator('..')
+    const className = await parent.getAttribute('class')
+    return className?.includes('bg-blue-500') || false
+  }
+}

+ 52 - 0
tests/e2e/travel-flow/pages/ScheduleListPage.ts

@@ -0,0 +1,52 @@
+import { Locator, Page } from '@playwright/test'
+
+export class ScheduleListPage {
+  readonly page: Page
+
+  // 页面元素定位器
+  readonly title: Locator
+  readonly activityInfo: Locator
+  readonly dateSelector: Locator
+  readonly scheduleList: Locator
+  readonly buyButton: Locator
+  readonly noSchedulesText: Locator
+
+  constructor(page: Page) {
+    this.page = page
+
+    // 初始化定位器
+    this.title = page.locator('text=可选班次')
+    this.activityInfo = page.locator('text=上海音乐节')
+    this.dateSelector = page.locator('text=选择出发日期')
+    this.scheduleList = page.locator('text=立即购票')
+    this.buyButton = page.locator('text=立即购票').first()
+    this.noSchedulesText = page.locator('text=暂无可用班次')
+  }
+
+  // 页面操作方法
+  async waitForPageLoad() {
+    await this.page.waitForURL(/\/pages\/schedule-list\/ScheduleListPage/)
+  }
+
+  async selectDate(date: string) {
+    await this.page.click(`text=${date}`)
+  }
+
+  async selectSchedule() {
+    if (await this.buyButton.isVisible()) {
+      await this.buyButton.click()
+    }
+  }
+
+  async getScheduleCount(): Promise<number> {
+    return await this.scheduleList.count()
+  }
+
+  async hasSchedules(): Promise<boolean> {
+    return await this.buyButton.isVisible()
+  }
+
+  async getActivityInfo(): Promise<string> {
+    return await this.activityInfo.textContent() || ''
+  }
+}

+ 188 - 0
tests/e2e/travel-flow/travel-flow-refactored.spec.ts

@@ -0,0 +1,188 @@
+import { test, expect } from '@playwright/test'
+import { HomePage } from './pages/HomePage'
+import { ActivitySelectPage } from './pages/ActivitySelectPage'
+import { ScheduleListPage } from './pages/ScheduleListPage'
+
+test.describe('完整出行流程E2E测试', () => {
+  let homePage: HomePage
+  let activitySelectPage: ActivitySelectPage
+  let scheduleListPage: ScheduleListPage
+
+  test.beforeEach(async ({ page }) => {
+    homePage = new HomePage(page)
+    activitySelectPage = new ActivitySelectPage(page)
+    scheduleListPage = new ScheduleListPage(page)
+  })
+
+  test('用户应该能够完成完整的出行查询流程', async ({ page }) => {
+    // 1. 访问首页
+    await homePage.goto()
+
+    // 验证首页加载成功
+    await expect(homePage.title).toBeVisible()
+    await expect(homePage.subtitle).toBeVisible()
+
+    // 2. 选择出行方式
+    await homePage.selectVehicleType('商务车')
+    await expect(homePage.isVehicleTypeSelected('商务车')).resolves.toBe(true)
+
+    // 3. 选择出发地区
+    await homePage.selectDepartureArea('北京市')
+    await expect(page.locator('text=北京市')).toBeVisible()
+
+    // 4. 选择出发地点
+    await homePage.searchDepartureLocation('北京')
+    await homePage.selectLocation('北京首都国际机场')
+    await expect(homePage.getSelectedLocationText()).resolves.toContain('北京首都国际机场')
+
+    // 5. 选择目的地区
+    await homePage.selectDestinationArea('上海市')
+    await expect(page.locator('text=上海市')).toBeVisible()
+
+    // 6. 选择目的地点
+    await homePage.searchDestinationLocation('上海')
+    await homePage.selectLocation('上海虹桥机场')
+    await expect(homePage.getSelectedLocationText()).resolves.toContain('上海虹桥机场')
+
+    // 7. 选择日期
+    const tomorrow = new Date()
+    tomorrow.setDate(tomorrow.getDate() + 1)
+    const tomorrowStr = tomorrow.toISOString().split('T')[0]
+    await homePage.setDate(tomorrowStr)
+
+    // 8. 查询路线
+    await homePage.searchRoutes()
+
+    // 9. 验证跳转到活动选择页面
+    await activitySelectPage.waitForPageLoad()
+    await expect(activitySelectPage.title).toBeVisible()
+    await expect(activitySelectPage.routeInfo).toBeVisible()
+
+    // 10. 选择活动
+    await activitySelectPage.selectActivity('上海音乐节')
+
+    // 11. 验证跳转到班次列表页面
+    await scheduleListPage.waitForPageLoad()
+    await expect(scheduleListPage.title).toBeVisible()
+    await expect(scheduleListPage.activityInfo).toBeVisible()
+
+    // 12. 选择不同日期
+    const dayAfterTomorrow = new Date()
+    dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 2)
+    const dayAfterTomorrowStr = dayAfterTomorrow.toISOString().split('T')[0]
+    await scheduleListPage.selectDate(dayAfterTomorrowStr)
+
+    // 13. 验证班次列表更新
+    await expect(scheduleListPage.dateSelector).toBeVisible()
+
+    // 14. 选择班次(如果有可用班次)
+    if (await scheduleListPage.hasSchedules()) {
+      await scheduleListPage.selectSchedule()
+      // 这里可以继续验证预订流程
+    }
+  })
+
+  test('省市区三级联动功能应该正常工作', async ({ page }) => {
+    await homePage.goto()
+
+    // 测试省份选择
+    await homePage.selectDepartureArea('广东省')
+    await expect(page.locator('text=广东省')).toBeVisible()
+
+    // 验证城市列表加载
+    await page.click('text=请选择城市')
+    await expect(page.locator('text=广州市')).toBeVisible()
+    await expect(page.locator('text=深圳市')).toBeVisible()
+
+    // 选择城市
+    await page.click('text=广州市')
+    await expect(page.locator('text=广州市')).toBeVisible()
+
+    // 验证区县列表加载
+    await page.click('text=请选择区县')
+    await expect(page.locator('text=天河区')).toBeVisible()
+    await expect(page.locator('text=越秀区')).toBeVisible()
+
+    // 选择区县
+    await page.click('text=天河区')
+    await expect(page.locator('text=广东省 广州市 天河区')).toBeVisible()
+  })
+
+  test('地点搜索功能应该正常工作', async ({ page }) => {
+    await homePage.goto()
+
+    // 测试地点搜索
+    await homePage.searchDepartureLocation('北京')
+
+    // 验证搜索结果
+    await expect(page.locator('text=北京首都国际机场')).toBeVisible()
+    await expect(page.locator('text=北京南站')).toBeVisible()
+
+    // 选择地点
+    await homePage.selectLocation('北京首都国际机场')
+    await expect(homePage.getSelectedLocationText()).resolves.toContain('北京首都国际机场')
+
+    // 验证输入框值更新
+    await expect(homePage.departureLocationSearch).toHaveValue('北京首都国际机场')
+  })
+
+  test('路线类型动态判断逻辑应该正确工作', async ({ page }) => {
+    await homePage.goto()
+
+    // 设置出发地和目的地
+    await homePage.selectDepartureArea('北京市')
+    await homePage.selectDestinationArea('上海市')
+
+    // 搜索地点
+    await homePage.searchDepartureLocation('北京首都国际机场')
+    await homePage.selectLocation('北京首都国际机场')
+
+    await homePage.searchDestinationLocation('上海虹桥机场')
+    await homePage.selectLocation('上海虹桥机场')
+
+    // 查询路线
+    await homePage.searchRoutes()
+
+    // 验证活动选择页面显示正确的路线类型
+    await activitySelectPage.waitForPageLoad()
+    await expect(activitySelectPage.departureActivitiesTab).toBeVisible()
+    await expect(activitySelectPage.returnActivitiesTab).toBeVisible()
+  })
+
+  test('边界场景:无数据时应该显示友好提示', async ({ page }) => {
+    await homePage.goto()
+
+    // 设置不存在的搜索条件
+    await homePage.searchDepartureLocation('不存在的城市')
+
+    // 验证显示无结果提示
+    await expect(homePage.noResultsText).toBeVisible()
+
+    // 查询无效路线
+    await homePage.searchRoutes()
+
+    // 验证活动页面显示无活动提示
+    await activitySelectPage.waitForPageLoad()
+    await expect(activitySelectPage.noActivitiesText).toBeVisible()
+  })
+
+  test('并发查询应该正确处理', async ({ page }) => {
+    await homePage.goto()
+
+    // 快速连续操作
+    await homePage.selectVehicleType('大巴拼车')
+    await homePage.selectVehicleType('商务车')
+    await homePage.selectVehicleType('包车')
+
+    // 快速输入搜索
+    await homePage.searchDepartureLocation('北京')
+    await homePage.searchDepartureLocation('上海')
+    await homePage.searchDepartureLocation('广州')
+
+    await page.waitForTimeout(500) // 等待防抖完成
+
+    // 验证最终状态正确
+    await expect(homePage.isVehicleTypeSelected('包车')).resolves.toBe(true)
+    await expect(homePage.departureLocationSearch).toHaveValue('广州')
+  })
+})