ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Playwright自动化测试入门:从环境搭建到实战应用

2026/8/15 13:04:56 拓冰建站 浏览量
Playwright自动化测试入门:从环境搭建到实战应用 1. 从“为什么是Playwright”开始聊起如果你最近在关注Web自动化测试或者爬虫领域大概率会听到一个名字Playwright。它不像Selenium那样有十多年的历史沉淀也不像Puppeteer那样与Chrome深度绑定但它的出现却实实在在地搅动了整个生态。我第一次接触Playwright是在一个需要同时处理多个浏览器、且对执行稳定性要求极高的项目里当时被Selenium的同步问题、Puppeteer的浏览器限制折腾得够呛直到尝试了Playwright才感觉找到了一个“全能型选手”。简单来说Playwright是一个由微软开源的现代化Web自动化测试和浏览器自动化库。它支持Chromium、Firefox和WebKit三大浏览器引擎这意味着你可以用同一套脚本在Chrome、Edge、Firefox和Safari上运行测试。这听起来可能只是多浏览器支持但背后是巨大的工程价值你再也不需要为不同浏览器维护多套脚本或者为Safari的怪异行为单独写一堆兼容代码。它的设计哲学是“为现代Web而生”原生支持单页应用SPA、网络拦截、文件上传下载、地理位置模拟等现代Web开发中常见的场景并且承诺提供更快、更可靠的自动化执行。那么它适合谁呢如果你是测试工程师正在寻找一个比Selenium更稳定、功能更强大的端到端E2E测试框架如果你是开发人员需要做UI层的自动化验收或者构建可视化爬虫甚至如果你是一个运维或DevOps需要编写脚本来自动化一些Web管理后台的操作Playwright都值得你投入时间学习。它的API设计非常直观对新手友好同时其强大的底层控制能力又能满足老手的各种“骚操作”需求。接下来我会带你从零开始深入Playwright的核心不仅告诉你“怎么用”更会分享在实际项目中“为什么这么用”以及“怎么用得更好”的经验。2. 环境搭建与项目初始化避开第一个坑万事开头难但Playwright的开头其实相当简单。不过简单不代表没有坑。很多人第一步就卡在环境上不是因为Playwright复杂而是因为对现代Node.js生态不熟悉。2.1 核心依赖Node.js版本的选择Playwright对Node.js版本有要求通常需要Node.js 14及以上。但我强烈建议你直接使用Node.js 16 LTS或18 LTS版本。这不是因为新版本有什么魔法而是因为更稳定的npm/yarn/pnpm包管理体验和更好的ES模块支持。你可以使用node -v检查当前版本。如果版本过低建议使用nvmNode Version Manager来管理多个Node.js版本这在同时维护多个不同年代的项目时是必备技能。# 使用nvm安装并切换Node.js 18 nvm install 18 nvm use 182.2 安装Playwright一行命令与背后的故事安装Playwright通常只需要一行命令npm init playwrightlatest这行命令会做几件事首先它会引导你创建一个新的npm项目如果你当前目录没有package.json然后它会安装playwright/test这个npm包最后也是最重要的一步它会下载浏览器二进制文件。注意这是Playwright与Selenium最大的不同之一。Selenium需要你手动下载对应浏览器的WebDriver并确保版本匹配经常出现“驱动版本不匹配导致脚本失败”的经典问题。Playwright则把浏览器引擎Chromium, Firefox, WebKit作为依赖包的一部分直接下载到本地项目默认在node_modules目录下。这样做的好处是环境隔离性极好你的项目永远使用特定版本的浏览器与系统全局安装的浏览器无关保证了测试的确定性。坏处是首次安装会下载几百MB的文件需要一点耐心。安装过程中命令行会交互式地询问你几个问题选择测试语言TypeScript还是JavaScript对于新项目我无条件推荐TypeScript。Playwright的API设计本身就类型友好使用TS可以获得极佳的代码提示和类型安全检查能避免很多低级错误如拼写错误的locator或method。选择测试文件夹位置默认是tests或e2e按需修改。是否添加GitHub Actions工作流如果你使用GitHub可以添加一个基础的CI配置非常贴心。是否安装Playwright浏览器一定要选“Yes”。这就是上面提到的下载浏览器步骤。安装完成后你的项目结构会类似这样your-project/ ├── node_modules/ ├── package.json ├── playwright.config.ts # 配置文件如果选了TS ├── tests/ │ ├── example.spec.ts # 示例测试文件 │ └──-examples/ └── tests-examples/ # 更多示例2.3 验证安装跑通第一个脚本安装后别急着写自己的脚本。先运行它提供的示例这是验证环境是否正确的黄金标准。npx playwright test这条命令会使用playwright/test这个测试运行器执行tests目录下所有以.spec.ts或.test.ts结尾的测试文件。你会看到浏览器一个个被打开默认是无头模式即不显示UI执行示例测试然后在命令行输出结果。如果所有测试通过恭喜你环境搭建成功。如果失败了常见原因和解决思路如下网络问题导致浏览器下载不完整删除node_modules目录和全局缓存中的Playwright浏览器位置通常在~/Library/Caches/ms-playwright(macOS/Linux)或%USERPROFILE%\AppData\Local\ms-playwright(Windows)然后重新运行npx playwright install。系统依赖缺失主要在LinuxPlaywright的浏览器需要一些系统库。安装命令会尝试提示你但有时会失败。可以手动安装例如在Ubuntu上sudo apt-get install libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant-2-2 libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libgles2 libevent-2.1-7。具体请参考Playwright官方文档的 系统要求 部分。权限问题确保你对项目目录有读写权限。3. 核心概念深度解析不止于“定位元素”很多人学自动化框架一上来就找“怎么定位元素”。这没错但Playwright的强大远不止于此。理解它的几个核心概念能让你写出更健壮、更高效的脚本。3.1 Browser, Context 和 Page三层隔离的艺术这是Playwright架构中最精妙的部分理解了它们就理解了Playwright的并发、隔离和资源管理。Browser代表一个浏览器实例。你可以把它想象成一个完整的、独立的浏览器程序。启动browser await chromium.launch()就是启动了一个Chromium进程。这个进程很“重”通常一个测试套件只需要启动一次。Context浏览器上下文。这是关键所在。一个Browser实例下可以创建多个独立的Context。每个Context都拥有完全独立的会话session、Cookie、本地存储和缓存就像你在电脑上打开了不同的“无痕浏览窗口”。这个设计解决了自动化测试中的一个老大难问题测试用例之间的隔离。在Selenium中如果不做特殊处理测试用例A设置的Cookie可能会影响测试用例B。而在Playwright中你只需要为每个测试用例创建一个新的Context它们就天然隔离互不干扰。这极大地提高了测试的可靠性和并行能力。// 创建两个互不干扰的上下文 const context1 await browser.newContext(); const context2 await browser.newContext(); const page1 await context1.newPage(); // 页面1在上下文1中 const page2 await context2.newPage(); // 页面2在上下文2中 // page1和page2的Cookie、localStorage完全独立Page标签页。一个Context下可以有多个Page对应浏览器中的多个标签页。你可以通过page.goto()导航在page上定位元素、执行操作。为什么这样设计性能创建Context比启动Browser快得多创建Page又比创建Context快。你可以在一个浏览器进程内快速创建多个隔离的测试环境。稳定性测试失败不会污染其他测试的环境。功能可以轻松模拟多用户场景每个用户一个Context、跨域访问等。3.2 Locator新一代定位策略告别脆弱的XPath定位元素是自动化的基石。Playwright极力推荐使用LocatorAPI它是对page.$()和page.$$()的封装和增强。// 传统方式不推荐 const button await page.$(button.submit); await button.click(); // Locator方式推荐 const button page.locator(button.submit); await button.click();Locator的核心优势在于它的智能等待和自动重试机制。当你执行locator.click()时Playwright会等待该元素出现在DOM中。等待该元素变得可见非隐藏、非0尺寸。等待该元素变得可交互未被禁用、未被其他元素遮挡。如果等待超时前条件满足则执行点击。如果元素在等待期间因为页面动态加载而位置变化Locator会自动重新计算位置并点击正确的地方。这几乎消除了因页面加载慢或元素动态渲染导致的“Element not found”或“Element is not clickable”错误。你不再需要到处写page.waitForSelector()了。定位器的最佳实践优先使用Role、Text和Placeholder这些是面向用户和可访问性的属性最稳定。await page.locator(button, { hasText: 提交 }).click(); await page.getByRole(button, { name: 提交 }).click(); await page.getByPlaceholder(请输入用户名).fill(test);慎用XPath和CSS选择器它们与页面结构强耦合前端一个div嵌套改动就可能导致定位失败。如果必须用尽量用相对简单、不依赖过多层级结构的。使用>// 这一行代码包含了导航、等待页面加载、等待元素出现、点击等一系列等待 await page.locator(textLoad Data).click(); // 接下来可以直接操作动态加载出来的元素Playwright会等待它 await page.locator(.loaded-item).first().click();4. 编写第一个真正的测试登录场景实战理论讲得再多不如动手写一个。我们以一个经典的Web应用登录场景为例覆盖导航、输入、点击、断言等核心操作。假设我们有一个登录页https://example.com/login有用户名输入框、密码输入框和一个提交按钮。4.1 基础测试结构首先在tests目录下创建一个新文件login.spec.ts。import { test, expect } from playwright/test; // 使用 test 和 expect它们是 playwright/test 运行器提供的 test(用户应该能够成功登录, async ({ page }) { // 测试用例内容写在这里 // page fixture 会自动为我们创建一个新的页面对象 });playwright/test采用了类似Jest的语法test函数定义一个测试用例expect用于断言。{ page }是一个fixture测试运行器会在每个测试开始前自动创建一个新的、隔离的page对象给我们用测试结束后会自动关闭无需手动管理生命周期。4.2 实现登录步骤与断言现在填充测试体。import { test, expect } from playwright/test; test(用户应该能够成功登录, async ({ page }) { // 1. 导航到登录页面 await page.goto(https://example.com/login); // 2. 定位并填写用户名和密码 // 假设输入框有placeholder属性 await page.getByPlaceholder(用户名/邮箱).fill(testuser); await page.getByPlaceholder(密码).fill(securepassword123); // 3. 点击登录按钮 // 假设按钮文字是“登录” await page.getByRole(button, { name: 登录 }).click(); // 4. 断言登录成功 // 方式一断言页面URL跳转到了仪表盘 await expect(page).toHaveURL(/dashboard/); // 方式二断言页面上出现了欢迎用户的文字 await expect(page.locator(text欢迎回来testuser)).toBeVisible(); // 方式三断言某个登录后才出现的特定元素存在 await expect(page.getByTestId(user-avatar)).toBeVisible(); });这个简单的测试涵盖了端到端测试的核心流程。page.goto会等待页面load事件触发。fill和click操作都内置了自动等待。expect断言也同样会等待直到条件满足或超时。4.3 处理常见登录场景验证码与等待策略真实世界的登录往往更复杂。比如有的网站有图片验证码这对自动化来说是障碍通常需要OCR或第三方打码服务不在基础入门范围。但更多的情况是登录后有一个加载过程。处理登录后加载test(登录后等待用户菜单加载, async ({ page }) { await page.goto(https://example.com/login); await page.getByPlaceholder(用户名).fill(user); await page.getByPlaceholder(密码).fill(pass); await page.getByRole(button, { name: 登录 }).click(); // 登录后页面可能异步加载用户信息 // 方法1等待特定元素出现推荐 await page.waitForSelector(.user-profile, { state: visible }); // 方法2等待网络请求完成如果知道登录后会发起某个API请求 // 等待一个包含‘profile’的请求完成 await page.waitForResponse(response response.url().includes(/api/profile) response.status() 200); // 然后再进行断言 await expect(page.locator(.user-profile)).toContainText(user); });使用page.waitForLoadState对于传统的多页应用登录可能是表单提交跳转。可以使用waitForLoadState确保新页面完全加载。// 在点击登录按钮后 await page.getByRole(button, { name: 登录 }).click(); // 等待网络空闲通常意味着主要资源加载完毕 await page.waitForLoadState(networkidle);5. 高级特性初探网络拦截、文件与多页面掌握了基础操作Playwright的一些高级特性可以让你处理更复杂的场景这些往往是Selenium中比较棘手的部分。5.1 网络请求拦截与模拟Mocking这是Playwright最强大的功能之一。你可以监听、修改或直接模拟Mock任何网络请求和响应这对于测试边缘情况、提高测试速度、模拟后端不可用场景至关重要。场景一拦截请求并修改// 在导航到页面之前设置请求拦截 await page.route(**/api/user/profile, async route { // 获取原始请求 const request route.request(); console.log(拦截到请求: ${request.url()}); // 修改请求头例如添加一个认证令牌 const headers { ...request.headers(), X-Test-Token: mock-token-123 }; // 继续发送修改后的请求 await route.continue({ headers }); }); await page.goto(https://example.com);场景二直接模拟响应Mock API// 拦截特定API请求并直接返回一个模拟的JSON响应完全不经过真实后端 await page.route(**/api/products, async route { await route.fulfill({ status: 200, contentType: application/json, body: JSON.stringify([ { id: 1, name: 模拟产品A, price: 99 }, { id: 2, name: 模拟产品B, price: 199 } ]) }); }); await page.goto(https://example.com/products); // 页面将显示我们模拟的产品数据测试不依赖后端服务场景三中止请求或响应// 阻止加载某些资源比如图片或广告加速测试 await page.route(**/*.{png,jpg,jpeg,svg,gif}, route route.abort()); // 或者模拟请求失败 await page.route(**/api/critical, route route.fulfill({ status: 500 }));5.2 文件上传与下载文件操作在自动化中一直是个难点Playwright让它变得异常简单。文件上传不需要像Selenium那样找input typefile元素然后sendKeys。Playwright可以直接设置文件。// 假设有一个文件上传输入框 const fileInput page.locator(input[typefile]); // 设置一个或多个本地文件路径 await fileInput.setInputFiles([/path/to/file1.pdf, /path/to/image2.jpg]); // 如果要模拟从页面上传一个空文件或者清除已选文件 await fileInput.setInputFiles([]);文件下载等待下载事件并保存文件。// 1. 等待下载事件开始 const [download] await Promise.all([ // 等待下载事件触发 page.waitForEvent(download), // 触发下载的动作比如点击下载链接 page.locator(text下载报告).click() ]); // 2. 获取下载建议的文件名 const suggestedFilename download.suggestedFilename(); // 3. 指定保存路径 const savePath ./downloads/${suggestedFilename}; await download.saveAs(savePath); console.log(文件已下载到: ${savePath});5.3 多页面标签页与弹窗处理现代Web应用经常打开新窗口或标签页。处理新打开的页面弹窗或链接// 在点击某个会打开新窗口的链接或按钮之前先监听‘popup’事件 const [newPage] await Promise.all([ page.waitForEvent(popup), // 等待新页面弹出 page.locator(text打开新窗口).click() // 触发动作 ]); // 现在可以操作新页面了 await newPage.bringToFront(); // 切换到新页面如果需要 await expect(newPage).toHaveURL(/new-window/); await newPage.locator(button).click(); await newPage.close(); // 操作完后关闭在多个已知页面间切换// 获取浏览器所有上下文中的所有页面 const allPages browser.contexts().flatMap(context context.pages()); // 或者如果你知道大概顺序 const page1 (await browserContext.pages())[0]; const page2 (await browserContext.pages())[1];6. 配置与最佳实践让测试更健壮、更高效写几个测试用例不难难的是构建一个健壮、可维护、高效的测试套件。playwright.config.ts配置文件和一系列最佳实践是关键。6.1 核心配置文件解读初始化项目时生成的playwright.config.ts文件包含了所有可配置项。这里挑几个最常用的讲。import { defineConfig, devices } from playwright/test; export default defineConfig({ // 1. 测试目录和文件匹配模式 testDir: ./tests, testMatch: **/*.spec.ts, // 2. 全局超时设置 timeout: 30 * 1000, // 每个测试用例的最大执行时间毫秒 expect: { timeout: 5000 // 每个expect断言的最大等待时间 }, // 3. 是否并行执行测试默认false fullyParallel: true, // 并行工作的最大数量默认CPU核心数一半 workers: process.env.CI ? 2 : undefined, // 4. 报告器 reporter: [ [html], // 生成漂亮的HTML报告 [list] // 在控制台输出简洁结果 ], // 5. 全局项目配置可以定义多套环境如桌面Chrome移动端Safari projects: [ { name: chromium, use: { ...devices[Desktop Chrome] }, }, { name: firefox, use: { ...devices[Desktop Firefox] }, }, { name: webkit, use: { ...devices[Desktop Safari] }, }, // 模拟iPhone上的Safari { name: Mobile Safari, use: { ...devices[iPhone 12] }, }, ], // 6. 全局Setup和Teardown在所有测试之前/之后运行 // globalSetup: ./global-setup, // globalTeardown: ./global-teardown, // 7. 每个测试的Fixtures配置覆盖默认的page fixture use: { // 所有测试的基线URLpage.goto(‘/login’) 会变成 baseURL ‘/login’ baseURL: https://example.com, // 每个测试自动录视频失败时保存 video: retain-on-failure, // 每个测试自动截图失败时保存 screenshot: only-on-failure, // 浏览器视口大小 viewport: { width: 1280, height: 720 }, // 忽略HTTPS证书错误用于测试环境 ignoreHTTPSErrors: true, // 模拟用户行为增加延迟更像真人操作 launchOptions: { slowMo: 100, // 每个操作延迟100毫秒 }, }, });6.2 测试数据管理与Fixture复用硬编码的测试数据如用户名、密码是坏味道。应该将它们管理起来。使用环境变量创建.env文件使用dotenv包读取。# .env TEST_USERNAMEtestuserexample.com TEST_PASSWORDMySecurePass123 BASE_URLhttps://staging.example.com在playwright.config.ts中读取import { defineConfig } from playwright/test; import dotenv from dotenv; dotenv.config(); export default defineConfig({ use: { baseURL: process.env.BASE_URL, }, });在测试文件中使用const username process.env.TEST_USERNAME!; const password process.env.TEST_PASSWORD!;创建自定义Fixture如果你需要在多个测试中复用一段逻辑比如登录可以创建自定义Fixture。// 在某个文件或专门的fixtures文件中定义 import { test as base, expect } from playwright/test; // 定义一个“loggedInPage” fixture export const test base.extend{ loggedInPage: Page }({ loggedInPage: async ({ page }, use) { // 在这个fixture中page是全新的 await page.goto(/login); await page.getByPlaceholder(用户名).fill(testuser); await page.getByPlaceholder(密码).fill(password); await page.getByRole(button, { name: 登录 }).click(); // 等待登录成功 await expect(page).toHaveURL(/dashboard/); // 将已登录的page传递给测试用例使用 await use(page); // 测试用例结束后可以在这里执行清理如退出登录 // await page.locator(text退出).click(); }, }); // 在测试文件中导入这个自定义的 test import { test, expect } from ./fixtures; test(使用已登录的页面进行操作, async ({ loggedInPage }) { // loggedInPage 已经是一个登录后的状态 await loggedInPage.goto(/profile); await expect(loggedInPage.locator(h1)).toContainText(个人资料); });6.3 调试技巧让排错不再痛苦测试失败了怎么办Playwright提供了丰富的调试工具。playwright inspector这是最强大的可视化调试工具。在运行测试时加上--debug标志。npx playwright test --debug它会打开一个浏览器窗口和Playwright Inspector界面。你可以逐行执行控制测试脚本一步一步运行。查看定位器将鼠标悬停在Inspector中的元素上它会生成对应的定位器代码。录制操作点击“Record”按钮手动在浏览器里操作它会自动生成Playwright脚本。playwright codegen快速生成脚本的利器。打开一个浏览器并开始录制你的操作。npx playwright codegen https://example.com这非常适合快速为已有网站创建一个自动化脚本原型。Trace Viewer当测试在CI中失败时光看日志很难知道发生了什么。启用trace记录可以完整复现场景。 在配置中启用use: { trace: retain-on-failure, // 或 ‘on’ 记录所有 ‘off’ 关闭 },测试失败后会生成一个trace.zip文件。使用命令查看npx playwright show-trace trace.zipTrace Viewer会展示测试执行全过程的视频、网络请求、控制台日志、每一步的DOM快照就像时光机一样让你精准定位问题。Console和网络日志在测试中监听控制台错误和网络请求。// 监听页面错误 page.on(pageerror, error console.error(页面错误: ${error})); // 监听网络请求失败 page.on(requestfailed, request console.error(请求失败: ${request.url()} - ${request.failure()?.errorText}));7. 从测试到自动化脚本超越测试的边界虽然Playwright常与playwright/test测试运行器绑定但其核心库playwright本身是一个强大的通用浏览器自动化库。你可以脱离测试框架将它用于爬虫、监控、RPA等场景。7.1 使用纯Playwright库编写脚本首先安装核心库npm install playwright然后你可以写一个独立的Node.js脚本// scraper.js const { chromium } require(playwright); // 或 firefox, webkit (async () { // 启动浏览器 const browser await chromium.launch({ headless: false }); // 显示浏览器窗口 // 创建上下文和页面 const context await browser.newContext(); const page await context.newPage(); try { await page.goto(https://news.ycombinator.com); // 提取新闻标题 const titles await page.locator(.titleline a).evaluateAll(elements elements.map(el ({ title: el.innerText, href: el.href })) ); console.log(抓取到 ${titles.length} 条新闻:); titles.slice(0, 5).forEach(item console.log( - ${item.title})); // 截图 await page.screenshot({ path: hn-frontpage.png, fullPage: true }); } catch (error) { console.error(抓取失败:, error); } finally { // 务必关闭浏览器释放资源 await browser.close(); } })();运行这个脚本node scraper.js。它就像Puppeteer一样工作但拥有多浏览器支持和更丰富的API。7.2 与现有Node.js项目集成你可以将Playwright作为你项目的一个工具模块。例如构建一个每天定时检查网站健康状态的监控脚本或者一个自动填写日报的机器人。其API的稳定性和丰富性使其非常适合这类生产级任务。一个简单的健康检查示例const { chromium } require(playwright); const nodemailer require(nodemailer); async function healthCheck(url) { const browser await chromium.launch({ headless: true }); const page await browser.newPage(); let isHealthy false; try { const response await page.goto(url, { waitUntil: networkidle, timeout: 30000 }); isHealthy response.ok(); // 状态码 200-299 // 进一步检查页面上是否有关键元素 const criticalElement await page.locator(#app).isVisible(); isHealthy isHealthy criticalElement; if (!isHealthy) { const screenshot await page.screenshot({ fullPage: true }); // 将screenshot作为附件发送邮件告警... console.error(网站 ${url} 健康检查失败); } } catch (error) { console.error(检查 ${url} 时发生错误:, error.message); isHealthy false; } finally { await browser.close(); } return isHealthy; } // 定时执行 setInterval(() healthCheck(https://my-production-app.com), 5 * 60 * 1000);从环境搭建、核心概念理解到编写第一个测试、处理复杂交互再到配置优化和高级应用Playwright提供了一套完整、现代且开发者友好的解决方案。它降低了过去在Web自动化中许多令人头疼的复杂度比如等待、跨浏览器、文件操作和网络拦截。无论是为了保障Web应用质量的测试工程师还是需要从Web中高效提取数据的开发者花时间掌握Playwright都是一笔高回报的投资。我自己的项目从Selenium迁移到Playwright后测试的稳定性和编写效率提升了不止一个量级那些因时机问题导致的“闪烁性失败”几乎绝迹。开始动手吧从你当前项目中的一个简单场景开始尝试你会很快感受到它的不同。