1. 项目概述为什么是Playwright如果你做过Web自动化测试大概率用过Selenium。它很经典但痛点也明显脚本不稳定经常因为元素加载慢、网络延迟而失败跨浏览器支持需要下载不同的驱动版本管理是个噩梦处理弹窗、文件上传、iframe这些复杂场景代码写起来又臭又长。我团队之前维护一个大型电商项目的测试脚本光是处理各种“等待”和“异常捕获”的代码就占了总代码量的三分之一调试时间比写代码还长。后来我们接触到了Playwright它来自微软是一个相对较新的Node.js/Python/.NET/Java库用于实现Web自动化。一开始我也持怀疑态度但用了一个月后我们决定把核心的UI自动化测试全部迁移到Playwright上。它最吸引我的点用一个词概括就是“可靠”。它内置了智能等待能自动等待元素可操作它提供了强大的选择器引擎能穿透Shadow DOM它对现代Web API如文件系统访问、地理位置模拟的支持是原生的。更重要的是它用一个API统一了Chromium、Firefox和WebKitSafari三大浏览器引擎的自动化这意味着你写一套脚本可以无痛地在三种浏览器上运行这对于确保跨浏览器兼容性来说效率提升是颠覆性的。这个项目我就想以一个过来人的身份和你深入聊聊如何用Python版的Playwright这把“利器”来构建稳定、高效的跨浏览器自动化测试。无论你是从零开始的新手还是从Selenium转型的老兵我相信其中的一些设计思路和实战技巧都能让你少走弯路。2. 核心设计Playwright的“三板斧”与选型考量选择任何一个工具理解其设计哲学比死记API更重要。Playwright的成功很大程度上源于它精准地解决了传统自动化工具的几大顽疾。2.1 架构革新浏览器上下文与自动化频道Playwright没有采用Selenium WebDriver那种基于JSON Wire Protocol的远程控制模式。相反它通过一个称为“自动化频道”的协议直接与浏览器进程通信。这带来了两个直接好处速度更快和能力更强。更关键的是它的“浏览器上下文”概念。你可以把它理解为一个完全独立的浏览器会话它拥有独立的cookie、本地存储、缓存甚至代理设置。这意味着你可以在一个浏览器实例中轻松创建多个互不干扰的“隐身”会话来模拟多个用户同时操作或者进行A/B测试场景的隔离测试。这在测试需要登录态或多账户交互的应用时比反复启动关闭浏览器要高效和稳定得多。为什么选PythonPlaywright支持多种语言我选择Python是因为它在测试领域生态成熟pytest, unittest在数据驱动、参数化测试方面写法优雅并且团队成员的学习成本相对较低。对于需要与CI/CD如Jenkins, GitLab CI深度集成、进行复杂测试数据准备的场景Python丰富的库支持是巨大优势。当然如果你的团队主力是Node.js或JavaPlaywright同样提供一流的支持。2.2 统一API与浏览器引擎管理这是Playwright宣称“跨浏览器”的底气。它通过playwright.chromium,playwright.firefox,playwright.webkit三个对象来分别启动对应内核的浏览器。但神奇的是除了极少数浏览器特有的API你的脚本代码几乎不需要为不同浏览器做特殊修改。安装时Playwright会通过playwright install命令一次性下载所有需要的浏览器二进制文件并管理好版本。你再也不需要去搜“ChromeDriver版本与Chrome浏览器版本对应表”了也告别了因驱动版本不匹配导致的脚本突然崩溃。这种“开箱即用”的体验对于团队协作和CI环境搭建来说省去了大量运维成本。注意虽然API统一但浏览器引擎本身的差异是客观存在的。例如WebKitSafari对某些CSS属性或JavaScript特性的支持可能与其他两者不同。Playwright的统一API保证了“能执行”但最终渲染或行为是否一致仍需通过实际运行测试来验证。这正是跨浏览器测试的意义所在。2.3 强大的选择器与自动等待机制元素定位是自动化脚本的基石也是脚本脆弱的根源。Playwright提供了多种定位策略其中最强力的是text和css选择器的增强版。文本选择器page.click(‘text登录’)。它会查找页面中所有包含“登录”文本的元素非常直观。它还支持正则表达式如text/Log\s*in/i。CSS选择器增强你可以使用组合选择器或者使用:has()、:is()等现代CSS伪类进行非常精细的定位。例如定位一个包含特定文本的div下的按钮page.click(‘div:has-text(“商品列表”) button’)。但比选择器更重要的是其自动等待。Playwright在执行如click,fill,check等操作前会自动执行一系列可操作性检查元素是否附着在DOM上。元素是否可见。元素是否稳定例如不再有动画效果。元素是否可交互如未被其他元素遮挡。元素是否启用。只有所有这些条件都满足操作才会执行。这几乎消除了因页面加载或动画未完成导致的“ElementNotInteractableException”错误。你仍然可以显式等待但大多数情况下你不再需要了。3. 环境搭建与核心API实战理论说再多不如动手跑一遍。我们来搭建一个可复用的测试环境并剖析几个最核心的API。3.1 一站式环境配置指南假设你已经安装了Python3.7接下来打开你的终端或命令行。第一步安装Playwright Python包pip install playwright这个命令会安装Playwright的核心Python库。第二步安装浏览器二进制文件安装完库后你需要下载浏览器。Playwright提供了一个非常方便的命令行工具playwright install这条命令会默认下载Chromium、Firefox和WebKit的最新稳定版本。如果你只想安装其中某一个可以指定playwright install chromium # 只安装Chromium playwright install firefox # 只安装Firefox playwright install webkit # 只安装WebKit所有浏览器都会被安装到用户目录下的一个缓存文件夹中与系统全局安装的浏览器互不干扰。第三步选择你的IDE和测试框架我强烈推荐使用VS Code并安装官方的“Playwright Test for VSCode”扩展。这个扩展提供了测试列表、代码透镜在代码上方直接运行测试、追踪查看器可视化回放测试过程等强大功能。 测试框架首选pytest。Playwright官方也推荐使用pytest-playwright插件它能无缝集成并提供诸如page这样的夹具让你无需手动管理浏览器生命周期。安装pytest及相关插件pip install pytest pytest-playwright至此你的环境就准备好了。整个过程相比配置Selenium的Driver PATH要清爽太多。3.2 从“Hello World”到真实页面操作让我们写第一个脚本感受一下Playwright的流畅。基础脚本示例打开百度并搜索import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动Chromium浏览器headlessFalse表示显示界面 browser await p.chromium.launch(headlessFalse) # 创建一个新的浏览器上下文独立会话 context await browser.new_context() # 在新上下文中打开一个页面 page await context.new_page() # 导航到百度 await page.goto(‘https://www.baidu.com’) # 定位搜索框并输入关键词 await page.fill(‘input#kw’, ‘Playwright自动化测试’) # 点击“百度一下”按钮 await page.click(‘input#su’) # 等待页面导航完成例如跳转到结果页 await page.wait_for_load_state(‘networkidle’) # 等待网络基本空闲 # 截图保存用于验证或报告 await page.screenshot(path‘search_results.png’) # 获取页面标题并打印 title await page.title() print(f‘页面标题: {title}’) # 关闭浏览器 await browser.close() # 运行异步函数 asyncio.run(main())这段代码展示了最基础的流程启动浏览器 - 打开页面 - 操作元素 - 等待 - 断言/保存证据 - 清理。注意我们使用了异步API (async/await)这是Playwright推荐的方式能获得最佳性能。如果你不熟悉异步Playwright也提供了完全同步的API只需从playwright.sync_api导入即可写法更接近传统Selenium。核心API深度解析launch参数精讲headless: 默认为True。调试时设为False可以看到浏览器操作过程。slow_mo: 减慢每个操作的速度毫秒调试时非常有用可以看清操作步骤。args: 传递额外的浏览器启动参数。例如禁用沙箱args[‘--no-sandbox’]或者设置代理args[‘--proxy-serverhttp://your-proxy:8080’]。new_context上下文配置 这是Playwright的精华所在。你可以在创建上下文时预设很多条件context await browser.new_context( viewport{‘width’: 1920, ‘height’: 1080}, # 设置视口大小 user_agent‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36…’, # 设置UA locale‘zh-CN’, # 设置语言区域 timezone_id‘Asia/Shanghai’, # 设置时区 ignore_https_errorsTrue, # 忽略HTTPS证书错误测试环境常用 # 模拟地理位置和权限 geolocation{‘longitude’: 121.4737, ‘latitude’: 31.2304}, permissions[‘geolocation’] )这些配置会应用到该上下文下的所有页面完美模拟特定用户环境。页面交互APIfill和type:fill会清除输入框原有内容后输入type是模拟键盘逐个输入。绝大多数情况下用fill更高效稳定。click: 支持多种选项如button‘right’右键点击click_count2双击。wait_for_selector/wait_for_function: 虽然Playwright有自动等待但在等待特定元素出现或某个JS条件成立时这两个方法非常有用。4. 高级特性与复杂场景攻克掌握了基础我们来看看Playwright如何处理那些让传统自动化脚本头疼的“硬骨头”。4.1 处理弹窗、文件上传与下载弹窗Dialog Playwright可以监听并响应alert,confirm,prompt等原生弹窗。# 在点击可能触发弹窗的操作前先监听 page.on(‘dialog’, lambda dialog: dialog.accept()) # 自动接受确定 # 或者更精细的控制 page.on(‘dialog’, lambda dialog: print(dialog.message); dialog.dismiss()) # 打印消息并取消 await page.click(‘#btn-delete’) # 触发删除确认弹窗对于现代Web应用常见的模态框Modal通常就是一个div层直接用选择器定位其中的按钮即可。文件上传 Playwright让文件上传变得极其简单无需模拟复杂的input[type“file”]的点击操作。# 假设有一个 input type“file” id“file-input” await page.set_input_files(‘#file-input’, ‘path/to/your/file.pdf’)如果要上传多个文件传递一个列表即可。甚至你可以模拟一个空文件列表来触发清除已选文件。文件下载 监听下载事件并等待下载完成。# 启动下载监听 async with page.expect_download() as download_info: await page.click(‘a#download-link’) # 点击触发下载的链接 download await download_info.value # 下载完成后可以获取文件信息并保存到指定路径 file_path ‘/tmp/’ download.suggested_filename await download.save_as(file_path) print(f‘文件已下载到: {file_path}’)4.2 驾驭iframe、多页面与网络请求iframe Playwright把iframe视为一个独立的Frame对象。你可以轻松地获取并切换到iframe内部进行操作。# 通过iframe的name属性或选择器定位iframe元素 frame page.frame(name‘iframe-login’) # 方式一 # 或 frame_element page.query_selector(‘iframe.title-frame’) frame await frame_element.content_frame() # 方式二 # 然后在frame对象上执行操作就像操作page一样 await frame.fill(‘#username’, ‘testuser’) await frame.click(‘#submit’)多页面标签页# 监听新页面打开例如点击一个target“_blank”的链接 async with context.expect_page() as new_page_info: await page.click(‘a[target“_blank”]’) new_page await new_page_info.value # 现在你可以在新页面new_page上操作了 await new_page.bring_to_front() # 切换到该标签页 # ... 操作新页面 await new_page.close() # 关闭它拦截和修改网络请求 这个功能对于测试来说非常强大。你可以模拟慢速网络、拦截特定请求并返回模拟数据Mock或者阻塞不必要的资源如图片、广告以加快测试速度。# 路由请求拦截所有图片请求并中止以加速测试 await page.route(“**/*.{png,jpg,jpeg,webp,gif}”, lambda route: route.abort()) # 拦截一个API请求并返回自定义的JSON响应 async def handle_route(route): if “/api/userinfo” in route.request.url: await route.fulfill( status200, content_type“application/json”, bodyjson.dumps({“name”: “Mock User”, “id”: 123}) ) else: await route.continue_() # 其他请求正常继续 await page.route(“**/api/**”, handle_route)4.3 视觉回归与追踪调试视觉回归测试 Playwright Test内置了对视觉比较的支持。你可以截取页面或元素的截图并与基线图进行比较。# 在pytest中使用 def test_homepage_snapshot(page): page.goto(‘/’) expect(page).to_have_screenshot(‘homepage.png’) # 首次运行生成基线图后续运行自动对比如果UI发生了预期之外的变化测试会失败并生成差异图。这对于防止CSS改动导致布局错乱非常有效。追踪查看器 这是Playwright的“杀手级”调试工具。它可以记录测试的每一个步骤生成一个可视化的追踪文件。 运行测试时加上--tracing on参数或者在代码中启动context await browser.new_context() await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) # ... 执行你的测试操作 ... await context.tracing.stop(path“trace.zip”)然后用Playwright的命令行工具或VS Code扩展打开这个trace.zip文件你可以像看视频一样回放整个测试过程查看每一步的DOM快照、网络请求、控制台日志定位问题瞬间变得直观。5. 工程化实践从脚本到测试套件单个脚本跑得再快也无法支撑一个项目。我们需要考虑如何组织代码、管理数据、集成到CI/CD。5.1 使用Pytest进行结构化测试pytest-playwright插件提供了开箱即用的夹具。# test_login.py import pytest def test_login_success(page): # page 夹具由pytest-playwright自动注入 page.goto(‘/login’) page.fill(‘#username’, ‘standard_user’) page.fill(‘#password’, ‘secret_sauce’) page.click(‘#login-button’) # 使用Playwright自带的断言 expect(page).to_have_url(‘/inventory.html’) expect(page.locator(‘.shopping_cart_link’)).to_be_visible() def test_login_failure(page): page.goto(‘/login’) page.fill(‘#username’, ‘locked_out_user’) page.fill(‘#password’, ‘secret_sauce’) page.click(‘#login-button’) # 断言错误信息出现 expect(page.locator(‘[data-test“error”]’)).to_contain_text(‘Sorry, this user has been locked out.’)你可以使用pytest的所有功能如夹具fixture复用、参数化测试、标记mark等。创建自定义夹具# conftest.py import pytest from playwright.sync_api import Page, expect pytest.fixture(scope“session”) def browser_context_args(browser_context_args): # 全局的浏览器上下文配置 return { **browser_context_args, “viewport”: {“width”: 1920, “height”: 1080}, “ignore_https_errors”: True, } pytest.fixture def login_page(page: Page): # 封装登录逻辑供多个测试用例使用 page.goto(‘/login’) page.fill(‘#username’, ‘standard_user’) page.fill(‘#password’, ‘secret_sauce’) page.click(‘#login-button’) expect(page).to_have_url(‘/inventory.html’) return page5.2 数据驱动与配置管理测试数据如用户名、商品ID和运行配置如测试环境URL、超时时间不应硬编码在脚本里。使用pytest.mark.parametrize进行数据驱动import pytest login_test_data [ (“standard_user”, “secret_sauce”, True), (“locked_out_user”, “secret_sauce”, False, “locked out”), (“invalid_user”, “wrong_pwd”, False, “username and password do not match”), ] pytest.mark.parametrize(“username,password,success,error_msg”, login_test_data) def test_login_with_data(page, username, password, success, error_msg): page.goto(‘/login’) page.fill(‘#username’, username) page.fill(‘#password’, password) page.click(‘#login-button’) if success: expect(page).to_have_url(‘/inventory.html’) else: expect(page.locator(‘[data-test“error”]’)).to_contain_text(error_msg)使用pytest.ini或pytest命令行参数管理配置# pytest.ini [pytest] addopts --base-url https://demo.test.com --headed # 调试时打开浏览器 --slowmo 100 --tracing on在代码中通过pytest的内置request夹具或自定义夹具来读取这些配置。5.3 CI/CD集成与测试报告在CI如GitLab CI, GitHub Actions, Jenkins中运行Playwright测试关键是处理好浏览器依赖和生成可读的报告。GitHub Actions 示例# .github/workflows/playwright.yml name: Playwright Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-pythonv4 with: python-version: ‘3.10’ - name: Install dependencies run: | pip install -r requirements.txt playwright install --with-deps chromium # 只安装CI需要的浏览器加速 - name: Run your tests run: pytest --base-url ${{ secrets.TEST_ENV_URL }} --htmlreport.html --self-contained-html - name: Upload test report uses: actions/upload-artifactv3 if: always() with: name: playwright-report path: | report.html test-results/这里我们使用了pytest-html插件生成漂亮的HTML报告。Playwright Test也自带一个更强大的HTML报告器可以通过--reporterhtml启用。6. 避坑指南与性能优化在实际项目中摸爬滚打我积累了一些宝贵的经验和教训。6.1 常见问题与排查技巧元素定位失败问题脚本报错找不到元素。排查使用Playwright Inspector运行脚本时设置环境变量PWDEBUG1或使用playwright codegen命令生成定位代码它会启动一个带拾取器的浏览器。检查是否在iframe内。检查元素是否有动态生成的属性如>await page.click(‘#submit-order’) # 等待订单成功提示出现 await page.wait_for_selector(‘text订单提交成功’, state‘visible’, timeout10000) # 或者等待某个API请求完成 async with page.expect_response(‘**/api/order/**’) as response_info: await page.click(‘#submit-order’) response await response_info.value assert response.ok6.2 性能优化与最佳实践复用浏览器上下文启动和关闭浏览器是昂贵的操作。在测试套件级别scope“session”启动浏览器在每个测试用例级别创建新的上下文和页面。上下文是轻量且隔离的这能极大提升测试速度。并行执行pytest可以很方便地通过pytest-xdist插件实现并行测试。Playwright支持多浏览器上下文并行运行只要确保测试用例之间没有状态依赖这本身就是良好测试设计的要求。选择性安装浏览器在CI环境中如果只测试Chrome就只安装Chromium可以显著缩短流水线准备时间。善用追踪和视频对于偶发性的失败开启--tracing on和--video onPlaywright Test支持是定位问题的终极武器。虽然会消耗更多磁盘空间和性能但在调试阶段非常值得。选择器策略优先使用get_by_role,get_by_text,get_by_test_id等语义化定位器。它们比脆弱的CSS选择器如.btn-primary:nth-child(2)更稳定。与开发团队约定使用>
从Selenium到Playwright:构建稳定高效的跨浏览器自动化测试实战
发布时间:2026/7/1 23:33:02
1. 项目概述为什么是Playwright如果你做过Web自动化测试大概率用过Selenium。它很经典但痛点也明显脚本不稳定经常因为元素加载慢、网络延迟而失败跨浏览器支持需要下载不同的驱动版本管理是个噩梦处理弹窗、文件上传、iframe这些复杂场景代码写起来又臭又长。我团队之前维护一个大型电商项目的测试脚本光是处理各种“等待”和“异常捕获”的代码就占了总代码量的三分之一调试时间比写代码还长。后来我们接触到了Playwright它来自微软是一个相对较新的Node.js/Python/.NET/Java库用于实现Web自动化。一开始我也持怀疑态度但用了一个月后我们决定把核心的UI自动化测试全部迁移到Playwright上。它最吸引我的点用一个词概括就是“可靠”。它内置了智能等待能自动等待元素可操作它提供了强大的选择器引擎能穿透Shadow DOM它对现代Web API如文件系统访问、地理位置模拟的支持是原生的。更重要的是它用一个API统一了Chromium、Firefox和WebKitSafari三大浏览器引擎的自动化这意味着你写一套脚本可以无痛地在三种浏览器上运行这对于确保跨浏览器兼容性来说效率提升是颠覆性的。这个项目我就想以一个过来人的身份和你深入聊聊如何用Python版的Playwright这把“利器”来构建稳定、高效的跨浏览器自动化测试。无论你是从零开始的新手还是从Selenium转型的老兵我相信其中的一些设计思路和实战技巧都能让你少走弯路。2. 核心设计Playwright的“三板斧”与选型考量选择任何一个工具理解其设计哲学比死记API更重要。Playwright的成功很大程度上源于它精准地解决了传统自动化工具的几大顽疾。2.1 架构革新浏览器上下文与自动化频道Playwright没有采用Selenium WebDriver那种基于JSON Wire Protocol的远程控制模式。相反它通过一个称为“自动化频道”的协议直接与浏览器进程通信。这带来了两个直接好处速度更快和能力更强。更关键的是它的“浏览器上下文”概念。你可以把它理解为一个完全独立的浏览器会话它拥有独立的cookie、本地存储、缓存甚至代理设置。这意味着你可以在一个浏览器实例中轻松创建多个互不干扰的“隐身”会话来模拟多个用户同时操作或者进行A/B测试场景的隔离测试。这在测试需要登录态或多账户交互的应用时比反复启动关闭浏览器要高效和稳定得多。为什么选PythonPlaywright支持多种语言我选择Python是因为它在测试领域生态成熟pytest, unittest在数据驱动、参数化测试方面写法优雅并且团队成员的学习成本相对较低。对于需要与CI/CD如Jenkins, GitLab CI深度集成、进行复杂测试数据准备的场景Python丰富的库支持是巨大优势。当然如果你的团队主力是Node.js或JavaPlaywright同样提供一流的支持。2.2 统一API与浏览器引擎管理这是Playwright宣称“跨浏览器”的底气。它通过playwright.chromium,playwright.firefox,playwright.webkit三个对象来分别启动对应内核的浏览器。但神奇的是除了极少数浏览器特有的API你的脚本代码几乎不需要为不同浏览器做特殊修改。安装时Playwright会通过playwright install命令一次性下载所有需要的浏览器二进制文件并管理好版本。你再也不需要去搜“ChromeDriver版本与Chrome浏览器版本对应表”了也告别了因驱动版本不匹配导致的脚本突然崩溃。这种“开箱即用”的体验对于团队协作和CI环境搭建来说省去了大量运维成本。注意虽然API统一但浏览器引擎本身的差异是客观存在的。例如WebKitSafari对某些CSS属性或JavaScript特性的支持可能与其他两者不同。Playwright的统一API保证了“能执行”但最终渲染或行为是否一致仍需通过实际运行测试来验证。这正是跨浏览器测试的意义所在。2.3 强大的选择器与自动等待机制元素定位是自动化脚本的基石也是脚本脆弱的根源。Playwright提供了多种定位策略其中最强力的是text和css选择器的增强版。文本选择器page.click(‘text登录’)。它会查找页面中所有包含“登录”文本的元素非常直观。它还支持正则表达式如text/Log\s*in/i。CSS选择器增强你可以使用组合选择器或者使用:has()、:is()等现代CSS伪类进行非常精细的定位。例如定位一个包含特定文本的div下的按钮page.click(‘div:has-text(“商品列表”) button’)。但比选择器更重要的是其自动等待。Playwright在执行如click,fill,check等操作前会自动执行一系列可操作性检查元素是否附着在DOM上。元素是否可见。元素是否稳定例如不再有动画效果。元素是否可交互如未被其他元素遮挡。元素是否启用。只有所有这些条件都满足操作才会执行。这几乎消除了因页面加载或动画未完成导致的“ElementNotInteractableException”错误。你仍然可以显式等待但大多数情况下你不再需要了。3. 环境搭建与核心API实战理论说再多不如动手跑一遍。我们来搭建一个可复用的测试环境并剖析几个最核心的API。3.1 一站式环境配置指南假设你已经安装了Python3.7接下来打开你的终端或命令行。第一步安装Playwright Python包pip install playwright这个命令会安装Playwright的核心Python库。第二步安装浏览器二进制文件安装完库后你需要下载浏览器。Playwright提供了一个非常方便的命令行工具playwright install这条命令会默认下载Chromium、Firefox和WebKit的最新稳定版本。如果你只想安装其中某一个可以指定playwright install chromium # 只安装Chromium playwright install firefox # 只安装Firefox playwright install webkit # 只安装WebKit所有浏览器都会被安装到用户目录下的一个缓存文件夹中与系统全局安装的浏览器互不干扰。第三步选择你的IDE和测试框架我强烈推荐使用VS Code并安装官方的“Playwright Test for VSCode”扩展。这个扩展提供了测试列表、代码透镜在代码上方直接运行测试、追踪查看器可视化回放测试过程等强大功能。 测试框架首选pytest。Playwright官方也推荐使用pytest-playwright插件它能无缝集成并提供诸如page这样的夹具让你无需手动管理浏览器生命周期。安装pytest及相关插件pip install pytest pytest-playwright至此你的环境就准备好了。整个过程相比配置Selenium的Driver PATH要清爽太多。3.2 从“Hello World”到真实页面操作让我们写第一个脚本感受一下Playwright的流畅。基础脚本示例打开百度并搜索import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动Chromium浏览器headlessFalse表示显示界面 browser await p.chromium.launch(headlessFalse) # 创建一个新的浏览器上下文独立会话 context await browser.new_context() # 在新上下文中打开一个页面 page await context.new_page() # 导航到百度 await page.goto(‘https://www.baidu.com’) # 定位搜索框并输入关键词 await page.fill(‘input#kw’, ‘Playwright自动化测试’) # 点击“百度一下”按钮 await page.click(‘input#su’) # 等待页面导航完成例如跳转到结果页 await page.wait_for_load_state(‘networkidle’) # 等待网络基本空闲 # 截图保存用于验证或报告 await page.screenshot(path‘search_results.png’) # 获取页面标题并打印 title await page.title() print(f‘页面标题: {title}’) # 关闭浏览器 await browser.close() # 运行异步函数 asyncio.run(main())这段代码展示了最基础的流程启动浏览器 - 打开页面 - 操作元素 - 等待 - 断言/保存证据 - 清理。注意我们使用了异步API (async/await)这是Playwright推荐的方式能获得最佳性能。如果你不熟悉异步Playwright也提供了完全同步的API只需从playwright.sync_api导入即可写法更接近传统Selenium。核心API深度解析launch参数精讲headless: 默认为True。调试时设为False可以看到浏览器操作过程。slow_mo: 减慢每个操作的速度毫秒调试时非常有用可以看清操作步骤。args: 传递额外的浏览器启动参数。例如禁用沙箱args[‘--no-sandbox’]或者设置代理args[‘--proxy-serverhttp://your-proxy:8080’]。new_context上下文配置 这是Playwright的精华所在。你可以在创建上下文时预设很多条件context await browser.new_context( viewport{‘width’: 1920, ‘height’: 1080}, # 设置视口大小 user_agent‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36…’, # 设置UA locale‘zh-CN’, # 设置语言区域 timezone_id‘Asia/Shanghai’, # 设置时区 ignore_https_errorsTrue, # 忽略HTTPS证书错误测试环境常用 # 模拟地理位置和权限 geolocation{‘longitude’: 121.4737, ‘latitude’: 31.2304}, permissions[‘geolocation’] )这些配置会应用到该上下文下的所有页面完美模拟特定用户环境。页面交互APIfill和type:fill会清除输入框原有内容后输入type是模拟键盘逐个输入。绝大多数情况下用fill更高效稳定。click: 支持多种选项如button‘right’右键点击click_count2双击。wait_for_selector/wait_for_function: 虽然Playwright有自动等待但在等待特定元素出现或某个JS条件成立时这两个方法非常有用。4. 高级特性与复杂场景攻克掌握了基础我们来看看Playwright如何处理那些让传统自动化脚本头疼的“硬骨头”。4.1 处理弹窗、文件上传与下载弹窗Dialog Playwright可以监听并响应alert,confirm,prompt等原生弹窗。# 在点击可能触发弹窗的操作前先监听 page.on(‘dialog’, lambda dialog: dialog.accept()) # 自动接受确定 # 或者更精细的控制 page.on(‘dialog’, lambda dialog: print(dialog.message); dialog.dismiss()) # 打印消息并取消 await page.click(‘#btn-delete’) # 触发删除确认弹窗对于现代Web应用常见的模态框Modal通常就是一个div层直接用选择器定位其中的按钮即可。文件上传 Playwright让文件上传变得极其简单无需模拟复杂的input[type“file”]的点击操作。# 假设有一个 input type“file” id“file-input” await page.set_input_files(‘#file-input’, ‘path/to/your/file.pdf’)如果要上传多个文件传递一个列表即可。甚至你可以模拟一个空文件列表来触发清除已选文件。文件下载 监听下载事件并等待下载完成。# 启动下载监听 async with page.expect_download() as download_info: await page.click(‘a#download-link’) # 点击触发下载的链接 download await download_info.value # 下载完成后可以获取文件信息并保存到指定路径 file_path ‘/tmp/’ download.suggested_filename await download.save_as(file_path) print(f‘文件已下载到: {file_path}’)4.2 驾驭iframe、多页面与网络请求iframe Playwright把iframe视为一个独立的Frame对象。你可以轻松地获取并切换到iframe内部进行操作。# 通过iframe的name属性或选择器定位iframe元素 frame page.frame(name‘iframe-login’) # 方式一 # 或 frame_element page.query_selector(‘iframe.title-frame’) frame await frame_element.content_frame() # 方式二 # 然后在frame对象上执行操作就像操作page一样 await frame.fill(‘#username’, ‘testuser’) await frame.click(‘#submit’)多页面标签页# 监听新页面打开例如点击一个target“_blank”的链接 async with context.expect_page() as new_page_info: await page.click(‘a[target“_blank”]’) new_page await new_page_info.value # 现在你可以在新页面new_page上操作了 await new_page.bring_to_front() # 切换到该标签页 # ... 操作新页面 await new_page.close() # 关闭它拦截和修改网络请求 这个功能对于测试来说非常强大。你可以模拟慢速网络、拦截特定请求并返回模拟数据Mock或者阻塞不必要的资源如图片、广告以加快测试速度。# 路由请求拦截所有图片请求并中止以加速测试 await page.route(“**/*.{png,jpg,jpeg,webp,gif}”, lambda route: route.abort()) # 拦截一个API请求并返回自定义的JSON响应 async def handle_route(route): if “/api/userinfo” in route.request.url: await route.fulfill( status200, content_type“application/json”, bodyjson.dumps({“name”: “Mock User”, “id”: 123}) ) else: await route.continue_() # 其他请求正常继续 await page.route(“**/api/**”, handle_route)4.3 视觉回归与追踪调试视觉回归测试 Playwright Test内置了对视觉比较的支持。你可以截取页面或元素的截图并与基线图进行比较。# 在pytest中使用 def test_homepage_snapshot(page): page.goto(‘/’) expect(page).to_have_screenshot(‘homepage.png’) # 首次运行生成基线图后续运行自动对比如果UI发生了预期之外的变化测试会失败并生成差异图。这对于防止CSS改动导致布局错乱非常有效。追踪查看器 这是Playwright的“杀手级”调试工具。它可以记录测试的每一个步骤生成一个可视化的追踪文件。 运行测试时加上--tracing on参数或者在代码中启动context await browser.new_context() await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) # ... 执行你的测试操作 ... await context.tracing.stop(path“trace.zip”)然后用Playwright的命令行工具或VS Code扩展打开这个trace.zip文件你可以像看视频一样回放整个测试过程查看每一步的DOM快照、网络请求、控制台日志定位问题瞬间变得直观。5. 工程化实践从脚本到测试套件单个脚本跑得再快也无法支撑一个项目。我们需要考虑如何组织代码、管理数据、集成到CI/CD。5.1 使用Pytest进行结构化测试pytest-playwright插件提供了开箱即用的夹具。# test_login.py import pytest def test_login_success(page): # page 夹具由pytest-playwright自动注入 page.goto(‘/login’) page.fill(‘#username’, ‘standard_user’) page.fill(‘#password’, ‘secret_sauce’) page.click(‘#login-button’) # 使用Playwright自带的断言 expect(page).to_have_url(‘/inventory.html’) expect(page.locator(‘.shopping_cart_link’)).to_be_visible() def test_login_failure(page): page.goto(‘/login’) page.fill(‘#username’, ‘locked_out_user’) page.fill(‘#password’, ‘secret_sauce’) page.click(‘#login-button’) # 断言错误信息出现 expect(page.locator(‘[data-test“error”]’)).to_contain_text(‘Sorry, this user has been locked out.’)你可以使用pytest的所有功能如夹具fixture复用、参数化测试、标记mark等。创建自定义夹具# conftest.py import pytest from playwright.sync_api import Page, expect pytest.fixture(scope“session”) def browser_context_args(browser_context_args): # 全局的浏览器上下文配置 return { **browser_context_args, “viewport”: {“width”: 1920, “height”: 1080}, “ignore_https_errors”: True, } pytest.fixture def login_page(page: Page): # 封装登录逻辑供多个测试用例使用 page.goto(‘/login’) page.fill(‘#username’, ‘standard_user’) page.fill(‘#password’, ‘secret_sauce’) page.click(‘#login-button’) expect(page).to_have_url(‘/inventory.html’) return page5.2 数据驱动与配置管理测试数据如用户名、商品ID和运行配置如测试环境URL、超时时间不应硬编码在脚本里。使用pytest.mark.parametrize进行数据驱动import pytest login_test_data [ (“standard_user”, “secret_sauce”, True), (“locked_out_user”, “secret_sauce”, False, “locked out”), (“invalid_user”, “wrong_pwd”, False, “username and password do not match”), ] pytest.mark.parametrize(“username,password,success,error_msg”, login_test_data) def test_login_with_data(page, username, password, success, error_msg): page.goto(‘/login’) page.fill(‘#username’, username) page.fill(‘#password’, password) page.click(‘#login-button’) if success: expect(page).to_have_url(‘/inventory.html’) else: expect(page.locator(‘[data-test“error”]’)).to_contain_text(error_msg)使用pytest.ini或pytest命令行参数管理配置# pytest.ini [pytest] addopts --base-url https://demo.test.com --headed # 调试时打开浏览器 --slowmo 100 --tracing on在代码中通过pytest的内置request夹具或自定义夹具来读取这些配置。5.3 CI/CD集成与测试报告在CI如GitLab CI, GitHub Actions, Jenkins中运行Playwright测试关键是处理好浏览器依赖和生成可读的报告。GitHub Actions 示例# .github/workflows/playwright.yml name: Playwright Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-pythonv4 with: python-version: ‘3.10’ - name: Install dependencies run: | pip install -r requirements.txt playwright install --with-deps chromium # 只安装CI需要的浏览器加速 - name: Run your tests run: pytest --base-url ${{ secrets.TEST_ENV_URL }} --htmlreport.html --self-contained-html - name: Upload test report uses: actions/upload-artifactv3 if: always() with: name: playwright-report path: | report.html test-results/这里我们使用了pytest-html插件生成漂亮的HTML报告。Playwright Test也自带一个更强大的HTML报告器可以通过--reporterhtml启用。6. 避坑指南与性能优化在实际项目中摸爬滚打我积累了一些宝贵的经验和教训。6.1 常见问题与排查技巧元素定位失败问题脚本报错找不到元素。排查使用Playwright Inspector运行脚本时设置环境变量PWDEBUG1或使用playwright codegen命令生成定位代码它会启动一个带拾取器的浏览器。检查是否在iframe内。检查元素是否有动态生成的属性如>await page.click(‘#submit-order’) # 等待订单成功提示出现 await page.wait_for_selector(‘text订单提交成功’, state‘visible’, timeout10000) # 或者等待某个API请求完成 async with page.expect_response(‘**/api/order/**’) as response_info: await page.click(‘#submit-order’) response await response_info.value assert response.ok6.2 性能优化与最佳实践复用浏览器上下文启动和关闭浏览器是昂贵的操作。在测试套件级别scope“session”启动浏览器在每个测试用例级别创建新的上下文和页面。上下文是轻量且隔离的这能极大提升测试速度。并行执行pytest可以很方便地通过pytest-xdist插件实现并行测试。Playwright支持多浏览器上下文并行运行只要确保测试用例之间没有状态依赖这本身就是良好测试设计的要求。选择性安装浏览器在CI环境中如果只测试Chrome就只安装Chromium可以显著缩短流水线准备时间。善用追踪和视频对于偶发性的失败开启--tracing on和--video onPlaywright Test支持是定位问题的终极武器。虽然会消耗更多磁盘空间和性能但在调试阶段非常值得。选择器策略优先使用get_by_role,get_by_text,get_by_test_id等语义化定位器。它们比脆弱的CSS选择器如.btn-primary:nth-child(2)更稳定。与开发团队约定使用>