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.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 page.waitForURL('/dashboard')
await use(page) // 将已登录的 page 传入测试
}
})
常见误区
- 大量使用
waitForTimeout(1000)硬等待,导致测试慢且易断 - Page Object 中直接操作 DOM 而非封装成语义方法
- 不使用
--retries掩盖不稳定性,应找根因并修复