1. 项目概述为什么选择 Python Playwright 作为你的 Web 自动化测试起点如果你正在为 Web 应用的回归测试、兼容性验证或者重复性操作而烦恼手动点点点不仅效率低下还容易出错。自动化测试是解决这个问题的标准答案但面对 Selenium、Cypress、Puppeteer 等众多框架新手往往会感到迷茫。今天我想和你深入聊聊Python Playwright这个组合它是我在经历了多个项目后认为目前最适合快速上手、功能强大且维护成本较低的 Web 自动化方案。简单来说Playwright 是一个由微软开源的现代化浏览器自动化库。它支持 Chromium、Firefox 和 WebKitSafari 的渲染引擎三大浏览器引擎这意味着你可以用一套脚本测试你的网站在 Chrome、Edge、Firefox 和 Safari 上的表现对于前端兼容性测试来说简直是神器。而 Python以其简洁优雅的语法和庞大的生态极大地降低了自动化脚本的编写和维护门槛。这个组合的核心价值在于“用开发者友好的方式实现稳定、快速且覆盖全面的浏览器自动化操作”。我最初从 Selenium 转向 Playwright最直接的动力是它解决了几个长期痛点稳定性、执行速度和强大的内置能力。Selenium 需要依赖浏览器驱动版本匹配是个头疼事而 Playwright 通过自带浏览器二进制文件实现了开箱即用。它的自动等待机制Auto-waiting能智能等待元素可操作状态避免了在脚本中到处写time.sleep的尴尬让脚本更加健壮。无论是前端开发者想验证页面交互还是测试工程师需要构建自动化测试套件甚至是运营同学想自动化一些数据抓取流程Python Playwright 都是一个值得投入时间学习的利器。2. 环境搭建与核心工具链配置工欲善其事必先利其器。一个顺畅的起步环境能避免很多后续的坑。这里我会详细拆解从零开始搭建 Python Playwright 开发环境的每一步并解释每个选择背后的原因。2.1 Python 环境安装与配置Python 是这一切的基础。对于自动化测试我强烈建议使用Python 3.8 及以上的版本以确保对 Playwright 等新库的良好支持。安装方式选择官方安装包从 Python 官网下载安装是最直接的方式。安装时务必勾选“Add Python to PATH”选项这能让你在命令行中直接使用python和pip命令避免后续配置环境变量的麻烦。使用 Miniconda/Anaconda如果你需要管理多个相互隔离的 Python 项目环境比如一个项目用 Python 3.8另一个用 3.11Conda 是一个更优秀的选择。它创建的是独立的虚拟环境库的依赖不会互相冲突。对于自动化测试项目我通常推荐使用Miniconda它更轻量。验证安装打开你的终端Windows 上是 CMD 或 PowerShellmacOS/Linux 上是 Terminal输入以下命令python --version # 或 python3 --version如果正确显示版本号如Python 3.10.11说明安装成功。同时检查 pipPython 包管理工具pip --version注意在 macOS 和部分 Linux 系统上系统自带的 Python 2 可能仍占用了python命令。确保你安装的 Python 3 可以通过python3调用。为了统一在本文后续命令中我将使用python和pip如果你的环境是python3和pip3请自行替换。2.2 集成开发环境IDE的选择与配置写 Python 脚本一个好用的 IDE 能事半功倍。我的首选是Visual Studio Code (VS Code)因为它免费、轻量、插件生态丰富对 Python 和 Playwright 的支持都非常好。VS Code 必备插件配置Python微软官方出品提供代码补全、智能提示、调试、linting 等核心功能。Pylance强大的语言服务器比默认的 Jedi 提供更快的补全和类型检查。Playwright Test for VSCodePlaywright 官方插件提供测试列表、运行、调试的图形化界面非常方便。Code Runner可以快速运行当前文件或选中的代码片段。安装好 Python 插件后在 VS Code 中按CtrlShiftPWindows/Linux或CmdShiftPmacOS输入 “Python: Select Interpreter”选择你刚安装的 Python 解释器。这样VS Code 就会基于这个解释器来提供智能提示和运行环境。2.3 Playwright 库的安装与浏览器部署环境准备好后就可以安装 Playwright 了。Playwright 的安装分为两部分Python 库本身和它需要操控的浏览器。安装 Playwright Python 库在你的项目目录下打开终端执行pip install playwright这个命令会从 PyPI 下载并安装playwright这个 Python 包。安装浏览器二进制文件Playwright 的强大之处在于它自带了经过优化和测试的浏览器版本确保了自动化环境的稳定性。安装完库后需要安装这些浏览器playwright install这个命令会下载 Chromium、Firefox 和 WebKit 的最新稳定版浏览器到你的本地缓存中。这个过程可能会花费一些时间因为需要下载几百MB的数据。实操心得playwright install默认会安装所有支持的浏览器。如果你确定只使用 Chromium这是最常见的选择兼容 Chrome 和 Edge可以使用playwright install chromium来只安装它以节省时间和磁盘空间。如果遇到下载慢或失败的情况特别是在国内网络环境可以尝试设置环境变量来使用镜像源例如在终端中先执行set PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwrightWindows或export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwrightmacOS/Linux然后再运行安装命令。至此你的核心开发环境就已经搭建完毕。你可以通过一个简单的脚本来验证一切是否正常。3. Playwright 核心概念与快速入门脚本解析在开始编写复杂的测试用例之前我们必须理解 Playwright 的几个核心抽象。这就像学开车先要了解方向盘、油门和刹车一样。3.1 核心对象模型Browser, Context, PagePlaywright 的操作围绕三个核心对象展开它们的关系是层层包含的Browser代表一个浏览器实例。你可以把它想象成一个完整的浏览器程序比如你桌面上的 Chrome 图标。通过 Playwright你可以启动launch一个浏览器可以是有头模式能看到界面或无头模式后台运行。Context浏览器上下文。这是Playwright 中一个非常强大且重要的概念。你可以把它理解为一个独立的“隐身会话”或“用户档案”。每个 Context 拥有独立的 cookies、本地存储、缓存和权限设置。这意味着你可以在一个脚本中轻松模拟多个用户同时登录同一个网站或者隔离不同的测试场景而不会互相干扰。Page页面。一个 Context 中可以包含多个 Page标签页。Page 对象代表一个具体的网页我们绝大部分的操作如点击、输入、获取文本都是在 Page 对象上完成的。它们的关系是Browser- 创建多个Context- 每个Context包含多个Page。3.2 你的第一个自动化脚本打开网页并截图理论说再多不如动手试一下。让我们创建一个最简单的脚本体验 Playwright 的基本工作流程。创建一个新文件命名为first_script.py输入以下代码import asyncio from playwright.async_api import async_playwright async def main(): # 1. 启动 Playwright管理浏览器生命周期 async with async_playwright() as p: # 2. 启动一个 Chromium 浏览器实例无头模式 browser await p.chromium.launch(headlessFalse) # 设置为 True 则无头运行 # 3. 创建一个新的浏览器上下文 context await browser.new_context() # 4. 在上下文中打开一个新页面 page await context.new_page() # 5. 导航到目标网址 await page.goto(https://www.example.com) # 6. 等待页面加载goto 本身会等待网络空闲这里额外等待一下可能动态加载的内容 await page.wait_for_load_state(networkidle) # 7. 对页面进行截图 await page.screenshot(pathexample.png, full_pageTrue) print(截图已保存为 example.png) # 8. 关闭浏览器 await browser.close() # 运行主函数 asyncio.run(main())逐行解析与注意事项async with async_playwright() as p:这是 Playwright 推荐的异步 API 使用方式。async with语句确保在代码块结束后Playwright 的资源会被正确清理。p对象是你的入口点通过它可以访问不同浏览器类型p.chromium,p.firefox,p.webkit。p.chromium.launch(headlessFalse)启动一个 Chromium 浏览器。headlessFalse意味着你将看到一个真实的浏览器窗口打开并执行操作这对于调试脚本非常有用。在生产环境或追求速度时应设置为True。browser.new_context()创建了一个独立的上下文。在这个简单的例子里我们暂时用不到上下文的隔离特性但养成创建 Context 的习惯是好的实践。page.goto()导航到指定 URL。这个方法会自动等待页面触发load事件。page.wait_for_load_state(networkidle)这是一个更严格的等待。networkidle表示在至少 500 毫秒内没有超过 2 个网络连接时才认为页面加载完成。这对于等待 AJAX 请求或动态加载的内容非常有效。page.screenshot()截图功能。full_pageTrue会截取整个可滚动页面的长图而不仅仅是首屏。await browser.close()关闭浏览器释放资源。虽然在async with块结束后会自动关闭但显式调用是一个好习惯。运行这个脚本 (python first_script.py)你会看到一个浏览器窗口打开访问 example.com然后截图保存最后关闭。恭喜你已经完成了第一次 Web 自动化实操心得Playwright 提供了同步和异步两套 API。上面的例子是异步 API (async/await)。对于简单的线性脚本你也可以使用同步 API代码更直观。只需导入from playwright.sync_api import sync_playwright并去掉所有的async和await关键字使用with sync_playwright() as p:即可。我个人的建议是如果你是新手或者脚本逻辑不复杂可以从同步 API 开始更容易理解。但异步 API 在并发执行多个浏览器操作时性能优势明显值得后续学习。4. 元素定位与交互自动化操作的基石自动化测试的核心是模拟人的操作找到页面上的元素按钮、输入框、链接然后与之交互点击、输入、悬停。Playwright 提供了多种强大且灵活的元素定位器Locators。4.1 定位器Locator详解与最佳实践定位器是 Playwright 中用于查找和操作元素的抽象。创建定位器不会立即执行查找只有在执行操作如click()时才会真正去页面上定位元素这种“惰性”设计提高了脚本的健壮性。最常用的定位策略按文本定位这是最直观的方式之一。# 点击文本内容为“登录”的元素可以是按钮、链接、div等 await page.locator(text登录).click() # 更精确的完全匹配 await page.locator(textSign in).click()按 CSS 选择器定位这是最强大、最通用的方式如果你熟悉 CSS会非常得心应手。# 通过 ID await page.locator(#username).fill(myuser) # 通过 Class await page.locator(.submit-btn).click() # 通过属性 await page.locator([data-testidlogin-button]).click() # 组合选择器 await page.locator(div.header nav a:has-text(Home)).click()按 XPath 定位XPath 功能强大但表达式可能复杂在 CSS 选择器无法满足复杂层级关系时使用。await page.locator(//button[idsubmit]).click()按角色Role定位这是 Playwright 推荐的一种语义化定位方式特别适合可访问性ARIA良好的现代 Web 应用。# 定位一个名为“Username”的文本框 await page.get_by_role(textbox, nameUsername).fill(test) # 定位一个按钮 await page.get_by_role(button, nameSubmit).click() # 定位一个链接 await page.get_by_role(link, namePrivacy Policy).click()最佳实践与避坑指南优先使用get_by_role,get_by_text,get_by_label这些 API 更具语义化与用户感知方式一致且通常比脆弱的 CSS 选择器更稳定。例如前端修改了样式类名你的get_by_role(‘button’, name‘Submit’)依然有效但.btn-primary可能就失效了。避免使用绝对 XPath像/html/body/div[3]/div[2]/button这样的 XPath 极其脆弱页面结构稍有变动就会失败。利用># 先找到表格再在表格里找包含特定文本的行 row page.locator(table).locator(tr, has_textJohn Doe) # 使用 filter 进行条件过滤 enabled_button page.locator(button).filter(has_textSave).and_(enabledTrue)4.2 核心交互操作点击、输入、选择定位到元素后就可以进行交互了。以下是最常用的操作点击与双击await page.locator(button#submit).click() # 带选项的点击例如强制点击即使用元素被遮挡 await page.locator(button).click(forceTrue) # 双击 await page.locator(canvas).dblclick()输入与清空# 在输入框填充文本会先清空原有内容 await page.locator(#email).fill(userexample.com) # 模拟逐个字符输入更接近用户真实行为会触发键盘事件 await page.locator(#search).type(Playwright automation, delay100) # delay 模拟输入间隔 # 清空输入框 await page.locator(#comment).clear()下拉框选择# 通过 value 选择 await page.locator(select#country).select_option(valueus) # 通过标签文本选择 await page.locator(select#country).select_option(labelUnited States) # 多选 await page.locator(select#colors).select_option(value[red, blue])复选框与单选框# 勾选复选框 await page.locator(input[typecheckbox]).check() # 取消勾选 await page.locator(input[typecheckbox]).uncheck() # 判断是否已勾选 is_checked await page.locator(input[typecheckbox]).is_checked() # 选择单选框 await page.locator(input[typeradio][valueoption1]).check()文件上传Playwright 处理文件上传非常简洁不需要模拟复杂的点击文件选择对话框的操作。# 设置文件输入框的值文件路径 await page.locator(input[typefile]).set_input_files(/path/to/myfile.pdf) # 上传多个文件 await page.locator(input[typefile]).set_input_files([file1.pdf, file2.jpg]) # 清空已选文件 await page.locator(input[typefile]).set_input_files([])悬停Hover# 鼠标悬停在元素上常用于触发下拉菜单或工具提示 await page.locator(.menu-item).hover()注意事项Playwright 的交互操作如click,fill内置了自动等待。它会等待元素满足可操作条件如可见、启用、稳定未动画后才执行操作。这意味着你通常不需要在操作前手动写等待这极大地简化了脚本并提高了稳定性。但如果遇到特殊场景如自定义控件你可能需要结合wait_for_selector或wait_for_function使用。5. 等待策略编写稳定自动化脚本的关键自动化脚本不稳定的一大元凶就是“时机不对”——脚本执行速度远快于页面加载或元素渲染速度。Playwright 通过一套智能的等待机制从根本上解决了这个问题。5.1 自动等待Auto-waiting机制这是 Playwright 最令人称道的特性之一。之前提到的click(),fill(),check()等操作在内部都执行了一系列检查确保元素可交互元素被附加到 DOM元素可见非隐藏、非display:none、非visibility:hidden、宽高大于0元素启用非disabled元素稳定例如CSS 过渡动画结束只有所有这些条件都满足操作才会执行。如果条件不满足Playwright 会重试直到超时默认 30 秒。这意味着你大多数情况下不需要写显式的sleep。5.2 显式等待应对复杂场景尽管自动等待很强大但在一些异步加载内容、动态元素出现的复杂场景下我们仍需要显式地告诉 Playwright“请等待某个特定条件发生”。1. 等待元素出现/可见/隐藏# 等待选择器匹配的元素出现在 DOM 中 await page.wait_for_selector(.loading-spinner, statehidden) # 等待加载动画消失 # 等待元素可见 await page.wait_for_selector(#success-message, statevisible) # 等待元素被从 DOM 中移除 await page.wait_for_selector(#old-element, statedetached)2. 等待导航# 在点击一个会导致页面跳转的链接前可以等待导航完成 async with page.expect_navigation(): await page.locator(a#next-page).click() # 或者等待特定的 URL await page.wait_for_url(**/dashboard)3. 等待页面事件# 等待页面加载完成load 事件 await page.wait_for_load_state(load) # 等待网络几乎空闲推荐用于单页应用 SPA await page.wait_for_load_state(networkidle) # 等待 DOM 内容加载完成DOMContentLoaded 事件 await page.wait_for_load_state(domcontentloaded)4. 等待自定义条件最灵活# 等待某个函数在页面上下文中返回真值 await page.wait_for_function( () { const el document.querySelector(.item-count); return el parseInt(el.textContent) 10; } ) # 或者使用 Python 函数通过传递参数 def wait_for_item_count(expected): # 这个函数会在浏览器环境中执行 selector .item-count element document.querySelector(selector) return element parseInt(element.textContent) expected await page.wait_for_function(wait_for_item_count, 10) # 等待数量 105. 等待超时设置所有等待方法都可以设置超时时间毫秒。try: # 只等待 5 秒 await page.wait_for_selector(.popup, statevisible, timeout5000) except TimeoutError: print(弹出框没有在5秒内出现)实操心得我的经验法则是优先依赖操作的自动等待仅在需要等待非交互性状态变化如元素出现、消失、文本变化或导航时才使用显式等待。避免滥用page.wait_for_timeout(3000)这种固定休眠它会让测试变慢且不可靠网络或机器性能差异可能导致3秒不够或浪费。使用基于条件的等待你的脚本才能在任何速度下都稳定运行。6. 高级特性与实战技巧掌握了基础操作和等待策略你已经能完成大部分自动化任务。接下来我们探讨一些高级特性和实战技巧让你的脚本更强大、更健壮、更易维护。6.1 处理弹窗、新窗口与 iframe对话框Alert, Confirm, PromptPlaywright 可以监听并响应 JavaScript 原生的对话框。# 监听对话框事件并在触发时接受点击“确定” page.on(dialog, lambda dialog: dialog.accept()) await page.locator(button#delete).click() # 点击会触发 confirm 对话框 # 更精细的控制获取对话框消息并选择操作 def handle_dialog(dialog): print(f对话框消息: {dialog.message}) if 确认删除 in dialog.message: dialog.accept() # 点击确定 else: dialog.dismiss() # 点击取消 page.on(dialog, handle_dialog)新窗口/标签页点击一个target_blank的链接会打开新窗口。# 在点击之前先监听新页面的创建 async with page.expect_popup() as popup_info: await page.locator(a[target_blank]).click() new_page await popup_info.value # 现在可以在新页面 new_page 上操作了 await new_page.locator(#new-page-content).click() await new_page.close() # 操作完后关闭新页面iframe 处理iframe内联框架内的元素不能直接用主页面的定位器找到。需要先定位到 iframe 元素然后获取其content_frame。# 通过选择器定位 iframe 元素 iframe_element page.locator(iframe#my-iframe) iframe await iframe_element.content_frame # 现在可以在 iframe 的上下文中定位元素了 await iframe.locator(button.submit).click() # 如果 iframe 有 name 或 src 属性也可以直接获取 iframe page.frame(namelogin-frame) # 通过 name # 或 iframe page.frame(url**/login.html) # 通过 URL 模式6.2 网络请求与响应拦截Playwright 允许你监听和修改页面发出的网络请求和收到的响应这对于模拟 API 数据、性能测试、断言接口调用非常有用。监听请求与响应# 监听所有请求 page.on(request, lambda request: print(f {request.method} {request.url})) # 监听所有响应 page.on(response, lambda response: print(f {response.status} {response.url})) # 更常用的在导航到某页面前开始监听特定请求 async with page.expect_response(**/api/user/profile) as response_info: await page.goto(/dashboard) response await response_info.value print(f用户资料API返回状态: {response.status}) # 可以解析响应体JSON格式 user_data await response.json() print(f用户名: {user_data[name]})拦截并修改请求# 路由Route请求可以中止、继续或返回模拟响应 await page.route(**/api/slow-data, lambda route: route.abort()) # 中止请求 # 或者返回一个模拟的 JSON 响应 async def handle_route(route): await route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({mock: data}) ) await page.route(**/api/config, handle_route)这个功能在测试中极其强大例如屏蔽第三方资源如广告、分析脚本以加速测试。模拟后端 API 返回进行前端逻辑测试无需启动真实后端。验证前端是否发送了正确的请求参数。6.3 执行 JavaScript 代码有时你需要直接操作 DOM 或获取一些通过 Playwright API 难以直接获取的信息。# 在页面上下文中执行 JavaScript并返回结果 dimensions await page.evaluate(() { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio }; }) print(dimensions) # 将元素句柄传入 evaluate 进行操作 button await page.locator(button#submit).element_handle() await page.evaluate((button) button.style.border 2px solid red, button) # 更简单的直接对定位器执行 JS text_content await page.locator(.title).evaluate(el el.textContent)6.4 模拟设备与地理位置Playwright 可以轻松模拟移动设备访问以及不同的地理位置和语言。from playwright.sync_api import sync_playwright def run(playwright): # 使用 iPhone 11 的设备描述符 iphone_11 playwright.devices[iPhone 11] # 创建上下文时传入设备参数 browser playwright.chromium.launch(headlessFalse) context browser.new_context(**iphone_11) # 这里传入了用户代理、视口大小、设备缩放因子等 page context.new_page() await page.goto(https://mobile.example.com) # 此时页面看到的将是移动端视图 # 模拟地理位置和语言 context await browser.new_context( localede-DE, # 语言设置为德语德国 geolocation{longitude: 13.404954, latitude: 52.520008}, # 柏林坐标 permissions[geolocation] # 授予地理位置权限 ) page await context.new_page() await page.goto(https://maps.example.com) # 网站将收到德语语言请求和柏林的地理位置信息7. 测试框架集成Pytest Playwright 实战将 Playwright 与专业的测试框架如 Pytest结合可以更好地组织测试用例、生成报告、管理夹具Fixture实现真正的自动化测试工程化。7.1 安装与基础配置首先安装 Pytest 和 Playwright 的 Pytest 插件pip install pytest pytest-playwrightPlaywright 的 Pytest 插件提供了许多有用的夹具最核心的是page。创建一个测试文件test_login.py# test_login.py def test_login_success(page): # page 夹具由插件提供无需自己创建 page.goto(https://demo.example.com/login) page.locator(#username).fill(standard_user) page.locator(#password).fill(secret_sauce) page.locator(#login-button).click() # 断言登录后跳转到了库存页面 assert /inventory.html in page.url # 断言页面中存在某个代表登录成功的元素 assert page.locator(.inventory_list).is_visible() def test_login_failure(page): page.goto(https://demo.example.com/login) page.locator(#username).fill(locked_out_user) page.locator(#password).fill(secret_sauce) page.locator(#login-button).click() # 断言出现了错误提示 error_message page.locator([data-testerror]) assert error_message.is_visible() assert Sorry, this user has been locked out. in error_message.inner_text()运行测试pytest test_login.py -v-v参数表示输出详细信息。插件会自动为你管理浏览器的启动和关闭。7.2 使用夹具进行高级配置Pytest 夹具可以帮助你进行测试前的准备和测试后的清理工作实现代码复用。1. 浏览器类型夹具你可以轻松指定用哪种浏览器运行测试。# 在命令行指定 pytest --browser chromium --browser firefox --browser webkit这会让每个测试用例在三种浏览器上各运行一次实现跨浏览器测试。2. 自定义上下文夹具如果你想为所有测试设置统一的上下文如视口大小、语言、权限。# conftest.py (Pytest 会自动发现这个文件中的夹具) import pytest pytest.fixture(scopesession) def browser_context_args(browser_context_args): # 继承默认参数并添加自定义设置 return { **browser_context_args, viewport: {width: 1920, height: 1080}, ignore_https_errors: True, # 忽略 HTTPS 证书错误常用于测试环境 locale: en-US, }3. 页面对象模型Page Object Model, POM夹具这是提高测试代码可维护性的关键模式。将页面的元素定位和操作封装成类。# pages/login_page.py class LoginPage: def __init__(self, page): self.page page self.username_input page.locator(#username) self.password_input page.locator(#password) self.login_button page.locator(#login-button) self.error_message page.locator([data-testerror]) def navigate(self): self.page.goto(https://demo.example.com/login) def login(self, username, password): self.username_input.fill(username) self.password_input.fill(password) self.login_button.click() # conftest.py import pytest from pages.login_page import LoginPage pytest.fixture def login_page(page): return LoginPage(page) # test_login_pom.py def test_login_with_pom(login_page): login_page.navigate() login_page.login(standard_user, secret_sauce) assert /inventory.html in login_page.page.url使用 POM 后如果登录页面的输入框 ID 改变了你只需要修改LoginPage类中的一处定义所有测试用例都不受影响。7.3 生成测试报告与追踪Playwright 内置了强大的测试追踪功能可以记录测试执行的每一个步骤生成可视化的报告这对于调试失败的测试至关重要。生成追踪文件在运行测试时添加--tracing on参数pytest --tracing on或者在你的测试夹具中启用# conftest.py pytest.fixture(scopefunction) def context(context): # 为每个测试启用追踪 await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) yield context # 测试结束后停止追踪并保存文件 await context.tracing.stop(path ftrace-{uuid.uuid4()}.zip)追踪文件.zip可以用 Playwright 的命令行工具或在线查看器打开它包含了操作截图、DOM 快照、网络请求、控制台日志等所有信息能帮你快速定位“测试当时到底发生了什么”。生成 HTML 报告Playwright Test 本身可以生成漂亮的 HTML 报告。对于 Pytest可以使用pytest-html等插件生成报告。pip install pytest-html pytest --htmlreport.html --self-contained-html8. 常见问题排查与性能优化即使有了完善的工具在实际项目中还是会遇到各种问题。这里记录了一些我踩过的坑和解决方案。8.1 典型问题速查表问题现象可能原因解决方案TimeoutError: Timeout 30000ms exceeded1. 元素定位器错误找不到元素。2. 页面加载/元素渲染太慢。3. 元素被遮挡或不可交互。1. 使用浏览器开发者工具检查定位器是否正确。2. 增加超时时间locator.click(timeout60000)。3. 使用page.pause()暂停脚本手动检查页面状态。4. 检查是否有 iframe、Shadow DOM。5. 尝试使用forceTrue参数谨慎使用。元素点击/输入没反应1. 元素非真正可见如被透明层覆盖。2. 有前置操作未完成如表单验证。3. 页面有未处理的弹窗阻塞。1. 使用locator.hover()或locator.scroll_into_view_if_needed()。2. 检查控制台是否有 JS 错误。3. 监听并处理dialog事件。4. 尝试用page.evaluate直接触发 JS 点击事件。脚本在 CI/CD 环境失败本地却成功1. CI 环境无头模式与本地有头模式行为差异。2. CI 环境网络慢或资源不同。3. 文件路径、环境变量差异。1. 本地也使用无头模式 (headlessTrue) 运行测试。2. 增加网络空闲等待wait_for_load_state(‘networkidle’)。3. 使用page.screenshot()和page.video记录 CI 失败现场。4. 确保 CI 环境已正确安装 Playwright 浏览器 (playwright install)。Target closed错误你试图操作一个已经被关闭的页面或上下文。检查代码逻辑确保在操作页面时它没有被意外的page.close()或context.close()关闭。通常发生在异步操作和页面跳转时确保使用expect_popup或expect_navigation来正确处理。文件上传不工作文件输入框可能是动态生成或样式隐藏的。1. 确保定位到了正确的input type”file”元素。2. 不要尝试点击它直接用set_input_files()方法。3. 如果元素被隐藏可能需要先通过 JS 使其可见。跨域 iframe 无法操作浏览器的同源策略限制。Playwright 默认在新上下文中禁用同源策略 (ignore_https_errors等设置可能影响)。确保 iframe 的 URL 与主页同源或使用--disable-web-security启动浏览器仅限测试环境。8.2 性能优化与最佳实践复用浏览器上下文启动和关闭浏览器是昂贵的操作。在测试套件级别scope’session’启动浏览器在测试函数级别scope’function’创建新的上下文和页面。这样每个测试都是隔离的但共享浏览器进程速度更快。并行执行测试Pytest 支持pytest-xdist插件进行并行测试。Playwright 可以很好地与它配合但需要确保为每个工作进程创建独立的浏览器上下文。pip install pytest-xdist pytest -n auto # 使用与CPU核心数相同的进程并行运行选择性安装浏览器如果只测试 Chromium就不要安装 Firefox 和 WebKit。合理使用无头模式在 CI/CD 管道和追求速度时始终使用headlessTrue。仅在调试复杂交互问题时才开启有头模式。拦截不必要的资源使用page.route拦截并中止对图片、样式表、字体等静态资源的请求可以显著加快测试页面加载速度。async def block_assets(route): if route.request.resource_type in [image, stylesheet, font]: await route.abort() else: await route.continue_() await page.route(**/*, block_assets)保持定位器稳定这是减少测试“脆性”的最重要一点。优先使用>
Python+Playwright自动化测试入门:环境搭建与核心操作详解
发布时间:2026/6/30 20:07:27
1. 项目概述为什么选择 Python Playwright 作为你的 Web 自动化测试起点如果你正在为 Web 应用的回归测试、兼容性验证或者重复性操作而烦恼手动点点点不仅效率低下还容易出错。自动化测试是解决这个问题的标准答案但面对 Selenium、Cypress、Puppeteer 等众多框架新手往往会感到迷茫。今天我想和你深入聊聊Python Playwright这个组合它是我在经历了多个项目后认为目前最适合快速上手、功能强大且维护成本较低的 Web 自动化方案。简单来说Playwright 是一个由微软开源的现代化浏览器自动化库。它支持 Chromium、Firefox 和 WebKitSafari 的渲染引擎三大浏览器引擎这意味着你可以用一套脚本测试你的网站在 Chrome、Edge、Firefox 和 Safari 上的表现对于前端兼容性测试来说简直是神器。而 Python以其简洁优雅的语法和庞大的生态极大地降低了自动化脚本的编写和维护门槛。这个组合的核心价值在于“用开发者友好的方式实现稳定、快速且覆盖全面的浏览器自动化操作”。我最初从 Selenium 转向 Playwright最直接的动力是它解决了几个长期痛点稳定性、执行速度和强大的内置能力。Selenium 需要依赖浏览器驱动版本匹配是个头疼事而 Playwright 通过自带浏览器二进制文件实现了开箱即用。它的自动等待机制Auto-waiting能智能等待元素可操作状态避免了在脚本中到处写time.sleep的尴尬让脚本更加健壮。无论是前端开发者想验证页面交互还是测试工程师需要构建自动化测试套件甚至是运营同学想自动化一些数据抓取流程Python Playwright 都是一个值得投入时间学习的利器。2. 环境搭建与核心工具链配置工欲善其事必先利其器。一个顺畅的起步环境能避免很多后续的坑。这里我会详细拆解从零开始搭建 Python Playwright 开发环境的每一步并解释每个选择背后的原因。2.1 Python 环境安装与配置Python 是这一切的基础。对于自动化测试我强烈建议使用Python 3.8 及以上的版本以确保对 Playwright 等新库的良好支持。安装方式选择官方安装包从 Python 官网下载安装是最直接的方式。安装时务必勾选“Add Python to PATH”选项这能让你在命令行中直接使用python和pip命令避免后续配置环境变量的麻烦。使用 Miniconda/Anaconda如果你需要管理多个相互隔离的 Python 项目环境比如一个项目用 Python 3.8另一个用 3.11Conda 是一个更优秀的选择。它创建的是独立的虚拟环境库的依赖不会互相冲突。对于自动化测试项目我通常推荐使用Miniconda它更轻量。验证安装打开你的终端Windows 上是 CMD 或 PowerShellmacOS/Linux 上是 Terminal输入以下命令python --version # 或 python3 --version如果正确显示版本号如Python 3.10.11说明安装成功。同时检查 pipPython 包管理工具pip --version注意在 macOS 和部分 Linux 系统上系统自带的 Python 2 可能仍占用了python命令。确保你安装的 Python 3 可以通过python3调用。为了统一在本文后续命令中我将使用python和pip如果你的环境是python3和pip3请自行替换。2.2 集成开发环境IDE的选择与配置写 Python 脚本一个好用的 IDE 能事半功倍。我的首选是Visual Studio Code (VS Code)因为它免费、轻量、插件生态丰富对 Python 和 Playwright 的支持都非常好。VS Code 必备插件配置Python微软官方出品提供代码补全、智能提示、调试、linting 等核心功能。Pylance强大的语言服务器比默认的 Jedi 提供更快的补全和类型检查。Playwright Test for VSCodePlaywright 官方插件提供测试列表、运行、调试的图形化界面非常方便。Code Runner可以快速运行当前文件或选中的代码片段。安装好 Python 插件后在 VS Code 中按CtrlShiftPWindows/Linux或CmdShiftPmacOS输入 “Python: Select Interpreter”选择你刚安装的 Python 解释器。这样VS Code 就会基于这个解释器来提供智能提示和运行环境。2.3 Playwright 库的安装与浏览器部署环境准备好后就可以安装 Playwright 了。Playwright 的安装分为两部分Python 库本身和它需要操控的浏览器。安装 Playwright Python 库在你的项目目录下打开终端执行pip install playwright这个命令会从 PyPI 下载并安装playwright这个 Python 包。安装浏览器二进制文件Playwright 的强大之处在于它自带了经过优化和测试的浏览器版本确保了自动化环境的稳定性。安装完库后需要安装这些浏览器playwright install这个命令会下载 Chromium、Firefox 和 WebKit 的最新稳定版浏览器到你的本地缓存中。这个过程可能会花费一些时间因为需要下载几百MB的数据。实操心得playwright install默认会安装所有支持的浏览器。如果你确定只使用 Chromium这是最常见的选择兼容 Chrome 和 Edge可以使用playwright install chromium来只安装它以节省时间和磁盘空间。如果遇到下载慢或失败的情况特别是在国内网络环境可以尝试设置环境变量来使用镜像源例如在终端中先执行set PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwrightWindows或export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwrightmacOS/Linux然后再运行安装命令。至此你的核心开发环境就已经搭建完毕。你可以通过一个简单的脚本来验证一切是否正常。3. Playwright 核心概念与快速入门脚本解析在开始编写复杂的测试用例之前我们必须理解 Playwright 的几个核心抽象。这就像学开车先要了解方向盘、油门和刹车一样。3.1 核心对象模型Browser, Context, PagePlaywright 的操作围绕三个核心对象展开它们的关系是层层包含的Browser代表一个浏览器实例。你可以把它想象成一个完整的浏览器程序比如你桌面上的 Chrome 图标。通过 Playwright你可以启动launch一个浏览器可以是有头模式能看到界面或无头模式后台运行。Context浏览器上下文。这是Playwright 中一个非常强大且重要的概念。你可以把它理解为一个独立的“隐身会话”或“用户档案”。每个 Context 拥有独立的 cookies、本地存储、缓存和权限设置。这意味着你可以在一个脚本中轻松模拟多个用户同时登录同一个网站或者隔离不同的测试场景而不会互相干扰。Page页面。一个 Context 中可以包含多个 Page标签页。Page 对象代表一个具体的网页我们绝大部分的操作如点击、输入、获取文本都是在 Page 对象上完成的。它们的关系是Browser- 创建多个Context- 每个Context包含多个Page。3.2 你的第一个自动化脚本打开网页并截图理论说再多不如动手试一下。让我们创建一个最简单的脚本体验 Playwright 的基本工作流程。创建一个新文件命名为first_script.py输入以下代码import asyncio from playwright.async_api import async_playwright async def main(): # 1. 启动 Playwright管理浏览器生命周期 async with async_playwright() as p: # 2. 启动一个 Chromium 浏览器实例无头模式 browser await p.chromium.launch(headlessFalse) # 设置为 True 则无头运行 # 3. 创建一个新的浏览器上下文 context await browser.new_context() # 4. 在上下文中打开一个新页面 page await context.new_page() # 5. 导航到目标网址 await page.goto(https://www.example.com) # 6. 等待页面加载goto 本身会等待网络空闲这里额外等待一下可能动态加载的内容 await page.wait_for_load_state(networkidle) # 7. 对页面进行截图 await page.screenshot(pathexample.png, full_pageTrue) print(截图已保存为 example.png) # 8. 关闭浏览器 await browser.close() # 运行主函数 asyncio.run(main())逐行解析与注意事项async with async_playwright() as p:这是 Playwright 推荐的异步 API 使用方式。async with语句确保在代码块结束后Playwright 的资源会被正确清理。p对象是你的入口点通过它可以访问不同浏览器类型p.chromium,p.firefox,p.webkit。p.chromium.launch(headlessFalse)启动一个 Chromium 浏览器。headlessFalse意味着你将看到一个真实的浏览器窗口打开并执行操作这对于调试脚本非常有用。在生产环境或追求速度时应设置为True。browser.new_context()创建了一个独立的上下文。在这个简单的例子里我们暂时用不到上下文的隔离特性但养成创建 Context 的习惯是好的实践。page.goto()导航到指定 URL。这个方法会自动等待页面触发load事件。page.wait_for_load_state(networkidle)这是一个更严格的等待。networkidle表示在至少 500 毫秒内没有超过 2 个网络连接时才认为页面加载完成。这对于等待 AJAX 请求或动态加载的内容非常有效。page.screenshot()截图功能。full_pageTrue会截取整个可滚动页面的长图而不仅仅是首屏。await browser.close()关闭浏览器释放资源。虽然在async with块结束后会自动关闭但显式调用是一个好习惯。运行这个脚本 (python first_script.py)你会看到一个浏览器窗口打开访问 example.com然后截图保存最后关闭。恭喜你已经完成了第一次 Web 自动化实操心得Playwright 提供了同步和异步两套 API。上面的例子是异步 API (async/await)。对于简单的线性脚本你也可以使用同步 API代码更直观。只需导入from playwright.sync_api import sync_playwright并去掉所有的async和await关键字使用with sync_playwright() as p:即可。我个人的建议是如果你是新手或者脚本逻辑不复杂可以从同步 API 开始更容易理解。但异步 API 在并发执行多个浏览器操作时性能优势明显值得后续学习。4. 元素定位与交互自动化操作的基石自动化测试的核心是模拟人的操作找到页面上的元素按钮、输入框、链接然后与之交互点击、输入、悬停。Playwright 提供了多种强大且灵活的元素定位器Locators。4.1 定位器Locator详解与最佳实践定位器是 Playwright 中用于查找和操作元素的抽象。创建定位器不会立即执行查找只有在执行操作如click()时才会真正去页面上定位元素这种“惰性”设计提高了脚本的健壮性。最常用的定位策略按文本定位这是最直观的方式之一。# 点击文本内容为“登录”的元素可以是按钮、链接、div等 await page.locator(text登录).click() # 更精确的完全匹配 await page.locator(textSign in).click()按 CSS 选择器定位这是最强大、最通用的方式如果你熟悉 CSS会非常得心应手。# 通过 ID await page.locator(#username).fill(myuser) # 通过 Class await page.locator(.submit-btn).click() # 通过属性 await page.locator([data-testidlogin-button]).click() # 组合选择器 await page.locator(div.header nav a:has-text(Home)).click()按 XPath 定位XPath 功能强大但表达式可能复杂在 CSS 选择器无法满足复杂层级关系时使用。await page.locator(//button[idsubmit]).click()按角色Role定位这是 Playwright 推荐的一种语义化定位方式特别适合可访问性ARIA良好的现代 Web 应用。# 定位一个名为“Username”的文本框 await page.get_by_role(textbox, nameUsername).fill(test) # 定位一个按钮 await page.get_by_role(button, nameSubmit).click() # 定位一个链接 await page.get_by_role(link, namePrivacy Policy).click()最佳实践与避坑指南优先使用get_by_role,get_by_text,get_by_label这些 API 更具语义化与用户感知方式一致且通常比脆弱的 CSS 选择器更稳定。例如前端修改了样式类名你的get_by_role(‘button’, name‘Submit’)依然有效但.btn-primary可能就失效了。避免使用绝对 XPath像/html/body/div[3]/div[2]/button这样的 XPath 极其脆弱页面结构稍有变动就会失败。利用># 先找到表格再在表格里找包含特定文本的行 row page.locator(table).locator(tr, has_textJohn Doe) # 使用 filter 进行条件过滤 enabled_button page.locator(button).filter(has_textSave).and_(enabledTrue)4.2 核心交互操作点击、输入、选择定位到元素后就可以进行交互了。以下是最常用的操作点击与双击await page.locator(button#submit).click() # 带选项的点击例如强制点击即使用元素被遮挡 await page.locator(button).click(forceTrue) # 双击 await page.locator(canvas).dblclick()输入与清空# 在输入框填充文本会先清空原有内容 await page.locator(#email).fill(userexample.com) # 模拟逐个字符输入更接近用户真实行为会触发键盘事件 await page.locator(#search).type(Playwright automation, delay100) # delay 模拟输入间隔 # 清空输入框 await page.locator(#comment).clear()下拉框选择# 通过 value 选择 await page.locator(select#country).select_option(valueus) # 通过标签文本选择 await page.locator(select#country).select_option(labelUnited States) # 多选 await page.locator(select#colors).select_option(value[red, blue])复选框与单选框# 勾选复选框 await page.locator(input[typecheckbox]).check() # 取消勾选 await page.locator(input[typecheckbox]).uncheck() # 判断是否已勾选 is_checked await page.locator(input[typecheckbox]).is_checked() # 选择单选框 await page.locator(input[typeradio][valueoption1]).check()文件上传Playwright 处理文件上传非常简洁不需要模拟复杂的点击文件选择对话框的操作。# 设置文件输入框的值文件路径 await page.locator(input[typefile]).set_input_files(/path/to/myfile.pdf) # 上传多个文件 await page.locator(input[typefile]).set_input_files([file1.pdf, file2.jpg]) # 清空已选文件 await page.locator(input[typefile]).set_input_files([])悬停Hover# 鼠标悬停在元素上常用于触发下拉菜单或工具提示 await page.locator(.menu-item).hover()注意事项Playwright 的交互操作如click,fill内置了自动等待。它会等待元素满足可操作条件如可见、启用、稳定未动画后才执行操作。这意味着你通常不需要在操作前手动写等待这极大地简化了脚本并提高了稳定性。但如果遇到特殊场景如自定义控件你可能需要结合wait_for_selector或wait_for_function使用。5. 等待策略编写稳定自动化脚本的关键自动化脚本不稳定的一大元凶就是“时机不对”——脚本执行速度远快于页面加载或元素渲染速度。Playwright 通过一套智能的等待机制从根本上解决了这个问题。5.1 自动等待Auto-waiting机制这是 Playwright 最令人称道的特性之一。之前提到的click(),fill(),check()等操作在内部都执行了一系列检查确保元素可交互元素被附加到 DOM元素可见非隐藏、非display:none、非visibility:hidden、宽高大于0元素启用非disabled元素稳定例如CSS 过渡动画结束只有所有这些条件都满足操作才会执行。如果条件不满足Playwright 会重试直到超时默认 30 秒。这意味着你大多数情况下不需要写显式的sleep。5.2 显式等待应对复杂场景尽管自动等待很强大但在一些异步加载内容、动态元素出现的复杂场景下我们仍需要显式地告诉 Playwright“请等待某个特定条件发生”。1. 等待元素出现/可见/隐藏# 等待选择器匹配的元素出现在 DOM 中 await page.wait_for_selector(.loading-spinner, statehidden) # 等待加载动画消失 # 等待元素可见 await page.wait_for_selector(#success-message, statevisible) # 等待元素被从 DOM 中移除 await page.wait_for_selector(#old-element, statedetached)2. 等待导航# 在点击一个会导致页面跳转的链接前可以等待导航完成 async with page.expect_navigation(): await page.locator(a#next-page).click() # 或者等待特定的 URL await page.wait_for_url(**/dashboard)3. 等待页面事件# 等待页面加载完成load 事件 await page.wait_for_load_state(load) # 等待网络几乎空闲推荐用于单页应用 SPA await page.wait_for_load_state(networkidle) # 等待 DOM 内容加载完成DOMContentLoaded 事件 await page.wait_for_load_state(domcontentloaded)4. 等待自定义条件最灵活# 等待某个函数在页面上下文中返回真值 await page.wait_for_function( () { const el document.querySelector(.item-count); return el parseInt(el.textContent) 10; } ) # 或者使用 Python 函数通过传递参数 def wait_for_item_count(expected): # 这个函数会在浏览器环境中执行 selector .item-count element document.querySelector(selector) return element parseInt(element.textContent) expected await page.wait_for_function(wait_for_item_count, 10) # 等待数量 105. 等待超时设置所有等待方法都可以设置超时时间毫秒。try: # 只等待 5 秒 await page.wait_for_selector(.popup, statevisible, timeout5000) except TimeoutError: print(弹出框没有在5秒内出现)实操心得我的经验法则是优先依赖操作的自动等待仅在需要等待非交互性状态变化如元素出现、消失、文本变化或导航时才使用显式等待。避免滥用page.wait_for_timeout(3000)这种固定休眠它会让测试变慢且不可靠网络或机器性能差异可能导致3秒不够或浪费。使用基于条件的等待你的脚本才能在任何速度下都稳定运行。6. 高级特性与实战技巧掌握了基础操作和等待策略你已经能完成大部分自动化任务。接下来我们探讨一些高级特性和实战技巧让你的脚本更强大、更健壮、更易维护。6.1 处理弹窗、新窗口与 iframe对话框Alert, Confirm, PromptPlaywright 可以监听并响应 JavaScript 原生的对话框。# 监听对话框事件并在触发时接受点击“确定” page.on(dialog, lambda dialog: dialog.accept()) await page.locator(button#delete).click() # 点击会触发 confirm 对话框 # 更精细的控制获取对话框消息并选择操作 def handle_dialog(dialog): print(f对话框消息: {dialog.message}) if 确认删除 in dialog.message: dialog.accept() # 点击确定 else: dialog.dismiss() # 点击取消 page.on(dialog, handle_dialog)新窗口/标签页点击一个target_blank的链接会打开新窗口。# 在点击之前先监听新页面的创建 async with page.expect_popup() as popup_info: await page.locator(a[target_blank]).click() new_page await popup_info.value # 现在可以在新页面 new_page 上操作了 await new_page.locator(#new-page-content).click() await new_page.close() # 操作完后关闭新页面iframe 处理iframe内联框架内的元素不能直接用主页面的定位器找到。需要先定位到 iframe 元素然后获取其content_frame。# 通过选择器定位 iframe 元素 iframe_element page.locator(iframe#my-iframe) iframe await iframe_element.content_frame # 现在可以在 iframe 的上下文中定位元素了 await iframe.locator(button.submit).click() # 如果 iframe 有 name 或 src 属性也可以直接获取 iframe page.frame(namelogin-frame) # 通过 name # 或 iframe page.frame(url**/login.html) # 通过 URL 模式6.2 网络请求与响应拦截Playwright 允许你监听和修改页面发出的网络请求和收到的响应这对于模拟 API 数据、性能测试、断言接口调用非常有用。监听请求与响应# 监听所有请求 page.on(request, lambda request: print(f {request.method} {request.url})) # 监听所有响应 page.on(response, lambda response: print(f {response.status} {response.url})) # 更常用的在导航到某页面前开始监听特定请求 async with page.expect_response(**/api/user/profile) as response_info: await page.goto(/dashboard) response await response_info.value print(f用户资料API返回状态: {response.status}) # 可以解析响应体JSON格式 user_data await response.json() print(f用户名: {user_data[name]})拦截并修改请求# 路由Route请求可以中止、继续或返回模拟响应 await page.route(**/api/slow-data, lambda route: route.abort()) # 中止请求 # 或者返回一个模拟的 JSON 响应 async def handle_route(route): await route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({mock: data}) ) await page.route(**/api/config, handle_route)这个功能在测试中极其强大例如屏蔽第三方资源如广告、分析脚本以加速测试。模拟后端 API 返回进行前端逻辑测试无需启动真实后端。验证前端是否发送了正确的请求参数。6.3 执行 JavaScript 代码有时你需要直接操作 DOM 或获取一些通过 Playwright API 难以直接获取的信息。# 在页面上下文中执行 JavaScript并返回结果 dimensions await page.evaluate(() { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio }; }) print(dimensions) # 将元素句柄传入 evaluate 进行操作 button await page.locator(button#submit).element_handle() await page.evaluate((button) button.style.border 2px solid red, button) # 更简单的直接对定位器执行 JS text_content await page.locator(.title).evaluate(el el.textContent)6.4 模拟设备与地理位置Playwright 可以轻松模拟移动设备访问以及不同的地理位置和语言。from playwright.sync_api import sync_playwright def run(playwright): # 使用 iPhone 11 的设备描述符 iphone_11 playwright.devices[iPhone 11] # 创建上下文时传入设备参数 browser playwright.chromium.launch(headlessFalse) context browser.new_context(**iphone_11) # 这里传入了用户代理、视口大小、设备缩放因子等 page context.new_page() await page.goto(https://mobile.example.com) # 此时页面看到的将是移动端视图 # 模拟地理位置和语言 context await browser.new_context( localede-DE, # 语言设置为德语德国 geolocation{longitude: 13.404954, latitude: 52.520008}, # 柏林坐标 permissions[geolocation] # 授予地理位置权限 ) page await context.new_page() await page.goto(https://maps.example.com) # 网站将收到德语语言请求和柏林的地理位置信息7. 测试框架集成Pytest Playwright 实战将 Playwright 与专业的测试框架如 Pytest结合可以更好地组织测试用例、生成报告、管理夹具Fixture实现真正的自动化测试工程化。7.1 安装与基础配置首先安装 Pytest 和 Playwright 的 Pytest 插件pip install pytest pytest-playwrightPlaywright 的 Pytest 插件提供了许多有用的夹具最核心的是page。创建一个测试文件test_login.py# test_login.py def test_login_success(page): # page 夹具由插件提供无需自己创建 page.goto(https://demo.example.com/login) page.locator(#username).fill(standard_user) page.locator(#password).fill(secret_sauce) page.locator(#login-button).click() # 断言登录后跳转到了库存页面 assert /inventory.html in page.url # 断言页面中存在某个代表登录成功的元素 assert page.locator(.inventory_list).is_visible() def test_login_failure(page): page.goto(https://demo.example.com/login) page.locator(#username).fill(locked_out_user) page.locator(#password).fill(secret_sauce) page.locator(#login-button).click() # 断言出现了错误提示 error_message page.locator([data-testerror]) assert error_message.is_visible() assert Sorry, this user has been locked out. in error_message.inner_text()运行测试pytest test_login.py -v-v参数表示输出详细信息。插件会自动为你管理浏览器的启动和关闭。7.2 使用夹具进行高级配置Pytest 夹具可以帮助你进行测试前的准备和测试后的清理工作实现代码复用。1. 浏览器类型夹具你可以轻松指定用哪种浏览器运行测试。# 在命令行指定 pytest --browser chromium --browser firefox --browser webkit这会让每个测试用例在三种浏览器上各运行一次实现跨浏览器测试。2. 自定义上下文夹具如果你想为所有测试设置统一的上下文如视口大小、语言、权限。# conftest.py (Pytest 会自动发现这个文件中的夹具) import pytest pytest.fixture(scopesession) def browser_context_args(browser_context_args): # 继承默认参数并添加自定义设置 return { **browser_context_args, viewport: {width: 1920, height: 1080}, ignore_https_errors: True, # 忽略 HTTPS 证书错误常用于测试环境 locale: en-US, }3. 页面对象模型Page Object Model, POM夹具这是提高测试代码可维护性的关键模式。将页面的元素定位和操作封装成类。# pages/login_page.py class LoginPage: def __init__(self, page): self.page page self.username_input page.locator(#username) self.password_input page.locator(#password) self.login_button page.locator(#login-button) self.error_message page.locator([data-testerror]) def navigate(self): self.page.goto(https://demo.example.com/login) def login(self, username, password): self.username_input.fill(username) self.password_input.fill(password) self.login_button.click() # conftest.py import pytest from pages.login_page import LoginPage pytest.fixture def login_page(page): return LoginPage(page) # test_login_pom.py def test_login_with_pom(login_page): login_page.navigate() login_page.login(standard_user, secret_sauce) assert /inventory.html in login_page.page.url使用 POM 后如果登录页面的输入框 ID 改变了你只需要修改LoginPage类中的一处定义所有测试用例都不受影响。7.3 生成测试报告与追踪Playwright 内置了强大的测试追踪功能可以记录测试执行的每一个步骤生成可视化的报告这对于调试失败的测试至关重要。生成追踪文件在运行测试时添加--tracing on参数pytest --tracing on或者在你的测试夹具中启用# conftest.py pytest.fixture(scopefunction) def context(context): # 为每个测试启用追踪 await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) yield context # 测试结束后停止追踪并保存文件 await context.tracing.stop(path ftrace-{uuid.uuid4()}.zip)追踪文件.zip可以用 Playwright 的命令行工具或在线查看器打开它包含了操作截图、DOM 快照、网络请求、控制台日志等所有信息能帮你快速定位“测试当时到底发生了什么”。生成 HTML 报告Playwright Test 本身可以生成漂亮的 HTML 报告。对于 Pytest可以使用pytest-html等插件生成报告。pip install pytest-html pytest --htmlreport.html --self-contained-html8. 常见问题排查与性能优化即使有了完善的工具在实际项目中还是会遇到各种问题。这里记录了一些我踩过的坑和解决方案。8.1 典型问题速查表问题现象可能原因解决方案TimeoutError: Timeout 30000ms exceeded1. 元素定位器错误找不到元素。2. 页面加载/元素渲染太慢。3. 元素被遮挡或不可交互。1. 使用浏览器开发者工具检查定位器是否正确。2. 增加超时时间locator.click(timeout60000)。3. 使用page.pause()暂停脚本手动检查页面状态。4. 检查是否有 iframe、Shadow DOM。5. 尝试使用forceTrue参数谨慎使用。元素点击/输入没反应1. 元素非真正可见如被透明层覆盖。2. 有前置操作未完成如表单验证。3. 页面有未处理的弹窗阻塞。1. 使用locator.hover()或locator.scroll_into_view_if_needed()。2. 检查控制台是否有 JS 错误。3. 监听并处理dialog事件。4. 尝试用page.evaluate直接触发 JS 点击事件。脚本在 CI/CD 环境失败本地却成功1. CI 环境无头模式与本地有头模式行为差异。2. CI 环境网络慢或资源不同。3. 文件路径、环境变量差异。1. 本地也使用无头模式 (headlessTrue) 运行测试。2. 增加网络空闲等待wait_for_load_state(‘networkidle’)。3. 使用page.screenshot()和page.video记录 CI 失败现场。4. 确保 CI 环境已正确安装 Playwright 浏览器 (playwright install)。Target closed错误你试图操作一个已经被关闭的页面或上下文。检查代码逻辑确保在操作页面时它没有被意外的page.close()或context.close()关闭。通常发生在异步操作和页面跳转时确保使用expect_popup或expect_navigation来正确处理。文件上传不工作文件输入框可能是动态生成或样式隐藏的。1. 确保定位到了正确的input type”file”元素。2. 不要尝试点击它直接用set_input_files()方法。3. 如果元素被隐藏可能需要先通过 JS 使其可见。跨域 iframe 无法操作浏览器的同源策略限制。Playwright 默认在新上下文中禁用同源策略 (ignore_https_errors等设置可能影响)。确保 iframe 的 URL 与主页同源或使用--disable-web-security启动浏览器仅限测试环境。8.2 性能优化与最佳实践复用浏览器上下文启动和关闭浏览器是昂贵的操作。在测试套件级别scope’session’启动浏览器在测试函数级别scope’function’创建新的上下文和页面。这样每个测试都是隔离的但共享浏览器进程速度更快。并行执行测试Pytest 支持pytest-xdist插件进行并行测试。Playwright 可以很好地与它配合但需要确保为每个工作进程创建独立的浏览器上下文。pip install pytest-xdist pytest -n auto # 使用与CPU核心数相同的进程并行运行选择性安装浏览器如果只测试 Chromium就不要安装 Firefox 和 WebKit。合理使用无头模式在 CI/CD 管道和追求速度时始终使用headlessTrue。仅在调试复杂交互问题时才开启有头模式。拦截不必要的资源使用page.route拦截并中止对图片、样式表、字体等静态资源的请求可以显著加快测试页面加载速度。async def block_assets(route): if route.request.resource_type in [image, stylesheet, font]: await route.abort() else: await route.continue_() await page.route(**/*, block_assets)保持定位器稳定这是减少测试“脆性”的最重要一点。优先使用>