单元测试框架 Playwright 使用入门

playwright 介绍

Playwright 是一个端到端(E2E)测试框架, 它可在所有现代浏览器中运行功能强大的测试和自动化。支持多种编程语言 API, 包括 JavaScript 、 TypeScript, Python, .NET Java。正因为它基于游览器,相当与模拟用户真实操作,因此不光能够用来跑测试用例,还可以用来写爬虫。

Playwright Test for VSCode

我们可以安装一个 vscode 插件 Playwright Test for VSCode,来帮助我们运行、录制、调试测试用例。

初始化项目

如果项目中没有安装Playwright NPM 包,或者重新开始一个新的测试项目,需要可以在 vscode 命令面板中的intsll Playwright

选择我们常用的浏览器,不必担心选错,后面可以在项目中更改。还可以选择 GitHub Action ,这样就可以轻松在 Github 中持续集成。

这里我选择 chromium,这样可以只下载一个浏览器内核。

点击OK后,插件会帮我们自动初始化程序, 下图是初始化的目录结构

配置文件都在 playwright.config.ts 中。

看下 package.json,只包含了一个包@playwright/test

运行测试

所有的测试用例都要写在 tests 文件夹中,默认有一个测试文件,包含有 2 个测试用例,代码在example.spec.ts 中。

第一个测试用例:确保标题包含 Playwright;

第二个测试用例:确保点击 "Get Started"后,跳转到 intro 的链接。

选择左侧的测试用例,并且勾选 Show browser,我们便可以直观的看到 Playwright 运行测试的过程。

以上例子默认是使用 chromium 来运行的,并且 chromium 不包含任何 cookie 和缓存信息。

playwright.config.ts 配置文件中, 可以配置启用的浏览器为 chrome,我们只需要增加一个参数 channel,让 Playwright 使用浏览器来运行。 也可以是其他浏览器,参数可以为: "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev","msedge-canary".

js 复制代码
use: {
+    channel:'chrome',
    /* Base URL to use in actions like `await page.goto('/')`. */
    // baseURL: 'http://127.0.0.1:3000',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',
  },

我们虽然改成了使用浏览器来运行,但是启动的浏览器也是一个无痕模式,不包含任何缓存信息。

测试的系统往往需要登录,而在运行每个测试用例之前运行,都需要登录,这肯定是繁琐的,因此我们可以在运行测试用例之前,手动拷贝 cookies,注入到浏览器中。

比如掘金的每日签到和抽奖,我就可以使用 Playwright 来实现自动化

首先建立一个测试文件

ts 复制代码
import { test, expect, type Page } from "@playwright/test";

test("登录", async ({page, context}) => {
    await context.addCookies([
        {
            name: "sessionid",
            value: "xxx",
            path: "/",
            domain: ".juejin.cn",
        },
        {
            name: "sessionid_ss",
            value: "xxx",
            path: "/",
            domain: ".juejin.cn",
        },
    ]);
    await page.goto("https://juejin.cn/");
});

打开 chrome 控制台,复制 cookies, 添加到代码中

此时点击左侧运行的测试用例,发现已经是登录状态。

录制一个测试用例

如果要手动去查找 dom ,从零开始写一个测试用例肯定是繁琐的,因此 Playwright VSCode 插件提供了录制功能。

运行上一次测试用例后,浏览器是未关闭的。此时我们点击 vscode 左侧的 Record new 按钮,vscode 便会自动创建一个测试文件,并且记录操作步骤。

录制时,浏览器又是一个全新的,不保留任何状态,那如果我们要测试的是登录后的功能,岂不是又要登录? 其实 playwright 可以保存登录状态。

在上面测试用例后加一句 storageState。

ts 复制代码
import { test, expect, type Page } from "@playwright/test";

test("登录", async ({page, context}) => {
    await context.addCookies([
        {
            name: "sessionid",
            value: "xxx",
            path: "/",
            domain: ".juejin.cn",
        },
        {
            name: "sessionid_ss",
            value: "xxx",
            path: "/",
            domain: ".juejin.cn",
        },
    ]);
    await page.goto("https://juejin.cn/");
+   await context.storageState({ path: 'state.json' });
});

并且在 playwright.config.ts 中,配置存储位置。

此时我们录制操作,就已经是登录状态了。

以下便是录制后的代码。

js 复制代码
import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
  await page.goto('https://juejin.cn/');
  await page.getByRole('button', { name: '去签到' }).click();
  await page.getByRole('button', { name: '立即签到' }).click();
  await page.getByRole('button', { name: '去抽奖' }).click();
  await page.getByText('免费抽奖次数:1次').click();
  await page.getByRole('button', { name: '收下奖励' }).click();
});

录制完成后,直接运行代码可能会报错,我们需要调整一下,因为有些文本是异步请求实现的,有些事件是请求成功后绑定的,在手动录制时,因为已经响应完成,因此没问题,我们加上 2 句延迟。

js 复制代码
test("test", async ({ page }) => {
  await page.goto("https://juejin.cn/");
+  await page.waitForTimeout(1000);
  await page.getByRole("button", { name: /去签到|已签到/ }).click();
+  await page.waitForTimeout(1000);
  await page.getByRole("button", { name: /今日已签到|立即签到/ }).click();
  await page.getByRole("button", { name: "去抽奖" }).click();


  const lotteryElement = await page.$("#turntable-item-0");
  const buttonText = await lotteryElement?.textContent();
  if (buttonText === "免费抽奖次数:1次") {
    await lotteryElement?.click();
    await page.getByRole("button", { name: "收下奖励" }).click();
  } else {
    expect(
      page.locator("#turntable-item-0", { hasText: /单抽/ })
    ).toBeDefined();
  }
});

便可以运行成功,注意这里我使用了 waitForTimeout 这个 api 在官网中已经被标记了废弃(deprecate)

实际测试场景中请使用改用网络事件、选择器变得可见等信号。

js 复制代码
await page.goto("https://juejin.cn/");
  await page.waitForResponse((res) =>
    res.url().includes("/user_api/v1/incentive_activity/award_after_login")
  );
  await page.getByRole("button", { name: /去签到|已签到/ }).click();

  await page.waitForResponse((res) =>
    res.url().includes("/growth_api/v2/get_today_status")
  );
  await page.getByRole("button", { name: /今日已签到|立即签到/ }).click();

等待接口响应成功后再出发点击事件。

还有一点就是,自动录制的代码,一般使用了语义化定位方法,比如getByRolegetByText,这些定位器往往不够准确,改动代码会导致测试用例失效。

因此我们可以使用 locator 定位器来替换。

在 Playwright 中,Locator 表示一种元素查找方式,是 Playwright 提供的一组方法,用于定位页面上的元素。

Locator 支持 XPath 和 CSS 选择器

js 复制代码
await page.locator(
    '#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input'
).click();

await page
    .locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
    .click();

在 vscode 中可以使用 Pick locator 快速活动当前的 dom 定位。

测试用例及断言

录制的测试代码只能确保业务能够跑通,但不能证明程序的可靠与健壮。一旦测试用例出错,也不知道是程序错误还是测试用例错误,因此我们还是需要根据测试用例来写可靠的测试代码。

比如上述掘金抽奖程序可以包含以下测试用例

  1. 签到的状态需要根据接口返回显示

通过 network 查看签到返回如下:

因此我的签到测试用例代码如下

js 复制代码
test("签到的状态根据接口返回显示", async ({ page }) => {
  await page.goto("https://juejin.cn/user/center/signin");
  const promise = await page.waitForResponse((res) =>
    res.url().includes("/growth_api/v2/get_today_status")
  );
  const res = await promise.json();
  if (res.data.check_in_done) {
    await expect(page.locator(".signedin")).toHaveText("今日已签到");
  } else {
    await expect(page.locator(".signedin")).toHaveText("立即签到");
    await page.getByRole("button", { name: /立即签到/ }).click();
    await page.getByRole("button", { name: "去抽奖" }).click();
    //调整到抽奖页面
    await expect(page).toHaveURL(/user\/center\/lottery/);
  }
});
  1. 抽奖页面,根据接口返回显示抽奖次数和奖品

通过 network,看到抽奖配置接口返回如下:

因此我的测试用例代码如下

js 复制代码
test("根据接口返回显示抽奖次数", async ({ page }) => {
  await page.goto("https://juejin.cn/user/center/lottery");
  const promise = await page.waitForResponse((res) =>
    res.url().includes("/growth_api/v1/lottery_config/get")
  );
  const res = await promise.json();
  const lotteryNames = res.data.lottery.map((item) => {
    if (item.unlock_count === 0) {
      return new RegExp(item.lottery_name);
    } else {
      return new RegExp(`再抽${item.unlock_count}次解锁`);
    }
  });

  await expect(page.locator(".item-container .turntable-item")).toHaveText(
    lotteryNames
  );
  
  if (res.data.free_count) {
    await expect(page.locator("#turntable-item-0")).toHaveText(
      `免费抽奖次数:${res.data.free_count}次`
    );
  } else {
    await expect(page.locator("#turntable-item-0")).toHaveText("单抽 200");
  }
});

有了以上断言,我们便可以确保前端页面显示与接口返回显示一致。

运行完成后,可以在 playwright-report 查看测试报告。

当然这是我学习 e2e 测试的一个例子,不够准确,也不够详情。

小结

本文介绍了 Playwright 单元测试框架的入门使用,Playwright 是一个功能强大的端到端(E2E)测试框架,支持多种编程语言 API,适用于现代浏览器,还可用于编写网络爬虫。

首先介绍了 Playwright Test for VSCode 插件,以及如何初始化测试项目,如何运行测试用例,并指出可以选择不同的浏览器作为测试环境, 如何添加 Cookie 来模拟登录状态,以及如何使用录制功能来自动生成测试代码。

另外,文章强调了使用 Locator 定位器替代语义化定位方法,以提高测试的准确性。最后,我们通过了一个掘金抽奖程序实例强调了断言的重要性,以确保测试代码的可靠性。

相关推荐
吃杠碰小鸡38 分钟前
commitlint校验git提交信息
前端
虾球xz1 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇1 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员2 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐2 小时前
前端图像处理(一)
前端
程序猿阿伟2 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒2 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪2 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背2 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript