多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Playwright安装与Python集成:探索跨浏览器测试的奇妙世界

Playwright安装与Python集成:探索跨浏览器测试的奇妙世界 1. 项目概述为什么我们需要Playwright如果你和我一样曾经被重复的网页操作、繁琐的测试流程或者复杂的数据抓取任务折磨得焦头烂额那你一定对“自动化”这个词充满渴望。几年前Selenium几乎是这个领域的代名词但用过的人都知道它像一台需要精心调校的老爷车——WebDriver版本要匹配、浏览器要对应、等待时间要手动设置一个不小心就抛锚。直到我遇到了Playwright那种感觉就像从手动挡换到了自动驾驶的特斯拉。Playwright是微软开源的一个现代化浏览器自动化库。它最吸引我的地方不是那些华丽的宣传语而是它真正解决了自动化测试和爬虫中的几个核心痛点跨浏览器一致性、可靠的自动等待机制以及对现代Web技术如单页应用、Shadow DOM的原生支持。它不仅仅是一个测试工具更像是一个全能的浏览器操控机器人。你可以用它来模拟用户的所有操作——点击、输入、滚动、拖拽甚至拦截和修改网络请求。对于Python开发者来说这意味着你可以用熟悉的语法让浏览器去做任何你想让它做的事情无论是自动化测试、数据抓取、网页监控还是RPA机器人流程自动化。这个项目标题“Playwright安装与Python集成探索跨浏览器测试的奇妙世界”其核心就是带你从零开始搭建一个稳定、高效的浏览器自动化环境。我们将不局限于简单的“安装-运行”而是深入探讨如何将Playwright无缝集成到你的Python工作流中并理解其背后“跨浏览器”能力的实现原理。无论你是测试工程师、数据分析师还是想解放双手的开发者这篇文章都将为你打开一扇新的大门。2. 环境准备与核心安装全解析在开始编写任何一行自动化代码之前一个干净、稳定的环境是成功的基石。很多新手在这里踩坑不是因为Playwright复杂而是因为基础环境没配置好。我将带你走一遍我实践过无数次的、最稳妥的路径。2.1 Python环境不仅仅是安装首先确保你有一个可用的Python环境。我强烈建议使用Python 3.8或更高版本因为Playwright充分利用了这些版本中的新异步特性。不要使用系统自带的Python以免权限和包管理冲突。我的首选方案是使用Miniconda或虚拟环境venv# 使用conda创建独立环境推荐便于管理不同项目的依赖 conda create -n playwright-env python3.10 conda activate playwright-env # 或者使用Python自带的venv python -m venv playwright-venv # Windows激活 playwright-venv\Scripts\activate # Linux/Mac激活 source playwright-venv/bin/activate创建虚拟环境的好处是隔离性。你在这个环境里安装的任何包都不会影响系统或其他项目避免了版本地狱。激活环境后你的命令行提示符前通常会显示环境名称如(playwright-env)。接下来是包管理工具pip的升级。虽然不是必须但一个最新版的pip能减少很多依赖解析错误。python -m pip install --upgrade pip2.2 Playwright库安装一行命令背后的故事安装Playwright Python库本身非常简单pip install playwright这条命令会从PyPI下载并安装playwright这个Python包。但请注意这仅仅安装了控制浏览器的“驱动程序”或“客户端库”。它本身不包含浏览器内核。这就是为什么很多人安装后直接运行代码会报错提示找不到浏览器。安装完成后我习惯立刻验证一下安装是否成功以及查看版本python -c “import playwright; print(playwright.__version__)”这能快速确认库是否可被正确导入。2.3 浏览器驱动安装核心步骤与避坑指南这是最关键也最容易出问题的一步。Playwright需要对应的浏览器二进制文件来实际执行操作。你需要运行playwright install这条命令会做以下几件事下载浏览器默认会下载ChromiumChrome/Edge的开源核心、Firefox和WebKitSafari的开源核心的特定版本。这些版本是经过Playwright团队严格测试保证API稳定的。安装依赖在Linux系统上它可能会自动安装一些系统库依赖比如libwoff2等字体库。配置路径将浏览器可执行文件放置在Playwright的私有缓存目录中通常在用户主目录下的.cache/ms-playwright并建立索引。你必须知道的注意事项和高级用法注意playwright install默认会下载所有三个浏览器Chromium, Firefox, WebKit。如果你的网络环境不佳或者磁盘空间紧张这可能会耗时较长总计约1GB。你可以选择只安装需要的浏览器playwright install chromium # 只安装Chromium playwright install firefox # 只安装Firefox playwright install webkit # 只安装WebKit安装过程常见问题与解决方案下载速度慢或失败Playwright默认从微软的Azure存储下载国内网络可能不稳定。解决方案是使用镜像源或手动下载。方案A使用环境变量指定下载镜像如果镜像源可用# 在运行安装命令前设置示例镜像地址需自行寻找可用的 set PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright playwright install方案B手动下载并放置最可靠首先让Playwright尝试安装它会在错误信息或日志中打印出它试图下载的文件的确切URL。复制这个URL。使用下载工具如wget, curl或浏览器下载该文件。找到Playwright的浏览器缓存目录~/.cache/ms-playwright或%USERPROFILE%\AppData\Local\ms-playwright。根据浏览器和版本将下载的文件放入对应的子目录中。通常需要重命名文件以匹配Playwright期望的名称如chrome-win.zip。再次运行playwright install它会检查文件已存在并跳过下载。权限错误Linux/Mac确保你对缓存目录有读写权限。有时需要手动创建目录或使用sudo不推荐最好修复用户权限。安装后验证安装完成后运行一个快速检查命令确保一切就绪playwright --version这会输出Playwright命令行工具的版本。更重要的验证是运行一个简单的测试脚本我们马上就会进行。2.4 集成开发环境IDE配置建议工欲善其事必先利其器。一个好的IDE能极大提升开发效率。VS Code我的主力选择。安装官方Python扩展和Playwright Test for VSCode扩展。后者提供了强大的测试运行、调试和代码生成功能。在设置中将Python解释器路径指向你刚创建的虚拟环境。PyCharm专业Python IDE。在File - Settings - Project - Python Interpreter中添加你的虚拟环境路径。它同样对Playwright有良好的代码补全支持。配置好IDE后创建一个新的Python文件我们就可以正式开始探索了。3. 第一个脚本从“Hello World”到理解核心API让我们摒弃复杂的理论直接动手写代码。第一个脚本的目标不是完成多么复杂的任务而是让你感受Playwright的工作流程并理解几个最核心的对象。3.1 同步与异步两种编程模式的选择Playwright的Python API提供了两种风格同步sync_api和异步async_api。对于初学者和大多数自动化脚本我强烈建议从同步API开始因为它更符合线性的思维习惯写起来像普通的脚本。异步API性能更高适合构建复杂的、高并发的应用但学习曲线更陡峭。同步模式示例from playwright.sync_api import sync_playwright def main(): # 1. 启动Playwright上下文管理器 with sync_playwright() as p: # 2. 启动浏览器实例这里选择ChromiumheadlessFalse表示显示浏览器界面 browser p.chromium.launch(headlessFalse) # 3. 创建一个新的浏览器上下文类似于一个独立的隐身会话 context browser.new_context() # 4. 在上下文中打开一个新页面 page context.new_page() # 5. 导航到目标网址 page.goto(“https://www.example.com”) # 6. 获取页面标题并打印 print(f“页面标题是{page.title()}”) # 7. 等待5秒方便我们观察 page.wait_for_timeout(5000) # 8. 按顺序关闭资源页面 - 上下文 - 浏览器 page.close() context.close() browser.close() if __name__ “__main__”: main()逐行解析与核心概念sync_playwright()这是同步模式的入口点。使用with语句可以确保在代码块执行完毕后Playwright的资源会被正确清理即使中间发生异常。这是一个好习惯。p.chromium.launch(headlessFalse)启动一个Chromium浏览器进程。p.chromium你也可以用p.firefox或p.webkit来启动其他浏览器。这就是“跨浏览器”能力的源头——同一套API切换一个单词就能换浏览器。headlessFalse无头模式意味着浏览器在后台运行没有图形界面。默认是True无头模式对于调试阶段设为False可以看到浏览器在做什么非常直观。browser.new_context()创建一个新的浏览器上下文。这是一个关键概念。你可以把它想象成一个独立的浏览器会话它拥有独立的cookie、本地存储、缓存和证书。如果你需要模拟多个用户登录创建多个context是最佳实践它们彼此隔离。context.new_page()在上下文中打开一个新的标签页页面对象。一个上下文可以有多个页面。page.goto()导航到指定的URL。它会自动等待页面触发load事件。page.title()获取当前页面的标题。这是一个简单的页面属性访问。page.wait_for_timeout(5000)强制等待5秒5000毫秒。请注意这是“硬等待”是最后的手段。Playwright的强大之处在于它的“自动等待”我们应尽量避免使用它。这里只是为了演示时让你能看到页面。关闭顺序虽然with sync_playwright()会最终清理但显式地按页面-上下文-浏览器的顺序关闭是一个好习惯尤其是在循环或创建大量实例时。运行这个脚本你应该能看到一个Chromium浏览器窗口打开访问example.com停留5秒后关闭。恭喜你你的第一个Playwright自动化程序成功了3.2 异步模式初探为了完整性这里也给出异步模式的等效代码。如果你熟悉asyncio可以在需要时使用。import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) context await browser.new_context() page await context.new_page() await page.goto(“https://www.example.com”) print(f“页面标题是{await page.title()}”) await page.wait_for_timeout(5000) await page.close() await context.close() await browser.close() asyncio.run(main())可以看到主要区别在于async/await关键字。在本文后续部分如无特别说明我们都使用同步API。4. 元素定位与网页交互的基石自动化脚本的核心是找到页面上的元素按钮、输入框、链接等并与之交互。Playwright提供了多种强大且稳定的定位器Locator策略。我的经验是定位器的选择直接决定了脚本的健壮性。一个脆弱的定位器会让你的脚本在页面稍有改动时就崩溃。4.1 定位器策略详解与最佳实践Playwright的定位器是惰性的它定义了一个查找元素的方法但只有在执行操作如click、fill时才会真正去查找。更重要的是它内置了自动等待和重试机制会等待元素变得可操作可见、启用、稳定。1. 最推荐按角色和文本定位 (get_by_role,get_by_text)这是最接近用户视角的方式可读性最好通常也最稳定。# 点击一个名为“登录”的按钮 page.get_by_role(“button”, name“登录”).click() # 在一个名为“用户名”的文本框中输入 page.get_by_role(“textbox”, name“用户名”).fill(“myuser”) # 点击包含“下一步”文本的元素 page.get_by_text(“下一步”).click() # 使用正则表达式匹配部分文本 import re page.get_by_text(re.compile(r“订单号\d”)).click()优势不依赖易变的CSS类名或ID直接对应UI文本和语义角色。即使前端重构样式只要按钮文本和角色不变脚本就依然有效。get_by_role需要元素有正确的ARIA角色role属性。现代前端框架如React, Vue通常会正确生成。你可以通过浏览器开发者工具的“元素”面板查看元素的role。2. 最精准按测试ID定位 (get_by_test_id)这是与前端开发协作的最佳模式。约定在元素上添加一个专用的测试属性如>!-- 前端代码 -- button># Python自动化代码 page.get_by_test_id(“submit-login”).click()优势绝对稳定专为测试设计完全不受样式或布局变更影响。强烈建议在团队项目中推行此规范。3. 传统但必要CSS选择器与XPath (locator)当以上方法不适用时我们才退而求其次使用CSS或XPath。# CSS选择器通过ID、类、属性等 page.locator(“#search-input”).fill(“query”) # ID page.locator(“.submit-btn”).click() # 类名 page.locator(“input[type‘email’]”).fill(“testexample.com”) # 属性 # XPath功能强大但通常更脆弱 page.locator(“//button[contains(class, ‘primary’)]”).click() page.locator(“//div[id‘container’]//a[text()‘详情’]”).click()CSS选择器建议优先使用ID其次使用具有明确语义的类名如.cart-button而非.btn-primary.mt-4。XPath警告尽量避免使用绝对路径如/html/body/div[3]/button和依赖元素顺序的索引如//div[5]这些在页面结构变化时极易失效。使用相对路径和属性匹配。4. 其他实用定位器# 通过关联的label标签文本定位输入框 page.get_by_label(“密码”).fill(“secret”) # 通过占位符文本定位 page.get_by_placeholder(“请输入手机号”).fill(“13800138000”)4.2 定位器的组合与过滤Playwright定位器可以链式调用和组合以应对复杂场景。# 组合先找到列表再找其中的特定项 product_list page.locator(“.product-list”) first_product product_list.locator(“.item”).first first_product.click() # 过滤在找到的一组元素中筛选出符合条件的 # 点击第一个状态为“有效”的按钮 page.locator(“button”).filter(has_text“有效”).first.click() # 找到包含特定文本的列表项 active_item page.locator(“tr”).filter(has_text“进行中”) # 父子/兄弟关系定位 parent_div page.locator(“div.parent”) child_span parent_div.locator(“span.child”) # 子元素 second_sibling page.locator(“ul li”).nth(1) # 第二个子元素索引从0开始4.3 调试定位器使用Playwright Inspector写定位器最头疼的就是不知道写对了没有。Playwright内置了一个强大的图形化调试工具——Inspector。有两种方式启动环境变量方式推荐在运行脚本前设置PWDEBUG1。# Linux/Mac PWDEBUG1 python your_script.py # Windows (Command Prompt) set PWDEBUG1 python your_script.py # Windows (PowerShell) $env:PWDEBUG1; python your_script.py设置后运行脚本会以“有头”模式打开浏览器并自动弹出Playwright Inspector窗口。你可以逐步执行使用“Step”按钮一步步运行代码。拾取元素点击“Pick locator”按钮然后在页面上点击元素Inspector会自动生成推荐的定位器代码。查看日志查看所有Playwright命令的执行详情和结果。代码方式在launch方法中传入devtoolsTrue。browser p.chromium.launch(headlessFalse, devtoolsTrue)这会打开浏览器的开发者工具但不如Inspector功能专一。实操心得在开发初期我几乎总是开着PWDEBUG1。用“拾取元素”功能生成定位器初稿然后根据其建议通常是get_by_role或get_by_test_id去优化我的代码。这比手动在开发者工具里猜选择器快十倍。5. 核心交互操作模拟真实用户行为定位到元素后下一步就是与之交互。Playwright的API设计得非常直观模拟了真实用户的几乎所有操作。5.1 基础操作点击、输入、选择# 1. 点击 - 最常用的操作 page.get_by_role(“button”, name“提交”).click() # 支持多种点击选项 page.locator(“#btn”).click(button“right”) # 右键点击 page.locator(“#btn”).click(forceTrue) # 即使元素被遮挡也强制点击 # 2. 输入文本 page.locator(“#username”).fill(“admin”) # fill() 会先清空输入框再输入 page.locator(“#comment”).type(“Hello, World!“, delay100) # type() 模拟逐个字符输入delay是毫秒间隔 # 3. 处理下拉框Select # 假设有一个 select id“city”.../select page.locator(“#city”).select_option(“beijing”) # 通过value选择 page.locator(“#city”).select_option(label“北京市”) # 通过显示的文本选择 # 4. 勾选复选框和单选框 page.locator(“#agree-terms”).check() # 勾选 page.locator(“#newsletter”).uncheck() # 取消勾选 page.locator(“input[name‘gender’][value‘female’]”).check() # 单选 # 5. 上传文件 page.locator(“input[type‘file’]”).set_input_files(“/path/to/your/file.jpg”) # 上传多个文件 page.locator(“input[type‘file’]”).set_input_files([“file1.jpg”, “file2.png”])5.2 高级交互悬停、拖放、键盘# 1. 鼠标悬停 - 常用于触发下拉菜单 page.locator(“#menu”).hover() # 2. 拖放元素 page.locator(“#source”).drag_to(page.locator(“#target”)) # 或者分步操作 page.locator(“#source”).hover() page.mouse.down() page.locator(“#target”).hover() page.mouse.up() # 3. 键盘操作 page.locator(“#input”).press(“Tab”) # 按下Tab键 page.locator(“#input”).press(“ShiftEnter”) # 组合键 # 更复杂的键盘输入 page.keyboard.type(“Hello”) # 模拟键盘输入 page.keyboard.press(“Backspace”) page.keyboard.down(“Control”) # 按住Control键 page.keyboard.press(“A”) # 按下A (CtrlA) page.keyboard.up(“Control”) # 松开Control键5.3 等待的艺术告别硬编码的sleep这是Playwright相对于Selenium最大的优势之一。你几乎不需要使用time.sleep()。1. 自动等待隐式等待Playwright在执行操作如click,fill,check前会自动等待元素满足一系列条件元素被附加到DOM元素可见非隐藏非0尺寸元素启用非disabled元素稳定停止动画 默认超时时间是30秒可以通过browser_context.set_default_timeout()修改。2. 显式等待推荐对于非元素操作或者需要等待特定状态时使用显式等待。# 等待页面导航完成例如点击链接后 page.click(“a#next-page”) page.wait_for_url(“**/dashboard”) # 等待URL包含dashboard # 等待元素出现/可见/隐藏 page.wait_for_selector(“.success-message”, state“visible”) # 等待成功提示出现 page.wait_for_selector(“.loading-spinner”, state“hidden”) # 等待加载动画消失 # 等待特定事件 page.wait_for_event(“load”) # 等待页面load事件 page.wait_for_function(“window.innerWidth 1000”) # 等待JS条件成立 # 等待网络请求 with page.expect_response(“**/api/data.json”) as response_info: page.click(“#load-data”) response response_info.value print(response.json()) # 获取响应数据3. 自定义等待条件from playwright.sync_api import expect # 使用expect断言可读性更好 expect(page.locator(“.status”)).to_have_text(“完成”) expect(page).to_have_url(“https://example.com/home”) expect(page.locator(“#list li”)).to_have_count(10) # 等待列表有10项我的避坑经验永远优先使用Playwright的内置等待自动等待和wait_for_*方法。只有在你明确知道需要固定延迟例如等待一个非网络、非DOM的第三方动画时才使用page.wait_for_timeout()并且要加上清晰的注释说明原因。6. 处理复杂页面结构iframe、Shadow DOM与新窗口现代网页充满了动态内容和复杂组件自动化脚本必须能应对这些挑战。6.1 征服iframeiframe内联框架是一个独立的HTML文档嵌入。你不能直接用主页面的定位器去操作iframe内的元素。# 方法1通过属性定位iframe然后切换到其上下文 iframe_element page.frame_locator(“iframe[name‘payment’]”) # 通过name # 或者 page.frame_locator(“#my-iframe”) # 通过ID # 在iframe上下文中定位元素并操作 iframe_element.locator(“button#pay-now”).click() # 方法2获取Frame对象适用于需要调用frame方法的情况 frame page.frame(name“payment”) # 通过name获取Frame对象 if frame: frame.click(“button#pay-now”) else: # 通过URL匹配获取 for f in page.frames: if “payment” in f.url: f.click(“button#pay-now”) break6.2 穿透Shadow DOMShadow DOM允许将封装的“影子”DOM树附加到元素实现样式和标记的封装。Playwright使用语法来穿透Shadow DOM边界。# 假设有一个自定义组件 my-component内部有Shadow DOM和一个input # 错误的做法page.locator(“my-component input”) 找不到 # 正确的做法使用 穿透 page.locator(“my-component input”).fill(“value inside shadow”) # 可以连续穿透多层 page.locator(“div#host shadow-rootopen span input”).click()技巧在浏览器开发者工具中需要先在Settings - Preferences里开启Show user agent shadow DOM才能查看和复制Shadow DOM内的元素选择器。6.3 管理多页面和弹出窗口点击一个链接或按钮可能会打开新标签页或弹出窗口。# 在点击可能打开新窗口的链接前监听‘popup’事件 with page.expect_popup() as popup_info: page.click(“a[target‘_blank’]”) # 或者触发window.open的按钮 new_page popup_info.value # 获取新页面的Page对象 print(f“新页面标题{new_page.title()}”) # 在新页面上操作 new_page.fill(“#search”, “something”) # 操作完成后可以关闭它 new_page.close() # 切换回原页面继续操作 page.bring_to_front() # 将原页面提到前台7. 高级特性与实战技巧掌握了基础我们来探索一些能让你的自动化脚本更强大、更智能的高级功能。7.1 网络请求拦截与模拟这是Playwright的王牌功能之一可以极大提升脚本效率和灵活性。# 1. 拦截并修改请求例如修改请求头 def handle_request(route, request): # 修改请求头模拟移动设备 headers request.headers headers[‘User-Agent’] ‘Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) ...’ # 继续发出修改后的请求 route.continue_(headersheaders) page.route(“**/*”, handle_request) # 拦截所有请求 # 2. 拦截并直接返回模拟响应Mock API def mock_response(route, request): if “/api/user” in request.url: route.fulfill( status200, content_type“application/json”, bodyjson.dumps({“name”: “Mock User”, “id”: 123}) ) else: route.continue_() page.route(“**/api/**”, mock_response) # 3. 阻塞某些请求如图片、样式表以加速测试 def block_assets(route, request): if request.resource_type in [“image”, “stylesheet”, “font”]: route.abort() else: route.continue_() page.route(“**/*”, block_assets)7.2 执行JavaScript代码有时需要通过执行JS来获取数据或操作页面。# 1. 在页面上下文中执行JS并返回值 dimensions page.evaluate(“””() { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio }; }“””) print(dimensions) # 2. 在元素上下文中执行JS href page.locator(“a#link”).evaluate(“element element.href”) print(href) # 3. 注入JS脚本文件 page.add_script_tag(path“./inject.js”) # 从文件注入 page.add_script_tag(content“window.myVar ‘injected’;”) # 注入代码片段7.3 设备模拟与地理位置测试响应式设计或需要特定地理位置的场景。from playwright.sync_api import sync_playwright def run(): with sync_playwright() as p: # 模拟iPhone 12 iphone_12 p.devices[“iPhone 12”] browser p.chromium.launch(headlessFalse) # 创建上下文时传入设备参数 context browser.new_context( **iphone_12, locale“zh-CN”, # 设置语言 timezone_id“Asia/Shanghai”, # 设置时区 geolocation{“longitude”: 116.397, “latitude”: 39.916}, # 设置北京地理位置 permissions[“geolocation”] # 允许地理位置权限 ) page context.new_page() page.goto(“https://maps.example.com”) # 页面现在会认为自己在iPhone 12上位于北京 page.screenshot(path“iphone-browser.png”) context.close() browser.close()7.4 文件下载与上传处理# 1. 处理文件下载需要监听‘download’事件 with page.expect_download() as download_info: page.click(“a#download-report”) # 点击触发下载的链接 download download_info.value # 等待下载完成并保存到指定路径 save_path f“./downloads/{download.suggested_filename}” download.save_as(save_path) print(f“文件已下载到{save_path}”) # 2. 文件上传前面基础操作已介绍这里是监听上传事件 def handle_file_chooser(file_chooser): file_chooser.set_files(“./my-file.pdf”) # 在点击上传按钮前监听文件选择器事件 page.on(“filechooser”, handle_file_chooser) page.click(“input[type‘file’]”) # 这会触发文件选择器进而触发上面的处理函数8. 项目实战构建一个健壮的自动化测试脚本让我们将以上所有知识融合编写一个模拟用户登录、搜索并断言结果的完整脚本。这个脚本将体现等待策略、错误处理和日志记录等工程化实践。import logging from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError import sys # 配置日志方便调试和记录 logging.basicConfig( levellogging.INFO, format‘%(asctime)s - %(levelname)s - %(message)s’, handlers[ logging.FileHandler(“automation.log”), logging.StreamHandler(sys.stdout) ] ) logger logging.getLogger(__name__) def test_baidu_search(): “”“一个完整的百度搜索自动化测试示例”“” with sync_playwright() as p: # 启动浏览器设置视窗大小记录日志 browser p.chromium.launch(headlessFalse, slow_mo1000) # slow_mo让操作变慢方便观察 context browser.new_context(viewport{‘width’: 1920, ‘height’: 1080}) page context.new_page() try: logger.info(“测试开始访问百度首页”) # 导航到百度等待网络空闲状态 page.goto(“https://www.baidu.com”, wait_until“networkidle”) # 断言页面标题应包含“百度” assert “百度” in page.title(), f“页面标题异常{page.title()}” logger.info(“成功访问百度首页”) # 定位搜索框并输入关键词 - 使用更稳定的定位器 search_box page.get_by_role(“textbox”, name“百度一下”) # 显式等待元素可见并可交互 search_box.wait_for(state“visible”) search_box.fill(“Playwright自动化测试”) logger.info(“已在搜索框输入关键词”) # 定位搜索按钮并点击 search_button page.get_by_role(“button”, name“百度一下”) search_button.click() logger.info(“已点击搜索按钮”) # 等待搜索结果页面加载完成URL应变化 page.wait_for_url(“**/s?**”, timeout10000) logger.info(“已跳转到搜索结果页”) # 等待第一个搜索结果出现 first_result page.locator(“#content_left h3 a”).first first_result.wait_for(state“visible”, timeout10000) # 获取第一个结果的文本和链接 result_text first_result.text_content() result_href first_result.get_attribute(“href”) logger.info(f“第一个搜索结果{result_text[:50]}... - {result_href}”) # 断言搜索结果应包含相关关键词 assert “playwright” in result_text.lower() or “自动化” in result_text.lower(), \ f“搜索结果不相关{result_text}” logger.info(“搜索结果相关性断言通过”) # 可选截图保存证据 page.screenshot(path“baidu_search_result.png”, full_pageTrue) logger.info(“已截图保存搜索结果页”) logger.info(“ 测试用例执行成功”) return True except PlaywrightTimeoutError as e: logger.error(f“操作超时{e}”) # 超时时截图便于排查 page.screenshot(path“error_timeout.png”, full_pageTrue) return False except AssertionError as e: logger.error(f“断言失败{e}”) page.screenshot(path“error_assertion.png”, full_pageTrue) return False except Exception as e: logger.error(f“发生未知错误{e}”, exc_infoTrue) page.screenshot(path“error_unknown.png”, full_pageTrue) return False finally: # 无论成功失败都关闭浏览器 logger.info(“清理资源关闭浏览器”) context.close() browser.close() if __name__ “__main__”: success test_baidu_search() sys.exit(0 if success else 1)这个脚本体现的工程化要点结构化与函数化将逻辑封装在函数内清晰可复用。全面的日志记录使用logging模块记录关键步骤和错误便于事后分析。健壮的错误处理使用try...except捕获超时、断言失败和其他异常并在失败时截图保存现场。明确的等待策略结合wait_until、wait_for和自动等待避免使用time.sleep。资源清理在finally块中确保浏览器被关闭避免资源泄漏。使用稳定的定位器优先使用get_by_role和get_by_text。可配置的参数headless、slow_mo、timeout等都可以提取为配置变量。9. 集成到Python项目与持续集成单个脚本很好但真正的力量在于将其集成到你的开发流程中。9.1 使用Pytest进行测试组织Playwright官方推荐使用pytest作为测试运行器并提供了强大的插件pytest-playwright。安装pip install pytest pytest-playwright编写一个Pytest测试用例创建一个文件test_baidu.py。import re import pytest from playwright.sync_api import Page, expect pytest.fixture(scope“function”) def page(browser): “”“为每个测试函数提供一个干净的页面。”“” context browser.new_context() page context.new_page() yield page context.close() def test_baidu_search_title(page: Page): “”“测试百度首页标题。”“” page.goto(“https://www.baidu.com”) expect(page).to_have_title(re.compile(r“百度一下”)) def test_baidu_search_functionality(page: Page): “”“测试百度搜索功能。”“” page.goto(“https://www.baidu.com”) page.get_by_role(“textbox”, name“百度一下”).fill(“Playwright”) page.get_by_role(“button”, name“百度一下”).click() # 等待搜索结果出现 expect(page.locator(“#content_left”)).to_be_visible() # 断言搜索结果包含关键词 expect(page.locator(“#content_left”)).to_contain_text(“Playwright”) def test_baidu_logo_present(page: Page): “”“测试百度Logo存在。”“” page.goto(“https://www.baidu.com”) logo page.locator(“#lg”) expect(logo).to_be_visible()运行测试# 运行所有测试 pytest # 运行特定文件 pytest test_baidu.py # 运行并显示详细日志 pytest -v # 运行失败时自动打开Playwright Inspector调试 pytest --headed --slowmo1000Pytest会自动管理浏览器的启动和关闭通过browserfixture并生成漂亮的测试报告。9.2 配置管理与参数化将浏览器类型、基础URL、超时时间等配置化。 创建conftest.py文件import pytest from playwright.sync_api import Playwright def pytest_addoption(parser): parser.addoption( “--browser”, action“store”, default“chromium”, help“browser to run tests: chromium, firefox, webkit” ) parser.addoption( “--headless”, action“store”, default“True”, help“run in headless mode: True or False” ) parser.addoption( “--base-url”, action“store”, default“https://www.baidu.com”, help“base url for tests” ) pytest.fixture(scope“session”) def browser_type_launch_args(pytestconfig): return {“headless”: pytestconfig.getoption(“headless”) “True”} pytest.fixture(scope“session”) def browser_type(pytestconfig, playwright: Playwright): browser_name pytestconfig.getoption(“browser”) if browser_name “chromium”: return playwright.chromium elif browser_name “firefox”: return playwright.firefox elif browser_name “webkit”: return playwright.webkit else: raise ValueError(f“Unsupported browser: {browser_name}”) pytest.fixture(scope“session”) def browser(browser_type, browser_type_launch_args): browser browser_type.launch(**browser_type_launch_args) yield browser browser.close() pytest.fixture def base_url(pytestconfig): return pytestconfig.getoption(“base-url”)然后在测试中就可以使用base_urlfixture了。这样你可以通过命令行参数轻松切换测试环境pytest --browserfirefox --headlessFalse --base-urlhttps://www.google.com9.3 集成到CI/CD以GitHub Actions为例在项目根目录创建.github/workflows/playwright.ymlname: Playwright Tests on: [push, pull_request] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-pythonv4 with: python-version: ‘3.10’ - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-playwright - name: Install Playwright Browsers run: playwright install --with-deps chromium firefox webkit - name: Run tests (Chromium) run: pytest --browserchromium --headlessTrue - name: Run tests (Firefox) run: pytest --browserfirefox --headlessTrue - name: Run tests (WebKit) run: pytest --browserwebkit --headlessTrue - name: Upload test artifacts (if failed) if: failure() uses: actions/upload-artifactv3 with: name: playwright-screenshots path: | **/*.png **/*.log这个工作流会在每次代码推送或拉取请求时在Ubuntu系统上安装依赖和浏览器然后依次在Chromium、Firefox和WebKit上运行测试实现真正的跨浏览器测试。如果测试失败会自动上传截图和日志供你分析。10. 性能优化与最佳实践总结最后分享一些让Playwright脚本运行得更快、更稳定的经验。1. 复用浏览器上下文避免重复登录每次启动浏览器和建立上下文都很耗时。对于需要登录的测试登录一次然后复用这个上下文。# 在conftest.py或setup中创建一个已登录的上下文 pytest.fixture(scope“session”) def logged_in_context(browser): context browser.new_context() page context.new_page() page.goto(“https://example.com/login”) # ... 执行登录操作 # 保存登录状态Cookies, Storage context.storage_state(path“./auth_state.json”) yield context context.close() # 在测试中直接使用已登录的上下文 def test_with_logged_in_user(logged_in_context): page logged_in_context.new_page() # 新页面也继承了登录状态2. 并行执行测试Pytest支持并行运行测试可以大幅缩短测试套件的总执行时间。pip install pytest-xdist pytest -n auto # 自动根据CPU核心数分配worker3. 使用无头模式Headless和禁用不必要的功能在CI/CD或生产环境运行时务必使用无头模式并禁用图片、视频等加载以提升速度。browser p.chromium.launch( headlessTrue, args[ ‘--disable-gpu’, ‘--disable-dev-shm-usage’, # 克服Docker内存限制 ‘--no-sandbox’, ] ) context browser.new_context( viewport{‘width’: 1920, ‘height’: 1080}, ignore_https_errorsTrue, # 忽略HTTPS证书错误测试环境 java_script_enabledTrue, # 拦截非必要资源 bypass_cspTrue, )4. 合理设置超时和等待策略全局超时设置要合理避免因个别元素加载慢导致整个测试套件超时。# 在context或page级别设置合理的默认超时 context.set_default_timeout(30000) # 30秒 context.set_default_navigation_timeout(60000) # 导航60秒 # 对于已知的慢操作可以单独设置更长的超时 page.locator(“.slow-element”).click(timeout60000)5. 定期更新Playwright和浏览器Playwright团队会定期发布更新修复bug并提升性能。定期更新能获得更好的稳定性和新功能。pip install --upgrade playwright playwright install --force # 强制更新浏览器到最新兼容版本6. 编写可维护的定位器使用Page Object模式将页面元素和操作封装成类提高代码复用性和可读性。定位器集中管理将所有的定位器字符串CSS选择器、文本等放在一个常量文件或配置文件中一旦页面变化只需修改一处。优先使用语义化定位器get_by_role、get_by_text、get_by_test_id远比脆弱的CSS路径稳定。从安装配置到高级实战我们完整地探索了Playwright与Python集成的世界。它不仅仅是一个测试工具更是一个强大的浏览器自动化平台。我个人的体会是一旦你习惯了它的“自动等待”和清晰的API就很难再回去用其他工具了。开始可能会在元素定位上花些时间但利用好Inspector和遵循最佳实践这个成本会越来越低。最重要的是动手去写从一个简单的脚本开始逐步增加复杂度你会发现自己自动化一切网页操作的能力在飞速增长。如果在实践中遇到任何问题Playwright官方文档非常详尽社区也很活跃绝大多数坑都能找到解决方案。
返回列表