跳到主要内容

编写 Playwright 测试

我们的 Playwright 测试是用 Playwright 测试框架写的。我们用这些测试来测我们的示例, 确保它们按预期工作——这反过来也就确保了我们的各个包按预期工作。

本指南将演示如何为我们的示例编写 Playwright 测试、如何新建示例并针对它做测试。

针对既有示例写测试​

如果你想使用某个既有示例,可以在 utils/ExampleRunner/example-info.json 文件中找到示例清单。用 exampleName 属性来引用你想用的那个示例。 例如想用 annotationToolModes 这个示例,可以这样写:

import { test } from '@playwright/test';
import { visitExample } from './utils/index';

test.beforeEach(async ({ page }) => {
await visitExample(page, 'annotationToolModes');
});

test.describe('Annotation Tool Modes', async () => {
test('should do something', async ({ page }) => {
// 你的测试代码写在这里
});
});

针对新增示例写测试​

我们的 playwright 测试是跑在示例上的。如果你想新增一个示例, 可以把它放到相应包根目录下的 examples 文件夹里, 例如 packages/tools/examples/{your_example_name}/index.ts, 然后在 utils/ExampleRunner/example-info.json 文件中把它注册到正确的分类下—— 比如它与工具相关,就可以放进既有的 tools-basic 分类。 如果找不到合适的分类,你也可以新建一个分类, 把它加到 example-info.json 的 categories 对象里。

{
"categories": {
"tools-basic": {
"description": "Tools library"
},
"examplesByCategory": {
"tools-basic": {
"your_example_name": {
"name": "Good title for your example",
"description": "Good description of what your example demonstrates"
}
}
}
}
}

做完这一步,就可以用 tests/utils/visitExample.ts 中的 visitExample 函数针对该示例写测试了。例如想为 your_example_name 这个示例写测试:

import { test } from '@playwright/test';
import { visitExample } from './utils/index';

test.beforeEach(async ({ page }) => {
await visitExample(page, 'your_example_name');
});

test.describe('Your Example Name', async () => {
test('should do something', async ({ page }) => {
// 你的测试代码写在这里
});
});

这样做还会让你的示例出现在我们的文档页面上,使用者就能看到该示例怎么用—— 所以新增一个示例其实是一举两得。

截图​

检查测试是否按预期工作的一个好办法,是在测试的不同阶段截图。 可以用位于 tests/utils/checkForScreenshot.ts 的 checkForScreenshot 函数来截图。你还应当提前规划好要截哪些图—— 截图需要先在 tests/utils/screenshotPaths.ts 文件中定义。 例如想在添加一次测量之后截图,可以这样定义截图路径:

const screenShotPaths = {
your_example_name: {
measurementAdded: 'measurementAdded.png',
measurementRemoved: 'measurementRemoved.png',
},
};

此时截图文件还不存在也没关系,下一步会处理。定义好截图路径之后, 就可以在测试里用 checkForScreenshot 函数来截图了。 例如想在添加一次测量之后对 cornerstone-canvas 元素截图:

import { test } from '@playwright/test';
import {
visitExample,
checkForScreenshot,
screenshotPath,
} from './utils/index';

test.beforeEach(async ({ page }) => {
await visitExample(page, 'your_example_name');
});

test.describe('Your Example Name', async () => {
test('should do something', async ({ page }) => {
// 你添加一次测量的测试代码写在这里
const locator = page.locator('.cornerstone-canvas');
await checkForScreenshot(
page,
locator,
screenshotPath.your_example_name.measurementAdded
);
});
});

这个测试第一次运行时必然会失败,但它会帮你把截图生成出来: 你会在 tests/screenshots 文件夹下看到 3 个新条目,分别位于 chromium/your-example.spec.js/measurementAdded.png、 firefox/your-example.spec.js/measurementAdded.png 和 webkit/your-example.spec.js/measurementAdded.png。 这时再跑一次测试,它就会拿这些截图与示例当前的状态做比对。 在提交这些基准截图、或用它们做比对之前,请先确认它们是正确的。

模拟鼠标拖拽​

如果想模拟鼠标拖拽,可以用位于 tests/utils/simulateDrag.ts 的 simulateDrag 函数。它可以在任意元素上模拟鼠标拖拽。 例如想在 cornerstone-canvas 元素上模拟拖拽:

import {
visitExample,
checkForScreenshot,
screenShotPaths,
simulateDrag,
} from './utils/index';

test.beforeEach(async ({ page }) => {
await visitExample(page, 'stackManipulationTools');
});

test.describe('Basic Stack Manipulation', async () => {
test('should manipulate the window level using the window level tool', async ({
page,
}) => {
await page.getByRole('combobox').selectOption('WindowLevel');
const locator = page.locator('.cornerstone-canvas');
await simulateDrag(page, locator);
await checkForScreenshot(
page,
locator,
screenShotPaths.stackManipulationTools.windowLevel
);
});
});

我们这个拖拽模拟工具可以在任意元素上模拟拖拽,并且不会拖出边界。 它会计算该元素的包围盒,确保拖拽保持在元素范围之内。 这对大多数工具来说已经够用,而且比自己传入自定义的 x、y 坐标更好—— 后者容易出错,也让代码难以维护。

运行测试​

写好测试之后,可以用下面的命令运行:

./scripts/run-playwright.sh
./scripts/run-playwright.sh --compat
./scripts/run-playwright.sh --cpu
./scripts/run-playwright.sh --next

这个包装脚本会运行 npx playwright test、按所选模式自动挑选测试文件, 并把带时间戳的日志和产物写到 reports/ 下。

例如:

reports/legacy-playwright/<timestamp>/
reports/compat-playwright/<timestamp>/
reports/compat-cpu-playwright/<timestamp>/
reports/generic-viewport-playwright/<timestamp>/

该包装脚本支持的标志:

  • --compat:以 ?type=next 打开示例页面。
  • --cpu:以 ?cpu=1 打开示例页面。
  • --next:只运行 tests/genericViewport/**/*.spec.ts。

Playwright 的 --next 与 Karma 的 --next 含义不同。 Playwright 用它只选取 tests/genericViewport 那套测试; 而 Karma 用它作为便捷模式,跑兼容模式和 CPU 两轮。

其余参数会被直接透传给 playwright test, 所以你仍然可以照常使用 Playwright 的 CLI:

./scripts/run-playwright.sh --project chromium --headed
./scripts/run-playwright.sh -g "stack viewport"
./scripts/run-playwright.sh --workers 1
./scripts/run-playwright.sh --update-snapshots

有用的环境变量:

  • PLAYWRIGHT_REUSE_EXISTING_SERVER=true|false: 控制是否复用已配置的本地示例服务器。
  • 包装脚本内部会设置 PLAYWRIGHT_FORCE_COMPAT、 PLAYWRIGHT_FORCE_CPU_RENDERING、PLAYWRIGHT_HTML_OUTPUT_DIR 和 PLAYWRIGHT_HTML_OPEN=never。

例如:

./scripts/run-playwright.sh
./scripts/run-playwright.sh --compat
./scripts/run-playwright.sh --project chromium --headed
./scripts/run-playwright.sh -g "stack viewport"
PLAYWRIGHT_REUSE_EXISTING_SERVER=true ./scripts/run-playwright.sh --project chromium
./scripts/run-playwright.sh --next

更新截图基线​

Playwright 的快照文件存放在 tests/screenshots/<project>/<spec>/<name>.png, 路径模板来自 playwright.config.ts。

常规运行会与那些已提交的截图做比对。要重写它们, 把 Playwright 原生的快照标志透传给包装脚本即可:

./scripts/run-playwright.sh --update-snapshots
./scripts/run-playwright.sh --next --update-snapshots
./scripts/run-playwright.sh --project chromium --update-snapshots

开发时手动托管示例​

默认情况下,当 Playwright 需要启动自己的本地服务器时, 它会在 playwright.globalSetup.ts 中构建这些示例, 随后配置好的 webServer 会在 http://localhost:3333 上托管 .static-examples。

如果你想在开发期间自己手动托管这些示例, 可以自己运行同一条命令,然后告诉 Playwright 复用这个已有的服务器:

yarn run build-and-serve-static-examples
PLAYWRIGHT_REUSE_EXISTING_SERVER=true ./scripts/run-playwright.sh

Playwright 的 VSCode 扩展与录制测试​

如果你用 VSCode,可以借助 Playwright 扩展来帮你写测试。 该扩展提供了测试运行器以及许多很好用的功能, 例如用鼠标点选 locator、录制一个新测试等等。 在 VSCode 的扩展页搜索 Playwright 即可安装, 也可以访问 Playwright 扩展页面。