Playwright 中route 方法模拟测试数据(Mocking)详解

Mocking 是 route 方法最重要的应用之一,用于在测试中模拟后端 API 响应,实现测试与真实后端服务的解耦。

1. 基础 Mocking 模式

基本响应模拟

python 复制代码
from playwright.sync_api import sync_playwright

def test_basic_mock():
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        
        # 模拟用户列表 API
        def mock_users(route):
            route.fulfill(
                status=200,
                content_type="application/json",
                body='{"users": [{"id": 1, "name": "张三"}, {"id": 2, "name": "李四"}]}'
            )
        
        page.route("**/api/users", mock_users)
        page.goto("http://localhost:3000")
        
        # 此时页面调用 /api/users 将返回模拟数据
        browser.close()

2. 动态 Mocking 数据

基于请求参数的动态响应

python 复制代码
def dynamic_mock_based_on_request(route):
    request = route.request
    
    # 根据查询参数返回不同数据
    if "user_id=1" in request.url:
        route.fulfill(
            status=200,
            body='{"id": 1, "name": "管理员", "role": "admin"}'
        )
    elif "user_id=2" in request.url:
        route.fulfill(
            status=200,
            body='{"id": 2, "name": "普通用户", "role": "user"}'
        )
    else:
        route.fulfill(status=404, body='{"error": "用户不存在"}')

page.route("**/api/user**", dynamic_mock_based_on_request)

基于请求体的动态响应

python 复制代码
def mock_based_on_post_data(route):
    request = route.request
    
    if request.method == "POST" and request.post_data:
        post_data = request.post_data
        
        if "login" in request.url:
            # 解析登录数据
            if "admin" in post_data:
                route.fulfill(
                    status=200,
                    body='{"token": "admin-token", "user": {"role": "admin"}}'
                )
            else:
                route.fulfill(
                    status=200,
                    body='{"token": "user-token", "user": {"role": "user"}}'
                )
        else:
            route.continue_()
    else:
        route.continue_()

page.route("**/api/**", mock_based_on_post_data)

3. 复杂业务场景 Mocking

完整的 CRUD 操作模拟

python 复制代码
class UserServiceMock:
    def __init__(self):
        self.users = [
            {"id": 1, "name": "张三", "email": "zhang@example.com"},
            {"id": 2, "name": "李四", "email": "li@example.com"}
        ]
        self.next_id = 3
    
    def handle_user_routes(self, route):
        request = route.request
        
        if request.method == "GET":
            self.handle_get(route)
        elif request.method == "POST":
            self.handle_post(route)
        elif request.method == "PUT":
            self.handle_put(route)
        elif request.method == "DELETE":
            self.handle_delete(route)
    
    def handle_get(self, route):
        if route.request.url.endswith("/api/users"):
            # 获取用户列表
            route.fulfill(
                status=200,
                body=json.dumps({"users": self.users})
            )
        else:
            # 获取单个用户
            user_id = int(route.request.url.split("/")[-1])
            user = next((u for u in self.users if u["id"] == user_id), None)
            if user:
                route.fulfill(status=200, body=json.dumps(user))
            else:
                route.fulfill(status=404, body='{"error": "用户不存在"}')
    
    def handle_post(self, route):
        # 创建用户
        post_data = json.loads(route.request.post_data)
        new_user = {
            "id": self.next_id,
            "name": post_data["name"],
            "email": post_data["email"]
        }
        self.users.append(new_user)
        self.next_id += 1
        route.fulfill(status=201, body=json.dumps(new_user))
    
    def handle_put(self, route):
        # 更新用户
        user_id = int(route.request.url.split("/")[-1])
        user_data = json.loads(route.request.post_data)
        
        for user in self.users:
            if user["id"] == user_id:
                user.update(user_data)
                route.fulfill(status=200, body=json.dumps(user))
                return
        route.fulfill(status=404, body='{"error": "用户不存在"}')
    
    def handle_delete(self, route):
        # 删除用户
        user_id = int(route.request.url.split("/")[-1])
        self.users = [u for u in self.users if u["id"] != user_id]
        route.fulfill(status=204)

# 使用模拟服务
user_mock = UserServiceMock()
page.route("**/api/users**", user_mock.handle_user_routes)

4. 错误场景模拟

HTTP 错误状态模拟

python 复制代码
def mock_error_scenarios(route):
    # 模拟各种错误场景
    if "/api/timeout" in route.request.url:
        # 模拟超时(需要异步处理)
        route.fulfill(status=408, body='{"error": "请求超时"}')
    elif "/api/server-error" in route.request.url:
        route.fulfill(status=500, body='{"error": "服务器内部错误"}')
    elif "/api/not-found" in route.request.url:
        route.fulfill(status=404, body='{"error": "资源不存在"}')
    elif "/api/unauthorized" in route.request.url:
        route.fulfill(status=401, body='{"error": "未授权"}')
    elif "/api/forbidden" in route.request.url:
        route.fulfill(status=403, body='{"error": "禁止访问"}')
    else:
        route.continue_()

page.route("**/api/**", mock_error_scenarios)

网络异常模拟

python 复制代码
def mock_network_issues(route):
    import random
    scenario = random.choice(["timeout", "error", "success"])
    
    if scenario == "timeout":
        # 在实际项目中可能需要异步处理
        route.fulfill(status=408, body='{"error": "请求超时"}')
    elif scenario == "error":
        route.fulfill(status=500, body='{"error": "服务器错误"}')
    else:
        route.fulfill(
            status=200,
            body='{"status": "success", "data": "正常响应"}'
        )

# 模拟不稳定的网络
page.route("**/api/unstable**", mock_network_issues)

5. 高级 Mocking 技巧

基于请求头的条件 Mocking

python 复制代码
def mock_based_on_headers(route):
    request = route.request
    auth_header = request.headers.get("authorization", "")
    
    if "admin-token" in auth_header:
        # 管理员权限数据
        route.fulfill(
            status=200,
            body='{"data": "管理员数据", "permissions": ["read", "write", "delete"]}'
        )
    elif "user-token" in auth_header:
        # 普通用户数据
        route.fulfill(
            status=200,
            body='{"data": "用户数据", "permissions": ["read"]}'
        )
    else:
        # 未授权
        route.fulfill(status=401, body='{"error": "未授权"}')

page.route("**/api/secure-data**", mock_based_on_headers)

延迟响应模拟

python 复制代码
import asyncio

async def mock_with_delay(route):
    # 模拟网络延迟
    await asyncio.sleep(2)
    
    await route.fulfill(
        status=200,
        body='{"message": "延迟响应", "delay": 2000}'
    )

await page.route("**/api/slow**", mock_with_delay)

6. 测试用例中的最佳实践

使用 fixture 封装 Mocking

python 复制代码
import pytest
from playwright.sync_api import Page

@pytest.fixture
def setup_user_mock(page: Page):
    """设置用户相关的 API Mock"""
    def mock_users(route):
        route.fulfill(
            status=200,
            body=json.dumps({
                "users": [
                    {"id": 1, "name": "测试用户1", "active": True},
                    {"id": 2, "name": "测试用户2", "active": False}
                ]
            })
        )
    
    page.route("**/api/users", mock_users)
    return page

def test_user_list(setup_user_mock):
    page = setup_user_mock
    page.goto("/users")
    
    # 断言页面显示了模拟的用户数据
    assert page.text_content(".user-name") == "测试用户1"

动态修改 Mock 数据

python 复制代码
class MockManager:
    def __init__(self, page):
        self.page = page
        self.user_data = {"users": []}
    
    def setup_user_mock(self):
        def user_handler(route):
            route.fulfill(
                status=200,
                body=json.dumps(self.user_data)
            )
        
        self.page.route("**/api/users", user_handler)
    
    def set_user_data(self, users):
        self.user_data = {"users": users}

# 在测试中使用
def test_different_user_scenarios():
    mock_manager = MockManager(page)
    mock_manager.setup_user_mock()
    
    # 测试空用户列表
    mock_manager.set_user_data([])
    page.goto("/users")
    assert page.locator(".no-users").is_visible()
    
    # 测试有用户的情况
    mock_manager.set_user_data([{"id": 1, "name": "测试用户"}])
    page.reload()
    assert page.locator(".user-item").is_visible()

7. 调试和验证

验证 Mock 是否生效

python 复制代码
def mock_with_verification(route):
    print(f"Mocking request: {route.request.url}")
    print(f"Request method: {route.request.method}")
    print(f"Request headers: {route.request.headers}")
    
    # 记录 Mock 被调用的次数
    mock_with_verification.call_count = getattr(mock_with_verification, 'call_count', 0) + 1
    
    route.fulfill(
        status=200,
        body='{"status": "mocked"}'
    )

mock_with_verification.call_count = 0
page.route("**/api/test**", mock_with_verification)

# 测试完成后验证
assert mock_with_verification.call_count > 0, "Mock 应该被调用"

这些 Mocking 技术可以帮助你创建稳定、可靠的测试环境,确保测试不依赖外部服务,提高测试速度和可靠性。

相关推荐
自学互联网8 分钟前
使用Python构建钢铁行业生产监控系统:从理论到实践
开发语言·python
无心水13 分钟前
【Python实战进阶】7、Python条件与循环实战详解:从基础语法到高级技巧
android·java·python·python列表推导式·python条件语句·python循环语句·python实战案例
十一.36620 分钟前
79-82 call和apply,arguments,Date对象,Math
开发语言·前端·javascript
霍格沃兹测试开发学社-小明25 分钟前
测试左移2.0:在开发周期前端筑起质量防线
前端·javascript·网络·人工智能·测试工具·easyui
用户990450177800928 分钟前
若依工作流-包含网关
前端
xwill*29 分钟前
RDT-1B: A DIFFUSION FOUNDATION MODEL FOR BIMANUAL MANIPULATION
人工智能·pytorch·python·深度学习
陈奕昆37 分钟前
n8n实战营Day2课时2:Loop+Merge节点进阶·Excel批量校验实操
人工智能·python·excel·n8n
by__csdn38 分钟前
Vue 中计算属性、监听属性与函数方法的区别详解
前端·javascript·vue.js·typescript·vue·css3·html5
程序猿追40 分钟前
PyTorch算子模板库技术解读:无缝衔接PyTorch模型与Ascend硬件的桥梁
人工智能·pytorch·python·深度学习·机器学习
on_pluto_1 小时前
【debug】关于如何让电脑里面的两个cuda共存
linux·服务器·前端