Midscene.js框架(4):测试框架集成
2026/7/15 5:18:51 网站建设 项目流程

本篇将聚焦于测试框架集成——重点讲解如何将 Midscene 的 AI 自动化能力与 Python 生态中最流行的Pytest测试框架深度结合,实现规范化的 UI 自动化测试。

前言

一、与 Pytest 深度集成

1.1环境准备

1.2配置模型

1.3编写第一个 Pytest 测试

二、Fixture 管理

2.1 为什么需要 Fixture 管理?

2.2 设计 Midscene 专用 Fixture

2.3 使用 Fixture 编写测试

三、测试生命周期

3.1 生命周期全景图

3.2 准备阶段:Setup

3.3 执行阶段:Test Body

3.4 断言策略:AI 断言 vs 传统断言

3.5 清理阶段:Teardown

3.6 完整生命周期示例

3.7 报告合并


前言

在前面的教程中,我们已经掌握了两种使用 Midscene.js 的方式:通过 JavaScript/TypeScript API 编程,以及通过 YAML 脚本快速执行。这两种方式适合快速验证和独立自动化任务,但在企业级测试场景中,我们还需要解决一系列工程化问题:

  • 环境管理:浏览器启动/关闭、模型配置、测试数据准备
  • 用例隔离:每条用例独立的浏览器上下文,互不干扰
  • 生命周期控制:用例执行前的初始化、执行后的清理与报告生成
  • 复用与共享:登录状态、公共配置、Agent 实例的跨用例复用
  • 持续集成:与 CI/CD 流水线无缝对接,支持批量执行和结果收集

Midscene.js 本身是 JavaScript/TypeScript 框架,官方提供了与 Playwright Test Runner 的深度集成。但对于 Python 技术栈的团队来说,Pytest才是最自然的选择。社区项目pymidscene提供了 Midscene.js 的 Python SDK,完整移植了核心 API,使得 Python 开发者也能享受 AI 驱动的 UI 自动化能力。

Pytest 的优势在于:

一、与 Pytest 深度集成

1.1环境准备

首先安装 pymidscene 和 Playwright 浏览器驱动:

pip install pymidscene playwright install chromium

pymidscene 要求 Python ≥ 3.10,且当前版本(v0.3.0)基于 Playwright 作为浏览器驱动层。

1.2配置模型

Midscene.js 的"灵魂"在于 AI 模型——所有元素的定位、操作的规划、断言判断都依赖大模型完成。在 pymidscene 中,创建.env文件或直接设置环境变量:

export MIDSCENE_MODEL_NAME="doubao-seed-1-6-251015" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_FAMILY="doubao-vision"

1.3编写第一个 Pytest 测试

pymidscene 的核心是 PlaywrightAgent 类,它封装了所有 AI 驱动的交互能力:

import asyncio import pytest from playwright.async_api import async_playwright from pymidscene import PlaywrightAgent @pytest.mark.asyncio async def test_search_flow(): """测试搜索功能的基本流程""" async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page() agent = PlaywrightAgent(page) # 导航到目标页面 await page.goto("https://www.example.com") # 使用 AI 进行页面交互 await agent.ai_click("搜索输入框") await agent.ai_input("搜索输入框", "Midscene AI testing") await agent.ai_click("搜索按钮") # AI 断言:验证搜索结果已显示 await agent.ai_assert("页面上显示了搜索结果列表") # AI 查询:提取结构化数据 result = await agent.ai_query({ "first_result_title": "第一个搜索结果的标题", "total_results_text": "搜索结果数量的文本" }) print(f"查询结果: {result}") # 生成报告 report_path = agent.finish() print(f"报告已保存至: {report_path}") await browser.close()

这段代码演示了 Midscene.js 测试的核心模式:

  1. 创建 Agent:将 Playwright 的 Page 对象注入 Agent
  2. AI 操作:用自然语言描述目标,AI 自动完成定位与执行
  3. AI 断言:用自然语言描述预期条件
  4. AI 查询:从页面提取结构化数据
  5. 报告生成:自动生成可视化 HTML 报告

二、Fixture 管理

2.1 为什么需要 Fixture 管理?

在上面的示例中,浏览器、页面、Agent 的创建和销毁逻辑都写在测试函数内部。当测试用例增多时,这种模式会带来严重的代码重复和维护问题。Pytest 的 Fixture 机制恰好能解决这个问题——将"可复用的前置准备"抽象为独立的 Fixture,在需要的测试中按需注入。

2.2 设计 Midscene 专用 Fixture

我们在 conftest.py 中创建三个核心 Fixture:

# conftest.py import pytest import os from playwright.async_api import async_playwright from pymidscene import PlaywrightAgent # ========================================== # 1. 全局配置:模型环境变量 # ========================================== @pytest.fixture(scope="session", autouse=True) def setup_model_config(): """会话级别:一次配置,所有测试复用""" os.environ["MIDSCENE_MODEL_NAME"] = "doubao-seed-1-6-251015" os.environ["MIDSCENE_MODEL_API_KEY"] = "your-api-key" os.environ["MIDSCENE_MODEL_BASE_URL"] = ( "https://ark.cn-beijing.volces.com/api/v3" ) os.environ["MIDSCENE_MODEL_FAMILY"] = "doubao-vision" yield # session 结束后无需清理环境变量 # ========================================== # 2. 浏览器 Fixture(Session 级别) # ========================================== @pytest.fixture(scope="session") async def browser(): """会话级别:整个测试会话共享一个浏览器实例""" async with async_playwright() as p: browser = await p.chromium.launch( headless=True, # CI 环境建议设为 True args=["--no-sandbox"] # Docker 环境必需 ) yield browser await browser.close() # ========================================== # 3. Agent Fixture(Function 级别) # ========================================== @pytest.fixture async def midscene_agent(browser): """函数级别:每个测试用例拥有独立的 Page 和 Agent""" context = await browser.new_context( viewport={"width": 1280, "height": 720} ) page = await context.new_page() agent = PlaywrightAgent(page) yield agent # 测试用例在此执行 # Teardown:生成报告并清理资源 agent.finish() await context.close()

这个 Fixture 架构借鉴了 Midscene.js 中 PlaywrightAiFixture 的设计理念,分层管理不同生命周期的资源:

┌─────────────────────────────────────┐
│ Session 级别(全局共享) │
│ ┌─────────┐ ┌──────────────────┐ │
│ │ Browser │ │ Model Config │ │
│ └─────────┘ └──────────────────┘ │
│ │ │
│ Function 级别(每用例独立) │
│ ┌──────────────────┐ │
│ │ Context → Page │ │
│ │ → Agent │ │
│ └──────────────────┘ │
└─────────────────────────────────────┘

2.3 使用 Fixture 编写测试

有了上述 Fixture 后,测试用例变得清晰简洁:

# test_search.py import pytest @pytest.mark.asyncio async def test_search_with_ai(midscene_agent): """使用 Fixture 注入 Agent,不再需要手动管理浏览器生命周期""" agent = midscene_agent page = agent.page # 可以通过 agent 获取底层 page 对象 await page.goto("https://www.example.com/search") # 直接用 AI 操作 await agent.ai_input("搜索框", "AI testing framework") await agent.ai_click("搜索按钮") # 等待结果加载 await agent.ai_wait_for("搜索结果列表出现", timeout=10000) # AI 断言 await agent.ai_assert("搜索结果数量大于 0") # 提取数据 items = await agent.ai_query({ "results": [ {"title": "结果标题", "link_text": "链接文字"} ] }) assert len(items.get("results", [])) > 0 @pytest.mark.asyncio async def test_filter_by_category(midscene_agent): """另一个测试用例,自动获得独立的 Page 和 Agent""" agent = midscene_agent page = agent.page await page.goto("https://www.example.com/products") await agent.ai_click("分类筛选器") await agent.ai_click("电子产品分类") await agent.ai_assert("页面只显示电子产品")

三、测试生命周期

理解 Midscene.js 测试的完整生命周期,是编写稳定可靠测试的关键。整个生命周期可以分为四个阶段:准备 → 执行 → 断言 → 清理。

3.1 生命周期全景图

┌──────────────────────────────────────────────────────────┐ │ Pytest 测试生命周期 │ ├───────────┬──────────────────────────────────────────────┤ │ session │ setup_model_config() │ │ start │ browser() launch │ ├───────────┼──────────────────────────────────────────────┤ │ function │ midscene_agent() │ │ setup │ ├── browser.new_context() │ │ │ ├── context.new_page() │ │ │ └── PlaywrightAgent(page) │ ├───────────┼──────────────────────────────────────────────┤ │ test │ ├── page.goto(url) ← 导航 │ │ body │ ├── agent.ai_click() ← AI 交互 │ │ │ ├── agent.ai_input() ← AI 输入 │ │ │ ├── agent.ai_wait_for() ← 等待条件 │ │ │ ├── agent.ai_query() ← 数据提取 │ │ │ └── agent.ai_assert() ← AI 断言 │ ├───────────┼──────────────────────────────────────────────┤ │ function │ midscene_agent() teardown │ │ teardown │ ├── agent.finish() ← 生成报告 │ │ │ └── context.close() ← 回收资源 │ ├───────────┼──────────────────────────────────────────────┤ │ session │ browser.close() │ │ end │ │ └───────────┴──────────────────────────────────────────────┘

3.2 准备阶段:Setup

准备阶段负责初始化所有测试所需的环境和资源:

# conftest.py 中的 setup 逻辑 @pytest.fixture(scope="session", autouse=True) def setup_model_config(): """🔧 Step 1: 配置 AI 模型(最早执行)""" os.environ["MIDSCENE_MODEL_NAME"] = "..." os.environ["MIDSCENE_MODEL_API_KEY"] = "..." # ... @pytest.fixture(scope="session") async def browser(): """🌐 Step 2: 启动浏览器(会话级别复用)""" async with async_playwright() as p: browser = await p.chromium.launch(headless=True) yield browser # 所有测试结束后才关闭浏览器 @pytest.fixture async def midscene_agent(browser): """🤖 Step 3: 创建 Agent(每个测试独立)""" context = await browser.new_context( viewport={"width": 1280, "height": 720} ) page = await context.new_page() agent = PlaywrightAgent(page) yield agent # ...

3.3 执行阶段:Test Body

执行阶段是测试的核心,通过 AI 方法驱动浏览器完成业务操作:

@pytest.mark.asyncio async def test_complete_checkout(midscene_agent): agent = midscene_agent page = agent.page # ---- 1. 导航 ---- await page.goto("https://shop.example.com") await page.wait_for_load_state("networkidle") # ---- 2. 浏览商品 ---- await agent.ai_click("「手机」分类") await agent.ai_wait_for("商品列表加载完成", timeout=8000) # ---- 3. 加入购物车 ---- await agent.ai_click("第一个商品的「加入购物车」按钮") await agent.ai_assert("购物车数量显示为 1") # ---- 4. 进入结算 ---- await agent.ai_click("购物车图标") await agent.ai_click("「去结算」按钮") # ---- 5. 填写收货信息 ---- await agent.ai_input("收货人姓名输入框", "张三") await agent.ai_input("手机号码输入框", "13800138000") await agent.ai_input("详细地址输入框", "北京市朝阳区xxx路xxx号") # ---- 6. 提交订单 ---- await agent.ai_click("「提交订单」按钮") await agent.ai_wait_for("订单提交成功提示出现", timeout=10000) # ---- 7. 最终断言 ---- await agent.ai_assert("页面显示「订单提交成功」")

3.4 断言策略:AI 断言 vs 传统断言

Midscene.js 提供了两种断言方式,各有适用场景:

最佳实践:结合使用两者。先用 ai_query 提取结构化数据,再用 Pytest 标准断言验证,可以减少 AI 推理成本并提高稳定性:

@pytest.mark.asyncio async def test_hybrid_assertion(midscene_agent): agent = midscene_agent # ✅ 推荐:传统断言验证数据 product = await agent.ai_query({ "name": "商品名称", "price": "商品价格(纯数字,如 99.00)", "in_stock": "是否显示「有货」(true/false)" }) assert product["in_stock"] is True assert product["price"] > 0 assert len(product["name"]) > 0 # ✅ AI 断言验证视觉条件 await agent.ai_assert("商品图片已正确加载显示")

3.5 清理阶段:Teardown

Teardown 阶段负责报告生成和资源回收。在 Fixture 的 yield 之后执行:

@pytest.fixture async def midscene_agent(browser): context = await browser.new_context() page = await context.new_page() agent = PlaywrightAgent(page) yield agent # ====== Teardown 逻辑 ====== # 1. 生成 HTML 报告 report_path = agent.finish() print(f"📊 测试报告: {report_path}") # 2. 关闭 context(回收所有 Page) await context.close()

agent.finish() 方法会生成一个包含操作截图、AI 推理过程、耗时统计的 HTML 报告文件,存放在 midscene_run/report/ 目录下。

3.6 完整生命周期示例

以下是一个完整的测试文件,展示了生命周期的实际运作:

# test_lifecycle_demo.py import pytest from datetime import datetime @pytest.fixture(scope="module", autouse=True) def module_setup(): """Module 级别的 setup""" print(f"\n📦 [Module Setup] 测试模块开始于 {datetime.now()}") yield print(f"\n📦 [Module Teardown] 测试模块结束于 {datetime.now()}") @pytest.fixture(autouse=True) def function_setup(): """Function 级别的 setup(每个用例都执行)""" print("\n🔧 [Function Setup] 准备测试用例") yield print("🔧 [Function Teardown] 测试用例结束") @pytest.mark.asyncio async def test_case_1(midscene_agent): """测试用例 1""" print(" 🧪 执行测试用例 1") agent = midscene_agent await agent.page.goto("https://www.example.com") await agent.ai_assert("页面已成功加载") @pytest.mark.asyncio async def test_case_2(midscene_agent): """测试用例 2""" print(" 🧪 执行测试用例 2") agent = midscene_agent await agent.page.goto("https://www.example.com/about") await agent.ai_assert("页面包含关于我们的信息")

执行 pytest -v -s test_lifecycle_demo.py,输出将清晰展示各阶段的执行顺序。

3.7 报告合并

当测试用例较多时,可以将多个 Agent 的报告合并为一个文件。在 pymidscene 中,你可以通过自定义 Fixture 实现报告收集:

# conftest.py import pytest @pytest.fixture(scope="session") def report_collector(): """Session 级别的报告收集器""" reports = [] yield reports # 所有测试结束后合并报告 if reports: print(f"\n📊 共生成 {len(reports)} 份报告") for r in reports: print(f" - {r}") @pytest.fixture async def midscene_agent(browser, report_collector): context = await browser.new_context() page = await context.new_page() agent = PlaywrightAgent(page) yield agent report_path = agent.finish() report_collector.append(report_path) await context.close()

总结

测试框架的集成是绕不开的关键环节。一个好的测试框架集成不仅要让测试用例编写高效,还要提供灵活的 Fixture 管理机制和清晰的生命周期控制。

参考资料:

  • Midscene.js 官方文档:Midscene - Vision-Driven UI Automation
  • pymidscene PyPI:pymidscene · PyPI
  • Midscene.js Playwright 集成指南:Integrate with Playwright - Midscene - Vision-Driven UI Automation
  • Pytest Fixture 官方文档:About fixtures - pytest documentation

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询