Playwright Route类实战:从拦截到篡改,构建灵活测试场景
1. Playwright Route类入门:拦截请求的基本原理
第一次接触Playwright的Route类时,我完全被它的能力震惊了。想象一下,你正在测试一个电商网站,突然想看看当支付接口返回500错误时,前端页面会如何展示。传统做法可能需要搭建mock服务器或者修改后端代码,但有了Route类,只需要几行代码就能实现这个场景。
Route类的核心原理就像高速公路上的检查站。当浏览器发出网络请求时,Playwright会在请求真正发送前将其拦截下来,交给我们自定义的处理函数。这个处理函数可以决定是放行(continue)、拒绝(abort)还是伪造响应(fulfill)。这种机制让我们能够在不修改实际服务端代码的情况下,模拟各种网络环境。
这里有个最简单的例子,我们拦截所有图片请求并阻止加载:
from playwright.sync_api import sync_playwright def handle_route(route): if route.request.resource_type == "image": route.abort() else: route.continue_() with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.route("**/*", handle_route) page.goto("https://example.com")这个脚本会阻止页面加载所有图片资源,对于测试页面在禁用图片时的布局非常有用。route.request.resource_type可以判断请求类型,常见值还有"stylesheet"、"script"、"xhr"等。
2. 拦截实战:四种核心方法详解
2.1 abort:模拟请求失败场景
在实际项目中,我最常用abort()来测试前端错误处理。比如测试一个文件上传功能时,可以这样模拟上传中断:
def handle_upload(route): if "/api/upload" in route.request.url: print("模拟上传中断") route.abort(error_code="failed") # 可选错误码:timeout, failed, aborted等 else: route.continue_() page.route("**/*", handle_upload)error_code参数特别有用,不同错误码会触发不同的网络错误事件。比如"timeout"会模拟请求超时,"accessdenied"会模拟CORS错误。我曾经用这个功能发现了前端没有正确处理403错误的bug。
2.2 continue:修改请求头实战
continue()不只是简单放行请求,还能先修改请求内容。比如测试CSRF防护时,可以这样移除token:
def remove_csrf_token(route): headers = route.request.headers del headers["X-CSRF-Token"] route.continue_(headers=headers) page.route("**/api/*", remove_csrf_token)这个技巧帮我发现了后端没有正确验证CSRF token的安全漏洞。continue()还可以修改postData来篡改请求体,这在测试表单提交时特别有用。
2.3 fulfill:完全掌控响应内容
fulfill()是我最喜欢的特性,它可以完全自定义响应。比如模拟一个分页接口:
def mock_pagination(route): if "/api/users" in route.request.url: page = int(route.request.url.split("page=")[1]) mock_data = { "data": [{"id": i, "name": f"User {i}"} for i in range(page*10, (page+1)*10)], "total": 100 } route.fulfill( status=200, headers={"Content-Type": "application/json"}, body=json.dumps(mock_data) ) else: route.continue_()这个mock数据会随着page参数变化,可以完美测试前端分页逻辑。我经常用这个方法来测试边界情况,比如当total为0时的空状态展示。
2.4 fetch + fulfill:修改真实响应
有时候我们需要基于真实响应进行修改,这时可以组合使用fetch()和fulfill():
async def modify_response(route): response = await route.fetch() json_data = await response.json() json_data["price"] *= 0.9 # 打九折 await route.fulfill(response=response, json=json_data) page.route("**/product/*", modify_response)这个例子会先获取真实响应,然后修改价格字段后再返回。我在测试促销活动页面时经常用这招,比直接mock更接近真实场景。
3. 复杂场景实战技巧
3.1 模拟限流和降级
测试API限流时,我们可以随机拒绝请求:
import random def rate_limit(route): if random.random() < 0.3: # 30%概率限流 route.fulfill( status=429, body='{"error": "Too many requests"}' ) else: route.continue_() page.route("**/api/*", rate_limit)对于服务降级测试,可以这样返回简化版数据:
def degrade_service(route): if "/api/complex" in route.request.url: route.fulfill( status=200, body='{"basic": true, "message": "Service degraded"}' ) else: route.continue_()3.2 动态路由匹配
Route支持多种匹配方式,非常灵活:
# 正则匹配 page.route(re.compile(r"\.(jpg|png)$"), lambda route: route.abort()) # 函数匹配 def should_intercept(request): return "analytics" in request.url and request.method == "POST" page.route(should_intercept, track_analytics)我曾经用动态匹配实现了只在特定时间段拦截请求的功能,用来测试时间敏感型功能。
3.3 请求/响应钩子
除了修改内容,Route还可以用来收集数据:
api_calls = [] def collect_metrics(route): start_time = time.time() route.continue_() api_calls.append({ "url": route.request.url, "duration": time.time() - start_time }) page.route("**/api/**", collect_metrics)这个技巧帮我找出了几个性能瓶颈API。同样的原理也可以用来做自动化监控。
4. 最佳实践与常见陷阱
4.1 性能优化建议
Route拦截会带来性能开销,我有几个优化心得:
- 尽量缩小拦截范围,避免使用"**/*"这样的宽泛匹配
- 在不需要时及时取消拦截:
page.unroute("**/api/**") - 对于复杂逻辑,使用async/await避免阻塞
4.2 调试技巧
调试Route时我常用的方法:
- 打印request和response详情:
print(f"{request.method} {request.url}") print(f"Headers: {request.headers}") print(f"Post Data: {request.post_data}")- 使用page.pause()在拦截时暂停
- 配合Playwright的trace功能记录完整过程
4.3 常见问题解决
我踩过的一些坑:
- 忘记调用continue()或fulfill()导致请求挂起
- 修改了不可变的header字段(如Content-Length)
- 异步上下文问题,特别是在Pytest中使用时
- 正则表达式性能问题导致超时
Route类是Playwright最强大的功能之一,但需要合理使用。我建议从简单场景开始,逐步尝试更复杂的拦截逻辑。在实际项目中,这些技巧帮我节省了数百小时的测试时间,特别是对于复杂的前端状态测试和边缘场景验证。
