前端怎么写端到端自动化测试 - Playwright介绍

143 阅读6分钟

Playwright 介绍

  1. Microsoft 开发的一款端到端的自动化测试工具。

什么是端到端?端到端就是模拟真实用户场景,从用户界面(前端)到接口、数据库(后端)的完整验证流程

  1. 多语言支持: JS/TS、Python、Java、.NET,本人作为前端切图仔,只介绍 JS/TS。
  2. 跨浏览器,一套测试用例,可以通过配置测试多浏览器兼容性。比如 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,其他安装参照官网

  1. 在已有项目运行 pnpm create playwright,这一步主要是进行一些配置,设置测试用例的目录,是否添加 Github 工作流,安装 playwright 浏览器(这一步要选 true, 跑自动化测试的时候需要启动 playwright 浏览器) image.png
  2. 安装完成后会生成 tests 文件夹和 playwright.config.ts 配置文件 image.png
  3. 同时会生成一些简单的测试用例,强烈建议运行 pnpm exec playwright test --ui 实际感受一下端到端测试用例的魅力(大佬可以跳过)。有了这个 UI 工具开发调试都很方便!吹爆!!!

image.png

如何在项目中实际应用

首先实现登录模块

  1. 实现通用登录模块
// 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('欢迎回来!');

}
  1. 将其封装成一个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;
  1. 在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',
  },
});
  1. 如上配置一个登录模块就写好啦,后续所有测试用例会共享登录状态。

关于添加、创建的测试用例怎么写

  1. 用以下表格、弹窗原型举例(公司项目打码较多,敬请谅解) image.png

image.png 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' });
});
  1. 运行 pnpm exec playwright test --ui 验证测试用例是否可行

image.png

关于编辑的测试用例怎么写

  1. 测试用例,补充在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' });
});

关于删除的测试用例怎么写

  1. 举例原型 image.png
  2. 测试用例,补充在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' });
});

其他测试用例如法炮制,增删改查的测试用例还算比较简单,叠加其他复杂业务场景就会复杂很多

总结

  1. 这是我第一次接触前端自动化测试用到了playwright感觉非常好用,十分推荐!!!
  2. 端到端的自动化测试也比较全面,前端后端的BUG都可以发现。之前也写过接口测试,不但写法复杂,前端的BUG还发现不了。
  3. 目前我司的测试大多数做的还是手工测试,在页面上点点点。如果有了这个工具,他们会轻松很多,测试的小伙伴可以学起来。