用Playwright实现接口自动化测试:从基础到实战

提到Playwright,多数人第一印象是其在UI自动化领域的统治力------跨浏览器兼容、智能自动等待、录制生成代码等特性让它成为前端测试的首选工具。但鲜少有人注意到,Playwright内置的APIRequestContext模块,其实是接口自动化测试的"隐藏王牌",尤其在**全链路测试(UI操作+接口校验)** 和**复杂会话状态处理**场景中,优势远超传统接口测试工具(如Requests、RestAssured)。本文将从基础用法到企业级实战,全面拆解Playwright接口自动化的实现方案,新增异常处理、参数化、多环境配置等核心场景,并附流程图直观展示关键流程。

一、为什么选择Playwright做接口测试?(补充核心场景对比)

传统接口测试工具虽各有优势,但在"UI与接口协同""复杂状态维护"等场景中存在明显短板。下表对比了Playwright与主流工具的核心差异,更清晰体现其独特价值:

|-----------|-----------------------------|--------------------------|------------------------|----------------|
| 特性/工具 | Playwright | Requests(Python) | RestAssured(Java) | Postman |
| 技术栈统一性 | ✅ UI/接口同一工具 | ❌ 仅接口,需配合UI工具 | ❌ 仅接口,需配合UI工具 | ❌ 仅接口,需配合UI工具 |
| 会话状态共享 | ✅ 复用浏览器Cookies/LocalStorage | ❌ 需手动维护Cookie/Token | ❌ 需手动维护Cookie/Token | ❌ 需手动同步状态 |
| 请求拦截与Mock | ✅ 内置路由拦截,支持复杂Mock | ❌ 需依赖第三方库(如responses) | ❌ 需依赖第三方库(如WireMock) | ✅ 支持Mock,但灵活性低 |
| 异步请求支持 | ✅ 原生异步/同步双模式 | ❌ 需手动封装异步逻辑 | ❌ 需依赖CompletableFuture | ❌ 仅支持简单异步 |
| 响应断言能力 | ✅ 内置JSON/XML校验 | ❌ 需依赖断言库(如pytest-assume) | ✅ 内置断言,但语法复杂 | ✅ 支持断言,但扩展性低 |

核心优势场景补充

  1. 全链路测试无缝衔接:例如"UI登录→接口校验用户信息→UI提交表单→接口校验数据入库",无需在UI工具和接口工具间切换状态;

  2. 动态Mock调试:前端开发未完成时,可通过Playwright拦截前端请求,返回Mock数据,同时验证前端渲染是否正确;

  3. 复杂认证场景处理:如OAuth2.0、SSO单点登录,可通过浏览器自动获取认证Token,无需手动破解认证流程。

二、环境准备与项目初始化(补充项目结构详解)

1. 环境安装(原有基础上补充依赖说明)

bash 复制代码
# 1. 安装Playwright核心库(含API请求模块)
pip install playwright==1.40.0  # 指定稳定版本,避免兼容性问题

# 2. 安装浏览器(接口测试可选,但全链路测试必须)
playwright install chromium  # 仅安装Chrome,减少空间占用(默认安装3个浏览器)

# 3. 安装辅助依赖(项目必备)
pip install pytest==7.4.3  # 测试框架,用于用例组织、参数化
pip install pytest-allure-adaptor==2.9.45  # 生成Allure报告
pip install configparser==5.3.0  # 处理多环境配置文件
pip install pyyaml==6.0.1  # 解析YAML格式测试数据

2. 企业级项目结构(新增)

合理的项目结构是自动化框架可维护性的核心,以下是推荐的结构设计,支持多模块、多环境、数据驱动:

bash 复制代码
playwright-api-auto/
├── config/                  # 配置文件目录
│   ├── config.ini           # 多环境地址配置(开发/测试/生产)
│   └── test_data.yaml       # 测试数据(参数化用例、请求体模板)
├── src/                     # 核心代码目录
│   ├── api/                 # API封装层(按业务模块划分)
│   │   ├── user_api.py      # 用户模块接口(登录、查询、修改)
│   │   └── order_api.py     # 订单模块接口(创建、查询、取消)
│   ├── common/              # 公共工具层
│   │   ├── api_client.py    # Playwright API客户端封装
│   │   ├── mock_handler.py  # 请求拦截与Mock工具
│   │   └── utils.py         # 通用工具(数据加密、时间格式化)
│   └── ui/                  # UI层(全链路测试用)
│       └── login_page.py    # 登录页面元素与操作封装
├── tests/                   # 测试用例目录(与src/api对应)
│   ├── test_user_api.py     # 用户模块接口测试
│   └── test_order_api.py    # 订单模块接口测试
├── reports/                 # 测试报告目录
│   ├── allure-results/      # Allure报告原始数据
│   └── html/                # 简易HTML报告
└── pytest.ini               # pytest配置文件(指定用例路径、报告格式)

三、基础接口测试实现(补充异常场景与数据校验)

Playwright的APIRequestContext支持所有HTTP方法,核心流程为:**创建请求上下文→发送请求→解析响应→断言结果**。以下补充异常场景处理(如超时、4xx/5xx错误)和精细化数据校验(如字段类型、数组长度)。

1. 发送GET请求(补充异常处理与复杂断言)

python 复制代码
from playwright.sync_api import sync_playwright, Playwright
import pytest
import json

def test_get_user_info_with_exception_handling():
    """测试GET请求:含超时处理、4xx/5xx断言、复杂响应校验"""
    with sync_playwright() as p:
        # 1. 创建请求上下文,设置超时时间(避免请求卡死)
        api_context = p.request.new_context(
            base_url="https://jsonplaceholder.typicode.com",
            timeout=5000  # 全局请求超时(单位:毫秒)
        )
        
        try:
            # 2. 发送GET请求(模拟异常场景:请求不存在的用户ID=999)
            response = api_context.get(
                "/users/999",
                headers={"Accept": "application/json"}  # 显式指定请求头
            )
            
            # 3. 异常场景断言:若返回404,验证错误信息
            if response.status == 404:
                assert response.json() == {}, "404场景下响应体应为空"
                print("✅ 404异常场景测试通过")
            # 正常场景断言(若ID=1)
            else:
                # 3.1 状态码断言
                assert response.ok, f"请求失败,状态码:{response.status}"
                assert response.status == 200, "预期状态码200"
                
                # 3.2 响应头断言
                assert response.headers["Content-Type"] == "application/json; charset=utf-8", "响应格式非JSON"
                
                # 3.3 响应体精细化校验(字段存在性、类型、值)
                user_data = response.json()
                required_fields = ["id", "name", "username", "email", "address"]
                for field in required_fields:
                    assert field in user_data, f"响应缺少必填字段:{field}"
                
                # 校验字段类型
                assert isinstance(user_data["id"], int), "用户ID应为整数"
                assert isinstance(user_data["email"], str) and "@" in user_data["email"], "邮箱格式无效"
                
                # 校验嵌套字段(address中的street)
                assert "street" in user_data["address"], "地址缺少street字段"
                
                print(f"✅ 正常场景测试通过,用户:{user_data['name']}")
        
        except Exception as e:
            # 捕获超时等异常
            assert "timeout" not in str(e).lower(), f"请求超时:{str(e)}"
            raise  # 非超时异常抛出,便于定位问题
        
        finally:
            # 无论成功失败,都关闭上下文(释放资源)
            api_context.dispose()

2. 发送POST请求(补充请求体模板与数据复用)

实际项目中,请求体常需复用模板(如创建订单的固定字段),可通过YAML文件管理测试数据,避免硬编码:

步骤1:编写测试数据模板(config/test_data.yaml)
bash 复制代码
create_post_template:
  title: "Playwright接口测试_{{random_str}}"  # 占位符,后续替换
  body: "这是使用Playwright创建的测试文章,时间:{{current_time}}"
  userId: 1
步骤2:POST请求代码(复用模板并动态替换占位符)
python 复制代码
import yaml
from datetime import datetime
import random
import string

def load_test_data(file_path: str) -> dict:
    """加载YAML测试数据模板"""
    with open(file_path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)

def replace_placeholders(data: dict) -> dict:
    """替换测试数据中的占位符(如{{random_str}}、{{current_time}})"""
    # 生成随机字符串(6位)
    random_str = "".join(random.choices(string.ascii_lowercase, k=6))
    # 生成当前时间
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    # 递归替换字典中的占位符
    def _replace(value):
        if isinstance(value, str):
            return value.replace("{{random_str}}", random_str).replace("{{current_time}}", current_time)
        elif isinstance(value, dict):
            return {k: _replace(v) for k, v in value.items()}
        return value
    
    return _replace(data)

def test_create_post_with_template():
    """使用YAML模板发送POST请求"""
    with sync_playwright() as p:
        # 1. 加载并处理测试数据
        test_data = load_test_data("config/test_data.yaml")
        post_body = replace_placeholders(test_data["create_post_template"])
        
        # 2. 创建请求上下文
        api_context = p.request.new_context(
            base_url="https://jsonplaceholder.typicode.com",
            extra_http_headers={"Content-Type": "application/json"}
        )
        
        # 3. 发送POST请求
        response = api_context.post("/posts", data=json.dumps(post_body))  # 手动序列化(避免特殊字符问题)
        
        # 4. 断言
        assert response.status == 201, "创建文章失败"
        created_post = response.json()
        assert created_post["title"] == post_body["title"], "标题不匹配"
        assert int(created_post["userId"]) == post_body["userId"], "用户ID不匹配"
        
        print(f"✅ 文章创建成功,ID:{created_post['id']},标题:{created_post['title']}")
        api_context.dispose()

四、进阶功能:参数化、多环境与接口关联(新增核心实战)

1. 参数化测试(批量执行多组用例)

使用pytest.mark.parametrize实现参数化,适用于"同一接口多组输入"场景(如多用户查询、多条件筛选):

bash 复制代码
import pytest

# 定义参数化数据(可从YAML文件加载,此处简化为列表)
user_query_data = [
    (1, "Leanne Graham", "Sincere@april.biz"),  # 正常用户1
    (2, "Ervin Howell", "Shanna@melissa.tv"),    # 正常用户2
    (999, None, None)                           # 不存在用户(异常场景)
]

@pytest.mark.parametrize("user_id, expected_name, expected_email", user_query_data)
def test_get_user_parametrize(user_id, expected_name, expected_email):
    """参数化测试:多组用户ID查询"""
    with sync_playwright() as p:
        api_context = p.request.new_context(base_url="https://jsonplaceholder.typicode.com")
        response = api_context.get(f"/users/{user_id}")
        
        if response.status == 200:
            # 正常场景断言
            user_data = response.json()
            assert user_data["name"] == expected_name, f"用户{user_id}名称不匹配"
            assert user_data["email"] == expected_email, f"用户{user_id}邮箱不匹配"
        else:
            # 异常场景断言(404)
            assert response.status == 404, f"用户{user_id}预期404,实际{response.status}"
            assert response.json() == {}, "404场景响应体应为空"
        
        api_context.dispose()

2. 多环境配置(动态切换开发/测试环境)

通过config.ini管理多环境地址,避免硬编码,便于维护:

步骤1:编写配置文件(config/config.ini)
bash 复制代码
[ENV]
default_env = test  # 默认环境

[dev]
base_url = https://api-dev.example.com
timeout = 10000

[test]
base_url = https://api-test.example.com
timeout = 5000

[prod]
base_url = https://api-prod.example.com
timeout = 3000
步骤2:封装配置读取工具(src/common/utils.py)
python 复制代码
import configparser
import os

def get_config(env: str = None) -> dict:
    """
    读取配置文件
    :param env: 环境名(dev/test/prod),默认读取default_env
    :return: 环境配置字典
    """
    config = configparser.ConfigParser()
    config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config/config.ini")
    config.read(config_path, encoding="utf-8")
    
    # 若未指定环境,使用默认环境
    if not env:
        env = config.get("ENV", "default_env")
    
    # 读取指定环境的配置
    env_config = {
        "base_url": config.get(env, "base_url"),
        "timeout": int(config.get(env, "timeout"))
    }
    return env_config
步骤3:通过pytest fixture切换环境
python 复制代码
# tests/conftest.py
import pytest
from src.common.api_client import ApiClient
from src.common.utils import get_config

@pytest.fixture(scope="session", params=["dev", "test"])  # 多环境参数化,依次执行
def api_client(request):
    """
    全局API客户端fixture,支持多环境切换
    :param request: pytest参数化对象
    """
    env = request.param  # 获取当前环境
    env_config = get_config(env)
    print(f"✅ 当前执行环境:{env},基础URL:{env_config['base_url']}")
    
    # 创建API客户端
    client = ApiClient(
        base_url=env_config["base_url"],
        timeout=env_config["timeout"]
    )
    
    yield client  # 提供客户端给测试用例
    
    # 测试结束后清理资源
    client.close()
    print(f"❌ 环境{env}测试结束,关闭API客户端")

3. 接口关联实战(业务流程串联)

实际业务中,接口常存在依赖关系(如"创建订单→查询订单→取消订单"),Playwright可通过上下文共享状态,简化关联逻辑:

python 复制代码
def test_order_business_flow(api_client):
    """
    订单业务全流程:创建订单→查询订单→取消订单
    :param api_client: 全局API客户端(fixture注入,自动切换环境)
    """
    # 1. 步骤1:创建订单(获取订单ID)
    create_order_body = {
        "userId": 1,
        "productId": "P12345",
        "quantity": 2,
        "amount": 99.9
    }
    create_response = api_client.post("/orders", data=json.dumps(create_order_body))
    assert create_response.status == 201, "创建订单失败"
    order_id = create_response.json()["id"]
    print(f"✅ 创建订单成功,订单ID:{order_id}")
    
    # 2. 步骤2:查询订单(校验订单信息)
    get_response = api_client.get(f"/orders/{order_id}")
    assert get_response.status == 200, f"查询订单{order_id}失败"
    order_info = get_response.json()
    assert order_info["productId"] == "P12345", "订单商品ID不匹配"
    assert order_info["quantity"] == 2, "订单数量不匹配"
    assert float(order_info["amount"]) == 99.9, "订单金额不匹配"
    print(f"✅ 查询订单成功,订单状态:{order_info['status']}")
    
    # 3. 步骤3:取消订单(校验取消结果)
    cancel_response = api_client.put(
        f"/orders/{order_id}/cancel",
        data=json.dumps({"reason": "测试取消"})
    )
    assert cancel_response.status == 200, f"取消订单{order_id}失败"
    
    # 4. 步骤4:再次查询,验证状态已更新
    get_response_after_cancel = api_client.get(f"/orders/{order_id}")
    order_info_after_cancel = get_response_after_cancel.json()
    assert order_info_after_cancel["status"] == "cancelled", "订单未成功取消"
    print(f"✅ 取消订单成功,订单状态:{order_info_after_cancel['status']}")

五、高级技巧:请求拦截与Mock(补充流程图与复杂场景)

Playwright的route方法支持拦截请求并返回Mock数据,适用于"后端未就绪""异常场景模拟"(如500错误、超时)等场景。以下补充**多条件Mock**和**流程图**,展示完整拦截逻辑。

1. 复杂Mock场景:按请求参数返回不同数据

python 复制代码
def test_mock_order_by_param(api_client):
    """按请求参数返回不同Mock数据:正常订单→Mock成功,异常订单→Mock失败"""
    with sync_playwright() as p:
        # 1. 创建新的请求上下文(单独配置拦截,不影响全局client)
        mock_context = p.request.new_context(base_url=api_client.base_url)
        
        # 2. 注册请求拦截器
        def mock_order_route(route):
            # 获取请求URL和参数
            request_url = route.request.url
            request_params = route.request.urlencoded_body()  # 解析URL参数
            
            # 条件1:查询正常订单(ID=100)→ 返回成功数据
            if "orders/100" in request_url:
                route.fulfill(
                    status=200,
                    json={
                        "id": 100,
                        "status": "paid",
                        "productId": "P12345",
                        "mock_flag": True
                    }
                )
            # 条件2:查询异常订单(ID=999)→ 返回500错误
            elif "orders/999" in request_url:
                route.fulfill(
                    status=500,
                    json={"error": "数据库连接失败", "code": "DB_ERROR"}
                )
            # 条件3:其他请求→放行,走真实接口
            else:
                route.continue_()
        
        # 3. 绑定拦截器(拦截所有/orders/*请求)
        mock_context.route("**/orders/*", mock_order_route)
        
        # 4. 测试Mock效果
        # 测试正常订单Mock
        normal_response = mock_context.get("/orders/100")
        assert normal_response.status == 200
        assert normal_response.json()["mock_flag"] is True, "正常订单Mock未生效"
        print("✅ 正常订单Mock测试通过")
        
        # 测试异常订单Mock
        error_response = mock_context.get("/orders/999")
        assert error_response.status == 500
        assert error_response.json()["code"] == "DB_ERROR", "异常订单Mock未生效"
        print("✅ 异常订单Mock测试通过")
        
        # 测试真实接口(未被拦截)
        real_response = mock_context.get("/users/1")
        assert real_response.status == 200
        assert "mock_flag" not in real_response.json(), "真实接口被误拦截"
        print("✅ 真实接口放行测试通过")
        
        mock_context.dispose()

2. 请求拦截与Mock流程图(新增)

六、全链路测试:UI操作+接口校验(新增流程图与实例)

Playwright的核心优势是"UI与接口协同",可在UI操作后立即通过接口校验数据正确性,避免仅依赖UI元素断言的不稳定性。

1. 全链路测试实例:UI登录→接口校验用户信息

python 复制代码
from src.ui.login_page import LoginPage  # UI页面封装

def test_ui_api_full_link(p):
    """
    全链路测试:UI登录→接口校验用户信息
    :param p: Playwright实例(fixture注入)
    """
    # 1. UI操作:打开登录页面并登录
    browser = p.chromium.launch(headless=False)  # 显示浏览器,便于调试
    page = browser.new_page()
    login_page = LoginPage(page)
    
    # 执行登录(UI操作)
    login_page.goto_login_page("https://example.com/login")
    login_page.input_username("test_user")
    login_page.input_password("test_pass")
    login_page.click_login_button()
    
    # 等待登录成功(UI断言)
    page.wait_for_url("https://example.com/home")
    assert "首页" in page.title(), "UI登录失败"
    print("✅ UI登录成功")
    
    # 2. 接口校验:复用浏览器Cookies,查询用户信息
    # 从浏览器上下文提取Cookies,创建API请求上下文
    api_context = p.request.new_context(
        base_url="https://api.example.com",
        cookies=page.context.cookies()  # 复用UI登录后的Cookies
    )
    
    # 发送接口请求(无需再次登录)
    user_response = api_context.get("/user/profile")
    assert user_response.status == 200, "接口查询用户信息失败"
    user_data = user_response.json()
    assert user_data["username"] == "test_user", "接口返回用户与UI登录用户不一致"
    print(f"✅ 接口校验成功,用户角色:{user_data['role']}")
    
    # 3. 清理资源
    api_context.dispose()
    browser.close()

2. 全链路(UI+接口)测试流程图(新增)

七、测试执行与报告生成(补充流程图与报告解读)

1. 测试执行命令(支持多环境、多模块)

bash 复制代码
# 1. 执行所有用例,生成Allure报告原始数据
pytest tests/ -v -s --alluredir=reports/allure-results --tb=short  # --tb=short简化错误日志

# 2. 仅执行订单模块用例,指定环境为test
pytest tests/test_order_api.py -v -s -k "order" --alluredir=reports/allure-results

# 3. 生成HTML格式简易报告(适合快速查看)
pytest tests/ --html=reports/html/test_report.html --self-contained-html  # --self-contained-html:报告独立文件

# 4. 启动Allure服务,查看交互式报告
allure serve reports/allure-results

2. 测试报告生成流程图(新增)

3. Allure报告核心模块解读

Allure报告提供丰富的测试数据可视化,核心关注以下模块:

  • Overview:用例总数、通过率、执行时间,快速掌握测试整体情况;

  • Behaviors:按功能模块(Feature/Story)分组,查看业务模块覆盖率;

  • Tests:单个用例执行详情,包括请求URL、响应数据、错误堆栈;

  • Graphs:用例通过率趋势图、环境执行对比图,辅助分析测试稳定性;

  • Attachments:可在测试中添加截图、日志等附件(如UI操作截图、接口响应JSON),便于问题定位。

八、总结与扩展方向(补充AI集成思路)

Playwright接口自动化框架的核心价值在于**"一站式解决UI+接口测试"**,避免工具链碎片化。结合本文内容,可进一步扩展以下方向:

  1. AI辅助测试用例生成:使用LangChain读取产品文档(如Swagger/OpenAPI),自动生成Playwright接口测试代码;

  2. 接口性能测试融合 :通过Playwright的request.timing()获取请求耗时,结合pytest-benchmark实现简单性能压测;

  3. 持续集成(CI/CD)集成:将测试命令配置到Jenkins/GitLab CI,实现"代码提交→自动测试→报告推送"全流程自动化;

  4. 数据驱动增强:结合Excel/MySQL管理测试数据,支持大批量用例动态生成。

Playwright的接口测试能力仍在快速迭代,建议关注官方文档(Playwright API Request),及时获取新特性(如WebSocket测试、GraphQL支持)。通过本文的框架设计和实战案例,可快速落地企业级接口自动化,并灵活应对复杂业务场景。

相关推荐
hhcgchpspk3 小时前
flask获取ip地址各种方法
python·tcp/ip·flask
站大爷IP3 小时前
Python SQLite模块:轻量级数据库的实战指南
python
慌糖3 小时前
自动化接口框架搭建分享-pytest
运维·自动化·pytest
站大爷IP3 小时前
用Requests+BeautifulSoup实现天气预报数据采集:从入门到实战
python
Rhys..3 小时前
Gerkin+Pytest(python)实现自动化(BDD)
python·自动化·pytest
大佐不会说日语~4 小时前
若依框架 (Spring Boot 3) 集成 knife4j 实现 OpenAPI 文档增强
spring boot·后端·python
MATLAB代码顾问4 小时前
Python实现手榴弹爆炸算法(Grenade Explosion Method, GEM)(附完整代码)
开发语言·python·算法
困鲲鲲4 小时前
NumPy 系列(六):numpy 数组函数
python·numpy
人工干智能4 小时前
Python的大杀器:Jupyter Notebook处理.ipynb文件
开发语言·python·jupyter