news 2026/10/3 1:09:45

Playwright测试框架实战:从零编写稳定可靠的Web端到端测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Playwright测试框架实战:从零编写稳定可靠的Web端到端测试

没接触过Playwright的同学,可能觉得它不过是又一个Selenium换皮工具。但真把测试用例写起来,你会发现它跟传统自动化测试的思考方式完全不一样——你不再天天跟time.sleep、显式等待、StaleElement异常搏斗,而是把“元素可见、可点、可输入”这些事直接交给框架去判断。这篇文档是Playwright中文系列的第一篇,主题就是“编写测试”,我会从环境准备一直讲到报告调试,尽量把每条命令背后的设计逻辑也讲清楚,让大家不只会抄代码,还能理解为什么这么写。

1. 为什么是Playwright:三个让测试不“脆”的设计核心

说句实话,早期我用Selenium写UI自动化,最痛苦的不是写用例,而是维护用例。今天元素加了个span包一层,明天按钮从click变成mouseover弹出,后天页面加载快了0.5秒导致等待超时——整个测试套件就像用沙子堆的城堡,一碰就塌。Playwright把这些问题从根上处理掉了,它的设计哲学不是“模拟用户操作浏览器”,而是“让浏览器按照用户的真实行为来完成操作”。

1.1 Web-First断言:测试意图更直接

传统断言是assert element.text == "xxx",你得先拿到元素再取值再比较,中间任何一步失败都会抛出一堆底层的WebDriver异常。Playwright的断言是await expect(page.locator(...)).toHaveText("xxx"),它会自动重试,直到元素出现、文本匹配或超时。这个区别理解起来就像“你去餐厅点菜,服务员会一直盯着后厨直到菜做好端上来”,而不是“你去窗口问一句好了没,没好就报错”。所有expect断言都内置了重试机制,默认超时5秒,你基本不用手写等待。

1.2 自动等待与操作链:告别sleep

page.click()、page.fill()、page.check()这些动作,在执行之前会自动检查元素是否附加到DOM、是否可见、是否稳定(不抖动)、是否能接收事件。有一个条件不满足,它就继续等,等到超时为止。这背后的机制叫Actionability检查,是它和Selenium最大的体验差异。你可以理解为Playwright把“等待”这件事从“测试代码”里剥离出来,下沉到了“浏览器操作层”,所以你的代码里基本看不到Thread.sleep(1000)这种垃圾代码。

1.3 浏览器上下文隔离:每个测试都是全新用户

每次测试运行,Playwright都会启动一个全新的浏览器上下文(Browser Context),相当于打开了一个隐身窗口。Cookie、LocalStorage、缓存全部是空的,测试之间零污染。这个设计特别适合需要登录态的测试——你想让用例B复用用例A的登录状态,那你就得显式去保存和复用存储状态,这就逼着你把用例设计成“每个用例独立可跑”,从根本上避免了测试套件里最常见的“顺序依赖”问题。

那到底什么项目适合用Playwright?我认为凡是基于Chromium、Firefox、WebKit的Web应用,无论前端是React、Vue还是老式jQuery,都可以用它做端到端测试。如果你是做爬虫、页面数据抓取、自动化脚本的同学,它也能当无头浏览器工具用,但这篇文档我们聚焦在测试编写上。

2. 安装与初始化:第一步就避开的三个坑

Playwright支持JavaScript/TypeScript、Python、Java、.NET四种语言。这篇文档按JavaScript/TypeScript的路子走,因为生态最完整、跟前端工具链融合得最好。不过Python版的API几乎一致,看完这篇你再切到Python也没什么学习成本。

2.1 环境准备与依赖安装

先确认机器上有Node.js 18以上版本(我用的是Node 20 LTS)。然后在你打算放测试代码的目录下执行:

npm init -y npm i -D @playwright/test npx playwright install

第三条命令会下载Chromium、Firefox、WebKit三个浏览器的二进制文件。这里我要提醒第一个坑:如果你在公司网络环境里,下载大概率会失败。要么给npm配置镜像,要么单独设置播放器浏览器的下载镜像环境变量,比如在.bashrc或PowerShell里设置:

# Windows PowerShell 示例 $env:PLAYWRIGHT_DOWNLOAD_HOST = "https://npmmirror.com/mirrors/playwright/" npx playwright install chromium

我只装了Chromium,因为绝大多数业务测试跑在Chrome内核上就够了,Firefox和WebKit主要用来做兼容性验证。等后面CI上真的需要多浏览器覆盖时,再补装不迟,没必要一开始就全量下载占磁盘。

2.2 初始化项目结构与配置文件

执行npx playwright test之前,建议先跑一下npx playwright test --init(新版本可用),它会生成一个基础配置文件和示例测试目录。不过我更习惯手动建目录,结构如下:

project-root/ ├── playwright.config.js ├── tests/ │ ├── login.spec.js │ └── cart.spec.js └── test-data/ └── users.json

playwright.config.js是核心配置,我最小化配置长这样:

// playwright.config.js const { defineConfig } = require('@playwright/test'); module.exports = defineConfig({ testDir: './tests', timeout: 30000, retries: 1, use: { baseURL: 'https://example.com', headless: true, viewport: { width: 1280, height: 720 }, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'retain-on-failure', }, projects: [ { name: 'chromium', use: { browserName: 'chromium' } }, ], });

这里要特别说明trace: 'retain-on-failure',它是Playwright的调试神器——测试失败时会自动录制一份完整的操作轨迹,包含DOM快照、网络请求、控制台日志。后面写复杂用例你就知道它多值钱了。还有retries: 1,我建议本地调试时设成0,否则失败用例会自动重跑一遍,影响我们判断问题。

2.3 同步API与异步API怎么选

Playwright同时提供sync_playwright(Python里)和playwright(异步)两种风格。JavaScript/TypeScript下主要用异步API,因为Node.js本身就是事件循环模型,异步写法才能充分利用I/O并发。很多新手会问:为什么page.click()前面要加await?因为这些操作走的都是CDP(Chrome DevTools Protocol)通道,本质上是异步发消息再等响应。你不需要深挖协议细节,你只需要记住:所有对页面有影响的操作、所有取值的操作,前面都加await,养成肌肉记忆就行。

第一阶段的安装和项目骨架到这里是够用的,MCP(Model Context Protocol)那些集成后面单独开篇再说,现在先聚焦到“写测试”这件事上。

3. 第一个测试用例:从定位到断言的完整链路

纸上谈兵没意思,直接上一个能跑的示例。假设我们要测试一个简单的搜索功能,页面有一个输入框和一个搜索按钮,搜索后结果区域会显示关键词。

3.1 写一个最小可运行用例

在tests/目录下新建search.spec.js:

// tests/search.spec.js const { test, expect } = require('@playwright/test'); test('用户可以通过关键词搜索到结果', async ({ page }) => { // 打开页面 await page.goto('https://example.com/search'); // 在输入框输入关键词 await page.getByLabel('搜索关键词').fill('playwright'); // 点击搜索按钮 await page.getByRole('button', { name: '搜索' }).click(); // 断言结果区域包含关键词 await expect(page.locator('.search-results')).toContainText('playwright'); });

就这么简单,没有一行关于等待的代码,没有try-catch,没有显式的sleep。我把每一步拆开讲一下:

  • page.goto():跳转页面,会等待页面load事件触发。但注意,它不等所有图片和XHR请求完成,如果你要测SPA应用,后面要结合waitForLoadState('networkidle'),或者更推荐对具体元素做断言。
  • page.getByLabel('搜索关键词'):这是Playwright的语义化定位器,它会找到label标签关联的输入框。比page.fill('input[name="keyword"]')这种CSS写法人性化太多。
  • page.getByRole('button', { name: '搜索' }):通过ARIA角色定位按钮,这模拟的是“残障用户使用读屏软件时如何识别页面”,是一种更接近用户视角的定位方式。
  • expect(page.locator('.search-results')).toContainText('playwright'):这个断言会自动等待最多5秒,直到.search-results元素中出现包含playwright的文本。

3.2 用test.describe组织业务场景

当用例多起来以后,建议用test.describe做分组,这样报告里层级清晰,也能给一组用例统一加前置和后置钩子:

test.describe('搜索功能', () => { test.beforeEach(async ({ page }) => { await page.goto('https://example.com/search'); }); test('搜索空关键词给出提示', async ({ page }) => { await page.getByRole('button', { name: '搜索' }).click(); await expect(page.locator('.empty-tip')).toBeVisible(); }); test('搜索特殊字符不报错', async ({ page }) => { await page.getByLabel('搜索关键词').fill('@#$%^&*'); await page.getByRole('button', { name: '搜索' }).click(); await expect(page.locator('.search-results')).toBeVisible(); }); });

beforeEach比beforeAll更适合UI测试,因为每个用例都要保证从干净状态开始。这个组织方式看多了你会发现,它跟Jest、Mocha的语法基本同构,上手成本为零。

3.3 命令行运行与参数说明

跑测试用:

npx playwright test

常用参数:

参数作用
--headed有头模式,肉眼看浏览器操作过程
--debug调试模式,会打开Playwright Inspector,可单步执行
--grep "搜索"只运行标题中包含“搜索”的用例
--project=chromium指定浏览器项目运行
--workers=1单线程执行,排错时优先用
--reporter=list使用list格式报告,调试时更直观

我日常开发时习惯用npx playwright test --debug来写用例,一边写一边看每一步在页面上的实际效果。运行完可以在终端看到测试通过或失败的汇总,失败时还会输出错误堆栈和定位器建议,比如“尝试使用getByRole获取该元素”,这个提示非常友好,照着改就行了。

4. 定位器与自动等待:测试稳定的根基

很多人写完第一版用例能跑通,但跑几次就偶发失败。大多数问题都出在定位器写得不稳,或者对自动等待机制理解不够深。这一节是把测试写稳的核心,也是我实际项目中投入时间最多的地方。

4.1 定位器优先级:从“人如何看页面”出发

Playwright推荐的定位器优先级是这样的:

  1. getByRole:按角色定位,比如按钮、链接、文本框、复选框。最接近用户感知,也是官方最推荐的。
  2. getByLabel:按表单标签定位,适合输入框、下拉框。
  3. getByPlaceholder:按输入框占位符定位。
  4. getByText:按文本内容定位,适合链接、按钮、div。
  5. getByTestId:按自定义>await page.locator('div.nav > ul > li:nth-child(2) > a').click();

    一旦菜单多了一个li,整个定位就废了。但用角色定位:

    await page.getByRole('link', { name: '产品中心' }).click();

    不管菜单怎么加项,只要链接文字还是“产品中心”,用例就不会挂。

    4.2 Actionability检查的五个维度

    click()之所以能解决大量偶发问题,是因为它在执行前会检查元素的五个状态:

    • 元素已附加到DOM(Attached)
    • 元素可见(Visible):有非空的边界框,且没有visibility: hidden
    • 元素稳定(Stable):在连续两次动画帧之间位置不变化
    • 元素能接收事件(Receives Events):不会被其他元素遮挡
    • 元素已启用(Enabled):不是disabled状态

    这五个条件全部满足后,click()才会真正执行。如果有条件不满足,Playwright默认会持续等待,直到超时。这种设计的价值在真实项目里感受特别明显——你不需要关心是接口数据慢导致按钮晚出现,还是某个动画挡住了点击,Playwright自己会处理。

    但要注意一点:如果元素一直在动(比如图表 loading 旋转动画),点击可能直接超时报错。这时候你可以用click({ force: true })跳过稳定性检查,但这属于“绕过”,不要滥用,最多是临时规避手段。真正合理的做法是等动画结束后再点击。

    4.3 Web-First断言详解

    除了一般的toBeVisible()、toHaveText()、toHaveValue(),我再补充几个场景里高频用到的:

    • await expect(locator).toHaveCount(3):用于校验列表项数量,比如搜索结果条数。它自动等待,直到数量匹配。
    • await expect(locator).toHaveAttribute('href', '/product/123'):校验属性。
    • await expect(page).toHaveURL(/\/search\?q=playwright/):校验当前URL,支持正则。
    • await expect(page).toHaveTitle(/搜索结果/):校验页面标题。

    这些断言都是异步的,前面一定记得加await。我见过很多人写expect(locator).toBeVisible()忘了await,结果断言对象没解析就执行下一步,测试出现各种奇怪行为。

    4.4 自定义超时与全局配置

    默认超时是5秒,大促页面、报表页面这种加载慢的,可以针对某个操作单独加超时:

    await page.getByText('加载完成').click({ timeout: 15000 });

    同时也支持在配置里全局调整:

    use: { actionTimeout: 10000, // 操作超时 navigationTimeout: 30000, // 导航超时 }

    我的习惯是:测试开发阶段全用默认值,等CI上确实因为网络不稳定频繁超时,再针对特定场景调大超时。“超时”类问题不能一上来就全局加大,那会把真实的性能回归问题掩盖掉。比如说页面加载从2秒变成10秒,如果你在配置里把导航超时设成30秒,这个问题可能直到用户投诉才会被发现。测试的其中一层价值,就是守住性能底线。

    5. 测试登录态、接口Mock与文件上传下载的实战写法

    第一篇文章如果只讲定位和断言,那其实还没完全脱离Selenium时代的思维框架。Playwright真正厉害的地方在于它对“浏览器能力”做了全面封装——网络层、存储层、对话框、多标签页、iframe都能直接控制。这一节全是干货,每段代码都是我实际项目里跑过的。

    5.1 登录态复用:storageState

    每个测试独立浏览器上下文,导致每个用例都要重新登录,登录一次还好,如果登录还要扫码、还要走短信验证码,那测试效率会被拖垮。Playwright的解法是storageState——先把登录后的存储状态保存下来,后续用例直接加载。

    // 登录并保存状态(一般单独跑一次) test('登录并保存认证信息', async ({ page }) => { await page.goto('https://example.com/login'); await page.getByLabel('用户名').fill('tester'); await page.getByLabel('密码').fill('pass123'); await page.getByRole('button', { name: '登录' }).click(); await page.waitForURL('**/dashboard'); // 保存cookie和localStorage到指定文件 await page.context().storageState({ path: 'auth/user.json' }); });

    后续用例在配置里直接指定:

    use: { storageState: 'auth/user.json', }

    这样每个用例起来就是已登录状态。需要注意:storageState保存的是cookie和localStorage,如果你的登录态存在sessionStorage里(某些SPA会这么干),是保存不了的,这时就得考虑让登录接口走page.request直接请求,或者每个用例自己登录。另外auth文件不要提交到代码仓库,里面是敏感信息,我在.gitignore里始终放着auth/目录。

    5.2 拦截与Mock网络请求

    UI自动化最怕外部依赖不稳定——第三方登录、支付回调、天气接口超时。Playwright用page.route()可以拦截请求并返回mock数据:

    test('支付成功页展示订单金额', async ({ page }) => { // 拦截支付状态查询请求 await page.route('**/api/payment/status?**', route => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ status: 'success', amount: '99.00' }), }); }); await page.goto('https://example.com/order/123'); await expect(page.locator('.pay-status')).toContainText('支付成功'); await expect(page.locator('.pay-amount')).toContainText('99.00'); });

    route.fulfill()直接从浏览器层面伪造响应,页面根本感知不到请求是真是假。这下你不再需要启动一个mock server,不需要改测试环境hosts,每个用例可以独立控制接口返回。搭配条件路由,你还能模拟失败场景:

    await page.route('**/api/user/info', route => { route.fulfill({ status: 500, contentType: 'application/json', body: '{"error":"server error"}' }); });

    用来测试前端对接口异常的处理。这是我在实际工作中最常用、也最提升用例稳定性的功能。原本依赖后端环境联调的用例,现在在本地就能全量跑。

    5.3 iframe与多标签页处理

    scrapy和playwright结合爬动态iframe页面是网上问得比较多的话题。如果页面里有嵌套的iframe,直接page.locator()是拿不到的,需要先切换到frame:

    const frame = page.frameLocator('#main-iframe'); await frame.getByLabel('用户名').fill('tester'); await frame.getByRole('button', { name: '提交' }).click();

    注意是frameLocator,返回的是一个专门的定位器,后续操作都在这个iframe内部查找。不需要像Selenium那样driver.switchTo().frame()然后再切回来,Playwright的frameLocator是链式的,也不影响外层页面的定位,心智负担小很多。

    多标签页的处理也简洁——用Promise.all同时监听新页面:

    const [newPage] = await Promise.all([ page.waitForEvent('popup'), page.getByRole('link', { name: '打开新窗口' }).click(), ]); await newPage.getByRole('heading', { name: '新页面内容' }).toBeVisible();

    这里有个经典坑:如果你先点击再waitForEvent('popup'),新页面已经打开你就错过了这个事件,所以必须用Promise.all把“等待事件”和“触发事件”同时挂起。很多新手在这卡很久,我当年也是踩了一遍才反应过来。

    5.4 文件上传与下载

    文件上传有两种常见交互。一种是<input type="file">元素,直接用setInputFiles():

    await page.getByLabel('上传头像').setInputFiles('test-data/avatar.png');

    如果是拖拽上传区域,没有input元素,那就要合成一个DataTransfer来触发drop事件。这块代码稍微繁琐,但原理就是通过page.dispatchEvent把文件放到拖拽事件里。需要的时候可以再查官方文档,我先把这类场景标记为“可做但不常用”。

    文件下载更简单:

    const downloadPromise = page.waitForEvent('download'); await page.getByRole('button', { name: '导出Excel' }).click(); const download = await downloadPromise; await download.saveAs('downloads/result.xlsx');

    saveAs()支持指定保存路径,下载到的文件名还可以用download.suggestedFilename()拿到。跑完用例后我一般会加一条断言,检查下载的文件大小不为0:

    const path = await download.path(); const fs = require('fs'); const stat = fs.statSync(path); expect(stat.size).toBeGreaterThan(0);

    别小看这条断言,我以前遇到过下载接口返回200但文件是空的——前端没报错,用户下载下来是个0字节文件,不检查根本发现不了。

    6. 运行与调试:从失败信息里快速定位问题

    用例写得再稳,总有挂掉的时候。能不能高效地从失败信息定位问题,直接决定自动化测试能不能真正跑进CI流程。这一节分享我自己平时排错的具体流程和工具用法。

    6.1 HTML报告与Trace Viewer

    跑完npx playwright test后,执行:

    npx playwright show-report

    浏览器会自动打开一份HTML测试报告。里面能看到每个用例的执行结果、运行时长、失败时自动截的截图和录屏。但我个人觉得最厉害的是Trace Viewer——打开失败用例的trace文件后,你可以在时间线上逐步回放整个测试过程,每一步都带DOM快照。

    比如某个用例失败在点击按钮那一步,Trace Viewer里你能看到:

    • 那一瞬间页面的真实DOM是什么样(元素是否存在、被什么遮挡)
    • 网络请求发了哪些、返回了什么状态码
    • 控制台有没有报错

    这个信息密度是传统日志完全没法比的。我自己的排错效率因为Trace Viewer至少提升了三倍。很多问题以前要复现好几次才能定位,现在打开Trace一次就能看到。所以配置里的trace: 'retain-on-failure'一定要开,这是后期排查问题的关键。

    6.2 调试模式:步进执行与实时查看元素

    用npx playwright test --debug跑用例,会自动打开Playwright Inspector。界面上能单步执行每一步操作,左侧是操作列表,右侧是页面实时状态。点击页面上的任何元素,Inspector会给出该元素的推荐定位器代码,直接复制就能用。

    这个功能还有个杀手锏——Timespans时间轴。在Inspector面板的右上角,可以查看每一步操作花了多少毫秒。如果某一步特别慢,说明页面当时正在等某个资源或者动画,这往往是测试不稳定的根源。

    配合调试,我建议本地把headless临时改成false,看着浏览器真实操作,比纯靠日志判断直觉准确得多。

    6.3 定位失败的常见原因与处理

    • 页面元素未出现就点击了:默认5秒超时不够,针对操作单独加timeout。
    • 有多个相似元素:getByRole('button', { name: '提交' })报strict mode violation,说明页面上有多个“提交”按钮。这时候需要把定位范围缩小,比如page.locator('.modal').getByRole('button', { name: '提交' }),先框定弹窗范围再定位。
    • 元素被遮挡:检查是否有弹窗、遮罩层覆盖。先处理遮罩,或者用expect(popup).toBeHidden()确认弹窗关闭后再继续。
    • iframe元素定位不到:看页面结构,用frameLocator切换上下文。
    • SPA路由延迟:点击后不立即出现新页面,用page.waitForURL()等待URL变化。

    调试问题我建议别依赖猜测,优先开Trace看现场。大多数错误信息都会提示具体是哪个定位器失败、超时多久、有哪些候选元素。按提示改定位器通常比乱试快得多。

    6.4 重试机制与Flaky用例的取舍

    测试偶发失败在业内叫Flaky Test,是自动化测试套件最大的敌人之一。配置中设置retries: 1或retries: 2能暂时掩盖这个问题,但我不建议单纯靠重试“治标”。你的目标应该是保证测试套件在无重试的情况下也能稳定通过。

    如果某个用例反复出现间歇性失败,先打开它的Trace看看失败瞬间页面在干什么。常见原因包括:

    • 接口响应在2秒和10秒之间波动,操作超时设置太紧
    • 第三方广告或埋点脚本阻塞主线程
    • 动画未结束导致元素位置不稳定
    • 前端日志上报导致页面资源竞争

    针对前两种,把相关超时调大、拦截广告脚本就能解决;针对动画,可以用expect先等待动画相关的样式稳定,或者干脆在测试环境禁用动画效果。一个经验法则是:如果一个用例重试3次里能挂1次,它迟早会在主干流水线上给你添堵。与其容忍,不如当天就把原因查了。

    6.5 与CI集成之后的一些维护体会

    最后聊点实际的维护心得。Playwright官方提供了Docker镜像,CI里可以这样跑:

    docker run --rm --network host mcr.microsoft.com/playwright:v1.40.0 npx playwright test

    或者更常见的是在GitHub Actions、GitLab CI里直接用官方维护的action。核心思路都是:在CI环境安装浏览器依赖后执行测试命令,再把HTML报告和trace上传为构建产物。这样开发同学能直接在流水线页面点开失败用例的Trace,根本不用本地复现。

    在实际项目里,我是这样安排测试分层的:

    • 每次提交跑冒烟测试(耗时3分钟以内,覆盖核心流程)
    • 每晚全量回归(覆盖全部用例,跑完自动发报告到企业微信/邮件)
    • 每周清理一次Flaky用例

    这套策略跑了一年多,测试从“Q3前的演示玩具”变成了“版本发布前的门禁标准”,核心原因就是Playwright的调试体验足够好,让整个团队愿意去维护用例。如果调试一个失败用例要花半天,没人会想碰它。

    7. 从测试生成器开始的另一种路径:codegen帮你快速起步

    写用例不一定要手写,Playwright自带一个测试生成器,不过它是基于录制的。适合用来快速生成一段可运行的测试骨架,再手动改造成真正的测试用例。

    在命令行执行:

    npx playwright codegen

    会弹出一个浏览器窗口和一个Inspector面板。你在浏览器里操作页面,Inspector会自动记录每一步操作并生成对应的代码。操作结束后,把代码复制进测试文件,补充断言就行了。

    这个工具真正有用的场景是:

    • 不熟悉某个新页面的DOM结构时,用它操作一遍GEnerated代码,再对照修改定位器。比自己翻DevTools快得多。
    • 需要快速验证某个操作序列在playwright里能不能跑通。
    • 面对复杂控件(比如富文本编辑器、日历组件),录制比手写定位器靠谱。

    但它生成的代码也存在一些缺点:定位器经常是page.locator('div:nth-child(2) > .ant-input')这种脆弱的CSS路径,断言也只会生成一些基础的expect。所以我的建议是:用它来“探路”,不要直接拿它生成的代码当最终产物。生成之后一定把定位器改成语义化的角色/文本定位。

    8. 从编程模型角度理解Playwright:同步等待与异步事件

    Playwright的编程模型跟大部分测试框架有本质区别。它不是一次性执行完所有测试命令就结束,而是维护了一套“事件循环+自动等待”机制。理解这一点,能帮助你在写复杂用例时避开很多深坑。

    8.1 事件驱动的等待机制

    以page.waitForSelector()为例(现在更推荐直接配合expect使用),它底层是一个Promise,等到元素出现或者超时才resolve。而page.click()底层的实现,也不仅仅是发射一个鼠标点击事件,而是先走完整个Actionability检查链,再通过CDP发射输入事件。这个模型的好处是,你不再需要关心页面是刚加载完,还是正在异步渲染中。

    8.2 并发执行与并发安全

    默认情况下Playwright会在多个worker中并行跑测试文件(所以--workers参数才存在)。每个worker是独立的浏览器进程,这约束着你写的测试代码必须是线程安全的。最常见的反例是:多个测试文件共用同一个临时目录或修改同一个全局配置,这会导致竞争条件。

    我的建议是,每个测试用例用到的测试数据都生成独立副本,不要共用可变状态。比如上传文件名的生成可以用test-info里的唯一标识:

    const fileName = `upload-${test.info().testId}.png`;

    9. 我自己的项目实践:从100个用例到500个用例的踩坑记录

    最后这段,我不讲框架,讲讲在真实业务场景里把Playwright用起来的完整经历。这套东西如果不落地到具体项目,永远只是“会写”而不是“会用”。

    9.1 初期:选对第一批用例

    我先选了一个最高频、收益最大的核心路径做试点——用户从登录到下单的全流程。大概20条用例,包含了登录、搜索、加购、下单、支付回调、订单列表。因为这是产品最核心的路径,开发频繁改动,回归成本高,用自动化替换人工回归收益立竿见影。

    第一批用例跑稳后,再逐步向外扩散到用户中心、订单中心、客服工作台等功能域。扩散时遵循一个原则:新用例必须能在本地跑通20次不失败,才会合入主测试套件。这条原则帮我挡住了大量Flaky用例。

    9.2 中期:测试数据管理

    500个用例跑起来后,最大的痛点变成了测试数据。每个用例要独立的商品数据、用户数据、订单数据。一开始我是在测试环境手工造数据,后来发现数据库被其他团队开发重置后就全挂了。现在的方案是:

    • 基础数据(商品、用户)用接口fixture在beforeAll的时候造
    • 业务数据(订单、优惠券)用page.request创建
    • 敏感或高成本数据用mock方式拦截

    三者结合,基本做到每个用例不依赖“当前测试环境残留了什么”。

    9.3 后期:失败通知与追踪

    每晚全量回归跑完,报告会推送到团队的即时通讯群。我要求开发同学看到失败通知的第一反应不是“谁又乱改页面了”,而是去Trace Viewer里把失败现场看完再决定怎么处理。这套流程跑顺后,前端代码合并前必须过一遍关联用例,回归问题在合并前就拦住了。

    9.4 关于Cherry Studio安装Playwright和MCP集成

    搜索热词里提到“cherry studio怎么安装playwright”和“playwright MCP”。Cherry Studio这种AI客户端,本质就是一个桌面壳子,它要的就是安装Playwright命令行工具,然后通过MCP协议暴露给AI去操作浏览器做自动化验证。其实思路跟我们在项目里做的一样——让非测试人员也能用自然语言驱动一套已验证的Playwright脚本库去验证页面。这块内容展开讲能写一整篇,这篇先点到为止,等后续文章再单独开专题。

    10. 书写下一章之前的一个小建议

    按这个系列往下走,下一篇应该会讲“页面对象模型(Page Object Model)”怎么落地,以及如何组织一个中大型项目的测试结构。第一条建议已经摆出来了:不要期望一次性把用例写到完美。

    先写能跑的,再逐步把定位器改为语义化、把公共逻辑提取成POM、把数据准备改成接口调用。Playwright的优势是它给你留了很大的重构空间,不会像早期框架那样改一次定位器就要动一整批用例。只要你能从小用例跑起,稳定积累,它就会成为你手上最顺手的回归利器。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 1:09:45

电商App算法黑盒分析:以Shopee为例拆解推荐与搜索排序

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:09:15

变压器UL认证测试项目全解析:从耐压、温升到异常工况

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:09:07

从零搭建开源医学影像Web阅片系统:OHIF + Orthanc 实战部署指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:09:07

数据库设计文档实战:从ER图到DDL与MD5密码加密规范

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:08:01

Spring @Autowired 依赖注入全解析:从原理到实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:08:01

FPGA与AD9361数字接口实战:从LVDS时序到EVM调优全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华