当前位置: 首页 > news >正文

Playwright自动化测试实战:从零搭建现代Web测试框架

1. 项目概述:为什么是 Playwright?

如果你正在为现代 Web 应用的自动化测试头疼,尤其是面对那些充斥着动态加载、复杂交互的单页应用(SPA),那么 Playwright 的出现,很可能就是你的解药。我接触过 Selenium、Puppeteer 等一众工具,最终在项目里全面转向 Playwright,核心原因就一个:它真正理解了测试工程师的痛点,并提供了“Web 优先”的解决方案。

简单说,Playwright 是一个由微软开源的 Node.js 库,它提供了一套统一的 API,可以跨 Chromium、Firefox 和 WebKit 三大浏览器引擎驱动自动化。这听起来和 Selenium 有点像?但它的设计哲学完全不同。Selenium 是基于 WebDriver 协议的“命令-响应”模式,而 Playwright 更像是浏览器的“原生伙伴”,它直接通过 DevTools Protocol 与浏览器内核对话,这意味着更快的执行速度、更强大的控制能力,以及最重要的——自动等待。你再也不用在代码里写满sleep(5)或者各种轮询等待元素出现了,Playwright 内置的智能等待机制,能极大地减少那些令人抓狂的“不稳定测试”。

这个实战指南,就是把我从零开始搭建 Playwright 测试框架,到处理各种复杂场景(文件下载、iframe、API 拦截、并行执行)的经验,系统地梳理出来。无论你是刚入门自动化测试的新手,还是正在评估新工具的老手,都能在这里找到可直接落地的配置、代码和避坑指南。

2. 核心设计思路与框架选型

2.1 Playwright vs. Selenium vs. Puppeteer:我们为什么选它?

在做技术选型时,我们对比了市面上主流的几个方案。Selenium 生态庞大但历史包袱重,WebDriver 的通信开销和等待问题在复杂应用中尤为明显。Puppeteer 性能强劲,但只绑定 Chromium,且更偏向于爬虫和脚本场景,其测试运行器功能相对较弱。

Playwright 可以看作是 Puppeteer 的“全面升级版”,它继承了高性能和深度控制能力,并针对测试场景做了大量优化。它的核心优势在于:

  1. 跨浏览器一致性:一套 API 覆盖三大浏览器引擎,确保你的应用在 Chrome、Firefox 和 Safari 上表现一致。这对于需要做跨浏览器兼容性测试的团队是刚需。
  2. 自动等待与 Web 优先断言:这是革命性的。Playwright 在执行点击、输入等操作前,会自动等待元素满足一系列可操作性条件(如可见、启用、稳定等)。它的断言(如expect(locator).toBeVisible())也是可重试的,会持续轮询直到条件满足或超时。这从根本上减少了因网络延迟或渲染速度导致的测试失败。
  3. 强大的测试隔离:每个测试用例都运行在一个全新的“浏览器上下文”中,这相当于一个独立的用户会话,拥有独立的 cookies、localStorage,但创建开销极小。这避免了测试间的相互污染,让测试可以安全地并行运行。
  4. 丰富的工具链:内置的测试生成器、追踪查看器、VS Code 扩展,构成了一个完整的开发调试闭环。特别是追踪查看器,当测试失败时,它能提供一个包含所有步骤截图、网络请求、控制台日志的时间轴,让你无需复现就能快速定位问题。

基于以上几点,对于需要构建稳定、快速、可维护的现代 Web 自动化测试体系的团队,Playwright 是目前最值得投入的技术选择。

2.2 项目结构与技术栈规划

一个易于维护的测试项目,结构清晰至关重要。我们的项目通常采用如下分层结构:

e2e-tests/ ├── package.json ├── playwright.config.ts # 主配置文件 ├── tests/ │ ├── fixtures/ # 测试夹具,如登录状态复用 │ │ └── auth.setup.ts │ ├── pages/ # 页面对象模型(Page Object) │ │ ├── login.page.ts │ │ └── dashboard.page.ts │ ├── specs/ # 测试用例 │ │ ├── login.spec.ts │ │ └── user-flow.spec.ts │ └── utils/ # 工具函数 │ └── helper.ts ├── test-results/ # 测试报告和追踪文件(.gitignore) └── .github/workflows/ # CI/CD 流水线配置 └── playwright.yml

技术栈说明

  • 语言:优先推荐TypeScript。Playwright 对 TypeScript 的支持是顶级的,完善的类型提示能极大提升开发效率和代码质量,避免许多低级错误。
  • 测试运行器:直接使用 Playwright Test。它是专门为 Playwright 设计的,深度集成,提供了并行、重试、报告等所有你需要的内置功能,无需再集成 Jest 或 Mocha。
  • 断言库:使用 Playwright Test 自带的expect,它经过了扩展,支持对 Locator 的异步断言。
  • CI/CD:GitHub Actions 是天然搭档,官方提供了@playwright/test的 GitHub Action,开箱即用。

3. 环境搭建与核心配置详解

3.1 安装与初始化:一步到位

安装 Playwright 的最佳实践是使用官方的初始化命令,它会帮你处理好一切。

# 1. 初始化一个新的测试项目或进入现有项目目录 npm init playwright@latest

这个交互式命令会问你几个问题:

  • 选择测试语言:TypeScript 或 JavaScript。(选 TypeScript)
  • 测试目录位置:默认testse2e
  • 是否添加 GitHub Actions 工作流:建议选“是”,它会生成一个基础的 CI 配置。
  • 是否安装 Playwright 浏览器:一定要选“是”。它会下载 Chromium、Firefox 和 WebKit 到本地node_modules目录,确保环境一致。

完成后,你会得到playwright.config.ts配置文件、示例测试文件以及package.json中的相关脚本。

注意:如果遇到playwright install chromium很慢或失败,通常是网络问题。可以尝试设置镜像源:

# 设置 npm 镜像(如淘宝源) npm config set registry https://registry.npmmirror.com # 设置 Playwright 二进制下载镜像 PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright npx playwright install

对于 CentOS 7 等老系统,需确保 glibc 版本满足要求(如 Playwright 1.54.0 需要 glibc >= 2.31)。若系统版本过低,可考虑在 Docker 容器中运行测试。

3.2 配置文件(playwright.config.ts)深度解析

这是 Playwright 测试的“大脑”,理解每个配置项至关重要。

import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ // 1. 测试目录和文件匹配模式 testDir: './tests/specs', testMatch: '**/*.spec.ts', // 只匹配 .spec.ts 文件 // 2. 全局超时设置 timeout: 30 * 1000, // 每个测试用例的超时时间(毫秒) expect: { timeout: 10 * 1000, // 每个断言的最大等待时间 }, // 3. 是否并行运行以及如何并行 fullyParallel: true, // 尽可能并行运行所有测试文件 workers: process.env.CI ? 2 : undefined, // CI环境固定2个worker,本地根据CPU核心数自动分配 retries: process.env.CI ? 2 : 0, // CI环境失败自动重试2次,提高稳定性 // 4. 报告系统 reporter: [ ['html', { outputFolder: 'playwright-report', open: 'never' }], // 生成漂亮的HTML报告 ['list'], // 在控制台输出简洁结果 ['junit', { outputFile: 'test-results/junit.xml' }], // 用于CI集成(如Jenkins) ], // 5. 全局项目配置:可以定义多套环境(如桌面端、移动端、不同浏览器) projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, // 可以添加移动端模拟 // { // name: 'Mobile Safari', // use: { ...devices['iPhone 12'] }, // }, ], // 6. 全局前置/后置钩子 globalSetup: './tests/fixtures/global-setup.ts', // 所有worker启动前执行一次(如启动服务) globalTeardown: './tests/fixtures/global-teardown.ts', // 所有worker结束后执行一次 use: { // 所有测试的默认配置 headless: true, // CI环境无头模式,本地调试可设为 false viewport: { width: 1920, height: 1080 }, ignoreHTTPSErrors: true, // 忽略HTTPS证书错误(用于测试环境) actionTimeout: 15 * 1000, // 每个操作(click, fill)的超时时间 navigationTimeout: 30 * 1000, // 页面导航的超时时间 screenshot: 'only-on-failure', // 仅在失败时截图 video: 'retain-on-failure', // 仅在失败时保留录像 trace: 'retain-on-failure', // 开启追踪,仅在失败时保留(排查神器) }, // 7. Web Server:在运行测试前自动启动你的开发服务器 webServer: { command: 'npm run start', // 启动本地服务的命令 url: 'http://localhost:3000', // 等待该URL返回200 reuseExistingServer: !process.env.CI, // 本地重用已有服务器,CI环境不重用 timeout: 120 * 1000, // 服务器启动超时时间 }, });

关键配置心得

  • workers:本地开发可以设为undefined以最大化利用 CPU。但在 CI 环境中(如 GitHub Actions 的 2 核机器),建议设置为24,避免因资源竞争导致测试变慢或不稳定。
  • retries:在 CI 中设置1-2次重试非常有用,可以过滤掉因网络瞬时波动或资源加载延迟导致的偶发性失败,让测试套件更稳定。但重试会掩盖真正的逻辑错误,所以本地开发通常设为0
  • trace: 'retain-on-failure'务必开启。当测试失败时,生成的trace.zip文件可以用playwright show-trace命令打开,里面包含了测试每一步的完整上下文(DOM、网络、日志、截图),是排查问题的核武器。
  • webServer:这个配置能让你实现“一键测试”。运行npx playwright test时,它会先启动你的应用,再运行测试,最后关闭应用,非常适合集成到 CI 流水线中。

4. 核心 API 与最佳实践

4.1 定位器(Locator):告别脆弱的 XPath/CSS Selector

Playwright 最棒的特性之一就是它的定位器哲学。它鼓励你使用面向用户的、语义化的定位方式,而不是脆弱的、基于实现细节的 CSS 或 XPath。

import { test, expect } from '@playwright/test'; test('使用最佳定位策略', async ({ page }) => { await page.goto('https://example.com/login'); // ❌ 避免:脆弱的 CSS/XPath,一旦前端样式或结构微调就会失败 await page.click('body > div > form > div:nth-child(2) > input'); await page.fill('#username-input', 'user'); // ID 可能动态生成 // ✅ 推荐:使用语义化、面向用户的定位器 // getByRole: 通过 ARIA 角色定位,最接近用户感知(屏幕阅读器也是这么“看”页面的) await page.getByRole('textbox', { name: '用户名' }).fill('testuser'); await page.getByRole('button', { name: '登录' }).click(); // getByLabel: 通过关联的标签文本定位 await page.getByLabel('邮箱地址').fill('test@example.com'); // getByPlaceholder: 通过占位符文本定位 await page.getByPlaceholder('请输入密码').fill('password123'); // getByTestId: 与前端约定好的测试 ID,最稳定(需前端配合添加>test('自动等待示例', async ({ page }) => { await page.goto('/dynamic-content'); // Playwright 在执行 click 前,会自动等待该元素: // 1. 被附加到 DOM // 2. 可见(非隐藏、非透明、有尺寸) // 3. 启用(非 disabled) // 4. 稳定(未在动画中) // 5. 可接收事件(未被其他元素遮挡) await page.getByRole('button', { name: '加载更多' }).click(); // 断言也会自动重试,直到条件满足(默认5秒) // 这行代码会持续检查,直到列表项数量达到10个,或者超时 await expect(page.locator('.item-list > li')).toHaveCount(10); // 等待导航(如点击后跳转新页面) await page.getByText('跳转到详情').click(); await page.waitForURL('**/details/*'); // 使用通配符匹配URL // 等待网络请求完成(非常适合验证 API 调用) const responsePromise = page.waitForResponse('**/api/user/profile'); await page.getByRole('button', { name: '刷新资料' }).click(); const response = await responsePromise; expect(response.status()).toBe(200); });

常见陷阱

  • 动态内容:这是录制脚本失败最常见的原因。录制时,工具会记录一个绝对的选择器路径。但如果元素是动态生成的(如列表项、模态框),其选择器下次可能就变了。解决方案:永远使用上文提到的语义化定位器(getByRole,getByTestId),或者使用相对定位、文本匹配。
  • 过度等待:虽然 Playwright 有自动等待,但某些自定义的、非标准的交互(如一个复杂的 Canvas 绘图完成后才出现按钮)可能需要显式等待。这时应使用page.waitForFunction等待一个特定的 JavaScript 条件成立,而不是死板的page.waitForTimeout(5000)

4.3 高级交互与复杂场景处理

现代 Web 应用远不止点击和输入。Playwright 提供了处理各种复杂场景的能力。

文件上传与下载

test('处理文件', async ({ page }) => { // 1. 文件上传(无需触发系统文件选择框) // 方法一:对于 input[type="file"] 元素,直接设置文件路径 await page.locator('input[type="file"]').setInputFiles(['/path/to/file1.pdf', '/path/to/file2.jpg']); // 方法二:监听文件选择对话框(如果页面弹出了对话框) page.on('filechooser', async (fileChooser) => { await fileChooser.setFiles(['/path/to/file.pdf']); }); await page.getByText('上传文件').click(); // 这会触发文件选择对话框 // 2. 文件下载 // 启动下载监听 const downloadPromise = page.waitForEvent('download'); await page.getByRole('link', { name: '导出报告' }).click(); const download = await downloadPromise; // 获取下载建议的文件名,并保存到指定路径 const suggestedFilename = download.suggestedFilename(); const filePath = `./test-results/downloads/${suggestedFilename}`; await download.saveAs(filePath); // 验证文件确实已下载(例如,检查文件是否存在或内容) const fs = require('fs'); expect(fs.existsSync(filePath)).toBeTruthy(); });

处理 iframe 和弹窗

test('与 iframe 和弹窗交互', async ({ page }) => { await page.goto('/page-with-iframe'); // 1. 定位到 iframe 内部 const iframe = page.frameLocator('iframe[name="embedded-content"]'); // 在 iframe 上下文中操作 await iframe.getByRole('button', { name: '内部按钮' }).click(); // 2. 处理新窗口/标签页 const [newPage] = await Promise.all([ page.context().waitForEvent('page'), // 监听新页面事件 page.getByRole('link', { name: '在新窗口打开' }).click(), // 触发打开新页面 ]); await newPage.waitForLoadState('domcontentloaded'); // 在新页面对象上操作 await newPage.getByText('新页面内容').click(); await newPage.close(); // 操作完后记得关闭 // 3. 处理 JavaScript 弹窗(alert, confirm, prompt) page.on('dialog', async dialog => { console.log(`弹窗消息: ${dialog.message()}`); await dialog.accept(); // 点击“确定” // await dialog.dismiss(); // 点击“取消” // 对于 prompt: await dialog.accept('输入的文字'); }); await page.getByRole('button', { name: '触发确认框' }).click(); });

模拟设备与网络条件

import { devices } from '@playwright/test'; test('移动端测试与网络模拟', async ({ browser }) => { // 1. 模拟移动设备(如 iPhone 12) const iPhone12 = devices['iPhone 12']; const context = await browser.newContext({ ...iPhone12, // 可以覆盖默认设置,如地理位置、权限 geolocation: { longitude: 116.397128, latitude: 39.916527 }, permissions: ['geolocation'], }); const mobilePage = await context.newPage(); await mobilePage.goto('/'); // 2. 模拟慢速网络(如 3G) const slowContext = await browser.newContext(); const slowPage = await slowContext.newPage(); await slowPage.route('**/*', (route) => { // 可以拦截并修改请求,这里我们只是模拟延迟 // 实际项目中,更常用的是 playwright.config 中的 `slowMo` 选项来全局降速观察 route.continue(); }); // 或者使用内置的 network 模拟(需在配置中设置) // context.setOffline(true); // 模拟离线 await mobilePage.close(); await context.close(); });

拦截和修改网络请求: 这是 Playwright 非常强大的功能,可以用于 Mock 数据、性能测试或验证 API 调用。

test('拦截网络请求', async ({ page }) => { // 1. 拦截并修改请求(例如,修改请求头或请求体) await page.route('**/api/user', async (route) => { const request = route.request(); const postData = request.postData(); // 可以修改 postData const modifiedData = JSON.stringify({ ...JSON.parse(postData || '{}'), mocked: true }); await route.continue({ postData: modifiedData }); }); // 2. 拦截并直接返回 Mock 响应(不发送真实请求) await page.route('**/api/products', async (route) => { const mockResponse = { status: 200, contentType: 'application/json', body: JSON.stringify([{ id: 1, name: 'Mock Product' }]), }; await route.fulfill(mockResponse); }); // 3. 拦截并中止请求(例如,阻止图片加载以加速测试) await page.route('**/*.{png,jpg,jpeg,svg}', (route) => route.abort()); await page.goto('/'); // 此时页面发出的 /api/products 请求将收到我们的 Mock 数据 });

5. 测试组织与高级模式

5.1 页面对象模型(Page Object Model, POM)

POM 是 UI 自动化测试中最重要的设计模式,它将页面的元素定位和交互逻辑封装成类,使测试用例更清晰、更易维护。

// tests/pages/login.page.ts import { Locator, Page } from '@playwright/test'; export class LoginPage { readonly page: Page; readonly usernameInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; readonly errorMessage: Locator; constructor(page: Page) { this.page = page; this.usernameInput = page.getByRole('textbox', { name: '用户名' }); this.passwordInput = page.getByLabel('密码'); this.submitButton = page.getByRole('button', { name: '登录' }); this.errorMessage = page.locator('.alert-error'); } async goto() { await this.page.goto('/login'); } async login(username: string, password: string) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.submitButton.click(); } async getErrorMessage(): Promise<string | null> { return await this.errorMessage.textContent(); } } // tests/specs/login.spec.ts import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; test('用户登录成功', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('validUser', 'validPass'); // 断言跳转或出现成功元素 await expect(page).toHaveURL('/dashboard'); }); test('用户登录失败显示错误信息', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('invalidUser', 'wrongPass'); await expect(loginPage.errorMessage).toBeVisible(); expect(await loginPage.getErrorMessage()).toContain('用户名或密码错误'); });

POM 进阶技巧

  • 组合模式:对于大型应用,可以创建“组件对象”(如HeaderComponent,SidebarComponent),然后在页面对象中组合它们。
  • 继承:如果有多个页面共享公共元素(如导航栏),可以创建一个BasePage类。

5.2 夹具(Fixtures)与测试隔离

Playwright Test 的夹具系统非常强大,它用于建立测试环境(如登录状态)并确保测试间的隔离。

// tests/fixtures/auth.setup.ts import { test as baseTest } from '@playwright/test'; import { DashboardPage } from '../pages/dashboard.page'; // 1. 定义一个扩展了基础测试的夹具 export const test = baseTest.extend<{ dashboardPage: DashboardPage; adminDashboardPage: DashboardPage; }>({ // 2. 一个需要登录的页面夹具 dashboardPage: async ({ page }, use) => { // 这部分在每个使用该夹具的测试开始前执行 await page.goto('/login'); await page.getByRole('textbox', { name: '用户名' }).fill('standard_user'); await page.getByLabel('密码').fill('secret_sauce'); await page.getByRole('button', { name: '登录' }).click(); // 等待登录成功,确保状态已就绪 await expect(page).toHaveURL(/.*inventory.html/); const dashboardPage = new DashboardPage(page); // 将创建好的页面对象传递给测试用例 await use(dashboardPage); // 这部分在每个使用该夹具的测试结束后执行(可选,用于清理) // 例如登出 // await page.getByRole('button', { name: '登出' }).click(); }, // 3. 另一个夹具,使用不同的用户登录 adminDashboardPage: async ({ page }, use) => { await page.goto('/login'); await page.getByRole('textbox', { name: '用户名' }).fill('admin_user'); await page.getByLabel('密码').fill('admin_pass'); await page.getByRole('button', { name: '登录' }).click(); await expect(page.getByText('管理面板')).toBeVisible(); await use(new DashboardPage(page)); }, }); export { expect } from '@playwright/test'; // 重新导出 expect // tests/specs/authorized-flow.spec.ts import { test, expect } from '../fixtures/auth.setup'; // 导入自定义夹具 // 现在测试可以直接使用已登录的 dashboardPage test('普通用户查看商品列表', async ({ dashboardPage }) => { await dashboardPage.goto(); const items = await dashboardPage.getProductItems(); expect(items.length).toBeGreaterThan(0); }); test('管理员可以删除商品', async ({ adminDashboardPage }) => { await adminDashboardPage.goto(); await adminDashboardPage.deleteProduct('某商品ID'); await expect(adminDashboardPage.getSuccessToast()).toBeVisible(); });

夹具的核心优势

  • 复用与封装:将通用的准备逻辑(如登录)封装起来,避免每个测试重复编写。
  • 自动清理:夹具的use函数之后的部分可以执行清理,确保测试间不互相影响。
  • 灵活组合:测试可以按需使用不同的夹具组合,构建不同的测试上下文。

5.3 并行执行与分片(Sharding)

对于大型测试套件,并行执行是缩短反馈周期的关键。Playwright Test 默认就支持并行。

# 运行所有测试,使用所有可用的 CPU 核心并行执行 npx playwright test # 指定 worker 数量 npx playwright test --workers=4 # 在 CI 中,通常使用分片:将测试分成 N 份,在 M 台机器上并行跑 # 机器 1: 运行第一份 npx playwright test --shard=1/3 # 机器 2: 运行第二份 npx playwright test --shard=2/3 # 机器 3: 运行第三份 npx playwright test --shard=3/3

playwright.config.ts中配置fullyParallel: trueworkers,Playwright 会自动尝试并行运行所有不相互依赖的测试文件。测试的隔离性(独立的浏览器上下文)是安全并行的前提。

6. 调试、报告与 CI/CD 集成

6.1 调试技巧:让问题无处可藏

当测试失败时,别急着改代码,先利用好 Playwright 的调试工具。

  1. 使用追踪查看器(Trace Viewer)

    # 首先,确保配置中启用了 trace(建议 'on-first-retry' 或 'retain-on-failure') # 测试失败后,会生成一个 trace.zip 文件 npx playwright show-trace test-results/你的测试-失败-chromium/trace.zip

    这个图形化工具会展示测试的完整时间线,你可以逐步骤查看当时的页面快照、网络请求、控制台输出,是定位“为什么点击没反应”、“为什么元素找不到”这类问题的最强工具。

  2. 使用 VS Code 扩展: 安装官方 “Playwright Test for VSCode” 扩展。它允许你直接在编辑器中运行、调试测试,设置断点,并实时查看浏览器。

  3. 无头模式与慢动作

    # 在无头模式下运行,但打开 UI 并放慢动作以便观察 npx playwright test --ui --headed --slow-mo=1000

    --ui打开 Playwright 的图形化测试运行器,--headed显示浏览器窗口,--slow-mo让每个操作延迟指定的毫秒数。

  4. 生成并查看截图与录像: 配置screenshot: 'on'video: 'on'可以在每次测试时都生成媒体文件,但会占用大量磁盘。通常建议设为'only-on-failure'

6.2 生成丰富的测试报告

Playwright 支持多种报告格式,HTML 报告是最直观的。

# 运行测试并生成 HTML 报告 npx playwright test --reporter=html # 报告默认生成在 playwright-report 目录,用浏览器打开 index.html 即可 npx playwright show-report

HTML 报告会展示通过率、执行时间、失败的截图和追踪链接。你还可以集成allure-playwright来生成更强大的 Allure 报告。

对于 CI 系统(如 Jenkins、GitLab CI),JUnit 格式的报告是标准。

npx playwright test --reporter=junit --output=test-results/junit.xml

6.3 集成到 CI/CD 流水线(以 GitHub Actions 为例)

将 Playwright 测试集成到 CI 中,可以实现代码提交即自动测试。以下是 GitHub Actions 的一个经典配置:

# .github/workflows/playwright.yml name: Playwright Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '18' - name: Install dependencies run: npm ci # 使用 ci 命令确保依赖锁一致 - name: Install Playwright Browsers run: npx playwright install --with-deps chromium # CI中通常只安装一个浏览器以加速 - name: Run Playwright tests run: npx playwright test env: # 传递测试环境变量,如基础URL BASE_URL: ${{ secrets.TEST_ENV_URL }} - name: Upload Playwright report if: always() # 即使测试失败也上传报告 uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report/ retention-days: 7 - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: test-results path: test-results/ retention-days: 7

CI 优化技巧

  • 缓存:缓存node_modules和 Playwright 浏览器二进制文件可以大幅缩短流水线时间。
  • 只安装必要浏览器:CI 中通常只需测试 Chromium,用npx playwright install chromium
  • 使用官方 Action:微软提供了@playwright/test的 GitHub Action (microsoft/playwright-github-action),它内置了浏览器安装和优化,更简单。
  • 分片与并行:在大型项目中,使用--shard参数将测试分发到多个 job 中并行执行。

7. 常见问题排查与性能优化

7.1 高频问题速查表

问题现象可能原因解决方案
locator.click()超时1. 元素不可见/被遮挡。
2. 元素处于禁用状态。
3. 有动画或过渡效果未完成。
4. 页面仍在加载或渲染。
1. 使用locator.hover()或检查元素样式。
2. 检查元素disabled属性。
3. 增加actionTimeout或使用page.waitForFunction等待动画结束。
4. 在操作前使用page.waitForLoadState('networkidle')
expect(locator).toBeVisible()失败1. 断言超时前元素始终未出现。
2. 元素在 DOM 中但display: nonevisibility: hidden
3. 视口外,需要滚动。
1. 检查定位器是否正确,或前端逻辑是否有误。
2. 使用toBeHidden()或检查 CSS。
3. 先执行locator.scrollIntoViewIfNeeded()
测试在 CI 中通过,本地失败(或反之)1. 环境差异(浏览器版本、屏幕分辨率、时区)。
2. 网络延迟或资源加载速度不同。
3. 测试数据状态不一致。
1. 使用 Docker 统一测试环境。
2. CI 中适当增加超时时间 (timeout,expect.timeout)。
3. 使用夹具确保每个测试有干净的初始状态。
录制脚本回放失败1. 动态内容导致选择器变化(最常见)。
2. 页面加载速度差异。
3. 有未处理的弹窗或导航。
1.放弃录制,手写定位器。使用getByRole,getByTestId等稳定定位方式。
2. 在关键步骤后添加page.waitForLoadState
3. 使用page.on('dialog')处理弹窗。
文件下载失败或找不到文件1. 下载路径未指定或无权访问。
2. 浏览器设置了“另存为”对话框,未自动下载。
3. 下载链接触发了新窗口。
1. 使用download.saveAs()指定绝对路径。
2. 在浏览器上下文中设置acceptDownloads: true
3. 监听download事件,而不是新页面事件。
page.goto()超时1. 网络问题或服务器未响应。
2. 页面有无限重定向。
3. 证书错误(测试环境常见)。
1. 检查网络和服务器状态。
2. 使用page.goto(url, { waitUntil: 'domcontentloaded' })减少等待。
3. 配置ignoreHTTPSErrors: true

7.2 性能优化与最佳实践

  1. 一个测试只测一件事:保持测试用例简短、独立。一个复杂的用户流可以拆分成多个测试,用夹具共享登录状态。
  2. 善用page.route进行 Mock:对于依赖第三方 API 或速度慢的后端接口,在测试中拦截并返回模拟数据,可以极大提升测试速度和稳定性。
  3. 避免不必要的导航:如果多个测试需要同一个页面状态,使用夹具在同一个页面上执行多个操作,而不是每个测试都重新page.goto
  4. 选择性安装浏览器:在 CI 环境中,如果不需要测试所有浏览器,只安装 Chromium (npx playwright install chromium) 可以节省大量时间和磁盘空间。
  5. 合理配置超时:根据应用的实际响应速度,在playwright.config.ts中设置合理的全局timeoutactionTimeoutnavigationTimeout。太短会导致不必要的失败,太长会拖慢测试套件。
  6. 定期清理测试产物test-resultsplaywright-report目录会积累大量截图、录像和追踪文件。确保.gitignore中包含它们,并在 CI 中设置合理的 artifact 保留时间。

从 Selenium 迁移过来,最大的感受是心智负担的减轻。以前要花大量精力处理同步、等待和跨浏览器差异,现在可以更专注于测试逻辑和业务场景本身。Playwright 的现代化设计,让它不仅仅是又一个自动化工具,而是一个完整的、为质量和效率服务的测试工程解决方案。开始可能会觉得它的 API 和理念需要适应,但一旦用上手,就很难再回去了。

http://www.gsyq.cn/news/1630300.html

相关文章:

  • SSRF漏洞攻防实战:从原理到绕过技巧与防御策略
  • Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决
  • ROS Noetic与Gazebo仿真小车搭建指南
  • 缺牙修复科普:常见义齿类型与选择参考
  • STM32F091RC与LTC6904实现高精度方波信号生成
  • LV3296与PIC32MZ2048EFM064构建高精度数据采集系统
  • 洞态IAST自定义规则实战:从原理到配置,打造精准漏洞检测
  • AI视频编辑自动化:基于文本转录与智能体协作的video-use实践指南
  • 基于TPA3128D2与STM32F7的高保真数字功放设计
  • Docker容器安全加固:从零构建定制化Seccomp白名单策略
  • 2026年AI音乐创作工具全解析与实战技巧
  • 自动驾驶与具身智能感知系统的设计优先级差异
  • AI大模型训练师:收藏!小白程序员转型AI的绝佳入口,抓住未来机遇!
  • 高性能数据采集与异步落盘系统优化实战
  • [Ru(bpy)2(pyip)]2+ 芘基咪唑并邻菲啰啉钌 (II) DNA 光开关配合物
  • REST Assured实战:15条核心实践构建商城API自动化测试堡垒
  • 实战方案:InvenTree开源库存管理系统助力企业实现精细化库存控制
  • 解密Chrome扩展:打造专业级Markdown阅读体验的技术实践
  • 惠普暗影精灵笔记本性能控制新方案:OmenSuperHub深度解析
  • Wand-Enhancer技术解析:WeMod客户端本地化增强方案
  • AI学习路径:从数学基础到工程实践的完整指南
  • KNN算法实战:鸢尾花分类与机器学习入门
  • 如何零代码获取B站视频?这款开源工具让你3分钟搞定
  • LARA-R6401 LTE模块与MKV44F64VLH16 MCU的硬件连接与优化实践
  • Java 线程池隔离:核心链路不要和 AI 任务共用执行资源
  • IIM-42652与PIC18LF25K40实现6DoF姿态追踪方案
  • 华硕笔记本终极性能控制:GHelper轻量化控制工具完整指南
  • 本地部署AI绘画:Codex与Cowart打造离线无限画布工作站
  • OpenMontage:AI智能体驱动的自动化视频生产系统部署与实战指南
  • 【2026最新】Java JDK全面解析