跳到主要内容

Playwright Page Object 与稳定性治理

🎭 Page Object 模式是 E2E 测试可维护性的基石。稳定性治理是持续交付的关键。

Page Object 模式

// pages/LoginPage.ts
import { type Page, type Locator } from '@playwright/test'

export class LoginPage {
private readonly emailInput: Locator
private readonly passwordInput: Locator
private readonly submitButton: Locator
private readonly errorMessage: Locator

constructor(private page: Page) {
this.emailInput = page.getByLabel('邮筱')
this.passwordInput = page.getByLabel('密码')
this.submitButton = page.getByRole('button', { name: '登录' })
this.errorMessage = page.getByRole('alert')
}

async goto() {
await this.page.goto('/login')
}

async login(email: string, password: string) {
await this.emailInput.fill(email)
await this.passwordInput.fill(password)
await this.submitButton.click()
}

async expectErrorMessage(text: string) {
await expect(this.errorMessage).toContainText(text)
}
}

在测试中使用

import { test, expect } from '@playwright/test'
import { LoginPage } from './pages/LoginPage'

test('登录失败应显示错误', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.goto()
await loginPage.login('wrong@email.com', 'wrongpassword')
await loginPage.expectErrorMessage('邮筱或密码错误')
})

定位器选择原则

// 推荐顺序:可访问性属性 > 展示文字 > 测试 ID > CSS

// ✅ 推荐
page.getByRole('button', { name: '提交' })
page.getByLabel('用户名')
page.getByText('登录成功')
page.getByTestId('submit-btn') // data-testid

// ⚠️ 避免
// page.locator('.submit-btn') // 布局变化就断
// page.locator('button:nth-child(2)') // 脚本化强

稳定性问题排查

常见不稳定原因

原因解决方案
动画未完成waitForAnimations()或禁用 CSS 动画
网络请求未完成waitForResponse() 或拖 Fixture 模拟
页面元素殊途waitForLoadState('networkidle')
时间依赖expect(locator).toBeVisible() 著替硬编码等待
异步渲染等待特定元素出现而非等 DOM ready

Trace 排错

// playwright.config.ts
export default defineConfig({
use: {
// 失败时保留跟踪
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
}
})
# 展示 Trace 文件
npx playwright show-trace trace.zip

Fixture 与状态备制

// fixtures.ts — 复用登录状态
import { test as base } from '@playwright/test'
import { LoginPage } from './pages/LoginPage'

export const test = base.extend({
loggedInPage: async ({ page }, use) => {
const loginPage = new LoginPage(page)
await loginPage.goto()
await loginPage.login('test@example.com', 'password')
await page.waitForURL('/dashboard')
await use(page) // 将已登录的 page 传入测试
}
})

常见误区

  • 大量使用 waitForTimeout(1000) 硬等待,导致测试慢且易断
  • Page Object 中直接操作 DOM 而非封装成语义方法
  • 不使用 --retries 掩盖不稳定性,应找根因并修复