Playwright 介绍
- Microsoft 开发的一款端到端的自动化测试工具。
什么是端到端?端到端就是模拟真实用户场景,从用户界面(前端)到接口、数据库(后端)的完整验证流程
- 多语言支持: JS/TS、Python、Java、.NET,本人作为前端切图仔,只介绍 JS/TS。
- 跨浏览器,一套测试用例,可以通过配置测试多浏览器兼容性。比如 PC 端,可以配置测试谷歌、IE、火狐浏览器是否运行正常。手机端,可以配置测试安卓、苹果浏览器是否运行正常。
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
],
});
Playwright 安装( pnpm,其他安装参照官网)
- 在已有项目运行
pnpm create playwright
,这一步主要是进行一些配置,设置测试用例的目录,是否添加 Github 工作流,安装 playwright 浏览器(这一步要选 true, 跑自动化测试的时候需要启动 playwright 浏览器) - 安装完成后会生成
tests
文件夹和playwright.config.ts
配置文件 - 同时会生成一些简单的测试用例,强烈建议运行
pnpm exec playwright test --ui
实际感受一下端到端测试用例的魅力(大佬可以跳过)。有了这个 UI 工具开发调试都很方便!吹爆!!!
如何在项目中实际应用
首先实现登录模块
- 实现通用登录模块
// login.ts
import { Page } from '@playwright/test';
export async function login(
page: Page,
username: string = 'username',
password: string = 'password',
) {
// 如果首页未登录可访问,地址可改为https://example.com/login,你网站的登录页
await page.goto('https://example.com');
// 确认登录页加载完成
await page.waitForSelector('input[placeholder="请输入手机号 / 邮箱 / 用户名"]', {
state: 'visible',
});
await page.getByPlaceholder('请输入手机号 / 邮箱 / 用户名').fill(username);
await page.getByPlaceholder('请输入登录密码').fill(password);
await page.click('button[text="登录"]');
// 验证登录成功(如检查跳转后的 URL 或页面元素)
await expect(page).toHaveURL(/dashboard/); // 检查是否跳转到仪表盘
await expect(page.locator('.welcome-message')).toHaveText('欢迎回来!');
}
- 将其封装成一个setup,setup是playwright一个全局配置
// login-setup.ts
import { chromium } from '@playwright/test';
import { login } from './login';
async function globalSetup() {
const browser = await chromium.launch();
const page = await browser.newPage();
await login(page);
// 配置存放位置
await page.context().storageState({
path: 'tests/storageState.json',
});
await browser.close();
}
export default globalSetup;
- 在playwright.config.ts应用登录的全局配置
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
// ...other config
// 在这里加载登录作为全局配置使用
globalSetup: require.resolve('./tests/auth/login-setup.ts'),
use: {
trace: 'on-first-retry',
storageState: 'tests/storageState.json',
},
});
- 如上配置一个登录模块就写好啦,后续所有测试用例会共享登录状态。
关于添加、创建的测试用例怎么写
- 用以下表格、弹窗原型举例(公司项目打码较多,敬请谅解)
2. 测试用例,各步骤详细注释都已在代码里面
// ip.spec.ts
import { expect, test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
// 跳到你要测试的页面
await page.goto('https://example/your_path');
});
// 配置测试用例同步进行,比如验证添加和删除操作都写在一个文件里,得先添加才能删除
test.describe.configure({ mode: 'serial' });
test('添加 IP', async ({ page }) => {
// 点击按钮
await page.click('button span:has-text("新建规则")');
// 确认弹窗出现
await page.waitForSelector('div.ant-modal-title:has-text("新建 IP")', {
state: 'visible',
});
// 填写表单
await page.getByPlaceholder('请输入名称').fill(name);
await page.getByPlaceholder('192.168.1.1\n192.168.1.0/24').fill('192.168.1.1');
await page.getByPlaceholder('请输入备注').fill('备注');
// 点击确定按钮
await page.click('button span:has-text("确 定")');
// 保证接口返回成功,测试用例编写完成
await page.waitForSelector(`text=创建成功`, { state: 'visible' });
});
- 运行
pnpm exec playwright test --ui
验证测试用例是否可行
关于编辑的测试用例怎么写
- 测试用例,补充在ip.spec.ts里面,各步骤详细注释都已在代码里面
// ip.spec.ts
import { expect, test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
// 跳到你要测试的页面
await page.goto('https://example/your_path');
});
// 配置测试用例同步进行,比如验证添加和删除操作都写在一个文件里,得先添加才能删除
test.describe.configure({ mode: 'serial' });
test('添加 IP', async ({ page }) => {
// 点击按钮
await page.click('button span:has-text("新建规则")');
// 确认弹窗出现
await page.waitForSelector('div.ant-modal-title:has-text("新建 IP")', {
state: 'visible',
});
// 填写表单
await page.getByPlaceholder('请输入名称').fill(name);
await page.getByPlaceholder('192.168.1.1\n192.168.1.0/24').fill('192.168.1.1');
await page.getByPlaceholder('请输入备注').fill('备注');
// 点击确定按钮
await page.click('button span:has-text("确 定")');
// 等待接口返回弹出创建成功提示
await page.waitForSelector(`text=创建成功`, { state: 'visible' });
});
test('编辑 IP', async ({ page }) => {
// 点击表格里的编辑按钮
await page.locator('tr:has-text("IP") a:has-text("编辑")').first().click();
// 编辑弹窗出现
await page.waitForSelector('div.ant-modal-title:has-text("编辑 IP")', {
state: 'visible',
});
// 表单填写
await page.getByPlaceholder('请输入名称').fill('IP1');
await page.getByPlaceholder('192.168.1.1\n192.168.1.0/24').fill('192.168.2.1');
await page.getByPlaceholder('请输入备注').fill('备注1');
// 点击确认
await page.click('button span:has-text("确 定")');
// 等待接口返回弹出编辑成功提示
await page.waitForSelector(`text=编辑成功`, { state: 'visible' });
});
关于删除的测试用例怎么写
- 举例原型
- 测试用例,补充在ip.spec.ts里面,各步骤详细注释都已在代码里面
// ip.spec.ts
import { expect, test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
// 跳到你要测试的页面
await page.goto('https://example/your_path');
});
// 配置测试用例同步进行,比如验证添加和删除操作都写在一个文件里,得先添加才能删除
test.describe.configure({ mode: 'serial' });
test('添加 IP', async ({ page }) => {
// 点击按钮
await page.click('button span:has-text("新建规则")');
// 确认弹窗出现
await page.waitForSelector('div.ant-modal-title:has-text("新建 IP")', {
state: 'visible',
});
// 填写表单
await page.getByPlaceholder('请输入名称').fill(name);
await page.getByPlaceholder('192.168.1.1\n192.168.1.0/24').fill('192.168.1.1');
await page.getByPlaceholder('请输入备注').fill('备注');
// 点击确定按钮
await page.click('button span:has-text("确 定")');
// 等待接口返回弹出创建成功提示
await page.waitForSelector(`text=创建成功`, { state: 'visible' });
});
test('编辑 IP', async ({ page }) => {
// 点击表格里的编辑按钮
await page.locator('tr:has-text("IP") a:has-text("编辑")').first().click();
// 编辑弹窗出现
await page.waitForSelector('div.ant-modal-title:has-text("编辑 IP")', {
state: 'visible',
});
// 表单填写
await page.getByPlaceholder('请输入名称').fill('IP1');
await page.getByPlaceholder('192.168.1.1\n192.168.1.0/24').fill('192.168.2.1');
await page.getByPlaceholder('请输入备注').fill('备注1');
// 点击确认
await page.click('button span:has-text("确 定")');
// 等待接口返回弹出编辑成功提示
await page.waitForSelector(`text=编辑成功`, { state: 'visible' });
});
test('删除 IP', async ({ page }) => {
await page.locator(`tr:has-text("IP") a:has-text("删除")`).first().click();
await page.waitForSelector('text=/确认删除 IP.*吗?/', { state: 'visible' });
await page.click('button span:has-text("确 定")');
// 等待接口返回弹出删除成功提示
await page.waitForSelector(`text=删除成功`, { state: 'visible' });
});
其他测试用例如法炮制,增删改查的测试用例还算比较简单,叠加其他复杂业务场景就会复杂很多
总结
- 这是我第一次接触前端自动化测试用到了playwright感觉非常好用,十分推荐!!!
- 端到端的自动化测试也比较全面,前端后端的BUG都可以发现。之前也写过接口测试,不但写法复杂,前端的BUG还发现不了。
- 目前我司的测试大多数做的还是手工测试,在页面上点点点。如果有了这个工具,他们会轻松很多,测试的小伙伴可以学起来。