跳至主要內容

下一代 UI 自动化测试工具 Playwright

Node.jsPythonTesting

下一代 UI 自动化测试工具 Playwright

前言

Playwright 是微软于 2020 年发布的一款 E2E testing 工具,跟社区成熟的 Cypress 相比,稍显年轻。然而 Playwright 的主要优势有:

  1. 支持多语言:Node.js、Java、Python,也即它并非是前端工程师专属的工具
  2. 开箱即用的代码生成功能(Cypress 现在也支持,不过要修改配置或安装插件)

另外,Playwright 的安装没什么门槛,不像 Cypress 可能需要黑魔法。

综上所述,笔者认为 Playwright 是值得在研发过程中引入的一款测试工具,它可以帮助研发、测试团队较平滑地走上自动化测试之路。它适用的典型场景之一,就是做回归测试——测试人员再也不用在界面上使用鼠标进行“点点点”,解放双手,提高测试效率。

安装

yarn create playwright

根据命令提示,输入如下:
image.png
默认会下载所有浏览器,如果没有浏览器兼容性测试的需求,推荐如上图所示,手动安装一个浏览器。

以安装 chromium 为例,相应操作步骤如下:

  1. 修改配置
vi playwright.config.ts

注释掉以下内容:
image.png

  1. 安装浏览器
yarn playwright install --with-deps chromium

等待一段时间即可,如果失败,请重试。
image.png

推荐再安装 VS Code 插件open in new window,获取更好的使用体验。

使用

代码生成

虽然可以参考 example.spec.ts去编写测试用例,但这不是 Playwright 独特之处。Playwright 最引入注目的,是代码生成功能。

yarn playwright codegen

上述命令会打开两个浏览器窗口:

  1. 一个是普通的浏览器界面
  2. 另一个是代码生成界面,在前一个窗口进行的任何操作,都会生成相应的代码

image.png
虽然默认生成代码是 Javascript,但可以选择切换语言:
image.png
注意到可以生成 Pytest 的代码,对测试工程师来说,简直是福音。这也提示我们,Playwright 既可以由前端研发来使用,也可以由测试人员来使用,并不限制使用者的职业身份。

点击"Copy"按钮
image.png
然后打开代码编辑器,把代码复制进去即可。

点击"Clear"按钮,
image.png
可以清空本次操作生成的代码,从而开始进行下一次操作的代码生成。

如果是使用 VS Code 插件,点击"Record new"即可。
image.png

修改代码

生成的代码,最好还是检查一下,也许需要去掉一些多余的操作记录。
如下面的代码,Tab的操作只是人工操作时为了方便而进行的按键,对机器而言,是多余的,应该去掉。

test('login', async ({ page }) => {
  await page.goto('http://172.16.202.6:3000/#/login');
  await page.getByRole('textbox').first().click();
  await page.getByRole('textbox').first().fill('username');
  //await page.getByRole('textbox').first().press('Tab');
  await page.getByRole('textbox').nth(1).fill('code');
  //await page.getByRole('textbox').nth(1).press('Tab');
  await page.locator('input[type="password"]').fill('password');
  await page.getByRole('button', { name: '登录' }).click();
});

如果想基于现在的测试代码,继续生成新的代码,可以使用 VS Code:

  • 把光标放到测试用例的最后一行
  • 点击"Record at cursor",即可继续录制
image.png
image.png

执行用例

yarn playwright test

image.png
如果用例失败了,想查看到底哪里错了,可以用以下命令显示浏览器,查看用例执行过程:

yarn playwright test --headed

如果是使用 VS Code,直接点击运行用例即可。
image.png
勾选左下角的"Show broswer",即可显示浏览器。
image.png

调试用例

对于失败的用例,如何 debug呢?添加 --debug 参数即可。

yarn playwright test --debug

点击"Step over" 即可执行下一行代码。
image.png

如果是使用 VS Code,找到相应的用例,右键出现"Debug Test",点击即可。
image.png

查看报告

在执行完用例后,本地会生成目录 playwright-report,可以通过以下命令查看测试报告

yarn playwright show-report
image.png
image.png

常见场景与解决方案

应用登录

下面给出一个自动登录、并保存用户数据的解决方案。

先创建文件夹,并让 git 忽略它

mkdir -p playwright/auth
echo "playwright/auth" >> .gitignore

创建 login.ts

import { chromium, expect, FullConfig } from '@playwright/test';

async function login(config: FullConfig) {
  console.log('setting up')
  const { storageState } = config.projects[0].use
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // 这里执行登录操作
  await page.goto('http://localhost:3000/login');
  await page.getByRole('textbox').first().fill('user');
  await page.locator('input[type="password"]').fill('pass');
  await page.getByRole('button', { name: '登录' }).click();

  // 等待成功
  await expect(page.getByRole('button', { name: '登录' })).not.toBeVisible()
  // End of authentication steps.

  await page.context().storageState({ path: storageState as string });
  await browser.close();
}

export default login

修改 playwright.config.ts

export default defineConfig({
  globalSetup: require.resolve('./login'), // 添加这一行
  projects: [
    {
      name: 'chromium',
      use: { 
        ...devices['Desktop Chrome'],
        storageState: 'playwright/auth/user.json', // 添加这一行
      },
    },
  ]
})

环境变量

通常不同的环境 url 前缀是不同的,我们希望通过变量注入的方式来适配不同的环境,而不是硬编码在测试用例里。

我们可以借助模块 dotenv, 来配置 baseURL。

首先安装该模块:

yarn add dotenv

在项目根目录新建 .env 文件:

vi .env

输入以下内容:

BASE_URL=http://dev-domain.company.com

修改 playwright.config.ts

// 加载配置
require('dotenv').config()

export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL ? process.env.BASE_URL : 'http://127.0.0.1:3000', // 如果没有设置 .env,就默认使用本地路径
  },
})

然后,测试用例里,url 路径只写相对路径 /path,程序会自动拼接成完整的路径:http://dev-domain.company.com/path

以后要测试不同的环境时,只需要修改 .env 的变量值即可。

超时时间

默认的超时时间不太够用,建议修改 playwright.config.ts:

export default defineConfig({
  timeout: 5 * 60 * 1000, // 单个用例的超时时间
  expect: {
    timeout: 10 * 1000, // expect 语句的超时时间
  },
})

同时要注意拆分用例,没有依赖关系的用例建议拆分开来,避免用例执行时间过长超时。

元素选择

人们对元素选择的第一反应是使用 CSS 或 XPath,但 Playwright 并不鼓励这样使用,因为这些选择器容易改变。较为好的办法是,为测试元素添加专门的属性 testid,如下所示:

<div data-testid="my-div"></div>

然后通过下列方式进行选择:

await page.getByTestId('my-div').click()

当然,这种方式会对源代码有侵入。更为折衷的方式是,优先使用下列官方推荐的方法open in new window进行元素选择,最后再使用业务 class,
image.png

这里值得一提的是,业务class是针对 Tailwind CSS 这种“解构主义”的纯样式class而言的。你会发现,如果全是 Tailwind 的class,没有业务样式,E2E测试代码很不好写。

更复杂的示例:

await page.locator('.el-table__row').first().locator('.el-table_1_column_1').click();

await page.locator('.indicator-explore-chart-dlg .el-button').last().click();

let treeRow = page.locator('.el-tree-node__content', { hasText: '全部' }).first()
let checkAllElement = treeRow.locator('label.is-checked')
await expect(checkAllElement).toHaveCount(1)

如果是使用 VS Code,有辅助办法:

  1. 点击“Pick locator”
    image.png
  2. 切换到浏览器界面,点击目标元素
  3. 切回 VS Code,即可看到相应的元素选择代码
    image.png

声明断言 && 检查元素是否存在

生成的代码是没有断言的,因此,很有可能页面报错了,用例执行报告仍然显示成功。为避免这种情况,每个用例至少要有一句断言。

常用的断言是,检查某一元素是否存在:

await expect(page.locator('.selected-item')).toHaveCount(1) // 1 代表相应的元素数量

当然也可以用以下方法,这取决于元素是否可见(元素存在,未必可见)。

await expect(page.locator('.selected-item')).toBeVisible() 

注意,Node.js 才可以在 locator 里写 CSS 选择器,如果是 Python, 需要使用query_selector

await expect(page.query_selector('.selected-item')).toBeVisible() 

更多断言写法,参考官方文档(https://playwright.dev/docs/test-assertions#auto-retrying-assertionsopen in new window)。

获取第n个元素

通过定位器得到的元素可能不止一个,可以使用以下代码获得具体某一个元素:

page.locator('.my-class').nth(0); // n 从 0 开始算起

遍历元素

使用定位器后,调用.all()

for (const row of await page.getByRole('listitem').all())
  console.log(await row.textContent());

获取元素属性

await page.getAttribute('href')

判断子元素数量

使用 $$$ 元素选择器open in new window

const hiddenColumns = await page.$('.table-section .hidden-columns');
expect(await hiddenColumns.$$('*')).toHaveLength(0)

鼠标悬浮

有些元素是在鼠标悬浮时才显示或创建的,可以使用以下代码

await page.locator('.my-class').hover() // 模拟鼠标悬浮事件
await page.locator('.my-class .hover-element').click() // 点击悬浮后显示的元素

这里有个问题,自己怎么知道悬浮后显示的元素是否正确地定位到了呢?可以通过下面的小技巧:

  1. 切换到 codegen 打开的浏览器页面
  2. 打开网页控制台(按F12)
  3. 鼠标悬浮在目标元素上面,然后右键,如下图所示
image.png
image.png
  1. 点击控制台内部,则此时元素不会丢失 hover 状态,如下图所示
image.png
image.png
  1. 切换到 VS Code
image.png
image.png

操作剪贴板

读写剪贴板需要设置权限,下面给出一个判断是否成功从剪贴板获取特定文本的测试用例:

  test('clipboard', async ({page, context}) => {
    context.grantPermissions(['clipboard-read'])
    await page.goto('www.my-home.com');

    await page.getByRole('button', { name: 'Copy' }).click()
    const copyText = await page.evaluate(() => navigator.clipboard.readText())
    await expect(copyText.indexOf('text from copy!') > -1).toBeTruthy()
  })

持续集成

以 Gitlab CI 为例,说明 Playwright 如何集成进 CI 流水线中。其他方式如 Jenkins,请参考文档open in new window

首先确保已安装 Gitlab Runner 并成功注册,具体操作可以参考安装文档

端对端的测试耗时较长,并且对环境的稳定性有要求,作为回归测试的实践时,一般倾向于借助定时任务跑测试用例。

新建调度:
image.png
设置调度时间及环境变量:

  • 每 6 小时跑一次
  • e2e 环境变量的值为 true
image.png
image.png

现在可以开始编写 .gitlab-ci.yml,下面只给出测试相关的配置。

image: node:lts # it doesn't matter because playwright will use another image

cache:
  paths:
    - node_modules/

stages:
  - test

e2e:
  stage: test
  tags:
    - your-runner-name # 把这里修改成实际的 gitlab runner 对应的 tag
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule" && $e2e'
  image:
    name: mcr.microsoft.com/playwright:v1.33.0-jammy
    entrypoint: ['/bin/bash', '-c', 'ln -snf /bin/bash /bin/sh && /bin/bash -c $0' ]
  script:
    - yarn --frozen-lockfile --ignore-engines
    - yarn playwright install --with-deps chromium
    - yarn playwright test

注意点:

  1. entrypoint 解决的是 shell not foundopen in new window 问题
  2. --ignore-engines 可以在不修改源码的情况下避免安装失败
  3. 只有定时调度才会触发该任务的执行

再修改 Gitlab Runner 的配置,解决yarn命令无法运行open in new window的问题:

vi /srv/gitlab-runner/config/config.toml

根据 token 找到对应的 Runner 配置,按照下图所示,把红框处的值设置成 true
image.png
config.toml 里面可能会有多个 Runner 配置,如何找到要修改哪一个呢?
可以在项目界面,根据下图所示的 token(w8exPBfA) 去查找。
image.png

修改完后,重启 Gitlab Runner

docker restart gitlab-runner 

最后,使用 Chromium 可能会出现内存超出限制的问题,需要对 Docker 设置 --ipc=host,配置 .gitlab-ci.yml 如下:

services:
  - name: docker:dind
    command: ["--insecure-registry", "registry.example.com", "--storage-driver=overlay2", "--iptables=false", "--ip-masq=false", "--ipv6=false", "--fixed-cidr=10.0.0.0/8", "--fixed-cidr-v6=fc00::/7", "-H", "tcp://0.0.0.0:2375", "-H", "unix:///var/run/docker.sock"]
    ipc: host

其他

setup.pyopen in new window bdist_wheel did not run successfully

Python安装时,可能会再现此错误。解决方案如下:

pip install cmake
# or 
pip3 install cmake

之后,再安装

pip install wheel setuptools --upgrade
# or
pip3 install wheel setuptools --upgrade

最后,重新安装Playwright即可

pip install playwright
# or
pip3 install playwright
playwright install --with-deps chromium
上次编辑于:
贡献者: levy