NodeJs 下 Playwright的使用

灯火 Lv4

参考

Playwright介绍

Playwright 是由 Microsoft 开发的一款开源自动化测试框架,它专为现代 Web 应用设计,提供了强大、灵活且易于使用的接口,广泛用于端到端测试(E2E Testing)。

✅ 核心优势

  • 跨浏览器支持

    • ✅ Chromium(如 Chrome、Edge)
    • ✅ Firefox
    • ✅ WebKit(Safari 所用内核,适用于 macOS/iOS 测试)
  • 多语言支持

    • Node.js(TypeScript / JavaScript)
    • Python
    • .NET
    • Java
  • 跨平台兼容

    • 支持在 Windows、Linux、macOS 上运行
  • 无需额外驱动

    • 不像 Selenium 需要手动配置浏览器驱动,Playwright 内置浏览器下载和驱动控制
  • 适配多种应用类型

    • Web 应用
    • 移动端仿真(视口、触摸、UA 伪装等)
    • Electron 桌面应用
    • PWA / SPA 现代框架项目

🚀 功能特性

  • 自动等待页面稳定(如等待网络空闲、元素可见)
  • 支持 iframe、popup、multi-tab 等复杂交互场景
  • 丰富的定位器支持(通过文本、aria、标签、CSS、XPath 等方式)
  • 快照比较 / 录像回放(Trace Viewer)
  • 网络请求拦截、Mock、Cookie 注入等高级能力
  • Headless / 有头浏览器运行模式切换
  • 可集成至 CI/CD 流水线

📲 Playwright 是否支持 App 测试?

  • ❌ 不支持原生移动 App(如 Android / iOS)
  • ✅ 支持混合 App(Hybrid App)中 WebView 的自动化操作
  • ✅ 支持 Electron、Tauri 等桌面端跨平台应用
  • 若需原生 App 测试建议使用:

安装

使用 Playwright 初始化一个新项目

1
2
# npm init playwright@latest
pnpm create playwright

运行安装命令并选择以下内容以开始:

  • 在 TypeScript 或 JavaScript 之间进行选择(默认为 TypeScript)

  • 测试文件夹的名称(默认为 tests,如果项目中已有 tests 文件夹,则默认为 e2e)

  • 添加 GitHub Actions 工作流程以轻松在 CI 上运行测试

  • 安装 Playwright 浏览器(默认为 true)

截图

基础命令

1
2
3
4
5
6
7
8
9
10
11
12
# 运行测试test 可以指定文件或者文件夹
# pnpm playwright test tests/pur.spec.ts
pnpm exec playwright test

# HTML 测试报告
pnpm exec playwright show-report

# 在 UI 模式下运行示例测试
pnpm exec playwright test --ui

# 运行代码生成器(自动生成代码)
pnpm exec playwright codegen

一些--后缀

  • --load-storage=auth.json:加载存储文件,用于加载登录状态
  • --save-storage=auth.json:保存存储文件,用于保存登录状态
  • --viewport-size=1280,720: 设置浏览器窗口大小
  • --project=webkit: 指定浏览器内核(chromium|firefox|webkit),默认为 chromium

配置路径别名

安装tsconfig-paths

1
pnpm add tsconfig-paths -D

playwright.config.ts 头部引入

1
2
3
4
import 'tsconfig-paths/register'; // 启用 tsconfig 路径映射
export default defineConfig({
// ...
})

tsconfig.json

1
2
3
4
5
6
7
8
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@utils/*": ["./utils/*"], // 精准映射特定目录
}
}
}

使用

1
import { get } from '@utils/helper';

一些使用

登录状态的保存与加载

方式一:运行这个命令,会自动打开浏览器,接着自己手动登录,登录状态就会保存到 auth.json 文件中

1
pnpm exec playwright codegen --save-storage=auth.json

方式二:配置全局注册globalSetup
这样每次跑测试前,都会自动执行这个函数,登录状态就会保存到 auth.json 文件中

playwright.config.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { defineConfig, GlobalSetup, chromium, type FullConfig } from '@playwright/test';

export default defineConfig({
use: {
// 状态存储文件
storageState: 'auth.json',
// 基础路径页面
baseURL: 'https://example.com',
// ...
},
// 这是一个简单的登录设置
globalSetup(config: FullConfig) {
const { baseURL, storageState } = config.projects[0].use;
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(baseURL!);
await page.getByLabel('User Name').fill('user');
await page.getByLabel('Password').fill('password');
await page.getByText('Sign in').click();
await page.context().storageState({ path: storageState as string });
await browser.close();
}
//...
})

外部环境变量传入与使用

执行tests/pur.spec.ts的脚本,并传入环境变量env=prod

1
env=prod pnpm playwright test tests/pur.spec.ts

接收

1
const env = process.env.env || 'dev'; // 获取命令行的环境

单个完整的测试用例

  • 使用的到UI Element-PlusVxe-Table,涉及输入框、下拉框、表格、弹窗
  • 流程填写表单(学生信息)、表格(家人)、提交表单、弹窗确认、审核通过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import { test, expect } from '@playwright/test';

/**
* 获取表头列下标
* @param {string} headerText 表头名称(支持部分匹配)
* @returns {number} 列下标(从0开始),未找到返回-1
*/
export async function getTableColumnIndex(page: Page | Locator, headerText: string | RegExp): Promise<number> {
const headers = await page.locator('thead th').all();

for (let i = 0; i < headers.length; i++) {
// const hasIcon = await headers[i].locator('.vxe-cell--edit-icon').count() > 0;
// const textContent = await headers[i].textContent();

// if (hasIcon && textContent?.includes(headerText)) {
// return i;
// }
const textContent = await headers[i].textContent();

if (typeof headerText === 'string') {
if (textContent?.includes(headerText)) return i
} else {
if (headerText.test(textContent)) return i
}
}

return -1;
}

/**
* 获取列的selector
* @param headerText 表头名称(支持部分匹配)
* @param rowIndex 从1开始
*/
export const getTableColumnLocator = async (page:Page | Locator, headerText: string | RegExp, rowIndex: number = 1) => {
const columnIndex = await getTableColumnIndex(page, headerText);
// nth-child是从1开始
return page.locator(`.vxe-table--body tr.vxe-body--row:nth-child(${rowIndex}) td:nth-child(${columnIndex + 1})`)
}

test('新增学生', async ({ page }) => {
await page.goto('https://example.com'); // 跳转到指定页面
await page.waitForLoadState('networkidle'); // 等待全局网络空闲
await expect(page.locator('.el-loading-mask')).toBeVisible({ visible: false }); // 等待该路由页面加载

// 填写表单(组件element-plus)
// 输入框
await page.getByPlaceholder('请输入姓名').fill('张三');
// await page.getByPlaceholder('请输入年龄').fill('18');
// await page.getByPlaceholder('请输入邮箱').fill('zhangsan@example.com');

// 下拉框
await page.getByText('请选择班级').click();
const classLocator = page.getByRole('option', { name: '一年级A班' })
await classLocator.scrollIntoViewIfNeeded() // 将 Element 滚动到视图中(防止下拉项过多,无法点击到目标)
await classLocator.click();

// // 3️⃣ 多选框(checkbox)
// await page.getByLabel('接受条款').check();

// // 4️⃣ 单选框(radio)
// await page.getByLabel('男').check();

// 填写亲属表格(组件vxe-grid表格,编辑类型为行编辑)
await page.getByRole('button', { name: '新增明细' }).click();
// 亲属名称
const relativeLocator = await getTableColumnLocator(page, '姓名')
// 防止列过多,无法点击到目标
await relativeLocator.scrollIntoViewIfNeeded(); // // 将 Element 滚动到视图中
// // 如果该表格为虚拟表格,使用以下代码
// // 先定位到表格容器
// const container = page.locator('.vxe-grid').first();
// await container.hover();
// await page.mouse.wheel(1000, 0); // 横向滚动1000像素,像素得自己估


await relativeLocator.click();// 点击触发表格编辑
await page.waitForTimeout(100); // 等待0.1
// 这时候该列变为可输入的input
await relativeLocator.locator('input').fill('张二');

await page.getByRole('button', { name: '保存' }).click();
// 此时弹出保存确认框(ElMessageBox),带 取消、确认 两个按钮
await page.getByRole('button', { name: '确定' }).click();
// 等待新增成功提示
await expect(page.getByText('新增成功')).toBeVisible({ visible: true }); // 自动重试直到超时

await page.getByRole('button', { name: '审核' }).click();
// 此时弹出保存确认框(ElMessageBox),带 取消、确认 两个按钮
await page.getByRole('button', { name: '确定' }).click();
// 等待审核成功提示
await expect(page.getByText('审核成功')).toBeVisible({ visible: true }); // 自动重试直到超时

})

截图

  • 标题: NodeJs 下 Playwright的使用
  • 作者: 灯火
  • 创建于 : 2025-08-01 03:55:50
  • 更新于 : 2025-08-01 08:17:52
  • 链接: https://blog.juniverse.top/2025/08/01/use-playwright-by-nodejs/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论