1. 为什么我抛弃了旧的自动化方案,转投 Playwright 阵营
先交代一下背景。我之前的项目里大量使用老牌的自动化测试框架做端到端测试,维护成本是真的高。每次跑测试,最头疼的不是业务逻辑变复杂,而是脚本和浏览器之间的“同步问题”:明明页面元素已经渲染出来了,脚本还在傻等;偶尔网络慢一点,用例就大面积误报。团队里每个人都曾被这种“不稳定”折磨过,最后大家甚至对自动化测试产生了某种抵触情绪。
后来我开始调研新一代的自动化测试工具,注意到 Playwright 出现的频率越来越高。GitHub 上星标涨得飞快,核心团队是从老牌框架项目出来的,属于“原生血统”的正规军。最初我抱着试试看的心态在几个小项目里跑了一下,结果发现几个痛点被精准解决了:
- 内置了自动等待机制,再也不用到处写
sleep(3000)或者是轮询轮询再轮询。 - 一套 API 同时覆盖 Chromium、Firefox、WebKit 三种浏览器内核,跨浏览器验证不再是奢望。
- 默认采用浏览器上下文隔离,每个测试用例天然独立,互相不污染。
- 自带Trace Viewer(追踪查看器),用例挂了能看到完整录屏、DOM 快照和网络请求,定位问题像看回放一样。
这几个特性几乎是在对着我之前踩过的坑逐一开火。所以这期内容我不打算只写一堆 API 文档翻译,而是想按照我自己的学习路径和实战经验,把 Playwright 从入门到落地整个流程串一遍。内容覆盖环境搭建、核心概念、常用 API、断言设计、调试技巧、测试报告以及持续集成接入,尽量做到“看完就能在自己项目里用起来”。
如果你正在做 Web 自动化测试,或者刚接触 Playwright 想快速进入实战状态,这篇文章应该能帮你省掉不少摸索时间。如果你对老框架已经很熟,想找个更稳的替代方案,这篇内容也可以给你一个相对客观的参考。
2. 环境准备与第一个测试用例:五分钟先跑起来
2.1 安装与内置浏览器管理
Playwright 的安装过程比我预想中还要顺。项目里只需要基于 Node.js(版本建议 16 以上),执行一条命令就能把核心库装好:
npm init -y npm install -D @playwright/test很多人会问,装完包是不是还得单独下载 Chrome 或者 Firefox?实际上 Playwright 会自己维护一套专用浏览器版本,你需要再执行一行命令把浏览器内核拉下来:
npx playwright install这里它默认会同时下载 Chromium、Firefox 和 WebKit 三件套。如果你只做业务上的回归测试,只下 Chromium 也能跑,但下载全量版本有个好处:跨浏览器兼容性验证可以随时做,不耽误事。
注意:
npx playwright install下载的浏览器路径和系统里你自己装的 Chrome 不是同一个东西,它们的版本是配套的,互不干扰。
2.2 第一个冒烟测试脚本
装完依赖之后,我习惯先在项目根目录建一个tests文件夹,然后丢一个最简单的冒烟脚本进去:
const { test, expect } = require('@playwright/test'); test('第一个冒烟用例', async ({ page }) => { await page.goto('https://example.com'); await expect(page).toHaveTitle('Example Domain'); });这里面的page对象是由框架自动注入的,每个测试都会拿到一个全新的页面实例,你不需要关心它的创建和销毁过程。然后执行:
npx playwright test框架会自动找到tests目录下的所有.spec.js或者.test.js文件,逐个跑完并输出一个美观的测试报告。
刚开始跑这个用例,就能感受到 exec 速度还不错。默认情况下,Playwright 是并行执行测试文件的,如果测试文件多了,它还会自动分配 worker 进程,尽量吃满 CPU 资源。
2.3 为什么 Playwright 能让脚本“更稳”
这里想多说一句,因为很多人用老框架写自动化,最后脚本全部变成了“睡眠大作战”:sleep(1秒)、sleep(2秒)、循环重试……这样写出来的脚本不但慢,而且特别脆。Playwright 之所以“稳”,核心在于它内置了一套可操作性的检查机制(Actionability Checks)。
在点击、填写、选择这类操作之前,它自动会做几件事:
- 元素是否已经附着在 DOM 上。
- 元素是否可见,不是隐藏状态。
- 元素是否稳定(比如动画还没停,它会等着)。
- 元素是否接收事件(比如有没有被遮罩层挡住)。
- 元素是否可用(不是 disabled 状态)。
这五条检查全部通过以后,操作才会真正执行。如果过了一段时间仍然不满足,才会报超时错误。这等于把以前大家手动写的“等待逻辑”内化到了框架层,脚本稳定性和开发效率一下子提升了一个档次。
3. 核心概念与定位器机制:不懂这些东西后面很容易踩坑
3.1 页面对象与浏览器上下文
在正式写复杂用例之前,有必要把 Playwright 的几个核心概念理清楚。
- Browser:浏览器实例,对应一个完整的浏览器进程。
- BrowserContext:浏览器上下文,可以简单理解成一个独立的“隐身窗口”会话。不同 context 之间的 Cookie、缓存、本地存储都是隔离的。
- Page:页面对象,表示一个标签页。大多数情况下,你的操作都是围绕 page 展开的。
这里最关键的设计是 BrowserContext。测试用例最怕什么?最怕相互影响。A 用例登录了,B 用例还是游客状态;A 用例往 localStorage 写了配置,B 用例读到了脏数据。在 Playwright 里,每个测试默认都跑在独立的 context 里,从源头上隔离了干扰。这也是我当初决定深入使用它的一个重要理由。
框架还给每个测试提供了固定的pagefixture,你甚至不用自己手动创建 context,直接拿去用即可。
3.2 定位器:从 CSS 选择器到逻辑定位
早期自动化测试里,大家习惯用page.click('#btn-submit')这类的写法,选择器写在操作里。这种写法最大的问题是改起来麻烦,代码里选择器到处散落,后期维护基本靠全局搜索。
Playwright 推荐的方式是定位器(Locator)。定位器不是立即去页面上找元素,而是先描述“我要找什么”,等到真正要执行操作或者断言时,再由框架去动态匹配。这个概念和函数式编程里的“惰性求值”有点像。
举个例子,原来我们可能会这样处理复选框:
await page.check('input[type="checkbox"]');但更好的方式是把定位器抽象出来:
const agreeCheckbox = page.locator('input[type="checkbox"]'); await agreeCheckbox.check();这样不仅代码可读性更高,也方便复用。Playwright 同时也提供了一组语义更强的定位器方法,比如getByRole()、getByText()、getByLabel()。在可访问性做得比较好的页面上,我更喜欢用角色定位器,例如:
await page.getByRole('button', { name: '提交订单' }).click();这个方法接近用户真实的使用视角——用户看到一个按钮,看到按钮上的文字,然后点击。脚本也按这个思路去找目标元素,别想着非得写一大长串 div 嵌套层级。
3.3 定位器优先级原则
很多新手上来就喜欢写一气呵成的 CSS 选择器,什么#root div.container > div.row > button.btn-primary,这种选择器的问题在于一旦页面结构改版,比如多加一层 div,用例就全挂了。我自己总结了一个优先级的参考顺序,可以给大家一个方向: