Python Playwright自动化测试入门:从零搭建首个Web测试脚本
1. 项目概述:为什么选择 Playwright 作为你的第一个自动化测试工具?
如果你正在寻找一个能让你快速上手、功能强大且对现代 Web 应用支持极佳的自动化测试工具,那么 Playwright 几乎是不二之选。我接触过 Selenium、Puppeteer 等一众工具,最终在项目里全面转向 Playwright,原因很简单:它解决了太多历史遗留的痛点。想象一下,你不再需要为不同浏览器下载和匹配不同版本的驱动,不再需要处理那些恼人的跨域 iframe 或等待元素时的随机超时,甚至能轻松录制你的操作并生成代码——这就是 Playwright 带来的体验。
这个项目,就是带你从零开始,用 Python 搭建第一个真正能跑起来的自动化测试脚本。我们不会停留在简单的“打开网页、点击按钮”,而是会深入到实际工作中你会遇到的场景:如何处理动态加载、如何与复杂表单交互、如何进行断言验证,以及如何组织你的代码让它更易于维护。无论你是测试工程师想提升效率,还是开发同学想为自己的项目补充自动化测试,甚至是运维同学希望通过脚本进行日常巡检,这篇内容都能给你一条清晰的路径。你会发现,写自动化测试脚本,和写普通的 Python 脚本一样直观。
2. 环境搭建与核心工具链解析
工欲善其事,必先利其器。在动手写代码之前,我们需要一个干净、可复现的 Python 环境,并理解 Playwright 工具链的构成。这能避免你未来陷入“在我的机器上能跑”的窘境。
2.1 Python 环境与包管理的最佳实践
我强烈建议你使用 venv 或 conda 来创建独立的虚拟环境。这能确保项目依赖的纯净性,避免与系统或其他项目的 Python 包发生冲突。这里以 venv 为例,因为它足够轻量且是 Python 标准库的一部分。
打开你的终端(Windows 用 CMD 或 PowerShell,Mac/Linux 用 Terminal),进入你计划存放项目的目录,执行以下命令:
# 创建项目目录并进入
mkdir playwright-first-script
cd playwright-first-script
# 创建虚拟环境,环境文件夹名为 .venv
python -m venv .venv
# 激活虚拟环境
# Windows (CMD/PowerShell)
.venv\Scripts\activate
# MacOS/Linux
source .venv/bin/activate
激活后,你的命令行提示符前通常会显示 (.venv) ,这表明你已处于虚拟环境中。接下来安装 Playwright 的 Python 包:
pip install playwright
这个命令会安装 playwright 这个核心库。但请注意,这 并不 包含浏览器本身。Playwright 的设计理念是浏览器作为独立实体进行管理,这带来了更好的隔离性和版本控制。
2.2 Playwright 命令行工具(CLI)的妙用
安装完库后,Playwright 提供了一个强大的命令行工具。首先,我们需要用它来安装浏览器二进制文件:
playwright install
这个命令会下载 Playwright 支持的所有浏览器(Chromium, Firefox, WebKit)的最新稳定版本。如果你只想安装 Chromium(最常用),可以运行 playwright install chromium 。这一步可能会花费一些时间,因为它需要下载几百兆的浏览器文件。完成后,这些浏览器会被存放在一个独立于你系统浏览器的缓存目录中,专供 Playwright 使用。
注意 :在某些企业网络环境下,直接下载可能会失败。你可以通过设置环境变量
PLAYWRIGHT_DOWNLOAD_HOST来使用镜像源,或者手动下载后指定路径。但作为新手,先尝试直接安装是最简单的。
CLI 的另一个“杀手级”功能是代码生成器。当你对某个网页的操作流程不确定时,可以先用它来录制:
playwright codegen https://www.example.com
执行后,它会打开一个浏览器和一个代码录制面板。你在浏览器中的所有操作(点击、输入、导航)都会被实时转换成 Python(或其他语言)代码,显示在面板中。这对于快速探索和生成脚本骨架极其有用,是你学习和编写脚本的得力助手。
2.3 IDE 选择与配置建议
对于 Python 项目,Visual Studio Code (VSCode) 是目前非常主流的选择。你需要安装 Python 扩展和 Pylance 语言服务器。关键一步是确保 VSCode 使用了我们刚创建的虚拟环境。你可以按 Ctrl+Shift+P (Windows/Linux) 或 Cmd+Shift+P (Mac),输入 “Python: Select Interpreter”,然后选择路径为 ./.venv/Scripts/python.exe (Windows) 或 ./.venv/bin/python (Mac/Linux) 的解释器。
这样配置后,VSCode 的智能提示、代码补全和导入包都会基于你的虚拟环境,避免很多错误。当然,PyCharm 也是极好的选择,其专业版对测试框架的支持更深入。
3. 第一个脚本:从“Hello World”到有意义的测试
现在,让我们告别概念,开始写代码。第一个脚本的目标不是炫技,而是打通从代码到浏览器执行的完整流程,并理解最基本的 API。
3.1 脚本骨架与上下文管理
在你的项目根目录下创建一个文件,命名为 test_demo.py 。我们将从最基础的导入和结构开始:
import asyncio
from playwright.async_api import async_playwright
async def main():
# 启动 Playwright,它负责管理浏览器进程
async with async_playwright() as p:
# 启动一个 Chromium 浏览器实例。`headless=False` 表示我们能看到浏览器界面。
browser = await p.chromium.launch(headless=False)
# 创建一个新的浏览器上下文(Context),类似于一个独立的隐身会话。
context = await browser.new_context()
# 在上下文中打开一个新页面(Page)。
page = await context.new_page()
# --- 你的操作代码将写在这里 ---
# 例如:导航到一个网页
await page.goto('https://playwright.dev/python')
# 等待一段时间以便观察,实际脚本中应使用更智能的等待
await page.wait_for_timeout(3000)
# 关闭浏览器
await browser.close()
# 运行主函数
asyncio.run(main())
代码解析与避坑点 :
- 异步 async/await :Playwright Python 的核心 API 是异步的。这意味着你需要使用
async def定义函数,并在调用 API 前加await。对于新手,记住这个模式即可。如果你非常熟悉同步编程,Playwright 也提供了同步 API (from playwright.sync_api import sync_playwright),但异步模式性能更好,是现代 Python 的推荐写法。 - Playwright -> Browser -> Context -> Page :这是 Playwright 的核心对象层级。
Playwright: 总入口,管理浏览器类型。Browser: 代表一个浏览器进程(如 Chrome)。Context: 非常重要 。它代表一个独立的“浏览器会话”,拥有独立的 cookies、缓存、权限设置。你可以创建多个 Context 来实现测试间的完全隔离,避免状态污染。Page: 代表一个标签页,是你进行大部分操作(导航、点击、输入)的对象。
-
headless=False:在调试阶段,让浏览器显示出来非常有用,你能直观看到脚本在做什么。当脚本稳定后,可以改为headless=True在后台无界面运行,节省资源且更适合持续集成(CI)环境。
3.2 核心操作:导航、定位与交互
让我们给这个骨架注入灵魂,实现一些实际功能。假设我们要在 Playwright 官网搜索文档。
# ... 省略前面的导入和 async with 语句 ...
browser = await p.chromium.launch(headless=False)
context = await browser.new_context()
page = await context.new_page()
# 1. 导航到官网
await page.goto('https://playwright.dev/python')
print(f"页面标题是:{await page.title()}")
# 2. 定位搜索按钮并点击
# 使用 CSS 选择器定位元素。这里通过 `aria-label` 属性定位。
await page.click('button[aria-label="Search"]')
# 3. 在出现的搜索框中输入关键词
# 等待搜索输入框可见并处于可输入状态
search_input = page.locator('input[type="search"]')
await search_input.wait_for(state='visible')
await search_input.fill('locator')
print("已输入搜索关键词 'locator'")
# 4. 等待搜索结果出现并获取第一条结果的文本
# 使用更精确的定位,等待结果列表的第一个链接
first_result = page.locator('.DocSearch-Hits a').first
await first_result.wait_for(state='visible')
result_text = await first_result.text_content()
print(f"第一条搜索结果是:{result_text}")
# 5. 点击第一条结果,导航到新页面
await first_result.click()
await page.wait_for_load_state('networkidle') # 等待页面基本加载完成
# 6. 验证新页面标题是否包含预期内容
new_title = await page.title()
assert 'Locator' in new_title, f"页面标题 '{new_title}' 中不包含 'Locator'"
print("断言通过!页面标题包含 'Locator'。")
await page.wait_for_timeout(2000)
await browser.close()
# ... 省略 asyncio.run ...
实操心得 :
-
page.click()vslocator.click():上面的例子混用了两种方式。page.click(selector)是快捷方法,适用于简单场景。但page.locator(selector)是更强大、更推荐的方式 。Locator对象代表一个元素定位策略,它支持链式调用(如.first,.nth(index))和更稳定的等待。例如,await page.locator('button').first.click()比await page.click('button:first-of-type')更易读和可靠。 - 等待策略是稳定的关键 :自动化脚本失败,十有八九是因为“等得不够”或“等错了对象”。Playwright 提供了智能的
auto-wait机制:在执行点击、输入等操作前,它会自动检查元素是否可见、可交互、稳定等。但有时你需要显式等待,如wait_for_load_state('networkidle')等待网络空闲,或locator.wait_for(state='visible')等待特定元素出现。 尽量避免使用page.wait_for_timeout(毫秒),这是固定等待,效率低下且不可靠,应作为调试时的临时手段。 - 定位器(Selector) :Playwright 支持 CSS 选择器、XPath、文本选择器(
text=)、React/Vue 组件测试选择器等。 优先使用 CSS 选择器 ,它性能好且可读性高。对于有特定文本的元素,page.locator('text=Submit')非常直观。XPath 虽然强大,但通常更脆弱,应作为最后的选择。
4. 脚本进阶:处理复杂场景与封装技巧
一个简单的线性脚本远远不够。真实的 Web 应用充满动态内容、弹窗和复杂状态。我们需要让脚本更健壮、更智能。
4.1 处理弹窗、新标签页与框架
现代网页的交互不再局限于单个页面。
# 处理对话框(Alert, Confirm, Prompt)
page.on('dialog', lambda dialog: dialog.accept()) # 监听并自动接受所有对话框
# 处理新标签页(Popup)
async with page.expect_popup() as popup_info:
await page.click('a[target="_blank"]') # 点击会打开新标签页的链接
new_page = await popup_info.value
# 现在可以在 new_page 上操作了
await new_page.goto('https://example.com')
await new_page.close() # 操作完后关闭新标签页
# 处理 iframe
# 先定位到 iframe 元素
frame_element = page.frame_locator('iframe[name="my-frame"]')
# 然后在 iframe 的上下文中定位元素
button_in_frame = frame_element.locator('button')
await button_in_frame.click()
注意事项 :弹窗监听器 ( page.on('dialog', ...) ) 最好在可能触发弹窗的操作 之前 设置。对于 iframe,如果其 src 是跨域的,Playwright 默认可能无法访问其内容,需要在创建浏览器上下文时配置权限。
4.2 模拟用户输入与设备环境
测试需要模拟真实用户。
# 创建上下文时模拟设备,如 iPhone 11
iphone_11 = p.devices['iPhone 11']
context = await browser.new_context(**iphone_11)
# 模拟地理位置和权限
await context.grant_permissions(['geolocation'])
await context.set_geolocation({'latitude': 52.52, 'longitude': 13.39})
# 模拟网络条件(慢速 3G)
slow_3g = p.request.new_context(
extra_http_headers={'network-conditions': 'slow-3g'}
)
# 使用这个 context 发起的请求都会受限制
# 文件上传(不再是难题!)
file_input = page.locator('input[type="file"]')
await file_input.set_input_files('/path/to/my/file.pdf')
# 甚至可以上传多个文件
await file_input.set_input_files(['file1.pdf', 'file2.jpg'])
实操心得 : set_input_files() 是 Playwright 的一大亮点,它直接设置文件路径,完全绕过了传统自动化工具中需要操作系统文件选择对话框的难题,极其稳定可靠。
4.3 断言与测试结构:引入 Pytest
虽然可以用 Python 自带的 assert ,但结合专业的测试框架如 pytest ,能让测试结构更清晰,报告更美观,并支持夹具(fixture)等高级功能。
首先,安装 pytest 和 pytest-playwright 插件(它提供了有用的 fixture):
pip install pytest pytest-playwright
然后,创建一个测试文件 test_playwright_search.py :
import re
import pytest
from playwright.async_api import Page, expect
# 使用 pytest.mark.asyncio 标记异步测试函数
@pytest.mark.asyncio
async def test_search_playwright_docs(page: Page):
"""
测试 Playwright 官网的搜索功能。
`page` fixture 由 pytest-playwright 提供,无需手动管理浏览器生命周期。
"""
# 1. 导航
await page.goto('https://playwright.dev/python')
# 2. 使用 `expect` API 进行断言,这是 Playwright 推荐的断言方式
await expect(page).to_have_title(re.compile(r'Playwright'))
# 3. 执行搜索操作
await page.click('button[aria-label="Search"]')
await page.locator('input[type="search"]').fill('locator')
# 4. 断言搜索结果出现
first_result = page.locator('.DocSearch-Hits a').first
await expect(first_result).to_be_visible()
result_text = await first_result.text_content()
# 使用 expect 断言文本内容
await expect(first_result).to_contain_text('locator', ignore_case=True)
# 5. 点击并导航
await first_result.click()
# 断言新页面 URL 包含特定路径
await expect(page).to_have_url(re.compile(r'.*/docs/locators$'))
# 可以运行 `pytest test_playwright_search.py -v` 来执行测试
为什么用 expect 而不用 assert ? expect API 内置了智能等待和丰富的匹配器,它会自动重试直到条件满足或超时,这让断言变得更稳定,是编写可靠测试的最佳实践。
5. 项目组织与持续集成(CI)集成雏形
当脚本多起来时,良好的项目结构至关重要。
5.1 基础项目目录结构
一个可维护的测试项目可能如下所示:
playwright-automation-project/
├── .venv/ # 虚拟环境(.gitignore 忽略)
├── .gitignore
├── requirements.txt # 项目依赖
├── pytest.ini # Pytest 配置文件
├── conftest.py # Pytest 共享 fixture
├── pages/ # 页面对象模型(Page Object Model)
│ ├── __init__.py
│ ├── base_page.py # 基础页面类
│ └── search_page.py # 搜索页面类
├── tests/ # 测试用例
│ ├── __init__.py
│ ├── test_search.py
│ └── test_auth.py
├── fixtures/ # 测试数据
│ └── test_data.json
├── reports/ # 测试报告输出目录
└── utils/ # 工具函数
└── helper.py
requirements.txt: 使用pip freeze > requirements.txt生成,确保他人能复现环境。pytest.ini: 配置 pytest 行为,例如默认命令行参数。[pytest] addopts = -v --tb=short --html=reports/report.html --self-contained-html asyncio_mode = autoconftest.py: 定义全局 fixture,比如自动为每个测试提供page对象,并配置浏览器参数。import pytest from playwright.async_api import async_playwright @pytest.fixture(scope='session') async def browser(): async with async_playwright() as p: # 在 CI 环境中通常以无头模式运行 browser = await p.chromium.launch(headless=True) yield browser await browser.close() @pytest.fixture async def page(browser): context = await browser.new_context(viewport={'width': 1920, 'height': 1080}) page = await context.new_page() yield page await context.close()
5.2 在 CI/CD 中运行 Playwright 测试
以 GitHub Actions 为例,创建一个 .github/workflows/playwright.yml 文件:
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install -r requirements.txt
playwright install --with-deps chromium # 只安装 Chromium 及其依赖
- name: Run tests
run: pytest tests/ -v
- name: Upload test report
if: always() # 即使测试失败也上传报告
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: reports/ # 假设 pytest-html 报告生成在这里
避坑技巧 :在 CI 环境中,确保安装了 Playwright 所需的系统依赖。 playwright install --with-deps 命令在 Linux 上会自动处理这些。如果遇到问题,查阅 Playwright 官方文档的 CI 部分,有针对 GitHub Actions、GitLab CI、Jenkins 等的详细指南。
6. 常见问题排查与性能优化实战记录
即使按照最佳实践,你依然会遇到问题。这里记录了几个我踩过的坑和解决方案。
6.1 元素定位失败:动态内容与等待策略
问题 :脚本报错 TimeoutError: Timeout 30000ms exceeded. ,提示找不到元素。
排查思路 :
- 检查选择器 :首先用浏览器的开发者工具(F12)检查你的 CSS 选择器或 XPath 在当前页面是否唯一匹配。页面结构可能已更改。
- 验证元素状态 :元素可能被样式隐藏(
display: none,visibility: hidden),或者被其他元素遮挡。Playwright 的auto-wait会检查这些。 - 处理动态加载 :最常见的原因。元素是 JavaScript 动态加载的,在脚本执行时尚未出现在 DOM 中。
- 解决方案 A(推荐) :使用
page.locator(selector).wait_for(state='visible')或page.wait_for_selector(selector)显式等待。 - 解决方案 B :等待某个网络请求完成。如果元素在某个 API 调用后出现,可以使用
page.wait_for_response('**/api/data')。 - 解决方案 C :等待特定文本出现。
page.wait_for_selector('text=Loading finished')。
- 解决方案 A(推荐) :使用
- 使用更稳健的定位器 :优先使用
data-testid这类专为测试添加的属性。如果不行,尝试结合文本和属性:page.locator('button:has-text("Submit")')。
6.2 异步操作与竞态条件
问题 :脚本执行顺序错乱,比如在页面加载完成前就尝试点击。
解决方案 :严格遵守异步编程模式。确保在任何一个 await 操作完成前,不进行依赖于其结果的下一步操作。善用 page.wait_for_load_state() ,它有几个状态:
load: 等待load事件触发。domcontentloaded: 等待DOMContentLoaded事件触发。networkidle: 等待网络活动基本停止(至少 500ms 没有网络请求)。 这是最常用的 ,表示页面主体已加载完毕。
await page.goto('https://example.com')
await page.wait_for_load_state('networkidle') # 等待页面真正“安静”下来
# 现在再开始查找和操作元素
6.3 脚本执行速度慢
问题 :测试套件运行时间过长。
优化策略 :
- 并行执行 :Pytest 可以通过
pytest-xdist插件并行运行测试。Playwright 支持多个浏览器上下文并行运行,互不干扰。 - 复用浏览器上下文 :创建和销毁浏览器开销很大。在
conftest.py中,将browserfixture 的scope设置为'session',让所有测试复用同一个浏览器进程。为每个测试创建独立的context和page以实现隔离。 - 减少不必要的等待 :用智能等待(
wait_for_selector,wait_for_load_state)替代固定的wait_for_timeout。 - 禁用非必要资源 :如果测试不关心图片、样式或字体,可以在创建上下文时拦截它们,加速页面加载。
context = await browser.new_context( bypass_csp=True, ignore_https_errors=True, # 仅测试环境使用 # 拦截并中止图片、样式表等请求 # 实际使用时需谨慎,可能影响页面功能 ) - 使用快照(Snapshot)进行视觉比对 :对于检查页面布局是否损坏,视觉回归测试比通过 DOM 属性判断更快更准。Playwright 提供了
page.screenshot()和expect(page).to_have_screenshot()功能。
6.4 在无头环境中运行失败
问题 :脚本在 headless=True (CI环境)下失败,但在 headless=False (本地)下成功。
排查 :
- 视口(Viewport)大小 :无头模式默认的视口大小可能与有头模式不同,影响响应式布局。始终在创建页面或上下文时显式设置视口:
await browser.new_context(viewport={'width': 1920, 'height': 1080})。 - 权限问题 :某些操作(如通知、地理位置)在无头模式下可能需要额外授权。通过
context.grant_permissions()提前授予。 - 字体渲染差异 :极少数情况下,字体缺失可能导致布局细微差异。确保 CI 镜像中安装了基本字体包,或使用 Playwright 的 Docker 镜像,它已包含所需环境。
从一行代码到一套能在 CI 上稳定运行的自动化测试,关键在于理解工具背后的设计理念,并遵循“显式等待、良好隔离、清晰结构”的原则。Playwright 的强大之处在于它为你处理了底层浏览器的复杂性,让你能更专注于测试逻辑本身。开始动手吧,用第一个脚本去打开这扇门,你会发现自动化测试并非遥不可及,而是提升你工作流效率的得力伙伴。如果在实践中遇到具体问题,不妨回头看看“常见问题”部分,或者利用 playwright codegen 这个“外挂”来辅助你理解页面交互。
更多推荐
所有评论(0)