AI 辅助测试之墨刀需求文档自动化处理

产品经理给了一个墨刀 URL,如何快速转化为可检索的结构化文档,并自动生成测试用例?


一、背景与痛点

在日常测试工作中,我们经常遇到这样的场景:

产品经理: "这是新的需求文档,墨刀链接:https://modao.cc/app/xxx"
测试工程师: 打开链接一看,是几十个页面的墨刀原型...

问题来了:

  1. 墨刀链接无法直接被 AI 读取 - AI 无法直接访问内网/私密的墨刀链接
  2. 页面太多 - 一个需求可能有几十个页面需要人工翻阅
  3. 信息分散 - 每个页面的文字、标注散落在各处
  4. 难以复用 - 无法被 AI 知识库检索,也无法直接生成测试用例

项目地址:

https://github.com/kkapro/modao-to-markdown

二、解决方案:两阶段处理流程

我们通过两阶段处理来解决这个问题:

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  墨刀 URL        │ → │  save_modao.py  │ → │  原始截图 + MD   │
│  (产品经理提供)   │    │  (页面采集)      │    │  (内容原始但     │
└─────────────────┘    └─────────────────┘    │   不够结构化)    │
                                                └────────┬────────┘
                                                         ↓
┌─────────────────┐    ┌─────────────────┐    ┌────────┬────────┐
│  测试用例        │ ← │  AI 生成         │ ← │  image_to_md.py │
│  (最终产物)      │    │  (结构化处理)    │    │  (AI 识别)     │
└─────────────────┘    └─────────────────┘    └─────────────────┘

阶段一:页面采集(save_modao.py)

  • 自动打开墨刀 URL
  • 遍历所有画布和页面
  • 截图 + 提取原始文字
  • 输出:原始截图 + 非结构化 MD

阶段二:AI 结构化(image_to_md.py)

  • 读取截图
  • 调用视觉大模型理解图片内容
  • 输出:结构化 Markdown

三、阶段一:墨刀页面采集

3.1 脚本功能

save_modao.py 核心能力:

  1. 自动登录 - 通过 Playwright 控制浏览器,无需手动操作
  2. 智能遍历 - 自动识别墨刀项目中的画布和页面
  3. 滚动截图 - 长页面自动滚动,分段截图后无缝拼接
  4. 内容提取 - 提取页面可见文字,生成 Markdown

3.2 核心技术实现

1. 浏览器自动化

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)  # 可视化模式
    page = browser.new_page()
    page.goto(url)  # 打开墨刀项目

2. 画布和页面识别

def find_canvas_list(page):
    """查找左侧画布列表"""
    lis = page.query_selector_all("li")
    for li in lis:
        text = li.inner_text().strip()
        # 过滤无关项(总览、演示、废纸篓等)
        if should_skip_item(text, CANVAS_SKIP_KEYWORDS):
            continue
        canvas_items.append(first_line)

def get_page_list_in_canvas(page):
    """获取当前画布下的页面列表"""
    elements = page.query_selector_all(".canvas-sortable-list > ul > li")
    for el in elements:
        page_name = el.inner_text().strip()
        data_cid = el.get_attribute("data-cid")

3. 长页面滚动截图

墨刀页面经常很长,一个屏幕截不完,需要分段截图后拼接:

def take_screenshot(page, screenshot_path, data_cid):
    # 1. 获取画布元素和位置
    canvas_el = page.query_selector(f'.tree-node.rResCanvas[data-cid="{data_cid}"]')
    box = canvas_el.bounding_box()
    
    # 2. 判断是否需要滚动
    visible_height = (VIEWPORT_HEIGHT - HEADER_OFFSET) * current_scale
    content_height = el_height * current_scale
    needs_scrolling = (content_height - visible_height) > 100
    
    # 3. 分段截图
    images = []
    for scroll_pos in range(0, el_height, SCROLL_STEP):
        # 滚动到指定位置
        page.evaluate(f"zoom.style.transform = 'translate(x, {scroll_pos}px) scale(s)'")
        page.screenshot(path=temp_path)
        cropped = crop_canvas_region(img, box)
        images.append(cropped)
    
    # 4. 智能拼接(特征匹配 + 平滑融合)
    result = stitch_images(images)
    result.save(screenshot_path)

4. 图像拼接算法

def find_optimal_overlap(prev_img, curr_img):
    """使用多区域特征匹配找最佳重叠区域"""
    # 多区域分析:中心、左侧、右侧
    regions = [(0.2, 0.8), (0.1, 0.3), (0.7, 0.9)]
    
    # 计算相似度(SSIM + 边缘检测)
    score = calculate_overlap_score(prev_region, curr_region, regions)
    
    # 置信度判断
    if score > 0.9:  # 高置信
        return best_overlap
    elif score > 0.7:  # 低置信,保守处理
        return int(best_overlap * 0.9)
    else:  # 无效重叠,直接拼接
        return 0

def apply_blend(result, prev_img, curr_img, overlap_height):
    """在重叠区域应用平滑融合,消除接缝"""
    for y in range(overlap_height):
        weight = 1.0 - (y / overlap_height)  # 渐变权重
        blended = prev_pixel * weight + curr_pixel * (1 - weight)
        result.putpixel((x, result_y), blended)

5. 内容提取与清洗

def get_page_content(page):
    """提取有效内容,过滤 UI 噪声"""
    ui_tags = [
        "头像", "用户昵称", "私聊", "聊天", "消息", "发送",
        "设置", "保存", "删除", "添加", "关闭", "确定",
        "关注", "粉丝", "礼物", "排行榜", "等级",
        # ... 更多 UI 标签
    ]
    
    # 过滤规则:
    # 1. 纯数字行(页码)
    # 2. 短 UI 标签(如"头像"、"昵称"单独出现)
    # 3. 版本号(如"V1.3.9")

3.3 使用方式

# 基础用法 - 直接采集墨刀页面
python save_modao.py "https://modao.cc/app/xxx"

# 采集指定画布(第 1 个画布)
python save_modao.py "https://modao.cc/app/xxx" 1

# Debug 模式(覆盖已有文件,不创建新目录)
python save_modao.py "https://modao.cc/app/xxx" debug

3.4 输出目录结构

modao-export/
├── 会员福利模块V2.0/              # 项目名称(自动从标题提取)
│   ├── images/                   # 采集的截图
│   │   ├── page_1_会员首页_入口.png
│   │   ├── page_2_会员权益_详情.png
│   │   └── ...
│   └── md/                      # 原始 MD 文档
│       ├── index.md              # 索引文件
│       ├── page_1_会员首页_入口.md
│       └── ...

3.5 原始 MD 输出示例

# 会员首页 - 会员入口

## 内容

- 页面顶部:'某App 你的ID:116'
- 头像区域:圆形头像,右侧显示用户昵称和等级
- 会员状态:'未开通'
- 开通按钮:橙色'立即开通'按钮
- 权益预览:'首充豪礼', '专属徽章', '专属客服'
...

注意:这个阶段的 MD 只有原始文字,没有结构化,不方便 AI 理解和检索。


四、阶段二:AI 结构化处理

4.1 脚本功能

image_to_md.py 核心能力:

  1. AI 图像理解 - 调用视觉大模型理解截图内容
  2. 结构化输出 - 生成规范化的 Markdown
  3. 增量处理 - 已处理的文件自动跳过
  4. 标注提取 - 完整保留序号、箭头、红字等标记

4.2 核心提示词设计

提示词是 AI 输出的关键,设计原则:

【核心原则】
1. 不编造:不添加任何图片中不存在的内容
2. 不遗漏:完整提取所有可见的文字、标注
3. 保留原文:严格按照原图的表述方式
4. 保留结构:还原原图的排版、层级、顺序

【输出格式】
输出JSON对象,字段根据图片内容自适应:
- 文档标题
- 内容区块(按原图顺序组织)
- 标注信息(序号、箭头标记等)
- 页面元素(按钮、输入框等)

4.3 使用方式

# 处理指定项目目录(自动查找 images/ 目录)
python image_to_md.py E:\project\需求文档\会员福利模块

# Debug 模式(覆盖已有文件)
python image_to_md.py E:\project\需求文档\会员福利模块 debug

4.4 输出目录结构

会员福利模块/
├── images/                   # 原始截图(从 modao-export 复制)
│   └── page_1_会员首页_入口.png
└── ai-md/                   # AI 结构化后的 MD
    ├── index.md             # 索引文件
    └── page_1_会员首页_入口.md

4.5 结构化 MD 输出示例

# page_1_会员首页_入口

## 文档标题

- **文档标题**: 新增会员福利发放入口

## 内容区块

### 区块标题: 页面顶部导航与功能区

- **区块内容**: 左上角:'某App', 左侧用户信息栏:'用户 ID:116 ●等级 Lv.33', '收藏', '消息 0', '设置', 右侧下拉菜单(点击'更多'后展开):, - 个人中心, - 会员中心, - 消息中心, - 设置中心, - 福利中心

### 区块标题: 主视觉区域(背景图+图标区)

- **区块内容**: 背景为渐变色+插画,右上角有'新用户专享'、'限时福利'等标签, 中间横向排列多个图标,标注为:'未开通'(共4个图标),其中第1个为'专属折扣',第2个为'专属客服',下方有小字'开通享多重权益', 右下角红色箭头指向一个按钮,标注:'新增会员福利入口', 底部权益展示区:包含'首充返利'、'专属徽章'、'专属客服'等图标及对应权益说明

### 区块标题: 会员福利功能模块(主操作区)

- **区块内容**: 标题:'会员福利发放 ①', 副标题:'上周积分:10000'(上方有红色箭头指向该文字), 左侧:'福利礼包'板块, - 已勾选项:'满减券'(橙色边框+对勾), - 其他选项:'折扣券'(含'查看详情'标签)、'兑换码'(含'查看详情'标签), - 下方两行:'月卡用户首开5折X1,数量10'、'月卡用户续费5折X1,数量10', - 底部说明:'<优惠券类福利可发放到用户ID,其他福利仅发放到主账号>', - 操作按钮:'确认发放'(橙色)、'已发放'(灰色), 右侧:'发放记录'表格(空表),上方有'数量 1'、'发放给 请输入ID'、'留送'按钮, 右下角弹窗:'福利发放说明'(带X关闭按钮), 1. 关于福利:'会员福利发放是针对会员周活跃达标的用户,进行奖励发放的模块,管理员可见及操作', 2. 关于领取时间:'每个自然周一的0点10分更新发放资格,获得奖励的管理员可在此操作。次周一0点后未操作的奖励将失效', 3. 关于优惠券发放:'多张优惠券可发放给一个人,也可发给不同的人,建议输入ID后验证'

## 标注信息

- **标注编号**: ① - 位置:页面左上角标题'会员福利发放'旁
- **标注编号**: ② - 位置:页面顶部右侧下拉菜单中'福利中心'项
- **标注编号**: ③ - 位置:页面中部红色箭头指向'新增会员福利入口'
- **标注编号**: ④ - 位置:页面中部红色箭头指向'上周积分:10000'

## 页面元素

- **下拉菜单**: 个人中心, 会员中心, 消息中心, 设置中心, 福利中心
- **按钮**: 确认发放(橙色), 已发放(灰色), 留送, 添加, 清空
- **输入框/计数器**: 数量 1 ^, 发放给 请输入ID ^

## 截图

![page_1_会员首页_入口](../images/page_1_会员首页_入口.png)

五、完整工作流

5.1 Step by Step

# Step 1: 采集墨刀页面
python save_modao.py "https://modao.cc/app/xxx"

# Step 2: 复制 images 目录到目标位置
# 将 modao-export/项目名/images/ 复制到目标目录

# Step 3: AI 结构化处理
python image_to_md.py E:\project\需求文档\会员福利模块

# Step 4: 查看输出
# 打开 ai-md/index.md 查看所有结构化文档

5.2 完整目录示例

E:\project\
└── ocr-modao\
    ├── save_modao.py              # 墨刀采集脚本
    ├── image_to_md.py             # AI 结构化脚本
    ├── README_USAGE.md            # 使用说明
    ├── 会员福利模块\
    │   ├── images\                # 截图(手动复制或软链接)
    │   │   ├── page_1.png
    │   │   ├── page_2.png
    │   │   └── ...
    │   └── ai-md\                 # AI 结构化输出
    │       ├── index.md
    │       ├── page_1.md
    │       └── ...
    └── modao-export\              # 采集原始文件
        └── 会员福利模块V2.0\
            ├── images\
            └── md\

六、进阶应用

6.1 AI 知识库

结构化后的 MD 可以接入 AI 知识库:

  • 智能问答 - “会员福利的领取时间是什么?”
  • 需求检索 - 搜索"优惠券发放",快速找到相关需求
  • 需求关联 - 自动发现功能之间的依赖

6.2 自动生成测试用例

基于结构化文档,用 AI 生成测试用例:

功能 测试点 预期结果
会员入口 导航菜单显示 菜单中显示"福利中心"
领取资格 周一0点10分后开放 符合条件时开放领取
数量校验 数量大于剩余 提示不能大于剩余数量
权限控制 普通管理员未授权 不可见入口

6.3 自动化测试

结合 Selenium/Appium:

# 读取 MD 中的操作步骤
def get_operation_steps(md_file):
    with open(md_file) as f:
        content = f.read()
    # 解析"区块内容"字段
    # 提取按钮名称、输入框、操作顺序
    return steps

# 自动执行
for step in get_operation_steps("page_1.md"):
    if step["type"] == "click":
        driver.click(step["element"])
    elif step["type"] == "input":
        driver.input(step["element"], step["value"])

七、关键特性

特性 说明
增量生成 已处理的文件自动跳过,避免重复 API 调用
中断恢复 支持 Ctrl+C 中断,后续可续传
自动重试 API 超时/失败时自动重试(最多2次)
保留标注 完整提取序号、箭头、红字等标记
滚动拼接 长页面自动滚动截图并平滑拼接
智能过滤 自动过滤 UI 噪声,保留需求内容

八、环境配置

8.1 依赖安装

pip install openai pillow playwright numpy scipy
playwright install chromium

8.2 API 配置

image_to_md.py 中配置:

BAILIANT_API_KEY = "sk-xxx"  # 阿里百炼 API Key
BAILIANT_MODEL = "qwen3-vl"

九、总结

通过两阶段处理流程,我们实现了:

  1. 从 URL 到结构化文档 - 墨刀 URL → 原始截图 → AI 结构化
  2. 效率提升 - 原本需要几小时的人工整理,现在分钟级完成
  3. 格式统一 - 输出的 Markdown 格式规范,便于 AI 理解和检索
  4. 知识沉淀 - 结构化文档可复用,支持 AI 知识库和测试用例生成
  5. 一键采集 - 自动化处理,减少人工干预

这套方案是 AI 辅助测试的入门级应用,适合团队日常使用。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐