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 技术可以帮助你创建稳定、可靠的测试环境,确保测试不依赖外部服务,提高测试速度和可靠性。

相关推荐
loooooongger2 小时前
(求虐)受不了前端天天改,自动化脚本天天崩!小弟肝了个工具,用“语义”干掉XPath,大佬们看这思路对吗?
低代码·测试
零号机2 小时前
使用TRAE 30分钟极速开发一款划词中英互译浏览器插件
前端·人工智能
今天没有盐2 小时前
Pandas缺失值处理完全指南:从基础操作到高级技巧
python·pycharm·编程语言
程序员小远2 小时前
快速定位bug,编写测试用例
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·bug
B站_计算机毕业设计之家2 小时前
大数据YOLOv8无人机目标检测跟踪识别系统 深度学习 PySide界面设计 大数据 ✅
大数据·python·深度学习·信息可视化·数据挖掘·数据分析·flask
疯狂踩坑人3 小时前
结合400行mini-react代码,图文解说React原理
前端·react.js·面试
Mintopia3 小时前
🚀 共绩算力:3分钟拥有自己的文生图AI服务-容器化部署 StableDiffusion1.5-WebUI 应用
前端·人工智能·aigc
街尾杂货店&3 小时前
CSS - transition 过渡属性及使用方法(示例代码)
前端·css
老歌老听老掉牙3 小时前
解决 PyQt5 中 sipPyTypeDict() 弃用警告的完整指南
python·qt