尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Playwright+Pytest网络请求监听:从原理到实战的自动化测试进阶

Playwright+Pytest网络请求监听:从原理到实战的自动化测试进阶 1. 项目概述为什么我们需要监听网络请求在Web自动化测试的世界里我们常常满足于“页面元素能点击”、“表单能提交”、“断言能通过”。但作为一名有追求的测试工程师你是否遇到过这样的场景一个看似成功的下单操作后台API却返回了500错误码而前端界面只是“优雅”地显示了一个加载动画或者一个关键的资源文件比如JS、CSS加载失败导致页面功能部分失效但你的UI测试脚本却浑然不觉依然报告“测试通过”。这些“灯下黑”的问题正是UI自动化测试的盲区。传统的Selenium或早期的Playwright脚本主要与DOM文档对象模型交互对网络层发生的事件感知有限。而现代Web应用高度依赖前后端分离架构页面的状态、数据、甚至交互逻辑都通过大量的异步网络请求XHR/Fetch来驱动。不监听网络就等于蒙着眼睛测试一个交响乐团——你只能看到指挥页面的动作却听不到任何乐器API的声音。这正是“pytest-playwright监听网络请求事件”这个主题的核心价值所在。它不再是简单的“点击-断言”模式而是将测试的触角深入到了应用的血脉——网络通信层。通过监听请求request和响应response我们可以实现精准断言不仅断言页面元素更断言后端API的调用次数、参数、返回状态和数据。性能监控捕获请求耗时定位慢接口为性能测试提供数据支撑。Mock与拦截在测试环境中模拟第三方服务、构造异常响应如超时、错误码测试前端容错能力。安全与合规检查检查是否有敏感信息如Token、密码在请求中明文传输或是否有非预期的域名请求。简单来说它为你的自动化测试脚本装上了一副“听诊器”和“手术刀”既能诊断应用内部的运行状态也能在必要时进行精准的干预。本系列课程的第59讲将聚焦于如何将这套强大的监听机制无缝集成到业界标准的pytest测试框架中形成一套可维护、可扩展的自动化测试解决方案。2. 核心思路与框架选型为什么是Playwright Pytest在开始动手之前我们必须理清技术选型背后的逻辑。市面上自动化测试工具众多为什么我们坚定地选择Playwright与Pytest这套组合拳2.1 Playwright不只是Selenium的替代品Playwright由微软开发它解决了许多Selenium时代的痛点多浏览器支持Chromium、Firefox、WebKitSafari引擎开箱即用且API高度统一。自动等待内置智能等待机制极大减少了编写time.sleep或复杂显式等待的需求。网络能力原生支持监听、修改、拦截网络请求这是本课程的核心也是相比Selenium的巨大优势。移动端模拟可以模拟手机设备、地理位置、权限等。强大的录制器可以快速生成脚本作为学习和编写测试的起点。对于网络监听这一特定需求Playwright提供了page.on(request)和page.on(response)事件监听器让我们能以异步回调的方式捕获到每一个经过页面的网络活动。这是实现我们目标的基石。2.2 Pytest测试框架的不二之选Pytest是Python社区事实上的标准测试框架其优势在于简洁的语法使用简单的assert语句进行断言测试函数即用例。丰富的Fixture用于提供测试依赖如浏览器实例、页面对象管理测试前置和后置操作实现资源的复用和隔离。这是我们集成Playwright监听器的关键载体。强大的插件生态拥有海量插件如pytest-playwright官方维护它为我们提供了与Pytest深度集成的Fixture如page让编写Playwright测试如同编写普通Pytest测试一样自然。灵活的配置与报告通过pytest.ini或conftest.py文件进行全局配置并轻松集成Allure等漂亮报告。组合优势pytest-playwright插件将两者完美结合。它负责管理浏览器的生命周期启动/关闭并为每个测试用例提供一个干净的、配置好的page对象。我们的任务就是在这个page对象上挂载我们自定义的网络监听逻辑。2.3 整体架构设计我们的目标架构清晰而高效测试用例层使用Pytest编写测试函数内部使用由pytest-playwright提供的pagefixture进行操作和断言。监听器层通过自定义的Pytest Fixture例如page_with_monitor在提供page对象的同时自动为其添加网络请求/响应的监听器。所有监听到的数据会被收集到一个共享的数据结构如列表或字典中。断言与验证层测试用例在执行过程中或结束后可以访问监听器收集到的网络数据并对其进行检查和断言。Mock/拦截层高级在监听器的基础上我们可以选择性地对特定请求进行拦截route并返回自定义的响应实现测试隔离或异常场景构造。这个架构确保了监听逻辑与测试业务逻辑分离监听器可以灵活地启用、禁用或针对不同测试模块进行定制。3. 环境搭建与核心依赖详解工欲善其事必先利其器。让我们从零开始搭建一个稳固的实战环境。3.1 Python环境与包管理首先确保你有一个干净的Python环境推荐3.8。使用虚拟环境venv或conda是绝对的最佳实践它能避免项目间的包版本冲突。# 创建项目目录并进入 mkdir playwright-network-monitor cd playwright-network-monitor # 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate接下来安装核心依赖。我们将使用pytest-playwright这个官方插件它会自动处理Playwright浏览器驱动的安装。# 安装pytest-playwright插件它会自动安装playwright和pytest pip install pytest-playwright # 安装Playwright所需的浏览器Chromium, Firefox, WebKit playwright install重要提示pytest-playwright插件版本与playwright库版本需要兼容。通常直接安装最新稳定版即可。如果遇到问题可以指定版本如pip install pytest-playwright0.4.3 playwright1.40.0。3.2 项目结构与配置文件一个清晰的项目结构有助于长期维护。建议如下playwright-network-monitor/ ├── conftest.py # Pytest全局配置和共享Fixture定义 ├── pytest.ini # Pytest运行配置 ├── requirements.txt # 项目依赖列表 ├── tests/ # 测试用例目录 │ ├── __init__.py │ ├── test_api_monitor.py # 专门测试API监听的用例 │ └── test_ui_with_network.py # 结合UI操作的网络监听用例 └── utils/ # 工具函数目录可选 └── network_utils.py # 网络监听相关的工具函数conftest.py这是Pytest的魔力所在。任何在此文件中定义的Fixture可以被该目录及其子目录下的所有测试文件自动发现和使用。我们将把核心的网络监听Fixture定义在这里。pytest.ini用于配置Pytest的默认行为。一个基础的pytest.ini配置示例[pytest] # 自动发现测试文件的模式 python_files test_*.py # 自动发现测试类和测试函数的模式 python_classes Test* python_functions test_* # 增加详细输出方便调试 addopts -v -s # 指定测试目录可选 testpaths tests3.3 编写第一个带监听的Fixture现在我们在conftest.py中创建第一个也是最重要的Fixturepage_with_network_monitor。# conftest.py import pytest from typing import Dict, List from playwright.sync_api import Page, Request, Response pytest.fixture def page_with_network_monitor(page: Page) - Page: 提供一个附加了网络请求/响应监听器的page对象。 所有监听到的请求和响应会被收集到page.network_events列表中。 network_events: List[Dict] [] def on_request(request: Request): 监听请求发出事件 # 只记录我们关心的请求例如过滤掉图片、字体等静态资源 if request.resource_type in [xhr, fetch, document]: event { type: request, method: request.method, url: request.url, resource_type: request.resource_type, headers: request.headers, post_data: request.post_data, # 对于POST请求这里可能有请求体 timestamp: pytest.time.time() # 需要导入time模块 } network_events.append(event) # 打印日志便于调试 print(f[Request] {request.method} {request.url}) def on_response(response: Response): 监听请求响应事件 request response.request if request.resource_type in [xhr, fetch, document]: event { type: response, url: response.url, status: response.status, status_text: response.status_text, headers: response.headers, request: { # 关联对应的请求信息 method: request.method, url: request.url }, timestamp: pytest.time.time() } # 注意直接读取response.body()会消耗响应体可能影响页面正常使用。 # 仅在需要断言响应体内容时才读取且最好克隆或谨慎处理。 # event[body] response.text() # 慎用 network_events.append(event) print(f[Response] {response.status} {response.url}) # 将监听器绑定到page对象 page.on(request, on_request) page.on(response, on_response) # 将收集到的事件列表挂载到page对象上方便测试用例访问 page.network_events network_events # 提供一个清理函数在测试结束时移除监听器虽然不是必须但更规范 def cleanup(): page.remove_listener(request, on_request) page.remove_listener(response, on_response) # 使用pytest的addfinalizer注册清理函数 pytest.addfinalizer(cleanup) # 返回增强后的page对象 return page代码解读与注意事项Fixture依赖page_with_network_monitorFixture接收一个参数page这个page是由pytest-playwright插件提供的默认Fixture。我们的Fixture在其基础上进行增强。事件过滤在on_request和on_response中我们通过request.resource_type进行过滤只关注xhrXMLHttpRequest、fetchFetch API和document页面主文档等类型的请求忽略image,stylesheet,font等静态资源避免数据噪音。数据存储我们将事件字典存储在一个列表network_events中并将其作为属性page.network_events动态附加到page对象上。这是一种简洁的共享数据方式。响应体读取response.text()或response.json()会消费响应体。如果测试用例或页面本身后续还需要这个响应体可能会出问题。最佳实践是除非必要否则不要在监听器中读取响应体。如果必须读取可以考虑使用response.clone()克隆一份响应或者确保你的测试逻辑不需要原始的响应体。资源清理我们使用pytest.addfinalizer注册了一个清理函数在测试结束时移除事件监听器。这是一个好习惯尤其是在复杂的测试套件中可以避免潜在的内存泄漏或事件冲突。4. 编写测试用例从监听数据到精准断言有了强大的Fixture编写测试用例就变得直观而有力。我们创建一个测试文件tests/test_api_monitor.py。4.1 基础用例验证页面加载的关键API假设我们测试一个用户仪表盘页面它加载时会调用/api/user/profile和/api/dashboard/data两个接口。# tests/test_api_monitor.py def test_dashboard_loads_critical_apis(page_with_network_monitor): 测试仪表盘页面加载时是否成功调用了关键API。 page page_with_network_monitor # 清空之前可能残留的事件确保测试隔离 page.network_events.clear() # 1. 导航到目标页面 page.goto(https://your-test-app.com/dashboard) # 等待页面加载完成可以等待某个特定元素出现 page.wait_for_selector(.dashboard-container) # 2. 从监听器中获取网络事件 events page.network_events print(fTotal network events captured: {len(events)}) # 3. 提取我们关心的请求响应进行断言 api_requests [e for e in events if e.get(type) request] api_responses [e for e in events if e.get(type) response] # 断言关键API被调用 profile_api_called any(/api/user/profile in req[url] for req in api_requests) dashboard_api_called any(/api/dashboard/data in req[url] for req in api_requests) assert profile_api_called, 用户Profile API未被调用 assert dashboard_api_called, 仪表盘数据API未被调用 # 断言关键API响应成功状态码2xx profile_response next((r for r in api_responses if /api/user/profile in r[url]), None) dashboard_response next((r for r in api_responses if /api/dashboard/data in r[url]), None) if profile_response: assert 200 profile_response[status] 300, fProfile API响应失败: {profile_response[status]} if dashboard_response: assert 200 dashboard_response[status] 300, fDashboard API响应失败: {dashboard_response[status]} # 可选打印出所有监听到的API请求用于调试 for req in api_requests: print(fAPI Request: {req[method]} {req[url]})4.2 进阶用例模拟用户操作并断言网络交互更常见的场景是用户执行某个操作如点击按钮、提交表单触发特定的网络请求。# tests/test_api_monitor.py def test_submit_order_triggers_correct_api(page_with_network_monitor): 测试提交订单操作是否触发了正确的创建订单API并且请求参数和响应符合预期。 page page_with_network_monitor page.network_events.clear() # 导航到商品页 page.goto(https://your-test-app.com/product/123) page.wait_for_selector(#btn-add-to-cart).click() page.wait_for_selector(#btn-checkout).click() # 在结算页面填写信息并提交 page.fill(#input-address, 测试地址) page.click(#btn-submit-order) # 等待订单创建成功的UI反馈例如一个成功提示框 page.wait_for_selector(.order-success-message, timeout10000) # 分析网络事件找到创建订单的API调用 events page.network_events # 通常创建订单是POST请求 order_requests [e for e in events if e[type]request and e[method]POST and /api/orders in e[url]] order_responses [e for e in events if e[type]response and e[request][method]POST and /api/orders in e[url]] assert len(order_requests) 1, f期望有1个创建订单请求实际发现{len(order_requests)}个 assert len(order_responses) 1, f期望有1个创建订单响应实际发现{len(order_responses)}个 order_request order_requests[0] order_response order_responses[0] # 断言请求方法、URL和状态码 assert order_request[method] POST assert /api/orders in order_request[url] assert order_response[status] 201 # 通常创建成功返回201 Created # 高级断言检查请求体POST数据 # 注意request.post_data 可能是字符串格式如JSON字符串或表单数据 if order_request.get(post_data): import json try: request_body json.loads(order_request[post_data]) # 断言请求体中包含必要字段 assert product_id in request_body assert request_body[product_id] 123 assert address in request_body # 这里可以添加更多业务逻辑断言 except json.JSONDecodeError: # 如果不是JSON可能是表单数据可以用其他方式解析 print(Request body is not JSON, might be form data:, order_request[post_data]) # 高级断言检查响应体需要谨慎见上文注意事项 # 如果确定需要且不影响测试可以尝试读取 # try: # # 注意这里需要从原始的response对象读取我们的事件字典里没有存body。 # # 更好的做法是在监听器中如果确定要断言这个响应就克隆并存储它。 # pass # except Exception as e: # print(fCould not read response body: {e})4.3 使用Pytest钩子进行更精细的控制有时我们希望为不同的测试模块或类应用不同的监听策略。这时可以结合Pytest的pytest.mark.usefixtures装饰器或直接在类级别使用Fixture。# tests/test_specific_module.py import pytest pytest.mark.usefixtures(page_with_network_monitor) class TestCheckoutFlow: 测试结算流程的类所有测试方法都会自动使用page_with_network_monitor fixture。 def test_guest_checkout(self, page_with_network_monitor): page page_with_network_monitor page.network_events.clear() # ... 测试访客结算逻辑 # 断言网络请求 assert any(api/guest/checkout in e[url] for e in page.network_events) def test_logged_in_user_checkout(self, page_with_network_monitor): page page_with_network_monitor page.network_events.clear() # ... 先登录再测试结算逻辑 # 断言网络请求包含用户token auth_requests [e for e in page.network_events if e.get(headers, {}).get(authorization)] assert len(auth_requests) 05. 高级应用请求拦截Mock与性能监控监听是观察拦截则是控制。Playwright的page.route()方法允许我们在请求发送前拦截它并修改请求或返回自定义响应。5.1 使用Route进行API Mock假设我们的测试环境没有支付网关或者我们想测试前端对“支付失败”场景的处理。# conftest.py 或一个专门的fixture文件中 import pytest from playwright.sync_api import Route, Request pytest.fixture def page_with_mocked_payment(page: Page) - Page: 提供一个拦截了支付API的page对象用于模拟支付失败。 def handle_payment_route(route: Route, request: Request): if /api/payment/process in request.url: # 拦截支付请求返回一个模拟的失败响应 route.fulfill( status402, content_typeapplication/json, bodyjson.dumps({success: False, error: Insufficient funds}) ) else: # 其他请求继续正常进行 route.continue_() # 在页面加载前就设置路由规则 page.route(**/api/payment/**, handle_payment_route) yield page # 测试结束后取消路由重要 page.unroute(**/api/payment/**, handle_payment_route) # 在测试用例中使用 def test_payment_failure_handling(page_with_mocked_payment): page page_with_mocked_payment page.goto(/checkout) page.click(#btn-pay) # 断言页面显示了正确的错误信息 assert page.is_visible(text支付失败余额不足) # 同时真正的支付API从未被调用5.2 监听与性能分析结合我们可以轻松地扩展监听器来收集性能数据。# 在conftest.py的监听器函数中补充 def on_response(response: Response): # ... 之前的记录逻辑 ... # 计算请求耗时需要记录请求开始时间 # 我们可以在on_request时记录时间戳 pass # 更专业的做法使用Playwright的request.timing对象 def on_response(response: Response): request response.request timing request.timing # 这是一个字典包含DNS、TCP、SSL、请求、响应等各阶段时间 total_duration timing.get(responseEnd, 0) - timing.get(requestStart, 0) if total_duration 1000: # 如果总耗时超过1秒 print(f[Performance Warning] {request.url} took {total_duration}ms) # 可以将慢请求信息记录到专门的列表供测试后分析 page.slow_requests.append({ url: request.url, duration: total_duration, timing: timing })然后你可以在测试套件级别的Fixture如session或module范围的Fixture中汇总所有测试的慢请求并在测试结束后生成一个简单的性能报告。6. 常见问题排查与实战技巧在实际使用中你肯定会遇到各种“坑”。以下是我从大量实践中总结出的经验。6.1 问题排查清单问题现象可能原因解决方案监听不到任何网络事件1. 监听器绑定时机太晚页面已加载。2.page对象在监听器绑定前已被导航。确保在page.goto()之前绑定监听器。将监听器绑定逻辑放在Fixture的最开始或使用page.on后立即goto。监听器重复触发或数据混乱1. Fixture作用域问题如function作用域但数据未清理。2. 多个测试共享同一个page对象且未清理事件列表。1. 在Fixture中每次返回新的page增强对象或像我们一样在测试开始时clear()事件列表。2. 使用pytest.addfinalizer正确清理监听器。读取response.body()报错或导致页面异常响应体已被消费例如被页面JS或之前的读取操作消费。最佳实践避免在通用监听器中读取body。如果必须读使用response.clone()克隆响应或者只为特定请求的监听器读取。异步操作导致断言时事件还未捕获页面操作触发的网络请求是异步的断言执行时请求可能还未发生或未完成。在触发操作后使用page.wait_for_event(response)等待特定响应或使用page.wait_for_function等待某个标志位如事件列表长度变化。跨域请求CORS相关问题监听器可以监听到跨域请求但默认无法读取其响应头/体浏览器安全限制。Playwright默认在无头模式下会忽略部分CORS限制但复杂场景可能需要启动浏览器时配置--disable-web-security仅限测试环境。6.2 实战技巧与心得精细化过滤不要记录所有请求。像我们例子中那样根据resource_typexhr,fetch,document或URL模式page.on(request, lambda req: print(req.url) if /api/ in req.url else None)进行过滤否则数据量巨大难以分析。使用上下文管理器对于复杂的监听或路由逻辑可以将其封装成一个上下文管理器确保进入和退出时的资源管理万无一失。from contextlib import contextmanager contextmanager def network_monitor_context(page): events [] def on_request(req): ... def on_response(resp): ... page.on(request, on_request) page.on(response, on_response) page.network_events events try: yield page finally: page.remove_listener(request, on_request) page.remove_listener(response, on_response) delattr(page, network_events)与Pytest夹具作用域结合pytest.fixture(scopemodule)可以创建一个模块级别的page对象所有测试共享同一个浏览器上下文和页面监听器收集的数据也会累积。这适用于测试一个完整流程。而scopefunction默认则为每个测试提供干净的环境。根据测试需求选择。调试利器playwright.debug()在测试脚本中插入page.pause()会启动Playwright的调试器你可以看到实时的网络请求、控制台日志并单步执行对于理解复杂的网络交互流程非常有帮助。序列化与报告将重要的网络事件如失败的API调用、慢请求在测试失败时打印出来或者通过pytest的request.node对象附加到测试报告中能极大提升问题定位效率。监听网络请求将你的自动化测试从“黑盒”变成了“灰盒”甚至“白盒”。它让你能洞察应用的内在运行机制编写出更健壮、更可靠、更能发现深层Bug的测试用例。这套Playwright Pytest的组合提供了从环境搭建、用例编写、到高级Mock和性能分析的完整工具链。掌握它你就能为你的Web应用质量保障体系筑起一道更坚固的防线。
返回列表