告别Selenium:5分钟用Playwright+Python搭建稳定Web自动化测试

发布时间:2026/7/1 20:52:25
告别Selenium:5分钟用Playwright+Python搭建稳定Web自动化测试 1. 项目概述与核心价值如果你还在用Selenium写Web自动化测试脚本每次都要花大量时间处理元素等待、iframe切换或者反爬检测那今天这个分享可能会让你换个思路。我最近把团队里几个老项目的自动化测试框架从Selenium迁移到了Playwright最大的感受就是原来写一个稳定可靠的Web自动化脚本可以这么快。就拿一个最常见的登录并检查用户信息的场景来说用Selenium可能需要处理各种显式等待和异常捕获代码写出来几十行但用Playwright配合Python核心功能5分钟就能搭出骨架代码简洁得让人怀疑以前是不是在“自虐”。这个“5分钟”不是夸张而是基于Playwright设计哲学带来的效率提升。它内置了智能等待、自动重试、多浏览器引擎支持还有对现代Web应用比如单页面应用SPA的原生友好支持。你不再需要到处写time.sleep(10)或者复杂的WebDriverWait也不用担心因为网络波动或元素加载慢导致脚本动不动就崩掉。对于测试工程师、爬虫开发者或者任何需要与浏览器进行自动化交互的开发者来说这意味着可以把精力从“让脚本跑起来”转移到“测试什么业务逻辑”或者“抓取什么数据”上。接下来我会用一个完整的、可运行的例子带你走一遍从零开始用Playwright Python搭建一个自动化测试脚本的全过程。这个脚本会模拟一个用户登录某个示例网站在登录成功后进行页面跳转并验证关键信息。我会把每一步为什么这么做、可能会遇到什么坑、以及如何调试都讲清楚。无论你是刚接触自动化测试的新手还是想从Selenium转型的老手都能直接跟着操作拿到一个能跑起来的脚本。2. 环境准备与工具选型解析在动手写代码之前把环境搭对是成功的一半。很多人卡在第一步不是因为工具难装而是没搞清楚这几个工具之间的关系和各自的最佳实践。2.1 Python环境与包管理首先确保你有一个可用的Python环境。我强烈建议使用Python 3.8或更高版本因为Playwright对较新的Python特性支持更好。检查版本很简单在终端或命令行里输入python --version或python3 --version。如果你看到版本号低于3.8需要先去Python官网下载安装最新版本。关于Python的安装有个小坑需要注意在Windows上安装时务必勾选“Add Python to PATH”这个选项这样你才能在任意目录下直接使用python命令。很多新手装完发现命令找不到就是因为漏了这一步。在macOS或Linux上通常系统会自带Python 3但版本可能较旧你可以通过HomebrewmacOS或系统包管理器Linux来安装更新的版本。接下来是包管理。Python世界最主流的包管理工具是pip。我们所有后续的库安装都依赖它。首先升级pip到最新版是个好习惯可以避免一些因版本过旧导致的依赖解析错误。命令是python -m pip install --upgrade pip。2.2 Playwright vs Selenium为什么换既然标题是“告别Selenium”我们得先搞清楚Playwright强在哪里。这不是说Selenium不好它依然是行业基石拥有最庞大的社区和资料。但Playwright是微软基于现代Web开发痛点“重新发明”的轮子解决了很多Selenium的顽疾。核心优势对比特性维度SeleniumPlaywright架构基于WebDriver协议需要对应浏览器的驱动如chromedriver。直接通过DevTools协议与浏览器内核通信无需单独驱动。等待机制需要手动设置显式/隐式等待否则易因元素未加载报错。自动等待。几乎所有操作点击、填充都会自动等待元素可交互。执行速度较慢因为要通过WebDriver代理。更快直接协议通信且支持无头模式下的硬件加速。浏览器支持支持所有主流浏览器但需要各自驱动。原生支持ChromiumChrome/Edge、Firefox、WebKitSafari引擎安装时自动下载。录制与调试有Selenium IDE但功能相对基础。内置强大的Codegen录制工具和Trace Viewer可视化调试器。网络拦截支持但配置较复杂。强大且简单可轻松模拟API响应、修改请求头、拦截资源。移动端模拟可通过特定配置实现。内置设备模拟如iPhone、Pixel包含视口、User-Agent、触摸事件。iframe与多页需要显式切换上下文。原生支持像处理普通页面一样简单。简单来说Playwright把Selenium时代需要开发者手动处理的“脏活累活”都包了。它的API设计更符合直觉比如page.click(button)它会自动等到这个按钮出现在DOM中、可见、且未被其他元素遮挡、稳定可点击时才执行点击。这省去了大量try...except和WebDriverWait的代码。2.3 安装Playwright for Python安装过程非常简单一行命令搞定。打开你的终端命令行、PowerShell或Terminal执行pip install playwright这条命令会从PyPIPython包索引下载并安装Playwright的Python客户端库。安装完成后我们还需要安装Playwright本身需要使用的浏览器内核。Playwright没有使用你系统里安装的Chrome或Firefox而是为了保证测试环境的一致性使用了自己管理的特定版本的浏览器。这是好事意味着你的脚本在任何机器上运行浏览器环境都是一模一样的。安装浏览器内核的命令是playwright install这条命令会下载Chromium、Firefox和WebKit三大浏览器引擎。下载可能需要几分钟取决于你的网络速度。如果你只想安装其中一个比如Chromium可以用playwright install chromium。注意playwright install命令可能会被某些网络环境下的防火墙或代理影响。如果下载速度极慢或失败可以尝试设置环境变量PLAYWRIGHT_DOWNLOAD_HOST为国内的镜像源例如https://npmmirror.com/mirrors/playwright/。具体镜像地址请查阅当时可用的镜像站。安装完成后你可以通过playwright --version来验证安装是否成功它会输出Playwright命令行工具和核心库的版本号。2.4 选择你的代码编辑器写Python脚本一个好用的编辑器能事半功倍。这里有几个主流选择Visual Studio Code (VSCode)目前最流行的免费选择。你需要安装Python扩展和Playwright Test for VSCode扩展。后者能提供测试运行、录制和调试的图形化界面非常强大。PyCharmJetBrains出品专业Python IDE。社区版免费专业版收费但功能更强特别是对Web开发和测试的集成。它对Playwright的支持也很好可以通过插件或直接运行配置来执行脚本。Jupyter Notebook / JupyterLab适合做探索性交互比如快速试验一些选择器或操作。但不适合作为最终自动化脚本的载体。我个人日常用VSCode因为它轻量、插件生态丰富而且微软官方对Playwright的支持非常积极。后续的演示我也会基于VSCode的环境。3. 第一个脚本从登录到验证理论说再多不如动手跑一遍。我们用一个经典的场景来构建第一个脚本访问一个公开的测试网站完成登录操作然后验证登录成功后的页面元素。3.1 目标网站与用例设计为了演示我们使用互联网上一个经典的测试登录页面https://the-internet.herokuapp.com/login。这个网站是专门为测试自动化搭建的有简单的登录表单登录成功和失败都有明确的反馈非常适合练手。我们的测试用例很简单打开登录页面。在用户名输入框输入tomsmith。在密码输入框输入SuperSecretPassword!。点击登录按钮。等待页面跳转到成功页面。验证页面上是否存在“You logged into a secure area!”的成功消息。关闭浏览器。3.2 编写基础脚本骨架在项目文件夹里新建一个Python文件比如叫first_test.py。我们先导入必要的模块并写出脚本的基本结构。import asyncio from playwright.async_api import async_playwright async def main(): # 启动Playwright它负责管理浏览器进程 async with async_playwright() as p: # 启动一个Chromium浏览器实例headlessFalse表示有界面方便我们看 browser await p.chromium.launch(headlessFalse, slow_mo1000) # slow_mo让动作慢一点方便观察 # 创建一个新的浏览器上下文类似于一个独立的会话 context await browser.new_context() # 在新上下文中打开一个标签页 page await context.new_page() # 这里将填充我们的主要操作步骤 # 操作完成后暂停一会儿让我们看看结果 await page.pause() # 关闭浏览器 await browser.close() # 运行主函数 asyncio.run(main())代码解析与注意事项异步编程Playwright的Python API主要使用async/await语法。这是因为浏览器操作导航、点击、等待本质上是I/O密集型任务异步可以避免阻塞提升脚本效率尤其是在并行执行多个测试时。如果你不熟悉异步可以暂时把它看作一种固定写法async def定义异步函数await调用异步操作。async_playwright()这是一个异步上下文管理器是使用Playwright的入口。async with语句确保在使用完毕后正确清理资源。browser.launch这里我们启动了Chromium。参数headlessFalse意味着我们会看到一个真实的浏览器窗口打开这对于调试初期非常有用。slow_mo1000表示每个Playwright操作后延迟1000毫秒1秒让我们能看清发生了什么。正式运行时可以去掉或设为0。browser.new_context()上下文Context是一个重要的概念。它代表一个独立的“浏览器会话”拥有独立的cookie、localStorage、缓存等。每个测试用例通常使用独立的context保证用例间隔离。page.pause()这行代码会让脚本执行到此暂停并打开Playwright Inspector一个调试工具。这是一个非常有用的调试命令我们后面会详细讲。asyncio.run(main())这是Python 3.7运行异步主程序的标准方式。现在运行这个脚本在终端进入文件所在目录执行python first_test.py你应该能看到一个Chromium浏览器窗口打开然后停住。这说明环境搭建和基础启动成功了。3.3 填充核心操作导航、输入与点击接下来我们把注释# 这里将填充我们的主要操作步骤替换成实际的自动化逻辑。# 1. 导航到登录页面 await page.goto(https://the-internet.herokuapp.com/login) print(f页面标题: {await page.title()}) # 打印标题确认导航成功 # 2. 输入用户名 # 使用CSS选择器定位用户名输入框。通过查看页面HTML发现它的id是username username_input page.locator(#username) await username_input.fill(tomsmith) # fill()方法会先清空输入框再输入文本并触发相关事件如input, change # 3. 输入密码 password_input page.locator(#password) # id是password await password_input.fill(SuperSecretPassword!) # 4. 点击登录按钮 # 按钮的CSS选择器是button[typesubmit]或者直接用text选择器按文本定位 login_button page.locator(button[typesubmit]) await login_button.click() # click()方法会等待按钮可点击可见、未被禁用、未被遮挡后才执行点击 # 5. 等待导航完成跳转到成功页面 # 点击登录后页面会跳转显式等待一下新页面加载是个好习惯 await page.wait_for_url(**/secure) # 使用通配符匹配包含/secure的URL # 也可以等待某个特定元素出现作为页面加载完成的标志 await page.wait_for_selector(h4, statevisible) # 等待成功页面的h4标题出现 # 6. 验证成功消息 success_message page.locator(textYou logged into a secure area!) # 检查这个元素是否在页面上可见 if await success_message.is_visible(): print(✅ 登录成功成功消息已显示。) else: print(❌ 登录失败未找到成功消息。) # 7. 为了演示我们还可以截图保存成功页面 await page.screenshot(pathlogin_success.png) print(截图已保存为 login_success.png)关键点解析page.goto()用于导航到一个URL。它会自动等待页面触发load事件即主要资源加载完成。对于单页面应用SPA你可能需要结合page.wait_for_load_state(networkidle)来等待网络请求基本停止。page.locator()这是Playwright最核心的定位器API。它返回一个Locator对象代表页面上一个或一组元素。重要locator是“懒加载”的它不会立即去查找元素而是在你执行操作如click(),fill()时才去查找并操作。这意味着你可以先定义好定位器但实际的查找和等待逻辑由Playwright在操作时智能处理。选择器策略优先使用稳定的选择器如id、># 找到所有具有 .item 类的元素然后过滤出其中文本包含“重要”的 important_items page.locator(.item).filter(has_text重要) # 找到表单然后在表单内部找输入框 username_in_form page.locator(form#loginForm).locator(input[nameuser]) # 查找第n个匹配项从0开始 second_button page.locator(button).nth(1)3. 最佳实践与避坑优先使用get_by_*系列方法Playwright提供了更语义化的定位方法如page.get_by_role(button, nameSubmit)page.get_by_label(User Name)page.get_by_placeholder(Enter email)page.get_by_test_id(login-submit)。这些方法通常比原始的CSS/XPath更稳定因为它们与用户感知的UI方式角色、标签、占位符绑定而不是与实现细节CSS类名绑定。避免过度依赖文本UI文本是最容易变化的。如果必须用尽量用部分匹配或正则并考虑国际化。为可测试性添加属性如果你是和自己团队的开发合作可以推动他们在关键元素上添加>context await browser.new_context() # 启动追踪 await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) # ... 执行你的测试操作 ... # 停止追踪并将数据保存到文件 await context.tracing.stop(path“trace.zip”)测试运行后你会得到一个trace.zip文件。使用命令playwright show-trace trace.zip打开Trace Viewer。这是一个Web界面你可以像看视频一样逐帧回放整个测试过程。查看每一步操作前后的DOM快照、网络请求、控制台日志。查看任何时间点的页面截图。直观地定位是哪个操作失败了失败时页面是什么状态。这个功能对于排查“在我机器上是好的”这类问题以及进行测试失败后的根因分析是革命性的。5. 构建健壮脚本异常处理、复用与最佳实践一个只能在你本地电脑上跑一次的脚本价值有限。我们需要让它健壮、可维护、可复用。5.1 结构化与封装Page Object Model (POM)当测试用例越来越多时把页面定位和操作逻辑直接写在测试脚本里会变得难以维护。Page Object Model (POM) 是一种设计模式它将每个页面封装成一个类页面的元素定位器和基本操作作为类的方法。测试脚本只关心业务逻辑流。以我们的登录为例可以这样重构pages/login_page.pyfrom playwright.async_api import Page class LoginPage: def __init__(self, page: Page): self.page page self.username_input page.get_by_label(Username) # 使用更稳定的定位方式 self.password_input page.get_by_label(Password) self.submit_button page.get_by_role(button, nameLogin) async def navigate(self): await self.page.goto(https://the-internet.herokuapp.com/login) async def login(self, username: str, password: str): await self.username_input.fill(username) await self.password_input.fill(password) await self.submit_button.click()pages/secure_page.pyfrom playwright.async_api import Page class SecurePage: def __init__(self, page: Page): self.page page self.success_message page.locator(textYou logged into a secure area!) self.logout_button page.get_by_role(link, nameLogout) async def get_success_message(self) - str: # 返回元素的文本内容 return await self.success_message.text_content() async def logout(self): await self.logout_button.click()test_login.pyimport asyncio from playwright.async_api import async_playwright from pages.login_page import LoginPage from pages.secure_page import SecurePage async def test_successful_login(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # CI环境用无头模式 context await browser.new_context() page await context.new_page() # 初始化页面对象 login_page LoginPage(page) secure_page SecurePage(page) # 注意此时页面还在登录页 # 业务逻辑 await login_page.navigate() await login_page.login(tomsmith, SuperSecretPassword!) # 等待并验证跳转成功 await page.wait_for_url(**/secure) message await secure_page.get_success_message() assert secure area in message.lower() print(f登录成功消息{message}) # 登出 await secure_page.logout() await page.wait_for_url(**/login) await browser.close() asyncio.run(test_successful_login())采用POM后如果登录页面的HTML结构变了你只需要修改LoginPage类中的定位器所有用到这个页面的测试用例都不需要改动。这大大提升了代码的可维护性。5.2 异常处理与断言自动化脚本必须能妥善处理意外情况并给出清晰的失败信息。1. 使用 try…except 捕获特定异常try: # 尝试点击一个可能不存在的按钮 await page.get_by_role(button, name可能消失的按钮).click(timeout5000) # 设置较短超时 except TimeoutError: print(警告未找到‘可能消失的按钮’跳过此步骤。) # 可以在这里执行备用逻辑或者只是记录日志继续执行 except Exception as e: print(f操作‘可能消失的按钮’时发生未知错误: {e}) # 根据情况决定是重试、失败还是继续2. 使用Playwright的内置断言Playwright Test框架另一个工具用于编写结构化测试有丰富的断言库。但在纯脚本中我们可以用Python的assert语句并结合Playwright的检查方法。# 检查元素可见 assert await page.get_by_text(Welcome).is_visible() # 检查URL包含特定字符串 assert /dashboard in page.url # 检查输入框的值 value await page.locator(#email).input_value() assert value userexample.com3. 失败时截图这是最重要的调试信息之一。我们可以在任何可能失败的地方或者在except块中截图。import traceback try: # 一些可能失败的操作 await critical_operation() except Exception as e: # 获取当前时间戳生成唯一的文件名 import datetime timestamp datetime.datetime.now().strftime(%Y%m%d_%H%M%S) screenshot_path ferror_{timestamp}.png await page.screenshot(pathscreenshot_path, full_pageTrue) print(f操作失败已截图保存至: {screenshot_path}) print(traceback.format_exc()) # 打印完整的错误堆栈 raise # 重新抛出异常让脚本停止5.3 配置与复用Context、Fixture与数据驱动1. 浏览器上下文Context的妙用Context是一个独立的会话环境。你可以为不同的测试场景创建不同的Context实现完全隔离。你还可以在Context级别设置一些全局配置这些配置会对该Context下的所有Page生效# 创建一个带有特定视口、语言和权限的上下文 context await browser.new_context( viewport{width: 1920, height: 1080}, localezh-CN, # 设置浏览器语言环境 permissions[geolocation], # 授予地理位置权限 ignore_https_errorsTrue, # 忽略HTTPS证书错误用于测试环境 # 模拟设备如iPhone 13 # device_scale_factor3, # has_touchTrue, # is_mobileTrue, # user_agent... ) # 设置所有请求的公共头 await context.set_extra_http_headers({ Authorization: Bearer your_token, X-Custom-Header: value })2. 数据驱动测试如果你的测试逻辑相同只是输入数据和预期结果不同可以使用数据驱动。最简单的方式是使用pytest这样的测试框架配合参数化。纯脚本也可以实现import asyncio import csv test_data [ {username: tomsmith, password: SuperSecretPassword!, expected: True}, {username: wrong, password: wrong, expected: False}, ] async def run_login_test(username, password, should_succeed): # ... 具体的登录测试逻辑 ... success await page.locator(textYou logged into).is_visible() assert success should_succeed, f登录预期{should_succeed}实际{success} async def main(): for data in test_data: print(f测试用例: {data[username]}/{data[password]}) try: await run_login_test(data[username], data[password], data[expected]) print( 通过) except AssertionError as e: print(f 失败: {e}) asyncio.run(main())6. 进阶实战处理复杂场景与常见问题掌握了基础我们来看看如何应对真实项目中更复杂的场景。6.1 处理文件上传与下载文件上传Playwright处理文件上传极其简单不需要像Selenium那样模拟键盘操作。# 假设有一个 input typefile idavatar file_input page.locator(input#avatar) # 只需设置输入框的文件路径即可 await file_input.set_input_files(path/to/your/avatar.png) # 上传多个文件 await file_input.set_input_files([file1.png, file2.png]) # 如果要清空已选择的文件 await file_input.set_input_files([])文件下载处理下载需要监听‘download’事件。# 启动下载监听 async with page.expect_download() as download_info: # 执行会触发下载的操作比如点击一个下载链接 await page.get_by_text(Download Report).click() # 等待下载事件发生并获取Download对象 download await download_info.value # 等待下载完成并保存到指定路径 save_path f./downloads/{download.suggested_filename} # 使用浏览器建议的文件名 await download.save_as(save_path) print(f文件已下载到: {save_path})6.2 处理弹窗、新窗口与iframe弹窗Dialog监听并处理alert,confirm,prompt。# 在点击可能触发弹窗的按钮前先监听对话框事件 page.on(dialog, lambda dialog: dialog.accept()) # 自动接受确定所有对话框 # 或者更精细的控制 def handle_dialog(dialog): print(f对话框消息: {dialog.message}) if dialog.type alert: dialog.accept() elif dialog.type confirm: # 根据业务逻辑决定是accept()还是dismiss() dialog.accept() elif dialog.type prompt: dialog.accept(这是输入到prompt的文本) # 向prompt输入文本并确定 page.on(dialog, handle_dialog) # 然后执行触发弹窗的操作新窗口/标签页# 监听新页面窗口/标签页打开事件 async with page.context.expect_page() as new_page_info: await page.get_by_text(Open in new window).click() # 点击会打开新窗口的链接 new_page await new_page_info.value # 获取新页面的Page对象 # 现在可以像操作普通page一样操作new_page了 await new_page.wait_for_load_state() print(await new_page.title())iframePlaywright处理iframe非常优雅你可以像获取普通元素一样获取iframe然后在其内部进行定位。# 通过选择器定位到iframe元素本身 iframe_element page.frame_locator(iframe#myIframe) # 在iframe内部定位元素并操作 await iframe_element.locator(button.submit).click() # 或者通过name或URL获取frame对象 frame page.frame(nameframeName) # 或 page.frame(url**/widget) if frame: await frame.click(button)6.3 网络请求拦截与模拟这是Playwright相比Selenium的另一大杀器可以用于模拟后端API响应、修改请求、拦截资源等非常适合测试前端在不同数据下的表现或者跳过一些不必要的资源加载加速测试。# 1. 路由Route拦截请求并返回自定义响应 await page.route(**/api/user/profile, lambda route: route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({name: Mock User, age: 30}) # 返回模拟数据 )) # 2. 继续请求但修改请求如添加头信息 await page.route(**/*, lambda route: route.continue_(headers{ **route.request.headers, X-Mocked: true, # 添加自定义头 })) # 3. 拦截请求并中止例如阻止图片加载以加速 await page.route(**/*.{png,jpg,jpeg}, lambda route: route.abort()) # 执行你的测试所有对 /api/user/profile 的请求都会得到模拟数据 await page.goto(https://example.com)6.4 常见问题排查清单即使有了强大的工具脚本运行时还是会遇到各种问题。这里是一个快速排查清单问题现象可能原因排查步骤与解决方案元素找不到TimeoutError1. 选择器写错了或不唯一。2. 元素在iframe里。3. 元素是动态加载的还没出现。4. 页面状态不对没跳转完。1. 使用page.pause()和Inspector检查元素确认选择器。2. 检查是否存在iframe用frame_locator。3. 在操作前增加wait_for_selector。4. 在操作前等待页面状态稳定wait_for_load_state(‘networkidle’)。点击/输入没反应1. 元素被遮挡弹窗、其他元素。2. 元素是disabled状态。3. 需要触发其他事件如focus,input。1. 截图查看当前页面状态。用Inspector检查元素是否可交互。2. 检查元素属性。可能需要先触发前置条件。3. 尝试用page.evaluate()执行JS直接点击或先用element.focus()。脚本在CI上失败本地却成功1. 环境差异浏览器版本、屏幕分辨率。2. 网络延迟或超时设置太短。3. 资源加载失败如图片、CSS。1. 确保CI环境安装了正确的Playwright版本和浏览器(playwright install)。2. 增加全局或关键操作的超时时间。3. 使用Trace Viewer (show-trace) 回看失败瞬间的页面和网络状态。这是最有效的手段。页面加载慢或超时1. 网络问题或测试环境慢。2. 页面有大量未优化的资源。1. 适当增加page.goto()或wait_for_load_state的超时。2. 考虑使用page.route拦截并屏蔽非必要的第三方资源如分析脚本、广告。被网站检测为自动化脚本一些网站会检测WebDriver特征。1. Playwright默认已经尝试隐藏一些自动化特征但可以进一步配置await browser.new_context(ignore_https_errorsTrue, viewportNone, has_touchFalse)并使用更真实的User-Agent。2. 尝试使用playwright.chromium.launch_persistent_context加载一个真实的用户数据目录模拟真实浏览器会话。记住Trace Viewer是你的最佳朋友。任何在CI上难以复现的失败第一件事就是保存并查看Trace。它能告诉你失败时页面到底长什么样网络请求是什么状态几乎能解决90%的“玄学”问题。从Selenium切换到Playwright最深的体会是心智负担的减轻。以前写脚本很大一部分精力在和各种等待、异常、浏览器兼容性作斗争。现在用Playwright我可以更专注于测试用例的业务逻辑本身。它的API设计非常人性化auto-waiting和强大的调试工具让脚本的稳定性上了一个大台阶。对于新的自动化项目Playwright无疑是我的首选。对于老项目如果维护Selenium脚本的成本已经很高那么花点时间迁移到Playwright从长期看绝对是值得的投资。