Published on

【质量保障】Playwright 使用入门

【质量保障】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".

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 来实现自动化

首先建立一个测试文件

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。

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 中,配置存储位置。

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

以下便是录制后的代码。

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 句延迟。

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)

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

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 选择器

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 查看签到返回如下:

实现签到测试用例代码如下

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,看到抽奖配置接口返回如下:

实现的测试用例代码如下

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 查看测试报告。

小结

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

使用 Playwright Test for VSCode 插件,可以快速初始化测试项目,运行测试用例,并添加 Cookie 来模拟登录状态,以及可以使用录制功能来自动生成测试代码。

另外, 可以使用 Locator 定位器替代语义化定位方法,以提高测试的准确性。最后,我们通过了一个掘金抽奖程序实例来说明测试代码的可靠性。

runjs-cool
关注微信公众号,获取最新原创文章(首发)View on GitHub