news 2026/6/30 20:07:08

Python+Playwright自动化测试入门:环境搭建与核心操作详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python+Playwright自动化测试入门:环境搭建与核心操作详解

1. 项目概述:为什么选择 Python + Playwright 作为你的 Web 自动化测试起点?

如果你正在为 Web 应用的回归测试、兼容性验证或者重复性操作而烦恼,手动点点点不仅效率低下,还容易出错。自动化测试是解决这个问题的标准答案,但面对 Selenium、Cypress、Puppeteer 等众多框架,新手往往会感到迷茫。今天,我想和你深入聊聊Python + Playwright这个组合,它是我在经历了多个项目后,认为目前最适合快速上手、功能强大且维护成本较低的 Web 自动化方案。

简单来说,Playwright 是一个由微软开源的现代化浏览器自动化库。它支持 Chromium、Firefox 和 WebKit(Safari 的渲染引擎)三大浏览器引擎,这意味着你可以用一套脚本测试你的网站在 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 等新库的良好支持。

安装方式选择:

  1. 官方安装包:从 Python 官网下载安装是最直接的方式。安装时务必勾选“Add Python to PATH”选项,这能让你在命令行中直接使用pythonpip命令,避免后续配置环境变量的麻烦。
  2. 使用 Miniconda/Anaconda:如果你需要管理多个相互隔离的 Python 项目环境(比如一个项目用 Python 3.8,另一个用 3.11),Conda 是一个更优秀的选择。它创建的是独立的虚拟环境,库的依赖不会互相冲突。对于自动化测试项目,我通常推荐使用Miniconda,它更轻量。

验证安装:打开你的终端(Windows 上是 CMD 或 PowerShell,macOS/Linux 上是 Terminal),输入以下命令:

python --version # 或 python3 --version

如果正确显示版本号(如Python 3.10.11),说明安装成功。同时检查 pip(Python 包管理工具):

pip --version

注意:在 macOS 和部分 Linux 系统上,系统自带的 Python 2 可能仍占用了python命令。确保你安装的 Python 3 可以通过python3调用。为了统一,在本文后续命令中,我将使用pythonpip,如果你的环境是python3pip3,请自行替换。

2.2 集成开发环境(IDE)的选择与配置

写 Python 脚本,一个好用的 IDE 能事半功倍。我的首选是Visual Studio Code (VS Code),因为它免费、轻量、插件生态丰富,对 Python 和 Playwright 的支持都非常好。

VS Code 必备插件配置:

  1. Python:微软官方出品,提供代码补全、智能提示、调试、linting 等核心功能。
  2. Pylance:强大的语言服务器,比默认的 Jedi 提供更快的补全和类型检查。
  3. Playwright Test for VSCode:Playwright 官方插件,提供测试列表、运行、调试的图形化界面,非常方便。
  4. Code Runner:可以快速运行当前文件或选中的代码片段。

安装好 Python 插件后,在 VS Code 中按Ctrl+Shift+P(Windows/Linux)或Cmd+Shift+P(macOS),输入 “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_HOST=https://npmmirror.com/mirrors/playwright(Windows)或export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright(macOS/Linux),然后再运行安装命令。

至此,你的核心开发环境就已经搭建完毕。你可以通过一个简单的脚本来验证一切是否正常。

3. Playwright 核心概念与快速入门脚本解析

在开始编写复杂的测试用例之前,我们必须理解 Playwright 的几个核心抽象。这就像学开车先要了解方向盘、油门和刹车一样。

3.1 核心对象模型:Browser, Context, Page

Playwright 的操作围绕三个核心对象展开,它们的关系是层层包含的:

  1. Browser:代表一个浏览器实例。你可以把它想象成一个完整的浏览器程序,比如你桌面上的 Chrome 图标。通过 Playwright,你可以启动(launch)一个浏览器,可以是有头模式(能看到界面)或无头模式(后台运行)。
  2. Context:浏览器上下文。这是Playwright 中一个非常强大且重要的概念。你可以把它理解为一个独立的“隐身会话”或“用户档案”。每个 Context 拥有独立的 cookies、本地存储、缓存和权限设置。这意味着你可以在一个脚本中轻松模拟多个用户同时登录同一个网站,或者隔离不同的测试场景,而不会互相干扰。
  3. 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(headless=False) # 设置为 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(path='example.png', full_page=True) 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(headless=False):启动一个 Chromium 浏览器。headless=False意味着你将看到一个真实的浏览器窗口打开并执行操作,这对于调试脚本非常有用。在生产环境或追求速度时,应设置为True
  • browser.new_context():创建了一个独立的上下文。在这个简单的例子里,我们暂时用不到上下文的隔离特性,但养成创建 Context 的习惯是好的实践。
  • page.goto():导航到指定 URL。这个方法会自动等待页面触发load事件。
  • page.wait_for_load_state('networkidle'):这是一个更严格的等待。networkidle表示在至少 500 毫秒内没有超过 2 个网络连接时,才认为页面加载完成。这对于等待 AJAX 请求或动态加载的内容非常有效。
  • page.screenshot():截图功能。full_page=True会截取整个可滚动页面的长图,而不仅仅是首屏。
  • 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,并去掉所有的asyncawait关键字,使用with sync_playwright() as p:即可。我个人的建议是,如果你是新手,或者脚本逻辑不复杂,可以从同步 API 开始,更容易理解。但异步 API 在并发执行多个浏览器操作时性能优势明显,值得后续学习。

4. 元素定位与交互:自动化操作的基石

自动化测试的核心是模拟人的操作:找到页面上的元素(按钮、输入框、链接),然后与之交互(点击、输入、悬停)。Playwright 提供了多种强大且灵活的元素定位器(Locators)。

4.1 定位器(Locator)详解与最佳实践

定位器是 Playwright 中用于查找和操作元素的抽象。创建定位器不会立即执行查找,只有在执行操作(如click())时才会真正去页面上定位元素,这种“惰性”设计提高了脚本的健壮性。

最常用的定位策略:

  1. 按文本定位:这是最直观的方式之一。

    # 点击文本内容为“登录”的元素(可以是按钮、链接、div等) await page.locator('text=登录').click() # 更精确的完全匹配 await page.locator('text="Sign in"').click()
  2. 按 CSS 选择器定位:这是最强大、最通用的方式,如果你熟悉 CSS,会非常得心应手。

    # 通过 ID await page.locator('#username').fill('myuser') # 通过 Class await page.locator('.submit-btn').click() # 通过属性 await page.locator('[data-testid="login-button"]').click() # 组合选择器 await page.locator('div.header > nav a:has-text("Home")').click()
  3. 按 XPath 定位:XPath 功能强大但表达式可能复杂,在 CSS 选择器无法满足复杂层级关系时使用。

    await page.locator('//button[@id="submit"]').click()
  4. 按角色(Role)定位:这是 Playwright 推荐的一种语义化定位方式,特别适合可访问性(ARIA)良好的现代 Web 应用。

    # 定位一个名为“Username”的文本框 await page.get_by_role('textbox', name='Username').fill('test') # 定位一个按钮 await page.get_by_role('button', name='Submit').click() # 定位一个链接 await page.get_by_role('link', name='Privacy 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_text='John Doe') # 使用 `filter` 进行条件过滤 enabled_button = page.locator('button').filter(has_text='Save').and_(enabled=True)

4.2 核心交互操作:点击、输入、选择

定位到元素后,就可以进行交互了。以下是最常用的操作:

点击与双击:

await page.locator('button#submit').click() # 带选项的点击,例如强制点击即使用元素被遮挡 await page.locator('button').click(force=True) # 双击 await page.locator('canvas').dblclick()

输入与清空:

# 在输入框填充文本(会先清空原有内容) await page.locator('#email').fill('user@example.com') # 模拟逐个字符输入(更接近用户真实行为,会触发键盘事件) await page.locator('#search').type('Playwright automation', delay=100) # delay 模拟输入间隔 # 清空输入框 await page.locator('#comment').clear()

下拉框选择:

# 通过 value 选择 await page.locator('select#country').select_option(value='us') # 通过标签文本选择 await page.locator('select#country').select_option(label='United States') # 多选 await page.locator('select#colors').select_option(value=['red', 'blue'])

复选框与单选框:

# 勾选复选框 await page.locator('input[type="checkbox"]').check() # 取消勾选 await page.locator('input[type="checkbox"]').uncheck() # 判断是否已勾选 is_checked = await page.locator('input[type="checkbox"]').is_checked() # 选择单选框 await page.locator('input[type="radio"][value="option1"]').check()

文件上传:Playwright 处理文件上传非常简洁,不需要模拟复杂的点击文件选择对话框的操作。

# 设置文件输入框的值(文件路径) await page.locator('input[type="file"]').set_input_files('/path/to/myfile.pdf') # 上传多个文件 await page.locator('input[type="file"]').set_input_files(['file1.pdf', 'file2.jpg']) # 清空已选文件 await page.locator('input[type="file"]').set_input_files([])

悬停(Hover):

# 鼠标悬停在元素上,常用于触发下拉菜单或工具提示 await page.locator('.menu-item').hover()

注意事项:Playwright 的交互操作(如click,fill内置了自动等待。它会等待元素满足可操作条件(如可见、启用、稳定未动画)后才执行操作。这意味着你通常不需要在操作前手动写等待,这极大地简化了脚本并提高了稳定性。但如果遇到特殊场景(如自定义控件),你可能需要结合wait_for_selectorwait_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', state='hidden') # 等待加载动画消失 # 等待元素可见 await page.wait_for_selector('#success-message', state='visible') # 等待元素被从 DOM 中移除 await page.wait_for_selector('#old-element', state='detached')

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) # 等待数量 >= 10

5. 等待超时设置:所有等待方法都可以设置超时时间(毫秒)。

try: # 只等待 5 秒 await page.wait_for_selector('.popup', state='visible', timeout=5000) except TimeoutError: print('弹出框没有在5秒内出现')

实操心得:我的经验法则是:优先依赖操作的自动等待,仅在需要等待非交互性状态变化(如元素出现、消失、文本变化)或导航时,才使用显式等待。避免滥用page.wait_for_timeout(3000)这种固定休眠,它会让测试变慢且不可靠(网络或机器性能差异可能导致3秒不够或浪费)。使用基于条件的等待,你的脚本才能在任何速度下都稳定运行。

6. 高级特性与实战技巧

掌握了基础操作和等待策略,你已经能完成大部分自动化任务。接下来,我们探讨一些高级特性和实战技巧,让你的脚本更强大、更健壮、更易维护。

6.1 处理弹窗、新窗口与 iframe

对话框(Alert, Confirm, Prompt):Playwright 可以监听并响应 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(name='login-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( status=200, content_type='application/json', body=json.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(headless=False) context = browser.new_context(**iphone_11) # 这里传入了用户代理、视口大小、设备缩放因子等 page = context.new_page() await page.goto('https://mobile.example.com') # 此时页面看到的将是移动端视图 # 模拟地理位置和语言 context = await browser.new_context( locale='de-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-playwright

Playwright 的 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-test="error"]') 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(scope='session') 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-test="error"]') 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(scope='function') def context(context): # 为每个测试启用追踪 await context.tracing.start(screenshots=True, snapshots=True, sources=True) yield context # 测试结束后停止追踪,并保存文件 await context.tracing.stop(path = f'trace-{uuid.uuid4()}.zip')

追踪文件(.zip)可以用 Playwright 的命令行工具或在线查看器打开,它包含了操作截图、DOM 快照、网络请求、控制台日志等所有信息,能帮你快速定位“测试当时到底发生了什么”。

生成 HTML 报告:Playwright Test 本身可以生成漂亮的 HTML 报告。对于 Pytest,可以使用pytest-html等插件生成报告。

pip install pytest-html pytest --html=report.html --self-contained-html

8. 常见问题排查与性能优化

即使有了完善的工具,在实际项目中还是会遇到各种问题。这里记录了一些我踩过的坑和解决方案。

8.1 典型问题速查表

问题现象可能原因解决方案
TimeoutError: Timeout 30000ms exceeded1. 元素定位器错误,找不到元素。
2. 页面加载/元素渲染太慢。
3. 元素被遮挡或不可交互。
1. 使用浏览器开发者工具检查定位器是否正确。
2. 增加超时时间locator.click(timeout=60000)
3. 使用page.pause()暂停脚本,手动检查页面状态。
4. 检查是否有 iframe、Shadow DOM。
5. 尝试使用force=True参数(谨慎使用)。
元素点击/输入没反应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. 本地也使用无头模式 (headless=True) 运行测试。
2. 增加网络空闲等待wait_for_load_state(‘networkidle’)
3. 使用page.screenshot()page.video记录 CI 失败现场。
4. 确保 CI 环境已正确安装 Playwright 浏览器 (playwright install)。
Target closed错误你试图操作一个已经被关闭的页面或上下文。检查代码逻辑,确保在操作页面时,它没有被意外的page.close()context.close()关闭。通常发生在异步操作和页面跳转时,确保使用expect_popupexpect_navigation来正确处理。
文件上传不工作文件输入框可能是动态生成或样式隐藏的。1. 确保定位到了正确的<input type=”file”>元素。
2. 不要尝试点击它,直接用set_input_files()方法。
3. 如果元素被隐藏,可能需要先通过 JS 使其可见。
跨域 iframe 无法操作浏览器的同源策略限制。Playwright 默认在新上下文中禁用同源策略 (ignore_https_errors等设置可能影响)。确保 iframe 的 URL 与主页同源,或使用--disable-web-security启动浏览器(仅限测试环境)。

8.2 性能优化与最佳实践

  1. 复用浏览器上下文:启动和关闭浏览器是昂贵的操作。在测试套件级别(scope=’session’)启动浏览器,在测试函数级别(scope=’function’)创建新的上下文和页面。这样每个测试都是隔离的,但共享浏览器进程,速度更快。
  2. 并行执行测试:Pytest 支持pytest-xdist插件进行并行测试。Playwright 可以很好地与它配合,但需要确保为每个工作进程创建独立的浏览器上下文。
    pip install pytest-xdist pytest -n auto # 使用与CPU核心数相同的进程并行运行
  3. 选择性安装浏览器:如果只测试 Chromium,就不要安装 Firefox 和 WebKit。
  4. 合理使用无头模式:在 CI/CD 管道和追求速度时,始终使用headless=True。仅在调试复杂交互问题时才开启有头模式。
  5. 拦截不必要的资源:使用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)
  6. 保持定位器稳定:这是减少测试“脆性”的最重要一点。优先使用>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/30 20:04:31

AgentKit与Sora 2:面向工程化的AI代理与时空生成新范式

1. 项目概述&#xff1a;这不是一场发布会&#xff0c;而是一次开发范式的集体重装 “TAI #173: OpenAI’s DevDay Deluge: Sora 2, AgentKit, and an App Store Reboot”——这个标题里藏着三个关键词&#xff1a; Sora 2、AgentKit、App Store Reboot 。它们不是孤立的新功…

作者头像 李华
网站建设 2026/6/30 20:03:06

Mythos模型:大模型在漏洞挖掘中的因果推理跃迁

1. 这不是一次普通升级&#xff1a;Mythos 的能力跃迁本质是什么&#xff1f;如果你过去三年持续关注大模型在安全领域的实际表现&#xff0c;看到 Anthropic 发布 Claude Mythos Preview 的第一反应不会是“又一个新模型”&#xff0c;而是“时间线被压缩了”。这不是渐进式优…

作者头像 李华
网站建设 2026/6/30 20:02:42

tModLoader终极创造:打造个性化泰拉瑞亚模组扩展生态

tModLoader终极创造&#xff1a;打造个性化泰拉瑞亚模组扩展生态 【免费下载链接】tModLoader A mod to make and play Terraria mods. Supports Terraria 1.4 (and earlier) installations 项目地址: https://gitcode.com/gh_mirrors/tm/tModLoader 还在为泰拉瑞亚原版…

作者头像 李华
网站建设 2026/6/30 20:02:32

如何在Windows电脑上轻松运行安卓应用:APK安装器完整指南

如何在Windows电脑上轻松运行安卓应用&#xff1a;APK安装器完整指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 你是否曾经想过&#xff0c;在Windows电脑上直接运…

作者头像 李华
网站建设 2026/6/30 20:00:03

图神经网络如何实现精准ETA预测

1. 项目概述&#xff1a;当导航不再只是“画线”&#xff0c;而是读懂城市脉搏你打开手机&#xff0c;输入目的地&#xff0c;Google Maps几秒内就给出三条路线、三个不同的ETA&#xff08;预估到达时间&#xff09;&#xff0c;还用红黄绿三色实时标注每一段路的拥堵状况。这看…

作者头像 李华
网站建设 2026/6/30 19:58:16

JMeter从零到一实战:手把手教你搭建HTTP接口压力测试环境

1. 项目概述&#xff1a;从零到一&#xff0c;构建你的性能测试利器 如果你是一名后端开发、测试工程师&#xff0c;或者正在负责一个即将上线的项目&#xff0c;那么“压力测试”这个词对你来说一定不陌生。当用户量激增、业务高峰期来临&#xff0c;你的应用服务器能否扛得住…

作者头像 李华

关于博客

这是一个专注于编程技术分享的极简博客,旨在为开发者提供高质量的技术文章和教程。

订阅更新

输入您的邮箱,获取最新文章更新。

© 2025 极简编程博客. 保留所有权利.