← 返回测试与调试目录
← 返回学习笔记首页
专题: Python 测试与调试系统学习
关键词: Python, 测试, 调试, Playwright, E2E测试, 浏览器测试, pytest-playwright, 视觉测试, Python测试
一、Playwright测试概述
Playwright是由Microsoft开发的新一代端到端测试框架,于2020年正式开源发布,迅速成为Web自动化测试领域的标杆工具。相比于传统的Selenium WebDriver,Playwright在设计理念和架构上进行了彻底的革新,解决了长期困扰测试工程师的众多痛点问题。Playwright通过单一的WebSocket连接与浏览器进行通信,采用CDP(Chrome DevTools Protocol)协议直接操控浏览器,避免了WebDriver繁琐的HTTP请求/响应循环,显著提升了执行速度和稳定性。
Playwright最显著的优势在于对三大浏览器引擎的原生支持:Chromium(Chrome、Edge、Opera等)、Firefox和WebKit(Safari)。与Selenium依赖各浏览器厂商维护的独立Driver不同,Playwright将三大浏览器引擎打包为自包含的二进制文件,通过单一API统一操控。这意味着测试工程师可以在同一套代码中无缝切换浏览器,无需安装额外的驱动程序,也无需担心浏览器版本与Driver版本之间的兼容性问题。Playwright团队会持续跟进各浏览器的最新版本,确保测试环境的稳定性。
自动等待(Auto-Waiting)是Playwright另一项革命性的设计。传统的Selenium测试中,工程师需要手动编写各种wait语句(显式等待、隐式等待、FluentWait)来应对页面的异步加载和元素动态渲染。Playwright内置了智能等待机制:在执行click、fill、get_attribute等操作前,框架会自动等待元素达到可操作状态(可见、稳定、不被遮挡、已启用)。这一设计消除了大量的样板代码,使测试脚本更加简洁、可读,同时大幅降低了因时序问题导致的不稳定测试(Flaky Test)。
在Python生态中,Playwright通过playwright库提供核心API,并通过pytest-playwright插件与pytest测试框架深度集成。安装过程十分简洁:使用pip安装playwright和pytest-playwright两个包,然后执行playwright install命令下载浏览器二进制文件即可。借助pytest的参数化机制,可以轻松实现跨浏览器测试矩阵。此外,Playwright还提供了代码生成器(Codegen),可以录制用户操作并自动生成测试脚本,极大降低了上手门槛。
pip install playwright pytest-playwright
playwright install
# 基本使用示例
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 启动Chromium浏览器
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com")
print(page.title())
browser.close()
# 多浏览器切换
from playwright.sync_api import sync_playwright
browsers = ["chromium", "firefox", "webkit"]
with sync_playwright() as p:
for name in browsers:
browser = getattr(p, name).launch()
page = browser.new_page()
page.goto("https://example.com")
print(f"{name}: {page.title()}")
browser.close()
二、元素定位
Playwright引入了全新的Locator API,彻底取代了传统Selenium中find_element/find_elements的定位方式。Locator是一种延迟求值的定位策略,它将元素定位的"声明"与"执行"分离:测试代码先声明要定位什么元素,直到实际执行交互动作时才会真正在DOM中查找该元素。这种设计结合自动等待机制,使得定位行为更加稳定可靠。Locator API支持自动重试:当元素尚未出现在DOM中时,定位器会等待一段时间(默认30秒),期间持续轮询,直到元素出现或超时。
Playwright提供了极为丰富的选择器体系,涵盖了现代Web前端开发中所有常见的定位需求。最基础的是CSS选择器和XPath选择器,兼容从Selenium迁移的遗留测试代码。在此基础上,Playwright扩展了文本选择器(text=)、标签选择器、属性选择器等便捷语法。特别值得一提的是角色选择器(role=),它基于WAI-ARIA规范,通过无障碍语义来定位元素,不仅使测试代码更具可读性,还能间接验证页面的无障碍访问合规性。
get_by系列方法是Locator API的核心入口,包括get_by_role、get_by_text、get_by_placeholder、get_by_label、get_by_test_id、get_by_alt_text、get_by_title等。其中get_by_test_id是Playwright官方推荐的首选定位策略:开发者在关键交互元素上添加data-testid属性,测试代码通过该属性定位。这种方法将测试定位与UI样式、文本内容完全解耦,即使前端重构导致CSS类名或文本改变,测试代码也无需修改。推荐使用data-testid属性的另一个原因是它能清晰地区分"测试专用属性"和"业务属性",便于代码审查和维护。
层级定位与过滤定位是处理复杂UI场景的利器。当页面中存在多个相似元素(如下拉选项、列表项、表格行)时,可以结合locator的first、last、nth方法进行索引定位,或使用filter方法通过子元素文本内容进行过滤。更强大的链式定位允许在某个父元素范围内二次查找子元素,这种组合定位方式在处理前端框架(如React、Vue)生成的动态列表时尤其有效。
# 多种定位方式演示
page = browser.new_page()
page.goto("https://example.com")
# get_by_role 定位(推荐)
page.get_by_role("button", name="提交").click()
page.get_by_role("link", name="了解更多").click()
page.get_by_role("heading", name="欢迎页").is_visible()
# get_by_text 定位
page.get_by_text("立即注册").click()
page.get_by_text("^登录$").click() # 精确匹配
# get_by_test_id 定位(最稳定)
page.get_by_test_id("submit-btn").click()
page.get_by_test_id("user-name").fill("张三")
# CSS与XPath(兼容旧代码)
page.locator("css=.btn-primary").click()
page.locator("xpath=//button[@type='submit']").click()
# 层级定位与过滤
page.goto("https://example.com/products")
# 链式定位:在某个父级下查找
product_card = page.locator(".product-card")
product_card.locator(".buy-btn").click()
# 过滤定位:按文本过滤
page.locator(".product-item").filter(has_text="iPhone").click()
# 索引定位
page.locator(".menu-item").first.click()
page.locator(".menu-item").nth(2).click()
# 组合过滤
page.locator("li").filter(
has=page.locator(".price", has_text="¥99")
).click()
三、交互与断言
Playwright提供了丰富而直观的交互API,覆盖了用户与网页之间所有常见的操作类型。点击(click)、输入(fill/type)、选择(select_option)、勾选(check/uncheck)、聚焦(focus)、悬停(hover)、拖拽(drag_and_drop)等操作均以方法形式直接暴露在Locator对象上。值得一提的是fill和type的区别:fill会清空输入框现有内容后填入新文本,适用于大部分表单填写场景;type则模拟键盘逐字输入,适合需要触发输入事件的场景(如搜索框的实时建议)。所有交互操作都内置了自动等待机制,确保元素可交互后才执行操作。
Playwright的断言系统通过expect函数配合各种匹配器(Matcher)实现,与Python的pytest框架天然集成。与传统assert语句不同,Playwright的expect断言具有自动重试能力:当断言条件不满足时,框架会在超时时间内持续重新检查,直到条件满足或超时。这一机制完美解决了异步渲染场景下常见的时序问题,使测试代码不再需要显式的time.sleep或WebDriverWait。默认超时时间为30秒,可以通过timeout参数灵活调整。
常用的断言匹配器包括:to_be_visible(元素可见)、to_be_hidden(元素隐藏)、to_contain_text(包含文本)、to_have_text(完全匹配文本)、to_have_value(输入框值)、to_be_checked(复选框选中)、to_have_attribute(属性值)、to_have_count(元素数量)、to_have_url(URL匹配)、to_have_title(标题匹配)等。这些匹配器既适用于Locator对象,也适用于Page对象,形成了完整的断言体系。当断言失败时,Playwright会自动生成详细的错误信息,包括期望值、实际值和DOM快照,大大简化了问题排查过程。
# 基础交互
page.goto("https://example.com/login")
# 填写表单
page.get_by_label("用户名").fill("testuser")
page.get_by_placeholder("请输入密码").fill("password123")
# 下拉选择
page.get_by_label("城市").select_option("北京")
page.get_by_label("城市").select_option(["北京", "上海"]) # 多选
# 复选框与单选
page.get_by_role("checkbox", name="同意条款").check()
page.get_by_role("radio", name="男").check()
# 拖拽操作
page.locator("#source").drag_to(page.locator("#target"))
# expect断言(自动重试)
from playwright.sync_api import expect
# 等待元素可见
expect(page.get_by_text("登录成功")).to_be_visible()
# 文本断言(自动等待)
expect(page.locator(".welcome")).to_contain_text("欢迎回来")
expect(page.locator(".title")).to_have_text("用户中心")
# 属性断言
expect(page.locator("#submit")).to_be_enabled()
expect(page.locator("#checkbox")).to_be_checked()
# 数量断言
expect(page.locator(".product-item")).to_have_count(10)
# URL与标题断言
expect(page).to_have_url("https://example.com/dashboard")
expect(page).to_have_title("控制台")
# 自定义超时
expect(page.get_by_text("处理完成")).to_be_visible(timeout=10000)
四、网络拦截
Playwright的网络拦截功能通过page.route方法实现,允许测试代码在浏览器发出HTTP请求到服务器返回响应之间的任意阶段进行干预。route方法接收一个URL模式(支持通配符和正则表达式)和一个处理函数(handler)。在处理函数中,可以调用route.fulfill返回自定义响应、route.abort中止请求、或route.continue放行请求到真实服务器。这一机制使得测试可以完全控制网络层面,模拟各种后端行为而无需启动真实的服务端。
Mock API是网络拦截最常用的场景之一。在测试前端页面时,往往不希望依赖真实的后端API服务——后端可能尚未开发完成,或者测试数据难以控制。通过Playwright的路由拦截,可以在测试中注入预设的JSON响应数据,模拟API的各种返回值,包括正常数据、空数据、错误码、超时等边界情况。这种测试方式使前端测试与后端开发解耦,支持并行开发流程,同时也能测试前端在各种异常情况下的容错处理能力。
网络条件模拟是另一项实用功能,尤其适合测试应用的弱网环境和离线状态。Playwright允许通过context.set_offline(true)模拟离线模式,也可以通过CDP(Chrome DevTools Protocol)模拟特定的网络条件,如3G网络、高延迟、低带宽等。这些测试手段能够有效发现应用在极端网络条件下的缺陷,如加载状态缺失、超时处理不当、离线缓存失效等问题。
请求抓取与分析功能使测试不仅可以拦截请求,还可以捕获和分析所有网络流量。测试代码可以监听request、response、requestfinished等事件,收集API请求的URL、方法、请求体、响应体、状态码、耗时等信息。这对于验证埋点上报、API调用顺序、性能指标等场景非常有价值。结合Playwright的Promise.all特性,还能实现"等待多个请求完成后再继续"的精确控制。
# Mock API响应
def mock_user_api(route):
"""拦截用户接口,返回模拟数据"""
json_data = {
"code": 200,
"data": {
"id": 1001,
"name": "张三",
"avatar": "https://example.com/avatar.png",
"level": "VIP会员"
}
}
route.fulfill(
status=200,
content_type="application/json",
body=json.dumps(json_data)
)
page.route("**/api/user/profile", mock_user_api)
page.goto("https://example.com/profile")
expect(page.get_by_text("张三")).to_be_visible()
# 模拟各种边界情况
# 模拟500错误
page.route("**/api/order/list", lambda r: r.fulfill(
status=500,
content_type="application/json",
body='{"error": "Internal Server Error"}'
))
# 模拟网络超时(不响应)
page.route("**/api/payment/status", lambda r: r.abort("timedout"))
# 模拟空数据
page.route("**/api/products", lambda r: r.fulfill(
content_type="application/json",
body='{"data": []}'
))
# 拦截图片资源加速测试
page.route("**/*.{png,jpg,jpeg,gif,webp}", lambda r: r.abort())
# 请求抓取分析
requests = []
page.on("response", lambda resp: requests.append({
"url": resp.url,
"status": resp.status,
"headers": resp.headers
}))
page.goto("https://example.com")
五、移动端模拟
Playwright提供了强大的移动设备模拟能力,使得在桌面浏览器中测试移动端Web应用成为可能。通过内置的device descriptor(设备描述符),测试代码可以模拟各种主流移动设备,包括iPhone、iPad、Samsung Galaxy、Google Pixel等数百种设备的屏幕尺寸、像素比(DPR)、用户代理(User-Agent)和视口(Viewport)设置。使用起来极为简单:只需在创建浏览器上下文时传入device_descriptor参数即可。这种方法使测试工程师无需准备真实的物理设备阵列即可覆盖主流的移动端访问场景。
地理定位与时区/语言模拟是移动测试的重要环节。很多Web应用会根据用户的地理位置和系统语言提供差异化的内容和服务。Playwright允许在浏览器上下文中精确设置经纬度坐标、系统语言(locale列表)和时区。配合page.grant_permissions方法还可以授予浏览器地理位置权限。这些功能使得测试团队可以在一条测试中覆盖全球不同地区用户的体验,验证国际化(i18n)和本地化(l10n)功能的正确性。
传感器模拟和触摸事件是移动端测试的高级特性。Playwright支持模拟设备的方向变化(横竖屏切换)、接近传感器等。更重要的是对触摸事件(touch events)的完整支持:可以通过locator.tap方法模拟手指点击,这对于测试移动端特定交互(如长按菜单、手势滑动、下拉刷新)至关重要。在模拟移动设备时,Playwright会自动启用触摸事件模型,确保测试结果与真实设备行为一致。
# 设备模拟
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 使用iPhone 13设备描述符
iphone_13 = p.devices["iPhone 13"]
context = p.chromium.new_context(**iphone_13)
page = context.new_page()
page.goto("https://example.com")
# 此时页面以iPhone 13的视口大小渲染
# 模拟Pixel 5
pixel_5 = p.devices["Pixel 5"]
context2 = p.chromium.new_context(**pixel_5)
page2 = context2.new_page()
page2.goto("https://example.com")
# 地理定位与权限
context = browser.new_context(
locale="zh-CN",
timezone_id="Asia/Shanghai",
geolocation={"longitude": 121.4737, "latitude": 31.2304},
permissions=["geolocation"]
)
page = context.new_page()
page.goto("https://example.com/map")
# 旋转设备方向(横屏)
context.set_geolocation({"longitude": -122.4194, "latitude": 37.7749})
page.evaluate("screen.orientation.lock('landscape')")
# 触摸事件
page.get_by_test_id("mobile-menu-btn").tap()
page.locator(".draggable-item").tap()
# 模拟多指触摸
page.touchscreen.tap(100, 200)
# 模拟缓慢滚动
page.evaluate("window.scrollTo({ top: 1000, behavior: 'smooth' })")
page.wait_for_timeout(2000) # 等待滚动动画完成
六、视觉测试
视觉测试(Visual Testing)也称为截图对比测试,是确保Web应用UI一致性的重要手段。传统的功能测试只能验证元素的存在和属性,而无法捕捉CSS样式异常、布局偏移、字体渲染差异、图片缺失等视觉层面的问题。Playwright内置了截图功能,并结合expect().to_have_screenshot()方法实现了自动化的视觉回归测试。其核心原理是:先截取当前页面的截图作为"基线"(baseline),在后续测试运行时再次截图,并与基线进行像素级对比,发现任何差异都标记为测试失败。
Playwright提供了多种截图粒度,满足不同测试场景的需求。全页面截图(page.screenshot(full_page=True))可以捕获整个页面的内容,包括需要滚动才能看到的区域,适合验证页面整体布局。元素级截图(locator.screenshot())仅截取特定元素的渲染结果,针对性强,适合验证按钮状态、弹窗样式、图标渲染等。区域截图可以通过clip参数指定具体的截图区域,在验证页面上某个固定区域的内容时非常有用。所有截图方法都支持设置像素比(scale)、图片质量(quality)等参数。
视觉测试的关键挑战在于处理"可接受的差异"。实际测试中,不同操作系统、不同字体版本、不同显卡驱动都可能导致像素级别的微小差异。Playwright允许通过max_diff_pixels和max_diff_ratio参数设置容错阈值,忽略低于阈值的小幅差异。更精细的控制可以通过set_full_page_screenshot_option或自定义像素对比函数来实现。此外,Playwright还支持配置匿名文本(mask),将动态内容(如时间戳、用户头像)区域遮盖后再对比,避免这些不可避免的变化导致测试失败。
# 基础截图对比
from playwright.sync_api import expect
page.goto("https://example.com")
# 全页截图对比(自动与基线对比)
expect(page).to_have_screenshot(
"homepage.png",
full_page=True
)
# 元素级截图
expect(page.get_by_test_id("header")).to_have_screenshot(
"header.png"
)
# 特定区域截图
expect(page).to_have_screenshot(
"hero-section.png",
clip={"x": 0, "y": 0, "width": 1200, "height": 600}
)
# 高级截图配置
# 设置容错阈值(忽略微小差异)
expect(page).to_have_screenshot(
max_diff_pixels=100, # 最多允许100个不同像素
max_diff_ratio=0.01, # 或允许1%的像素不同
timeout=10000 # 截图超时时间
)
# 遮盖动态内容后对比
expect(page).to_have_screenshot(
mask=[
page.get_by_test_id("live-timestamp"),
page.get_by_test_id("user-avatar"),
page.locator(".ads-banner")
]
)
# 手动截图保存(用于调试)
page.screenshot(path="debug_before_click.png")
page.get_by_test_id("expand-btn").click()
page.screenshot(path="debug_after_click.png")
七、Trace Viewer
Trace Viewer是Playwright提供的可视化调试工具,堪称测试排错的"黑匣子记录仪"。当测试运行时,Playwright可以录制完整的执行轨迹(trace),包括每一个操作的时间线、操作前后的DOM快照、网络请求详情、控制台日志、页面截图等信息。测试运行完毕后,可以通过Playwright内置的Trace Viewer界面打开trace文件,以时间线形式回放整个测试执行过程。对于CI/CD环境中难以复现的失败测试,Trace Viewer提供了宝贵的现场还原数据。
开启Trace录制非常简单,只需在浏览器上下文创建时传入trace选项即可。Playwright支持三种录制模式:on-first-retry(仅在第一次重试时录制)、on-all-retries(所有重试都录制)和on(始终录制)。建议在CI环境中使用on-first-retry模式,既能在测试失败时获取trace信息,又避免了每次成功运行都产生trace文件浪费存储空间。trace文件保存为.zip格式,可以通过Playwright的命令行工具或VS Code扩展打开查看。
Trace Viewer的界面分为几个区域:顶部是操作时间线,以时间轴形式展示每个操作的执行顺序和耗时;左侧是操作列表,可以点击任意操作跳转到对应的状态;右侧主区域展示操作发生时的DOM快照和网络日志。特别有用的是"Before/After"对比功能:点击某个操作后,可以查看操作前后的DOM快照,清晰看到操作引起的页面变化。网络日志面板则列出了操作期间发起的每一个HTTP请求,包括请求头、请求体、响应头和响应体的完整内容。
# 配置Trace录制
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(
record_video_dir="videos/", # 同时录制视频
)
# 启用Trace
context.tracing.start(
screenshots=True, # 录制截图
snapshots=True, # 录制DOM快照
sources=True # 录制源代码
)
page = context.new_page()
page.goto("https://example.com")
page.get_by_text("登录").click()
page.get_by_label("用户名").fill("test")
# 保存Trace文件
context.tracing.stop(path="trace.zip")
browser.close()
# 查看Trace:playwright show-trace trace.zip
# pytest中集成Trace录制
import pytest
from playwright.sync_api import Page
def test_login_with_trace(page: Page):
# Trace将在pytest-playwright中自动配置
page.goto("https://example.com/login")
page.get_by_label("用户名").fill("admin")
page.get_by_label("密码").fill("password")
page.get_by_role("button", name="登录").click()
expect(page).to_have_url("https://example.com/dashboard")
# conftest.py 中配置条件录制
@pytest.fixture(scope="function")
def context(context):
context.tracing.start(
screenshots=True,
snapshots=True
)
yield context
# 仅测试失败时保存trace
if not request.node.rep_call.passed:
context.tracing.stop(
path=f"traces/{request.node.name}.zip"
)
八、pytest-playwright
pytest-playwright是Playwright官方提供的pytest插件,实现了测试框架与浏览器自动化的深度集成。该插件通过pytest的fixture机制,自动管理浏览器实例的生命周期:browser fixture对应一个浏览器进程实例,context fixture对应一个浏览器上下文(类似无痕窗口),page fixture对应一个标签页。测试函数只要声明page参数,pytest-playwright就会自动创建并注入一个已打开的页面对象,测试完成后自动清理浏览器资源。这种设计遵循了"约定优于配置"的原则,使测试代码极为简洁。
多浏览器参数化是pytest-playwright最强大的特性之一。通过--browser-name命令行参数或pytest的mark机制,可以轻松实现同一个测试用例在Chromium、Firefox、WebKit三个浏览器引擎上并行执行。在CI环境中,结合pytest-xdist插件可以实现多进程并行测试,大幅缩短测试耗时。pytest-playwright还支持设置--browser-channel参数,用于测试特定品牌的浏览器(如Google Chrome、Microsoft Edge),满足基于浏览器特性的兼容性测试需求。
conftest.py配置是实现测试基础架构的中心。在conftest.py中可以自定义browser_type_launch_args(浏览器启动参数,如headless模式、代理设置)、browser_context_args(上下文参数,如视口大小、语言、权限)等fixture。这些配置可以灵活组合,实现针对不同测试环境的参数定制。例如,可以在CI环境中启用headless模式,在本地开发环境中使用headed模式;也可以针对移动端测试设置特定的视口尺寸和设备参数。通过pytest的mark机制,还可以选择性地在某些测试上跳过特定浏览器:@pytest.mark.skip_browser("webkit")。
# pytest-playwright基础用法
# test_example.py
import pytest
from playwright.sync_api import Page, expect
def test_homepage_title(page: Page):
"""自动获取page fixture"""
page.goto("https://example.com")
expect(page).to_have_title("Example Domain")
def test_login_flow(page: Page):
page.goto("https://example.com/login")
page.get_by_test_id("username").fill("admin")
page.get_by_test_id("password").fill("pass123")
page.get_by_test_id("submit").click()
expect(page.get_by_text("欢迎回来")).to_be_visible()
# 通过命令行指定浏览器
# pytest test_example.py --browser=chromium
# pytest test_example.py --browser=firefox --browser=webkit
# pytest test_example.py --browser=chromium --headed # 有界面模式
# conftest.py 高级配置
import pytest
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
"""全局浏览器上下文配置"""
return {
**browser_context_args,
"viewport": {"width": 1920, "height": 1080},
"locale": "zh-CN",
"timezone_id": "Asia/Shanghai",
}
@pytest.fixture(scope="session")
def browser_type_launch_args(browser_type_launch_args):
"""全局浏览器启动配置"""
return {
**browser_type_launch_args,
"headless": True, # CI环境默认无头模式
"slow_mo": 100, # 全局操作延迟(调试用)
}
# 标记跳过特定浏览器
@pytest.mark.skip_browser("firefox")
def test_safari_only_feature(page: Page):
page.goto("https://example.com")
# 仅在Chromium和WebKit上执行
# 自定义fixture:模拟移动端
@pytest.fixture
def mobile_page(context):
iphone = context.browser._playwright.devices["iPhone 13"]
return context.new_page(**iphone)
def test_mobile_layout(mobile_page: Page):
mobile_page.goto("https://example.com")
expect(mobile_page).to_have_screenshot("mobile.png")
九、实战案例
电商网站端到端测试是Playwright最典型的应用场景之一。一个完整的电商下单流程涉及商品浏览、搜索筛选、加入购物车、结算支付、订单确认等多个步骤,覆盖了页面导航、表单填写、动态加载、异步请求验证等多种测试技术。通过Playwright组织这样的E2E测试,可以将用户的核心业务流程完整地自动化,确保每次代码变更都不会破坏关键路径。测试代码应当遵循Page Object模式,将每个页面封装为独立的Page类,提高代码复用性和可维护性。
多浏览器兼容验证是E2E测试中不可或缺的环节。虽然现代前端框架(React、Vue、Angular)通过抽象层屏蔽了大部分浏览器差异,但在实际项目中仍然存在很多浏览器特有的问题:CSS Grid/Flexbox布局差异、Web字体渲染效果、Canvas/WebGL性能差异、LocalStorage/SessionStorage行为差异、事件处理时序差异等。通过在pytest-playwright中配置多浏览器参数化,可以在一次测试执行中自动覆盖Chromium、Firefox和WebKit三个引擎,显著降低浏览器兼容性Bug逃逸到生产环境的概率。
表单填写自动化验证涵盖了对输入校验、提交反馈、错误提示等交互的全面测试。现代Web表单往往包含复杂的验证逻辑:必填项检查、格式校验(邮箱、电话、身份证)、密码强度规则、异步唯一性校验等。Playwright可以模拟用户的各种输入行为,包括正常输入、边界输入、特殊字符、空值提交等,然后验证页面的响应是否符合预期。结合网络拦截功能,还能模拟后端返回各种验证错误(如用户名已存在、验证码错误),测试前端的错误提示和处理逻辑是否健壮。
# 电商下单E2E测试
def test_e2e_purchase_flow(page: Page):
# 步骤1:搜索商品
page.goto("https://example.com")
page.get_by_test_id("search-input").fill("无线耳机")
page.get_by_test_id("search-btn").click()
expect(page.get_by_text("搜索结果")).to_be_visible()
# 步骤2:选择商品并加入购物车
page.get_by_test_id("product-card").first.click()
expect(page.get_by_text("商品详情")).to_be_visible()
page.get_by_test_id("add-to-cart").click()
expect(page.get_by_text("已加入购物车")).to_be_visible()
# 步骤3:进入购物车结算
page.get_by_test_id("cart-icon").click()
expect(page.get_by_text("购物车")).to_be_visible()
expect(page.get_by_text("无线耳机")).to_contain_text("无线耳机")
# 步骤4:填写收货信息
page.get_by_test_id("checkout-btn").click()
page.get_by_label("收货人").fill("张三")
page.get_by_label("手机号").fill("13800138000")
page.get_by_label("详细地址").fill("北京市朝阳区某某街道100号")
# 步骤5:提交订单
page.get_by_test_id("submit-order").click()
expect(page.get_by_text("订单提交成功")).to_be_visible()
expect(page.get_by_text("订单编号")).to_be_visible()
# 多浏览器兼容性测试
import pytest
from playwright.sync_api import Page, expect
# 同一个测试函数在所有配置的浏览器上执行
@pytest.mark.parametrize("browser_name", ["chromium", "firefox", "webkit"])
def test_cross_browser_compatibility(browser_name, page: Page):
page.goto("https://example.com")
# 验证核心功能
page.get_by_test_id("nav-menu").is_visible()
page.get_by_text("首页").is_visible()
# 验证页面布局
body = page.locator("body")
expect(body).to_have_css("font-family", "inherit")
# 验证交互功能
page.get_by_test_id("search-input").fill("test")
page.get_by_test_id("search-btn").click()
expect(page).to_have_url(/search\?q=test/)
# 表单验证自动化测试
def test_form_validation(page: Page):
page.goto("https://example.com/register")
# 测试1:必填项验证
page.get_by_test_id("register-btn").click()
expect(page.get_by_text("请输入用户名")).to_be_visible()
expect(page.get_by_text("请输入邮箱")).to_be_visible()
# 测试2:邮箱格式验证
page.get_by_label("邮箱").fill("invalid-email")
page.get_by_test_id("register-btn").click()
expect(page.get_by_text("邮箱格式不正确")).to_be_visible()
# 测试3:密码一致性验证
page.get_by_label("密码").fill("Pass1234")
page.get_by_label("确认密码").fill("Pass5678")
page.get_by_test_id("register-btn").click()
expect(page.get_by_text("两次密码不一致")).to_be_visible()
# 测试4:正常注册
page.get_by_label("用户名").fill("testuser")
page.get_by_label("邮箱").fill("test@example.com")
page.get_by_label("密码").fill("Pass1234")
page.get_by_label("确认密码").fill("Pass1234")
page.get_by_test_id("register-btn").click()
expect(page.get_by_text("注册成功")).to_be_visible()
# 测试5:重复注册(Mock方式)
page.route("**/api/user/register", lambda r: r.fulfill(
status=409,
content_type="application/json",
body='{"error": "用户名已存在"}'
))
page.get_by_test_id("register-btn").click()
expect(page.get_by_text("用户名已存在")).to_be_visible()