
1. 项目概述为什么说Playwright是“新宠”如果你最近在关注自动化测试或者网页开发大概率会频繁听到一个名字Playwright。它不再是那个需要费力解释“和Selenium有什么区别”的新面孔而是实实在在地成为了许多团队技术栈中的标配甚至被一些开发者称为“新宠”。这个称号背后其实是一系列精准击中现代开发痛点的能力集合。简单来说Playwright是一个由微软开源的、用于实现端到端E2E测试和浏览器自动化的强大框架。它支持Chromium、Firefox和WebKit三大浏览器引擎这意味着你可以用一套脚本在几乎所有的现代浏览器上测试你的网页应用无论是桌面端还是移动端。但它的魅力远不止于此。传统的自动化测试工具常常让人头疼于不稳定的等待、难以处理的动态元素或者复杂的跨域、iframe场景。Playwright在设计之初就瞄准了这些痛点。它提供了自动等待、强大的选择器、网络拦截、文件上传下载、甚至模拟地理位置和设备传感器等“开箱即用”的功能。对于开发者而言这意味着你可以花更少的时间去写那些繁琐的、用于稳定测试的“胶水代码”而将精力集中在测试逻辑和业务验证本身。对于前端开发者它也是一个绝佳的“机器人伙伴”可以用来做页面截图、生成PDF、性能监控或者自动化一些重复的网页操作。所以当大家说它是“新宠”时其实是在说我们终于有了一个更可靠、更强大、也更符合现代Web开发节奏的自动化工具。2. 核心设计思路Playwright如何做到“以一敌三”要理解Playwright的无限可能得先拆解它的核心设计哲学。它不像一些工具那样仅仅是对浏览器驱动进行了一层薄薄的封装。Playwright选择了一条更彻底、也更困难的路与浏览器引擎进行深度集成。2.1 架构革新从“遥控器”到“内置指挥官”我们可以把传统的基于WebDriver协议的工具如Selenium想象成一个“遥控器”。你写脚本按遥控器按钮遥控器通过一个标准协议红外信号发送指令给浏览器驱动接收器驱动再翻译成浏览器能懂的命令去操作浏览器。这个链条长任何一个环节的延迟或不稳定都会导致测试失败比如元素还没加载完指令已经发出去了。Playwright则更像一个“内置指挥官”。它通过一个名为“Playwright协议”的私有通道直接与浏览器进程通信。这个协议比WebDriver协议更丰富、更底层。它允许Playwright直接命令浏览器“在这个页面加载前先拦截这个网络请求并修改它”、“自动等待这个元素达到可点击状态再操作”、“录制整个页面的操作视频”。这种深度集成带来了几个决定性优势稳定性极大提升因为通信是直接且同步的避免了因网络延迟或驱动翻译导致的时序问题。其内置的“自动等待”机制能智能判断元素是否可交互如可见、启用、稳定从根本上减少了sleep或固定等待的使用。能力边界大幅扩展可以轻松实现网络拦截与模拟、访问浏览器上下文如Cookie、LocalStorage、模拟移动设备、触摸事件、地理位置等这些在传统架构下要么很难要么不可能。多浏览器一致性由于Playwright团队同时维护对三大引擎的适配它们提供的API是统一的。你写一个脚本在Chrome、Firefox和Safari上的行为高度一致简化了跨浏览器测试的复杂度。2.2 选择器引擎告别脆弱的XPath和CSS元素定位是自动化脚本的基石也是脚本“脆弱”的主要来源。Playwright内置了一套极其强大的选择器引擎它鼓励使用面向用户的、语义化的定位方式。文本选择器page.click(‘text登录’)。直接点击页面上文本为“登录”的元素。这非常符合测试直觉因为用户就是看文字点击的。角色选择器ARIApage.click(‘rolebutton[name”提交”]’)。这是基于可访问性角色的定位非常稳定因为按钮的角色role通常不会因为CSS样式改变而改变。布局选择器page.click(‘button:right-of(#search-box)’)。可以基于元素之间的相对位置进行定位在处理没有唯一标识的动态元素时非常有用。注意虽然Playwright也完全支持CSS和XPath但其官方最佳实践是优先使用文本、角色等语义化选择器。它们通常更稳定更能体现用户的真实操作意图也使得测试代码更易读。2.3 浏览器上下文实现真正的隔离与并行这是Playwright另一个精妙的设计。每个测试用例都可以在一个全新的“浏览器上下文”中运行。你可以把它理解为一个完全独立的浏览器会话它拥有独立的Cookie、LocalStorage、历史记录甚至代理设置。这意味着测试完全隔离用例A登录产生的会话绝不会影响到用例B。轻松实现多用户场景你可以创建多个上下文来模拟多个用户同时操作。高效并行结合测试运行器每个上下文可以独立运行充分利用多核CPU大幅缩短测试套件的总执行时间。3. 超越测试Playwright的多元化应用场景实操当我们跳出“自动化测试框架”这个框框Playwright作为一个高性能的浏览器自动化控制库其可能性就被极大地打开了。下面我分享几个我实际用过或深度研究过的场景。3.1 自动化网页内容抓取与监控虽然爬虫有requests、Scrapy等专用工具但对于严重依赖JavaScript渲染的现代单页应用或者需要模拟复杂交互如登录、翻页、点击加载更多才能获取数据的场景Playwright是降维打击。实操示例监控某商品价格变动假设我们需要每天监控某个电商网站的商品价格该页面价格是通过JS动态加载的。const { chromium } require(‘playwright’); (async () { const browser await chromium.launch({ headless: true }); // 无头模式运行 const context await browser.newContext(); const page await context.newPage(); try { await page.goto(‘https://example.com/product/123’); // 等待价格元素出现 await page.waitForSelector(‘.product-price’); // 获取价格文本 const priceText await page.textContent(‘.product-price’); const price parseFloat(priceText.replace(/[^0-9.]/g, ‘’)); console.log([${new Date().toISOString()}] 当前价格: ${price}); // 这里可以添加逻辑如果价格低于阈值发送邮件或钉钉通知 if (price 100) { console.log(‘价格低于阈值触发通知’); // await sendNotification(price); } } catch (error) { console.error(‘抓取失败:’, error); } finally { await browser.close(); } })();心得对于抓取任务务必使用try...catch...finally确保浏览器被正确关闭避免资源泄漏。另外合理设置waitForSelector的超时时间并考虑使用page.waitForLoadState(‘networkidle’)来等待页面网络活动基本停止确保数据加载完成。3.2 自动生成页面截图与PDF报告这个功能对于前端开发、内容运营和设计走查来说非常实用。你可以一键为整个网站生成一套完整的屏幕截图用于不同视口下的样式检查或者将某个页面生成为PDF文档用于存档或分享。实操示例生成网站关键页面在不同设备下的截图const { chromium, devices } require(‘playwright’); const fs require(‘fs’).promises; (async () { const browser await chromium.launch(); const context await browser.newContext(); const page await context.newPage(); const urls [‘/’, ‘/about’, ‘/product’]; const viewports [ { name: ‘desktop’, width: 1920, height: 1080 }, { name: ‘tablet’, width: 768, height: 1024 }, { name: ‘mobile’, width: 375, height: 667 } ]; for (const url of urls) { await page.goto(https://your-site.com${url}); for (const vp of viewports) { await page.setViewportSize(vp); // 确保页面在视口大小调整后稳定 await page.waitForTimeout(500); const safeName url.replace(/\//g, ‘_’); const screenshotPath ./screenshots/${safeName}_${vp.name}.png; await page.screenshot({ path: screenshotPath, fullPage: true }); console.log(已生成: ${screenshotPath}); } } await browser.close(); })();心得fullPage: true参数可以截取整个可滚动页面的长图。在调整视口后最好等待一小段时间waitForTimeout让页面的CSS媒体查询和布局重新计算完成避免截到布局混乱的中间状态。3.3 性能分析与用户体验模拟Playwright可以接入Chrome DevTools Protocol这意味着你能以编程方式获取到丰富的性能数据。实操示例测量页面加载关键性能指标const { chromium } require(‘playwright’); (async () { const browser await chromium.launch(); const page await browser.newPage(); // 开始收集性能时间线 await page.context().tracing.start({ screenshots: true, snapshots: true }); await page.goto(‘https://example.com’); // 等待某个代表页面加载完成的关键元素 await page.waitForSelector(‘.main-content’); // 停止收集并保存数据 const tracePath ‘./trace.zip’; await page.context().tracing.stop({ path: tracePath }); // 通过Performance API获取更多指标 const perfEntries await page.evaluate(() JSON.stringify(performance.getEntriesByType(‘navigation’)) ); console.log(‘Performance Navigation Timing:’, perfEntries); await browser.close(); console.log(性能追踪文件已保存至: ${tracePath}); // 可以使用Chrome DevTools的 chrome://tracing 或 edge://tracing 打开此文件进行分析 })();心得生成的trace.zip文件包含了加载过程的详细时间线、内存占用、屏幕截图等。用Chrome浏览器打开chrome://tracing然后加载这个文件就能获得一个可视化的、可交互的性能分析报告对于定位加载慢、内存泄漏等问题非常有帮助。3.4 与CI/CD管道深度集成这才是Playwright在现代研发流程中发挥威力的地方。你可以将它无缝集成到GitHub Actions、GitLab CI、Jenkins等工具中实现每次代码提交或合并请求都自动运行测试。一个典型的GitHub Actions工作流配置示例 (.github/workflows/playwright.yml)name: Playwright Tests on: [push, pull_request] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: ‘18’ - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps chromium - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifactv4 if: always() # 无论测试成功与否都上传 with: name: playwright-report path: playwright-report/ retention-days: 30心得在CI环境中通常只安装一个浏览器如Chromium以加快速度。使用--with-deps参数可以确保安装所有必要的系统依赖。if: always()确保测试报告在用例失败时也能被上传便于事后排查。对于大型项目可以将测试分片sharding到多个机器上并行执行进一步缩短反馈时间。4. 从零到一构建健壮的Playwright自动化测试项目理解了核心概念和场景后我们动手搭建一个结构清晰、易于维护的测试项目。这里以Node.js环境为例。4.1 环境搭建与项目初始化首先确保你的系统已安装Node.js (版本14或更高)。然后创建一个新目录并初始化项目。mkdir my-playwright-project cd my-playwright-project npm init -y接下来安装Playwright。推荐安装playwright/test这个测试运行器它集成了Playwright库和一个类似Jest/Mocha的测试框架比单独使用Playwright库更方便。npm init playwrightlatest运行这个命令后它会引导你进行一系列选择是否使用TypeScript推荐选择“Yes”TypeScript能提供更好的类型提示和代码补全。测试目录放在哪里默认是tests或e2e。是否添加GitHub Actions工作流推荐选择“Yes”它会自动生成上面提到的CI配置文件。是否安装Playwright浏览器选择“Yes”。安装完成后项目结构大致如下my-playwright-project/ ├── node_modules/ ├── tests/ │ ├── example.spec.ts # 示例测试文件 │ └── auth.setup.ts # 全局Setup文件示例 ├── playwright.config.ts # Playwright主配置文件 ├── package.json └── .github/workflows/ # CI工作流文件如果选择了4.2 编写你的第一个端到端测试让我们写一个测试验证在某个假设的登录页面的功能。在tests目录下创建login.spec.ts。import { test, expect } from ‘playwright/test’; // 使用 test.describe 组织相关测试用例 test.describe(‘登录功能’, () { // test.beforeEach 会在每个用例前执行常用于跳转到起始页面 test.beforeEach(async ({ page }) { await page.goto(‘https://your-app.com/login’); }); test(‘使用正确凭据应登录成功’, async ({ page }) { // 使用角色选择器定位用户名输入框 await page.fill(‘roletextbox[name”用户名”]’, ‘valid_user’); await page.fill(‘roletextbox[name”密码”]’, ‘valid_password’); await page.click(‘rolebutton[name”登录”]’); // 断言登录后应跳转到仪表盘页面且URL包含‘/dashboard’ await expect(page).toHaveURL(/.*dashboard/); // 断言页面应显示欢迎用户的文本 await expect(page.getByText(‘欢迎valid_user’)).toBeVisible(); }); test(‘使用错误密码应显示错误信息’, async ({ page }) { await page.fill(‘roletextbox[name”用户名”]’, ‘valid_user’); await page.fill(‘roletextbox[name”密码”]’, ‘wrong_password’); await page.click(‘rolebutton[name”登录”]’); // 断言错误提示信息应该出现 await expect(page.getByText(‘用户名或密码错误’)).toBeVisible(); // 断言页面应该仍然停留在登录页面 await expect(page).toHaveURL(‘https://your-app.com/login’); }); });关键点解析test和expect是从playwright/test导入的全局对象。async ({ page })Playwright Test运行器会自动为每个测试提供pagefixture一个独立的页面对象无需手动创建和关闭。await expect(...).toBeVisible()这是Playwright Test提供的断言API它内置了智能等待会持续轮询直到条件满足或超时极大地增强了测试的稳定性。4.3 配置管理与数据驱动硬编码的测试数据和URL不利于维护。Playwright支持通过.env文件和环境变量进行配置。1. 创建环境变量文件创建.env文件记得加入.gitignoreBASE_URLhttps://staging.your-app.com ADMIN_USERNAMEadmin ADMIN_PASSWORDsecret2. 修改playwright.config.ts读取配置import { defineConfig, devices } from ‘playwright/test’; import dotenv from ‘dotenv’; import path from ‘path’; // 加载 .env 文件 dotenv.config({ path: path.resolve(__dirname, ‘.env’) }); export default defineConfig({ // 全局超时时间 timeout: 30 * 1000, // 每个测试用例的超时时间 expect: { timeout: 10000 }, // 全局重试次数对于不稳定的测试可以设置1-2次 retries: process.env.CI ? 2 : 0, // 测试报告配置 reporter: [[‘html’], [‘list’]], use: { // 从环境变量读取基础URL baseURL: process.env.BASE_URL || ‘http://localhost:3000’, // 每个测试的截图配置仅在失败时截图 screenshot: ‘only-on-failure’, // 录制视频仅在失败时保存 video: ‘retain-on-failure’, // 追踪文件仅在失败时保存 trace: ‘retain-on-failure’, }, projects: [ { name: ‘chromium’, use: { ...devices[‘Desktop Chrome’] }, }, { name: ‘firefox’, use: { ...devices[‘Desktop Firefox’] }, }, ], });3. 在测试中使用配置现在测试用例中的page.goto可以使用相对路径了await page.goto(‘/login’); // 会自动拼接 baseURL // 使用环境变量 const username process.env.ADMIN_USERNAME;4. 数据驱动测试对于需要多组数据验证的用例可以使用参数化测试import { test, expect } from ‘playwright/test’; const loginTestData [ { username: ‘user1’, password: ‘pass1’, shouldSucceed: true }, { username: ‘user2’, password: ‘wrong’, shouldSucceed: false }, { username: ‘’, password: ‘pass1’, shouldSucceed: false }, // 空用户名 ]; for (const data of loginTestData) { test(登录测试 - 用户: ${data.username}, async ({ page }) { await page.goto(‘/login’); await page.fill(‘[name”username”]’, data.username); await page.fill(‘[name”password”]’, data.password); await page.click(‘button[type”submit”]’); if (data.shouldSucceed) { await expect(page).toHaveURL(/.*dashboard/); } else { await expect(page.getByText(‘登录失败’)).toBeVisible(); } }); }5. 进阶技巧与实战避坑指南在实际项目中摸爬滚打一段时间后我积累了一些能让Playwright用得更顺手、脚本更健壮的经验和技巧。5.1 处理动态内容与复杂等待虽然Playwright的自动等待很强大但面对一些极端动态的内容如第三方聊天插件、复杂动画后的元素仍需小心。优先使用Playwright内置的等待断言expect(locator).toBeVisible()比page.waitForSelector()更好因为它更语义化且与断言库集成。自定义等待条件当内置条件不满足时使用page.waitForFunction。// 等待某个元素内部的文本变为特定值 await page.waitForFunction( selector document.querySelector(selector)?.innerText ‘加载完成’, ‘.status-indicator’ );应对“元素被遮挡”错误这是常见错误。通常是因为有弹窗、悬浮提示层覆盖。解决方案检查并关闭可能覆盖的弹层。使用locator.hover()先悬停。使用force: true选项强制点击需谨慎可能不符合真实用户行为。使用locator.scrollIntoViewIfNeeded()确保元素在视口中。5.2 网络请求的拦截与模拟这是Playwright的杀手级功能用于测试边缘场景或模拟后端服务。拦截并修改API响应await page.route(‘**/api/user/profile’, async route { // 获取原始响应 const response await route.fetch(); const body await response.text(); const json JSON.parse(body); // 修改响应数据 json.name ‘模拟用户’; // 使用修改后的数据继续请求 await route.fulfill({ response, body: JSON.stringify(json), }); }); // 现在页面中调用 /api/user/profile 将收到修改后的数据模拟网络错误或超时// 模拟某个API接口失败 await page.route(‘**/api/payment’, route route.abort(‘failed’)); // 模拟网络慢 await page.route(‘**/api/data’, route route.fulfill({ status: 200, body: ‘模拟数据’, // 延迟2秒响应 delay: 2000, }));5.3 复用认证状态加速测试套件登录操作往往很耗时。我们可以通过全局Setup在第一个测试前登录一次然后将认证状态Cookie、LocalStorage保存下来供所有测试复用。1. 创建全局Setup文件 (tests/auth.setup.ts):import { test as setup, expect } from ‘playwright/test’; // 这个文件中的测试不会出现在常规测试报告中 setup(‘进行全局登录并保存状态’, async ({ page, context }) { await page.goto(‘/login’); await page.fill(‘[name”email”]’, process.env.TEST_USER!); await page.fill(‘[name”password”]’, process.env.TEST_PASS!); await page.click(‘button[type”submit”]’); // 等待登录成功的标志出现 await expect(page.getByText(‘我的仪表盘’)).toBeVisible(); // 将当前上下文的认证状态保存到文件中 await context.storageState({ path: ‘playwright/.auth/user.json’ }); });2. 在playwright.config.ts中配置全局Setup和项目复用:export default defineConfig({ // ... 其他配置 globalSetup: require.resolve(‘./tests/auth.setup.ts’), // 指定全局Setup文件 projects: [ { name: ‘已登录状态测试套件’, testMatch: /.*\.loggedin\.spec\.ts/, // 匹配特定命名规则的测试文件 use: { // 所有在这个项目下的测试都会自动加载保存的认证状态 storageState: ‘playwright/.auth/user.json’, }, }, { name: ‘普通测试套件’, testMatch: ‘**/*.spec.ts’, // 不加载状态用于测试未登录场景 }, ], });3. 编写一个复用状态的测试 (tests/dashboard.loggedin.spec.ts):import { test, expect } from ‘playwright/test’; test(‘登录后应能看到用户信息’, async ({ page }) { // 由于配置了storageState页面打开时已经是登录状态 await page.goto(‘/dashboard’); // 直接验证登录后的内容无需再走登录流程 await expect(page.getByText(‘欢迎回来’)).toBeVisible(); });5.4 并行执行与测试分片当测试用例成百上千时串行执行会非常慢。Playwright Test天生支持并行执行。工作进程并行在playwright.config.ts中设置workers: 4它会启动4个工作进程同时运行测试文件。注意测试文件之间的隔离由浏览器上下文保证所以是安全的。测试分片在CI中可以将测试套件分割成多个“分片”在不同的机器上并行运行。# 将测试分成3个分片运行第1个分片 npx playwright test --shard1/3 # 在另一台机器上运行第2个分片 npx playwright test --shard2/3在GitHub Actions中可以使用matrix策略轻松实现分片。6. 常见问题排查与调试技巧实录即使工具再强大在实际编写和运行测试时也难免会遇到问题。这里记录了几个我踩过的坑和对应的解决方法。6.1 元素定位失败Selector not found这是最常见的问题。问题现象可能原因排查与解决步骤控制台报错TimeoutError: locator.click: Timeout 30000ms exceeded.1. 选择器写错了。2. 元素在iframe内。3. 元素是动态生成的出现时机晚。4. 页面有多个匹配元素。1.使用Playwright Inspector运行测试时加上--debug标志npx playwright test --debug它会打开一个GUI可以实时查看页面、拾取元素、生成选择器并单步执行测试。2.检查iframe使用page.frameLocator(‘iframeSelector’)来定位iframe内的元素。3.增加等待或使用更稳定的选择器优先使用getByRole,getByText等语义化定位器。使用waitForSelector或expect(locator).toBeVisible()。4.使用:nth-match()或更精确的父级选择器如page.locator(‘button’).first()或page.locator(‘.container button’)。实操心得养成使用Playwright Inspector调试的习惯。它不仅能帮你快速定位问题还能通过“录制”功能生成基础脚本是学习选择器写法的好工具。6.2 测试在CI环境中失败本地却成功这是一个经典的“在我机器上能跑”的问题。排查方向检查点环境差异1.浏览器版本CI服务器安装的浏览器版本是否和本地一致确保CI步骤中运行了npx playwright install。2.屏幕尺寸/视口CI环境可能是无头模式且视口较小。在playwright.config.ts中为所有项目统一设置一个默认视口大小viewport: { width: 1280, height: 720 }。3.网络与依赖被测应用在CI环境是否能正常访问后端API服务是否已启动时序与竞态1.等待不充分CI服务器性能可能较差网络更慢。适当增加全局timeout和expect.timeout。2.动画影响本地运行快动画瞬间完成CI上慢动画未结束就操作。在操作前使用page.waitForTimeout(100)短暂等待或使用locator.waitFor({ state: ‘attached’ })。资源与状态1.测试隔离不彻底某个测试修改了全局状态如数据库影响了其他测试。确保每个测试使用独立的浏览器上下文并且测试前后有数据清理如调用后端清理接口。2.内存/CPU不足CI机器资源紧张导致浏览器崩溃。尝试减少并行工作进程workers。我的标准排查流程在CI配置中启用video: ‘on’和screenshot: ‘on’失败后下载产物看失败瞬间的屏幕截图和视频回放。在CI运行命令中添加--traceon生成详细的追踪文件用Trace Viewer (playwright show-trace trace.zip) 分析每一步操作和网络请求。尝试在本地用headless模式与CI默认一致复现npx playwright test --headedfalse。6.3 处理文件上传与下载文件操作是另一个常见需求点。文件上传Playwright非常优雅地解决了这个传统自动化中的难题。// 假设有一个 input type”file” 元素 const fileInput page.locator(‘input[type”file”]’); // 一行代码即可无需触发文件选择对话框 await fileInput.setInputFiles(‘path/to/your/file.pdf’); // 上传多个文件 await fileInput.setInputFiles([ ‘file1.pdf’, ‘file2.jpg’, ]); // 清除已选择的文件 await fileInput.setInputFiles([]);文件下载// 1. 等待下载开始 const [download] await Promise.all([ // 必须等待 download 事件 page.waitForEvent(‘download’), // 触发下载的动作例如点击一个下载链接 page.click(‘a#download-link’), ]); // 2. 获取下载建议的文件名和保存路径 const suggestedFilename download.suggestedFilename(); const savePath ./downloads/${suggestedFilename}; // 3. 将下载的文件保存到指定路径 await download.saveAs(savePath); console.log(文件已下载至: ${savePath});心得对于下载关键是要在触发下载动作之前就开始等待download事件。使用Promise.all可以确保不会错过这个瞬间的事件。6.4 与Visual Regression Testing (视觉回归测试) 结合视觉回归测试用于检测UI的意外变化。Playwright可以很方便地与playwright/test自带的截图功能或专业工具如jest-image-snapshot结合。使用Playwright内置的截图对比import { test, expect } from ‘playwright/test’; test(‘主页布局应与基准图一致’, async ({ page }) { await page.goto(‘/’); await expect(page).toHaveScreenshot(‘homepage.png’); // 首次运行会生成基准图后续运行会与之对比。 // 可以在playwright.config中配置阈值容忍微小差异。 });在playwright.config.ts中配置视觉比较export default defineConfig({ use: { // ... 其他配置 screenshot: ‘only-on-failure’, // 通常只在失败时截图 }, expect: { // 配置视觉测试的阈值 toHaveScreenshot: { maxDiffPixels: 100, // 允许的最大差异像素数 threshold: 0.2, // 允许的差异阈值 (0-1) }, }, });心得视觉回归测试对动态内容日期、随机数、动画非常敏感。在截图前需要确保页面处于稳定状态。常用的方法是使用page.waitForLoadState(‘networkidle’)等待网络空闲。使用page.evaluate(() document.fonts.ready)等待字体加载。通过Mock或拦截请求将动态数据固定下来。对于不可避免的动画可以用page.waitForTimeout等待其结束或用expect(page).toHaveScreenshot({ animations: ‘disabled’ })禁用动画。