我的测试学习站TEST LEARNING HUB
开始学习
首页/我的面试题/如何进行 TS 的 UI 自动化

如何进行 TS 的 UI 自动化

技术选型

推荐 TypeScript + Playwright:

  • 原生支持 TypeScript,类型提示好。
  • 自动等待,减少 sleep。
  • 多浏览器、多标签页、iframe、网络拦截都支持。
  • 内置 trace、video、screenshot,失败定位方便。
  • 支持 API 请求,方便用接口准备数据。
  • 并行和分片能力好,适合 CI。

Cypress 也适合,但跨域、多标签页和浏览器支持不如 Playwright 灵活。

初始化

npm init -y
npm i -D @playwright/test typescript
npx playwright install
npx playwright test

playwright.config.ts:

import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  reporter: [['html'], ['list']],
  use: {
    baseURL: 'https://staging.example.com',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'mobile', use: { ...devices['iPhone 13'] } },
  ],
})

目录结构

tests/
  auth/
    login.spec.ts
  orders/
    create-order.spec.ts
pages/
  LoginPage.ts
  OrderPage.ts
fixtures/
  auth.setup.ts
  test-data.ts
utils/
  api.ts
  random.ts
playwright.config.ts

Page Object Model

import { Page, expect } from '@playwright/test'

export class LoginPage {
  constructor(private page: Page) {}

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

  async login(username: string, password: string) {
    await this.page.getByLabel('账号').fill(username)
    await this.page.getByLabel('密码').fill(password)
    await this.page.getByRole('button', { name: '登录' }).click()
    await expect(this.page.getByText('退出登录')).toBeVisible()
  }
}

用例:

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

test('普通用户可以登录', async ({ page }) => {
  const login = new LoginPage(page)
  await login.goto()
  await login.login('qa01', process.env.QA_PASSWORD!)
  await expect(page).toHaveURL('/dashboard')
})

稳定性设计

1. 选择器

优先级:

  1. getByRole:按可访问角色和名称。
  2. getByLabel / getByPlaceholder:表单。
  3. getByTestId:稳定的测试标识。
  4. 最后才用 CSS/XPath。

不要依赖:

  • 绝对 XPath
  • 多层 nth-child
  • 会变化的文案
  • 自动生成的 class

2. 自动等待

Playwright 的断言和操作会自动等待。不要写固定 sleep:

// 好
await expect(page.getByText('订单创建成功')).toBeVisible()

// 不好
await page.waitForTimeout(3000)

3. 登录态复用

用 storageState 保存登录态,避免每个用例都登录:

// fixtures/auth.setup.ts
import { test as setup } from '@playwright/test'

setup('登录', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('账号').fill('qa01')
  await page.getByLabel('密码').fill(process.env.QA_PASSWORD!)
  await page.getByRole('button', { name: '登录' }).click()
  await page.context().storageState({ path: '.auth/user.json' })
})
// playwright.config.ts
use: { storageState: '.auth/user.json' }

4. 用 API 准备数据

UI 只验证关键流程,前置数据用接口创建:

const response = await request.post('/api/orders', {
  data: { productId: 101, quantity: 1 },
})
expect(response.ok()).toBeTruthy()

5. 网络拦截

模拟异常、慢接口、权限失败:

await page.route('**/api/orders', route =>
  route.fulfill({ status: 500, body: '{"message":"server error"}' })
)

6. 失败取证

  • trace: 'on-first-retry'
  • 失败截图和视频
  • 控制台日志和网络请求
  • 在 CI 中上传 playwright-report/ 和 test-results/

7. 并行和隔离

  • 每个用例独立数据和账号。
  • 不要依赖执行顺序。
  • 用 worker 级 fixture 管理账号池。
  • 对共享资源加锁或使用独立租户。

CI 集成

- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/

最佳实践

  • UI 自动化只覆盖关键用户旅程,业务逻辑用接口测试。
  • 选择器稳定、语义化。
  • 用例独立、可并行、可重复。
  • 失败有 trace、截图、视频和日志。
  • flaky 用例隔离并修复,不靠无限重试。
  • 移动端和桌面端分 project 跑。

反模式

  • 用 sleep 等页面。
  • 依赖绝对 XPath 和自动生成 class。
  • 每个用例都从登录开始。
  • 用 UI 创建所有测试数据。
  • 用例之间共享账号和订单。
  • 失败只看截图,不看 trace 和网络。
  • 把所有业务校验都放在 UI 层。

面试回答模板

我会用 TypeScript + Playwright,按 Page Object 组织页面和操作,用 role、label、test-id 这类稳定选择器,依靠自动等待而不是 sleep。登录态用 storageState 复用,前置数据尽量用 API 创建,UI 只覆盖关键用户旅程。失败时开启 trace、截图和视频,CI 里并行执行并上传报告。最后把 flaky 用例隔离修复,而不是靠重试掩盖问题。