Skill: yontlly/wiki2doc (version 1.0.0)

# wiki2doc 将 Confluence Wiki 页面内容转换为 Word 文档 (.docx) 的工具。 ## 功能特性 - ✅ 自动登录 Confluence（支持账号密码） - ✅ 提取页面标题（#title-text） - ✅ 提取 #splitter-content 中的全部文本和图片 - ✅ 过滤 CSS 隐藏的图片（display:none / visibility:hidden） - ✅ 图片自动转为 JPG 格式 - ✅ 排除 GIF/SVG/视频等非图片文件 - ✅ 保持文本与图片原始顺序 - ✅ 生成可编辑的 .docx Word 文档 - ✅ 支持单个 URL 或批量 URL 处理 - ✅ 自动创建 demand/ 输出目录 - ✅ 需求分析（检测遗漏点、矛盾点、不明确点） - ✅ 自动生成结构化测试用例（支持多种测试设计方法） - ✅ 输出测试用例到Markdown和Excel格式 - ✅ 完整自动化工作流：从Wiki到Excel的一键生成 ## 使用方式 ### 工具概览 本技能提供两种使用模式： #### 模式一：完整自动化流程（推荐） 一键完成从Wiki页面到测试用例Excel的完整转换： ```bash /skill wiki2doc --auto http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052 ``` 执行步骤： 1. ✓ 从Wiki提取内容生成Word文档 2. ✓ 分析需求检测遗漏点、矛盾点 3. ✓ 自动生成测试用例（Markdown格式） 4. ✓ 转换为Excel格式 #### 模式二：独立工具使用 可以单独使用各个工具模块： ##### 1. 基本用法：Wiki转Word（单个页面） ```bash /skill wiki2doc http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052 ``` ##### 2. 批量处理（多个页面） ```bash /skill wiki2doc --batch "http://10.225.1.76:8090/page1,http://10.225.1.76:8090/page2" ``` ##### 3. 需求分析 ```bash python bin/analyze/analyze_requirements.py demand/需求文档.docx ``` 输出： - JSON格式分析报告 - TXT格式人类可读报告 ##### 4. 测试用例生成 ```bash python bin/generate/generate_testcases.py demand/需求文档.docx ``` 输出： - Markdown格式测试用例 - JSON格式测试用例 ##### 5. Markdown转Excel ```bash python bin/convert/md2excel.py demand/测试用例.md ``` 输出： - Excel格式测试用例表格 ### 输出位置 所有生成的文件保存在： ``` ~/.claude/skills/wiki2doc/demand/ ``` 文件命名规范： - Word文档：`{页面标题}.docx` - 分析报告：`{页面标题}_analysis_report.txt` / `.json` - 测试用例MD：`{页面标题}_testcases.md` / `.json` - 测试用例Excel：`{页面标题}_testcases.xlsx` ## 安装依赖 首次使用前请确保已安装依赖： ```bash pip install playwright beautifulsoup4 python-docx requests Pillow lxml ``` ## 注意事项 - 仅支持内网 Confluence（默认地址：http://10.225.1.76:8090/login.action） - 默认账号：`yanghua`，密码：`Aa123123` - 图片将自动转换为 JPG，保留原始顺序 - 如果页面无 `#splitter-content` 元素，将返回空文档 - 部分图片下载失败不影响其他内容 - 会话 Cookie 在运行期间保持，批量处理时无需重复登录 ## 示例 ```bash # 单个页面 /skill wiki2doc http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052 # 批量处理三个页面 /skill wiki2doc --batch "http://10.225.1.76:8090/page1,http://10.225.1.76:8090/page2,http://10.225.1.76:8090/page3" ``` ## 故障排查 | 问题 | 解决方案 | |------|----------| | 登录失败 | 检查账号密码是否正确，或手动登录后重试 | | 页面加载超时 | 检查网络连接，或尝试访问 URL 是否正常 | | 提示 `No such element: #splitter-content` | 该页面结构不同，无法提取内容 | | 图片未显示 | 图片可能被 CSS 隐藏，或链接失效 | | 生成空文档 | 页面无有效内容，或标题提取失败 | > 本技能由 Claude Code 自动生成，适用于内部知识管理场景。

path: bin/analyze/analyze_requirements.py
content: #!/usr/bin/env python3
"""
需求分析工具 - analyze_requirements.py

分析 .docx 中的需求内容，检测遗漏点、矛盾点和不明确之处
"""

import sys
import re
import json
from pathlib import Path
from docx import Document
from typing import List, Dict, Tuple, Any


class RequirementAnalyzer:
    """需求分析器"""

    def __init__(self, doc_path):
        """
        初始化需求分析器

        Args:
            doc_path: Word 文档路径
        """
        self.doc_path = Path(doc_path)
        self.doc = None
        self.requirements = []
        self.issues = {
            "missing_points": [],    # 遗漏点
            "contradictory_points": [],  # 矛盾点
            "unclear_points": [],    # 不明确点
            "incomplete_points": []  # 不完整点
        }

    def load_document(self):
        """加载 Word 文档"""
        try:
            self.doc = Document(self.doc_path)
            print(f"成功加载文档: {self.doc_path}")
            return True
        except Exception as e:
            print(f"文档加载失败: {e}")
            return False

    def extract_text_content(self):
        """
        提取文档中的文本内容

        Returns:
            str: 文档的完整文本内容
        """
        if not self.doc:
            return ""

        full_text = []
        for para in self.doc.paragraphs:
            if para.text.strip():
                full_text.append(para.text)

        return "\n".join(full_text)

    def analyze_missing_points(self, text: str) -> List[Dict[str, Any]]:
        """
        分析需求中的遗漏点

        Args:
            text: 需求文本内容

        Returns:
            List[Dict]: 遗漏点列表
        """
        missing_points = []
        lines = text.split('\n')

        # 检查每个段落是否有功能描述但没有测试要点
        for i, line in enumerate(lines):
            line = line.strip()
            if not line:
                continue

            # 检查功能描述
            if any(keyword in line for keyword in ["功能", "模块", "菜单", "按钮"]):
                # 查找前后10行是否有测试相关内容
                start_idx = max(0, i - 5)
                end_idx = min(len(lines), i + 10)
                context_lines = lines[start_idx:end_idx]
                context_text = '\n'.join(context_lines)

                if not any(keyword in context_text for keyword in ["测试", "验证", "检查", "步骤"]):
                    missing_points.append({
                        "type": "功能缺少测试",
                        "description": "该功能描述缺少相应的测试要点或验证步骤",
                        "severity": "high",
                        "suggestion": "建议为该功能添加具体的测试要点和验证方法",
                        "location": f"第{i+1}行附近",
                        "context": line,
                        "surrounding_text": context_text
                    })

            # 检查数据字段
            if any(keyword in line for keyword in ["字段", "输入框", "输入", "数据项"]):
                # 查找前后5行是否有校验说明
                start_idx = max(0, i - 3)
                end_idx = min(len(lines), i + 5)
                context_lines = lines[start_idx:end_idx]
                context_text = '\n'.join(context_lines)

                if not any(keyword in context_text for keyword in ["校验", "验证", "格式", "限制", "长度", "类型"]):
                    missing_points.append({
                        "type": "数据缺少校验",
                        "description": "该数据字段缺少格式校验或限制说明",
                        "severity": "medium",
                        "suggestion": "建议添加数据格式、长度限制、类型等校验规则",
                        "location": f"第{i+1}行",
                        "context": line,
                        "surrounding_text": context_text
                    })

            # 检查界面元素
            if any(keyword in line for keyword in ["按钮", "菜单", "图标", "图片"]):
                start_idx = max(0, i - 3)
                end_idx = min(len(lines), i + 5)
                context_lines = lines[start_idx:end_idx]
                context_text = '\n'.join(context_lines)

                if not any(keyword in context_text for keyword in ["样式", "颜色", "大小", "位置", "尺寸", "宽度", "高度"]):
                    missing_points.append({
                        "type": "UI缺少具体说明",
                        "description": "该界面元素缺少具体的样式或尺寸说明",
                        "severity": "medium",
                        "suggestion": "建议添加界面元素的具体规格（大小、颜色、位置等）",
                        "location": f"第{i+1}行",
                        "context": line,
                        "surrounding_text": context_text
                    })

            # 检查异常情况
            if any(keyword in line for keyword in ["异常", "错误", "失败", "网络异常", "权限不足"]):
                start_idx = max(0, i - 2)
                end_idx = min(len(lines), i + 5)
                context_lines = lines[start_idx:end_idx]
                context_text = '\n'.join(context_lines)

                if not any(keyword in context_text for keyword in ["提示", "处理", "跳转", "容错", "重试"]):
                    missing_points.append({
                        "type": "缺少异常处理说明",
                        "description": "该异常情况缺少具体的处理方式或提示信息",
                        "severity": "high",
                        "suggestion": "建议添加异常处理逻辑、错误提示信息和恢复方案",
                        "location": f"第{i+1}行",
                        "context": line,
                        "surrounding_text": context_text
                    })

        return missing_points

    def analyze_contradictory_points(self, text: str) -> List[Dict[str, Any]]:
        """
        分析需求中的矛盾点

        Args:
            text: 需求文本内容

        Returns:
            List[Dict]: 矛盾点列表
        """
        contradictory_points = []
        lines = text.split('\n')

        # 提取数字和单位
        number_patterns = []
        patterns = [
            (r'(\d+)\s*个\s*菜单', "菜单数量"),
            (r'(\d+)\s*px|(\d+)\s*像素', "尺寸"),
            (r'(\d+)\s*秒|(\d+)\s*分钟', "时间"),
            (r'(\d+)\s*KB|(\d+)\s*KB|(\d+)\s*MB', "数据量"),
        ]

        for pattern, category in patterns:
            for i, line in enumerate(lines):
                matches = re.findall(pattern, line, re.IGNORECASE)
                for match in matches:
                    if isinstance(match, tuple):
                        num = next((m for m in match if m.isdigit()), None)
                    else:
                        num = match

                    if num:
                        number_patterns.append({
                            "category": category,
                            "value": num,
                            "context": line,
                            "line_number": i + 1
                        })

        # 检查同一类别是否有不同数值
        categories = {}
        for item in number_patterns:
            cat = item["category"]
            if cat not in categories:
                categories[cat] = []
            categories[cat].append(item)

        for cat, items in categories.items():
            if len(items) > 1:
                values = [item["value"] for item in items]
                if len(set(values)) > 1:
                    # 收集所有矛盾的上下文
                    contexts = []
                    for item in items:
                        contexts.append(f"第{item['line_number']}行: {item['context']}")

                    contradictory_points.append({
                        "type": "数值矛盾",
                        "description": f"关于{cat}的描述存在不同数值: {values}",
                        "severity": "high",
                        "suggestion": "请确认正确的{cat}数值",
                        "location": "多个位置",
                        "conflicting_contexts": contexts
                    })

        # 检查相互矛盾的描述
        矛盾对 = [
            ("必须", "可选", "强制性矛盾"),
            ("固定", "可变", "性质矛盾"),
            ("自动", "手动", "操作方式矛盾"),
            ("开启", "关闭", "状态矛盾")
        ]

        for word1, word2, issue_type in 矛盾对:
            for i, line in enumerate(lines):
                if word1 in line and word2 in line:
                    contradictory_points.append({
                        "type": issue_type,
                        "description": f"在该描述中同时出现了'{word1}'和'{word2}'，可能存在逻辑矛盾",
                        "severity": "medium",
                        "suggestion": "请确认正确的操作逻辑，或者说明两者的适用场景",
                        "location": f"第{i+1}行",
                        "context": line
                    })

        return contradictory_points

    def analyze_unclear_points(self, text: str) -> List[Dict[str, Any]]:
        """
        分析需求中的不明确点

        Args:
            text: 需求文本内容

        Returns:
            List[Dict]: 不明确点列表
        """
        unclear_points = []
        lines = text.split('\n')

        # 检查模糊描述词
        vague_terms = {
            "适当": "没有具体的参数范围或数值",
            "合理": "缺少具体的评判标准和具体值",
            "优化": "缺少具体的优化目标和可衡量指标",
            "改善": "没有明确的改善方向和量化指标",
            "尽快": "缺少具体的时间要求",
            "等": "列表不完整，缺少其他项",
            "等等": "说明不充分",
            "包括但不限于": "边界不明确"
        }

        for i, line in enumerate(lines):
            for term, issue in vague_terms.items():
                if term in line:
                    unclear_points.append({
                        "type": "模糊描述",
                        "description": f"使用模糊词汇'{term}'，{issue}",
                        "severity": "medium",
                        "suggestion": f"建议将'{term}'替换为具体的数值、范围或标准",
                        "location": f"第{i+1}行",
                        "context": line
                    })

            # 检查是否有缩写但无全称
            common_abbreviations = ["UI", "UX", "API", "DB", "URL", "HTTP", "HTTPS"]
            for abbrev in common_abbreviations:
                if abbrev in line:
                    # 简单检查是否有全称说明
                    if abbrev not in ["UI", "UX", "API"]:  # 这些是常见缩写，不提示
                        # 检查同一行或前后行是否有说明
                        start_idx = max(0, i - 1)
                        end_idx = min(len(lines), i + 2)
                        context_lines = lines[start_idx:end_idx]
                        context_text = '\n'.join(context_lines)

                        if f"（{abbrev}）" not in context_text and f"({abbrev})" not in context_text:
                            unclear_points.append({
                                "type": "缩写不明确",
                                "description": f"使用了缩写'{abbrev}'但没有全称说明",
                                "severity": "low",
                                "suggestion": "建议提供缩写的全称或技术标准",
                                "location": f"第{i+1}行",
                                "context": line
                            })

        return unclear_points

    def analyze_incomplete_points(self, text: str) -> List[Dict[str, Any]]:
        """
        分析需求中的不完整点

        Args:
            text: 需求文本内容

        Returns:
            List[Dict]: 不完整点列表
        """
        incomplete_points = []

        # 检查是否有列表但是数量不符合
        list_content = re.findall(r'\d+[.、)\]]\s*[^0-9]', text)
        if list_content:
            if len(list_content) > 8:
                incomplete_points.append({
                    "type": "列表过长",
                    "description": f"发现{len(list_content)}列表项，可能需要分类整理",
                    "severity": "low",
                    "suggestion": "建议将长列表按照功能或类型进行分组"
                })

        # 检查是否有未完成的句子
        incomplete_sentences = re.findall(r'[。！？，、,][^。！？\n]{0,5}$', text, re.MULTILINE)
        if incomplete_sentences:
            incomplete_points.append({
                "type": "句子不完整",
                "description": "发现可能未完成的句子，内容可能不完整",
                "severity": "medium",
                "suggestion": "建议检查是否有遗漏的内容描述"
            })

        return incomplete_points

    def analyze(self):
        """执行完整的需求分析"""
        if not self.load_document():
            return False

        # 提取文本内容
        text = self.extract_text_content()
        print(f"文档文本长度: {len(text)} 字符")

        # 执行各类分析
        print("\n开始分析需求...")

        self.issues["missing_points"] = self.analyze_missing_points(text)
        print(f"发现遗漏点: {len(self.issues['missing_points'])} 个")

        self.issues["contradictory_points"] = self.analyze_contradictory_points(text)
        print(f"发现矛盾点: {len(self.issues['contradictory_points'])} 个")

        self.issues["unclear_points"] = self.analyze_unclear_points(text)
        print(f"发现不明确点: {len(self.issues['unclear_points'])} 个")

        self.issues["incomplete_points"] = self.analyze_incomplete_points(text)
        print(f"发现不完整点: {len(self.issues['incomplete_points'])} 个")

        return True

    def generate_report(self, output_path=None):
        """
        生成分析报告

        Args:
            output_path: 报告输出路径，默认为文档同级目录下的JSON文件

        Returns:
            str: 报告文件路径
        """
        if output_path is None:
            output_path = self.doc_path.parent / f"{self.doc_path.stem}_analysis_report.json"

        report = {
            "document": str(self.doc_path),
            "analyzed_at": __import__('datetime').datetime.now().isoformat(),
            "summary": {
                "missing_points": len(self.issues["missing_points"]),
                "contradictory_points": len(self.issues["contradictory_points"]),
                "unclear_points": len(self.issues["unclear_points"]),
                "incomplete_points": len(self.issues["incomplete_points"])
            },
            "issues": self.issues
        }

        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(report, f, ensure_ascii=False, indent=2)

        print(f"\n分析报告已保存到: {output_path}")

        # 生成人类可读的报告
        report_file = self.doc_path.parent / f"{self.doc_path.stem}_analysis_report.txt"
        self._generate_human_readable_report(report_file)

        return str(report_file)

    def _generate_human_readable_report(self, output_path):
        """生成人类可读的报告"""
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write("="*70 + "\n")
            f.write("需求分析报告\n")
            f.write("="*70 + "\n\n")

            f.write(f"分析文件: {self.doc_path}\n")
            f.write(f"分析时间: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")

            # 遗漏点
            if self.issues["missing_points"]:
                f.write("【遗漏点】\n")
                f.write("-"*70 + "\n\n")
                for i, issue in enumerate(self.issues["missing_points"], 1):
                    f.write(f"{i}. {issue['type']} (严重程度: {issue['severity']})\n")
                    f.write(f"   位置: {issue.get('location', '未知')}\n")
                    f.write(f"   问题描述: {issue['description']}\n")

                    # 显示需求原文
                    if "context" in issue:
                        f.write(f"   需求原文: {issue['context']}\n")

                    # 显示上下文
                    if "surrounding_text" in issue and issue['surrounding_text']:
                        f.write(f"   上下文: {issue['surrounding_text'][:200]}...\n")

                    f.write(f"   改进建议: {issue['suggestion']}\n")
                    f.write(f"\n")
            else:
                f.write("【遗漏点】[OK] 暂无明显遗漏点\n\n")

            # 矛盾点
            if self.issues["contradictory_points"]:
                f.write("【矛盾点】\n")
                f.write("-"*70 + "\n\n")
                for i, issue in enumerate(self.issues["contradictory_points"], 1):
                    f.write(f"{i}. {issue['type']} (严重程度: {issue['severity']})\n")
                    f.write(f"   位置: {issue.get('location', '多个位置')}\n")
                    f.write(f"   问题描述: {issue['description']}\n")

                    # 显示需求原文
                    if "context" in issue:
                        f.write(f"   需求原文: {issue['context']}\n")

                    # 显示矛盾的上下文
                    if "conflicting_contexts" in issue:
                        f.write(f"   矛盾的具体位置:\n")
                        for ctx in issue['conflicting_contexts']:
                            f.write(f"     - {ctx}\n")

                    f.write(f"   改进建议: {issue['suggestion']}\n")
                    f.write(f"\n")
            else:
                f.write("【矛盾点】[OK] 暂无明显矛盾点\n\n")

            # 不明确点
            if self.issues["unclear_points"]:
                f.write("【不明确点】\n")
                f.write("-"*70 + "\n\n")
                for i, issue in enumerate(self.issues["unclear_points"], 1):
                    f.write(f"{i}. {issue['type']} (严重程度: {issue['severity']})\n")
                    f.write(f"   位置: {issue.get('location', '未知')}\n")
                    f.write(f"   问题描述: {issue['description']}\n")

                    # 显示需求原文
                    if "context" in issue:
                        f.write(f"   需求原文: {issue['context']}\n")

                    f.write(f"   改进建议: {issue['suggestion']}\n")
                    f.write(f"\n")
            else:
                f.write("【不明确点】[OK] 暂无明显不明确点\n\n")

            # 不完整点
            if self.issues["incomplete_points"]:
                f.write("【不完整点】\n")
                f.write("-"*70 + "\n\n")
                for i, issue in enumerate(self.issues["incomplete_points"], 1):
                    f.write(f"{i}. {issue['type']} (严重程度: {issue['severity']})\n")
                    f.write(f"   位置: {issue.get('location', '未知')}\n")
                    f.write(f"   问题描述: {issue['description']}\n")

                    # 显示需求原文
                    if "context" in issue:
                        f.write(f"   需求原文: {issue['context']}\n")

                    f.write(f"   改进建议: {issue['suggestion']}\n")
                    f.write(f"\n")
            else:
                f.write("【不完整点】[OK] 暂无明显不完整点\n\n")

            f.write("="*70 + "\n")
            f.write(f"分析完成 | 总问题数: {sum([len(v) for v in self.issues.values()])}\n")
            f.write(f"  - 遗漏点: {len(self.issues['missing_points'])}\n")
            f.write(f"  - 矛盾点: {len(self.issues['contradictory_points'])}\n")
            f.write(f"  - 不明确点: {len(self.issues['unclear_points'])}\n")
            f.write(f"  - 不完整点: {len(self.issues['incomplete_points'])}\n")
            f.write("="*70 + "\n")

        print(f"人类可读报告已保存到: {output_path}")

    def get_issues(self):
        """获取分析结果"""
        return self.issues


def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(
        description="需求分析工具 - 分析Word文档中的遗漏点、矛盾点和不明确点",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  python analyze_requirements.py demand/需求文档.docx
  python analyze_requirements.py demand/需求文档.docx --output custom_report.json
        """
    )

    parser.add_argument("doc_file", help="Word文档路径")
    parser.add_argument("--output", help="输出报告路径（可选）")

    args = parser.parse_args()

    # 创建分析器
    analyzer = RequirementAnalyzer(args.doc_file)

    # 执行分析
    if analyzer.analyze():
        # 生成报告
        report_path = analyzer.generate_report(args.output)
        print(f"\n分析完成！")
        print(f"报告文件: {report_path}")
    else:
        print("\n分析失败！")
        sys.exit(1)


if __name__ == "__main__":
    main()

path: bin/convert/md2excel.py
content: #!/usr/bin/env python3
"""
Markdown转Excel工具 - md2excel.py

将测试用例的Markdown文件转换为Excel格式的测试用例表格
"""

import sys
import re
import pandas as pd
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from typing import List, Dict, Any


class MarkdownToExcelConverter:
    """Markdown到Excel转换器"""

    def __init__(self, md_path):
        """
        初始化转换器

        Args:
            md_path: Markdown文件路径
        """
        self.md_path = Path(md_path)
        self.content = ""
        self.test_cases = []

    def load_markdown(self):
        """加载Markdown文件"""
        try:
            with open(self.md_path, 'r', encoding='utf-8') as f:
                self.content = f.read()
            print(f"成功加载Markdown文件: {self.md_path}")
            return True
        except Exception as e:
            print(f"Markdown文件加载失败: {e}")
            return False

    def extract_test_cases(self):
        """
        从Markdown中提取测试用例

        Returns:
            List[Dict]: 测试用例列表
        """
        test_cases = []

        # 分割文档为段落
        lines = self.content.split('\n')
        current_case = None
        in_code_block = False

        for line in lines:
            line = line.rstrip()

            # 检查代码块开始/结束
            if line.startswith('```'):
                in_code_block = not in_code_block
                continue

            # 跳过代码块中的内容
            if in_code_block:
                continue

            # 检查是否是新的测试用例标题
            case_match = re.match(r'^### (TC\d+): (.+)$', line)
            if case_match:
                # 如果有上一个用例，保存它
                if current_case:
                    test_cases.append(current_case)

                # 开始新的用例
                case_id = case_match.group(1)
                title = case_match.group(2)
                current_case = {
                    "id": case_id,
                    "title": title,
                    "module": "",
                    "preconditions": "",
                    "steps": "",
                    "priority": "中",
                    "expected_result": "",
                    "method": ""
                }
                continue

            # 检查字段
            if current_case:
                # 提取模块（从二级标题获取）
                module_match = re.match(r'^## (.+)$', line)
                if module_match and current_case["module"] == "":
                    current_case["module"] = module_match.group(1)

                # 检查优先级
                priority_match = re.match(r'^\*\*优先级\*\*: (.+)$', line)
                if priority_match:
                    current_case["priority"] = priority_match.group(1)
                    continue

                # 检查测试方法
                method_match = re.match(r'^\*\*测试方法\*\*: (.+)$', line)
                if method_match:
                    current_case["method"] = method_match.group(1)
                    continue

                # 检查前置条件
                if line.startswith('**前置条件**:'):  # 从行首开始
                    preconditions = line[len('**前置条件**:'):]  # 去掉前缀
                    # 检查是否是代码块
                    if line.endswith('```'):
                        # 下面的行是代码块内容
                        current_case["preconditions"] = ""
                        continue
                    else:
                        current_case["preconditions"] = preconditions.strip()
                    continue

                # 检查测试步骤
                if line.startswith('**测试步骤**:'):  # 从行首开始
                    steps = line[len('**测试步骤**:'):]  # 去掉前缀
                    current_case["steps"] = steps.strip()
                    continue

                # 检查预期结果
                if line.startswith('**预期结果**:'):  # 从行首开始
                    expected = line[len('**预期结果**:'):]  # 去掉前缀
                    current_case["expected_result"] = expected.strip()
                    continue

                # 检查代码块中的内容（如果前面有字段标记）
                if line.startswith('```') and current_case:
                    # 这是上一个字段的代码块内容
                    continue

                # 如果行不是空行且不在代码块中，尝试追加到当前字段
                if line.strip() and not line.startswith('### ') and not line.startswith('## ') and not line.startswith('---'):
                    # 追加到当前字段（如果有）
                    if current_case:
                        if not current_case["preconditions"] and line.startswith('1.'):
                            current_case["preconditions"] = line
                        elif not current_case["steps"] and line.startswith('1.'):
                            current_case["steps"] = line
                        elif not current_case["expected_result"] and line.startswith('1.'):
                            current_case["expected_result"] = line
                        elif current_case["preconditions"] and not current_case["steps"]:
                            # 追加到前置条件
                            current_case["preconditions"] += "\n" + line
                        elif current_case["steps"] and not current_case["expected_result"]:
                            # 追加到测试步骤
                            current_case["steps"] += "\n" + line
                        elif current_case["expected_result"]:
                            # 追加到预期结果
                            current_case["expected_result"] += "\n" + line

        # 添加最后一个用例
        if current_case:
            test_cases.append(current_case)

        print(f"提取到 {len(test_cases)} 个测试用例")
        return test_cases

    def convert_to_excel(self, output_path=None):
        """
        转换为Excel文件

        Args:
            output_path: 输出文件路径，默认为Markdown同级目录

        Returns:
            str: 输出文件路径
        """
        if output_path is None:
            output_path = self.md_path.parent / f"{self.md_path.stem}.xlsx"

        # 准备数据
        headers = ['用例编号', '用例模块', '用例标题', '前置条件', '测试步骤', '优先级', '预期结果']
        data = [headers]

        for tc in self.test_cases:
            row = [
                tc.get('id', ''),
                tc.get('module', ''),
                tc.get('title', ''),
                tc.get('preconditions', ''),
                tc.get('steps', ''),
                tc.get('priority', '中'),
                tc.get('expected_result', '')
            ]
            data.append(row)

        # 创建DataFrame
        df = pd.DataFrame(data[1:], columns=data[0])

        # 保存为Excel
        df.to_excel(output_path, index=False, engine='openpyxl')

        # 加载Excel进行格式化
        wb = Workbook()
        ws = wb.active

        # 写入数据
        for row_idx, row in enumerate(data, 1):
            for col_idx, value in enumerate(row, 1):
                ws.cell(row=row_idx, column=col_idx, value=value)

        # 设置表头样式
        header_font = Font(bold=True, color='FFFFFF', size=11)
        header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
        header_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)

        # 设置边框
        thin_border = Border(
            left=Side(style='thin'),
            right=Side(style='thin'),
            top=Side(style='thin'),
            bottom=Side(style='thin')
        )

        # 应用表头样式
        for cell in ws[1]:
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = header_alignment
            cell.border = thin_border

        # 设置列宽
        column_widths = {'A': 12, 'B': 12, 'C': 35, 'D': 30, 'E': 50, 'F': 10, 'G': 45}
        for col, width in column_widths.items():
            ws.column_dimensions[col].width = width

        # 设置数据行样式
        data_alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
        for row in ws.iter_rows(min_row=2, max_row=ws.max_row, min_col=1, max_col=ws.max_column):
            for cell in row:
                cell.alignment = data_alignment
                cell.border = thin_border

        # 设置行高
        ws.row_dimensions[1].height = 30
        for i in range(2, ws.max_row + 1):
            ws.row_dimensions[i].height = 60

        # 保存
        wb.save(output_path)
        print(f"Excel文件已生成: {output_path}")
        print(f"总计测试用例: {len(self.test_cases)} 条")

        return str(output_path)


def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(
        description="Markdown转Excel工具 - 将测试用例Markdown文件转换为Excel格式",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  python md2excel.py testcases.md
  python md2excel.py testcases.md --output testcases.xlsx
        """
    )

    parser.add_argument("md_file", help="Markdown文件路径")
    parser.add_argument("--output", help="输出Excel文件路径（可选）")

    args = parser.parse_args()

    # 创建转换器
    converter = MarkdownToExcelConverter(args.md_file)

    # 加载并转换
    if converter.load_markdown():
        converter.test_cases = converter.extract_test_cases()
        excel_path = converter.convert_to_excel(args.output)
        print(f"\n转换完成！")
        print(f"Excel文件: {excel_path}")
    else:
        print("\n转换失败！")
        sys.exit(1)


if __name__ == "__main__":
    main()


path: bin/convert/md2excel_deep.py
content: #!/usr/bin/env python3
"""
深度分析版Markdown转Excel工具 - md2excel_deep.py

专为深度分析版测试用例格式设计的转换工具

支持解析：
- ### TC001: 标题
- **测试方法**: 正向测试
- **优先级**: 高
- **前置条件**:
```
1. 系统已部署
```
- **测试步骤**:
```
1. 进入模块
```
- **预期结果**:
```
1. 功能正常
```
"""

import sys
import re
import pandas as pd
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class DeepMarkdownToExcelConverter:
    """深度分析版Markdown到Excel转换器"""

    def __init__(self, md_path):
        """
        初始化转换器

        Args:
            md_path: Markdown文件路径
        """
        self.md_path = Path(md_path)
        self.content = ""
        self.test_cases = []

    def load_markdown(self):
        """加载Markdown文件"""
        try:
            with open(self.md_path, 'r', encoding='utf-8') as f:
                self.content = f.read()
            logger.info(f"成功加载Markdown文件: {self.md_path}")
            return True
        except Exception as e:
            logger.error(f"Markdown文件加载失败: {e}")
            return False

    def extract_test_cases(self):
        """
        从深度分析版Markdown中提取测试用例

        Returns:
            List[Dict]: 测试用例列表
        """
        test_cases = []
        lines = self.content.split('\n')
        current_case = None
        in_code_block = False
        code_block_type = None
        code_content = []

        for i, line in enumerate(lines):
            line = line.rstrip()

            # 检查代码块开始/结束
            if line.startswith('```'):
                if in_code_block:
                    # 结束代码块
                    if code_block_type == "preconditions":
                        current_case["preconditions"] = '\n'.join(code_content)
                    elif code_block_type == "steps":
                        current_case["steps"] = '\n'.join(code_content)
                    elif code_block_type == "expected_result":
                        current_case["expected_result"] = '\n'.join(code_content)
                    in_code_block = False
                    code_block_type = None
                    code_content = []
                else:
                    # 开始代码块
                    in_code_block = True
                continue

            # 在代码块内时，收集内容
            if in_code_block:
                code_content.append(line)
                continue

            # 检查是否是新的测试用例标题
            case_match = re.match(r'^### TC(\d+): (.+)$', line)
            if case_match:
                # 如果有上一个用例，保存它
                if current_case:
                    test_cases.append(current_case)

                # 开始新的用例
                case_id = f"TC{case_match.group(1)}"
                title = case_match.group(2)
                current_case = {
                    "id": case_id,
                    "title": title,
                    "module": "",
                    "preconditions": "",
                    "steps": "",
                    "priority": "",
                    "expected_result": "",
                    "method": ""
                }
                continue

            # 检查字段
            if current_case:
                # 测试方法
                method_match = re.match(r'^\*\*测试方法\*\*: (.+)$', line)
                if method_match:
                    current_case["method"] = method_match.group(1)
                    continue

                # 优先级
                priority_match = re.match(r'^\*\*优先级\*\*: (.+)$', line)
                if priority_match:
                    current_case["priority"] = priority_match.group(1)
                    continue

                # 前置条件
                if line.startswith('**前置条件**:'):
                    code_block_type = "preconditions"
                    continue

                # 测试步骤
                if line.startswith('**测试步骤**:'):
                    code_block_type = "steps"
                    continue

                # 预期结果
                if line.startswith('**预期结果**:'):
                    code_block_type = "expected_result"
                    continue

                # 模块
                module_match = re.match(r'^## (.+)$', line)
                if module_match:
                    current_case["module"] = module_match.group(1)
                    continue

        # 添加最后一个用例
        if current_case:
            test_cases.append(current_case)

        logger.info(f"提取到 {len(test_cases)} 个测试用例")
        return test_cases

    def convert_to_excel(self, output_path=None):
        """
        转换为Excel文件

        Args:
            output_path: 输出文件路径，默认为Markdown同级目录

        Returns:
            str: 输出文件路径
        """
        if output_path is None:
            output_path = self.md_path.parent / f"{self.md_path.stem}.xlsx"

        # 准备数据
        headers = ['用例编号', '用例模块', '用例标题', '前置条件', '测试步骤', '优先级', '预期结果']
        data = [headers]

        for tc in self.test_cases:
            row = [
                tc.get('id', ''),
                tc.get('module', ''),
                tc.get('title', ''),
                tc.get('preconditions', ''),
                tc.get('steps', ''),
                tc.get('priority', ''),
                tc.get('expected_result', '')
            ]
            data.append(row)

        # 创建Excel工作簿
        wb = Workbook()
        ws = wb.active

        # 写入数据
        for row_idx, row in enumerate(data, 1):
            for col_idx, value in enumerate(row, 1):
                ws.cell(row=row_idx, column=col_idx, value=value)

        # 设置表头样式
        header_font = Font(bold=True, color='FFFFFF', size=11)
        header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
        header_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)

        # 设置边框
        thin_border = Border(
            left=Side(style='thin'),
            right=Side(style='thin'),
            top=Side(style='thin'),
            bottom=Side(style='thin')
        )

        # 应用表头样式
        for cell in ws[1]:
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = header_alignment
            cell.border = thin_border

        # 设置列宽
        column_widths = {'A': 12, 'B': 12, 'C': 35, 'D': 30, 'E': 50, 'F': 10, 'G': 45}
        for col, width in column_widths.items():
            ws.column_dimensions[col].width = width

        # 设置数据行样式
        data_alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
        for row in ws.iter_rows(min_row=2, max_row=ws.max_row, min_col=1, max_col=ws.max_column):
            for cell in row:
                cell.alignment = data_alignment
                cell.border = thin_border

        # 设置行高
        ws.row_dimensions[1].height = 30
        for i in range(2, ws.max_row + 1):
            ws.row_dimensions[i].height = 60

        # 保存
        wb.save(output_path)
        logger.info(f"Excel文件已生成: {output_path}")
        print(f"Excel文件已生成: {output_path}")
        print(f"总计测试用例: {len(self.test_cases)} 条")

        return str(output_path)


def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(
        description="深度分析版Markdown转Excel工具 - 支持复杂测试用例格式",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  python md2excel_deep.py 深度分析_完整测试用例集.md
  python md2excel_deep.py 深度分析_完整测试用例集.md --output 测试用例.xlsx
        """
    )

    parser.add_argument("md_file", help="Markdown文件路径")
    parser.add_argument("--output", help="输出Excel文件路径（可选）")

    args = parser.parse_args()

    # 创建转换器
    converter = DeepMarkdownToExcelConverter(args.md_file)

    # 加载并转换
    if converter.load_markdown():
        converter.test_cases = converter.extract_test_cases()
        excel_path = converter.convert_to_excel(args.output)
        print(f"\n转换完成！")
        print(f"Excel文件: {excel_path}")
    else:
        print("\n转换失败！")
        sys.exit(1)


if __name__ == "__main__":
    main()

path: bin/generate/generate_testcases.py
content: #!/usr/bin/env python3
"""
测试用例生成工具 - generate_testcases.py

根据需求文档自动生成测试用例，支持多种测试设计方法
"""

import sys
import re
import json
from pathlib import Path
from docx import Document
from typing import List, Dict, Any, Tuple
from datetime import datetime


class TestCaseGenerator:
    """测试用例生成器"""

    def __init__(self, doc_path):
        """
        初始化测试用例生成器

        Args:
            doc_path: Word 文档路径
        """
        self.doc_path = Path(doc_path)
        self.doc = None
        self.content = ""
        self.test_cases = []
        self.tc_counter = 1

    def load_document(self):
        """加载 Word 文档"""
        try:
            self.doc = Document(self.doc_path)
            print(f"成功加载文档: {self.doc_path}")
            return True
        except Exception as e:
            print(f"文档加载失败: {e}")
            return False

    def extract_content(self):
        """提取文档内容"""
        if not self.doc:
            return

        content_parts = []
        for para in self.doc.paragraphs:
            if para.text.strip():
                content_parts.append(para.text)

        self.content = "\n".join(content_parts)
        print(f"提取文本内容: {len(self.content)} 字符")

    def add_test_case(self, module: str, title: str, preconditions: str,
                      steps: str, priority: str, expected_result: str, method: str = ""):
        """
        添加测试用例

        Args:
            module: 测试模块
            title: 用例标题
            preconditions: 前置条件
            steps: 测试步骤
            priority: 优先级
            expected_result: 预期结果
            method: 测试方法
        """
        self.test_cases.append({
            "id": f"TC{self.tc_counter:03d}",
            "module": module,
            "title": title,
            "preconditions": preconditions,
            "steps": steps,
            "priority": priority,
            "expected_result": expected_result,
            "method": method
        })
        self.tc_counter += 1

    def extract_features_from_text(self) -> List[Dict[str, Any]]:
        """
        从文本中提取功能特性

        Returns:
            List[Dict]: 功能特性列表
        """
        features = []

        # 提取标题和功能模块
        lines = self.content.split('\n')
        current_module = "全局"

        for line in lines:
            line = line.strip()

            # 识别标题（可能是模块名）
            if line.startswith('##') or line.startswith('# '):
                module_name = line.lstrip('# ')
                if module_name and len(module_name) < 20:  # 假设模块名不超过20个字
                    current_module = module_name

            # 识别功能描述
            feature_keywords = ["功能", "模块", "支持", "显示", "验证", "测试", "实现"]
            if any(keyword in line for keyword in feature_keywords) and len(line) > 10:
                features.append({
                    "module": current_module,
                    "description": line
                })

        print(f"提取到 {len(features)} 个功能特性")
        return features

    def generate_positive_test_cases(self, features: List[Dict[str, Any]]):
        """
        生成正向测试用例（基于功能特性）

        Args:
            features: 功能特性列表
        """
        print("\n生成正向测试用例...")

        for feature in features[:20]:  # 限制数量
            module = feature["module"]
            desc = feature["description"]

            # 提取关键词
            action = ""
            if "验证" in desc:
                action = "验证"
            elif "测试" in desc:
                action = "测试"
            elif "显示" in desc:
                action = "验证显示"
            elif "支持" in desc:
                action = "验证支持"
            else:
                action = "测试"

            title = f"{action}{module}功能正常"
            preconditions = f"1. 系统已部署\n2. {module}模块已配置"
            steps = f"1. 进入{module}模块\n2. 执行相关操作\n3. 观察结果"
            expected = f"1. {module}功能正常工作\n2. 无错误提示\n3. 界面显示正常"

            self.add_test_case(module, title, preconditions, steps, "高", expected, "正向测试")

    def generate_boundary_test_cases(self, features: List[Dict[str, Any]]):
        """
        生成边界值测试用例

        Args:
            features: 功能特性列表
        """
        print("\n生成边界值测试用例...")

        # 提取数字和限制
        numbers = re.findall(r'(\d+)\s*(个|条|次|秒|分钟|小时|天)', self.content)

        # 去重并限制数量，转换为列表后切片
        unique_numbers = list(set(numbers))[:5]
        for i, (num, unit) in enumerate(unique_numbers):
            num = int(num)
            module = "边界值测试"

            # 最小值测试
            title = f"验证{unit}数为{max(0, num-1)}时的边界"
            preconditions = f"1. 系统已部署\n2. 可配置{unit}数"
            steps = f"1. 设置{unit}数为{max(0, num-1)}\n2. 执行操作\n3. 观察结果"
            expected = f"1. 系统正常处理\n2. 无错误提示\n3. 功能正常"
            self.add_test_case(module, title, preconditions, steps, "高", expected, "边界值测试")

            # 最大值+1测试
            title = f"验证{unit}数为{num+1}时的边界"
            steps = f"1. 设置{unit}数为{num+1}\n2. 执行操作\n3. 观察结果"
            expected = f"1. 系统正确处理或提示上限\n2. 无崩溃\n3. 有明确提示信息"
            self.add_test_case(module, title, preconditions, steps, "中", expected, "边界值测试")

    def generate_exception_test_cases(self, features: List[Dict[str, Any]]):
        """
        生成异常测试用例（错误推测法）

        Args:
            features: 功能特性列表
        """
        print("\n生成异常测试用例...")

        # 常见异常场景
        exception_scenarios = [
            ("网络异常", "验证网络断开时的系统表现", "断网", "系统有明确的网络错误提示，无崩溃"),
            ("权限异常", "验证权限不足时的系统表现", "移除权限", "系统提示权限不足，引导用户授权"),
            ("数据异常", "验证数据为空时的系统表现", "清空数据", "显示空状态提示，无崩溃"),
            ("输入异常", "验证特殊字符输入的处理", "输入特殊字符<>'\"", "正确处理或过滤，无XSS风险"),
            ("并发异常", "验证多用户并发操作", "模拟并发请求", "系统正确处理并发，无数据错乱"),
            ("超时异常", "验证请求超时的处理", "模拟超时场景", "系统提示超时，支持重试"),
            ("存储异常", "验证存储空间不足时的处理", "模拟存储已满", "系统提示存储不足，引导清理"),
        ]

        for module, title, step_add, expected in exception_scenarios:
            preconditions = "1. 系统已部署\n2. 测试环境已准备"
            steps = f"1. {step_add}\n2. 执行相关操作\n3. 观察系统反应"
            self.add_test_case(module, title, preconditions, steps, "中", expected, "错误推测")

    def generate_scene_test_cases(self, features: List[Dict[str, Any]]):
        """
        生成场景测试用例（场景法）

        Args:
            features: 功能特性列表
        """
        print("\n生成场景测试用例...")

        # 基本流程场景
        scenarios = [
            ("登录流程", "验证完整登录流程",
             "1. 用户账号已创建",
             "1. 打开应用\n2. 输入账号密码\n3. 点击登录\n4. 完成验证",
             "登录成功，进入主页"),
            ("核心功能流程", "验证核心功能完整流程",
             "1. 已登录系统\n2. 有操作权限",
             "1. 进入核心功能模块\n2. 执行完整操作流程\n3. 完成业务流程",
             "流程顺畅，功能正常，数据正确"),
            ("退出流程", "验证安全退出流程",
             "1. 已登录系统",
             "1. 点击退出\n2. 确认退出\n3. 观察退出过程",
             "安全退出，数据保存，跳转登录页"),
        ]

        for module, title, preconditions, steps, expected in scenarios:
            self.add_test_case(module, title, preconditions, steps, "高", f"1. {expected}", "场景法")

    def generate_ui_test_cases(self, features: List[Dict[str, Any]]):
        """
        生成UI测试用例

        Args:
            features: 功能特性列表
        """
        print("\n生成UI测试用例...")

        # 提取界面相关的特性
        ui_keywords = ["界面", "页面", "按钮", "菜单", "图标", "显示"]

        for feature in features[:10]:  # 限制数量
            desc = feature["description"]
            if any(keyword in desc for keyword in ui_keywords):
                module = "UI测试"
                title = f"验证{desc[:30]}的正确显示"
                preconditions = "1. 系统已部署\n2. 设备已准备"
                steps = f"1. 查看相关界面\n2. 检查{desc[:20]}\n3. 验证显示效果"
                expected = "1. 显示清晰无错位\n2. 样式符合设计\n3. 无兼容性问题"

                self.add_test_case(module, title, preconditions, steps, "中", expected, "UI测试")

    def generate_compatibility_test_cases(self):
        """生成兼容性测试用例"""
        print("\n生成兼容性测试用例...")

        compatibility_scenarios = [
            ("兼容性", "验证不同浏览器兼容性",
             "1. 准备多种浏览器",
             "1. 在Chrome测试\n2. 在Firefox测试\n3. 在Safari测试",
             "所有浏览器功能正常"),
            ("兼容性", "验证不同分辨率适配",
             "1. 准备不同分辨率设备",
             "1. 测试高分辨率\n2. 测试标准分辨率\n3. 测试低分辨率",
             "界面自适应正常，无错位"),
            ("兼容性", "验证不同系统版本兼容性",
             "1. 准备不同系统版本",
             "1. 在高版本系统测试\n2. 在标准版本测试\n3. 在低版本测试",
             "所有版本功能正常"),
        ]

        for module, title, preconditions, steps, expected in compatibility_scenarios:
            self.add_test_case(module, title, preconditions, steps, "中", f"1. {expected}", "兼容性测试")

    def generate_performance_test_cases(self):
        """生成性能测试用例"""
        print("\n生成性能测试用例...")

        performance_scenarios = [
            ("性能", "验证页面加载性能",
             "1. 系统已部署",
             "1. 清除缓存\n2. 访问主要页面\n3. 记录加载时间",
             "页面加载时间在可接受范围内（<3秒）"),
            ("性能", "验证并发用户性能",
             "1. 准备性能测试环境",
             "1. 模拟并发用户\n2. 执行关键操作\n3. 监控系统性能",
             "系统支持预期并发数，无性能瓶颈"),
            ("性能", "验证大数据量性能",
             "1. 准备大数据量环境",
             "1. 加载大数据列表\n2. 执行查询操作\n3. 记录响应时间",
             "大数据量下响应时间可接受"),
        ]

        for module, title, preconditions, steps, expected in performance_scenarios:
            self.add_test_case(module, title, preconditions, steps, "低", f"1. {expected}", "性能测试")

    def generate_test_cases(self):
        """生成所有测试用例"""
        if not self.load_document():
            return False

        self.extract_content()

        # 提取功能特性
        features = self.extract_features_from_text()

        # 按不同方法生成测试用例
        self.generate_positive_test_cases(features)
        self.generate_boundary_test_cases(features)
        self.generate_exception_test_cases(features)
        self.generate_scene_test_cases(features)
        self.generate_ui_test_cases(features)
        self.generate_compatibility_test_cases()
        self.generate_performance_test_cases()

        print(f"\n总计生成测试用例: {len(self.test_cases)} 个")
        return True

    def save_to_markdown(self, output_path=None):
        """
        保存测试用例到Markdown文件

        Args:
            output_path: 输出文件路径，默认为文档同级目录

        Returns:
            str: 输出文件路径
        """
        if output_path is None:
            output_path = self.doc_path.parent / f"{self.doc_path.stem}_testcases.md"

        with open(output_path, 'w', encoding='utf-8') as f:
            # 写入标题
            f.write(f"# 测试用例\n\n")
            f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
            f.write(f"基于需求文档: {self.doc_path.name}\n\n")
            f.write(f"总用例数: {len(self.test_cases)}\n\n")
            f.write("---\n\n")

            # 按模块分组
            modules = {}
            for tc in self.test_cases:
                module = tc["module"]
                if module not in modules:
                    modules[module] = []
                modules[module].append(tc)

            # 写入各模块的测试用例
            for module, cases in modules.items():
                f.write(f"## {module}\n\n")

                for tc in cases:
                    f.write(f"### {tc['id']}: {tc['title']}\n\n")
                    f.write(f"**测试方法**: {tc.get('method', '')}\n\n")
                    f.write(f"**优先级**: {tc['priority']}\n\n")
                    f.write(f"**前置条件**:\n```\n{tc['preconditions']}\n```\n\n")
                    f.write(f"**测试步骤**:\n```\n{tc['steps']}\n```\n\n")
                    f.write(f"**预期结果**:\n```\n{tc['expected_result']}\n```\n\n")
                    f.write("---\n\n")

        print(f"测试用例已保存到Markdown: {output_path}")
        return str(output_path)

    def save_to_json(self, output_path=None):
        """
        保存测试用例到JSON文件

        Args:
            output_path: 输出文件路径，默认为文档同级目录

        Returns:
            str: 输出文件路径
        """
        if output_path is None:
            output_path = self.doc_path.parent / f"{self.doc_path.stem}_testcases.json"

        data = {
            "generated_at": datetime.now().isoformat(),
            "source_document": str(self.doc_path),
            "total_cases": len(self.test_cases),
            "test_cases": self.test_cases
        }

        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

        print(f"测试用例已保存到JSON: {output_path}")
        return str(output_path)


def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(
        description="测试用例生成工具 - 根据需求文档自动生成测试用例",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  python generate_testcases.py demand/需求文档.docx
  python generate_testcases.py demand/需求文档.docx --output testcases.md
        """
    )

    parser.add_argument("doc_file", help="需求文档路径（Word格式）")
    parser.add_argument("--output", help="输出文件路径（可选）")

    args = parser.parse_args()

    # 创建生成器
    generator = TestCaseGenerator(args.doc_file)

    # 生成测试用例
    if generator.generate_test_cases():
        # 保存到Markdown
        md_path = generator.save_to_markdown(args.output)
        # 同时保存JSON
        json_path = generator.save_to_json()

        print(f"\n生成完成！")
        print(f"Markdown文件: {md_path}")
        print(f"JSON文件: {json_path}")
    else:
        print("\n生成失败！")
        sys.exit(1)


if __name__ == "__main__":
    main()


path: bin/generate/generate_testcases_deep.py
content: #!/usr/bin/env python3
"""
深度分析版测试用例生成工具 - generate_testcases_deep.py

专为复杂需求文档设计的深度分析工具

特点：
- 为每个功能点生成3-5个测试用例（非1个）
- 为每个表格行生成1-3个测试用例
- 使用所有8种测试设计方法
- 生成200+个测试用例
- 完全避免模板化描述
"""

import sys
import re
import json
from pathlib import Path
from docx import Document
from typing import List, Dict, Any, Tuple
from datetime import datetime
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class DeepAnalysisTestCaseGenerator:
    """深度分析测试用例生成器 - 针对复杂需求"""

    def __init__(self, doc_path):
        """
        初始化测试用例生成器

        Args:
            doc_path: Word文档路径
        """
        self.doc_path = Path(doc_path)
        self.doc = None
        self.content = ""
        self.test_cases = []
        self.tc_counter = 1
        self.function_points = []  # 提取的所有功能点
        self.tables_data = []      # 提取的所有表格数据

        # 测试方法类型定义
        self.test_methods = [
            "正向测试", "边界值测试", "错误推测", "场景法",
            "UI测试", "兼容性测试", "性能测试", "安全测试", "异常测试"
        ]

    def load_document(self):
        """加载 Word 文档"""
        try:
            self.doc = Document(self.doc_path)
            logger.info(f"成功加载文档: {self.doc_path}")
            return True
        except Exception as e:
            logger.error(f"文档加载失败: {e}")
            return False

    def extract_function_points(self):
        """
        深度提取功能点

        识别方式：
        1. 标题层级（1-4级）
        2. 以功能动词开头的段落（增加、修改、删除、支持、验证）
        3. 表格中的功能项
        4. 带编号的功能列表
        5. 功能描述段落
        """
        logger.info("开始深度提取功能点...")

        # 收集所有段落
        paragraphs = []
        for para in self.doc.paragraphs:
            if para.text.strip():
                paragraphs.append({
                    'text': para.text.strip(),
                    'style': para.style.name if para.style else '',
                    'level': self._get_heading_level(para)
                })

        # 1. 提取标题作为功能点（1-4级）
        for i, para in enumerate(paragraphs):
            if para['level'] > 0 and para['level'] <= 4:
                # 检查是否为有效功能描述
                if self._is_function_description(para['text']):
                    context = self._get_context(paragraphs, i, 3)
                    self.function_points.append({
                        'title': para['text'],
                        'level': para['level'],
                        'context': context,
                        'type': 'heading',
                        'source': f'段落{i+1}'
                    })

        # 2. 提取以功能动词开头的段落
        function_verbs = ['增加', '修改', '删除', '支持', '验证', '实现', '提供', '允许', '配置', '设置', '开启', '关闭', '限制', '显示', '隐藏', '控制', '管理']

        for i, para in enumerate(paragraphs):
            text = para['text']
            if any(text.startswith(verb) for verb in function_verbs) and len(text) > 5:
                # 排除标题（已处理）
                if para['level'] == 0:  # 非标题段落
                    context = self._get_context(paragraphs, i, 2)
                    self.function_points.append({
                        'title': text,
                        'level': 5,  # 自定义级别
                        'context': context,
                        'type': 'verb',
                        'source': f'段落{i+1}'
                    })

        # 3. 提取表格中的功能项
        for table_idx, table in enumerate(self.doc.tables):
            # 分析表格结构
            rows = []
            for row in table.rows:
                row_data = [cell.text.strip() for cell in row.cells]
                if any(row_data):  # 非空行
                    rows.append(row_data)

            if len(rows) > 1:  # 有数据的表格
                # 尝试识别列标题
                headers = rows[0]
                # 如果有'功能'、'模块'、'权限'等关键词，视为功能点表格
                if any(keyword in str(headers).lower() for keyword in ['功能', '模块', '权限', '菜单', '按钮', '字段', '属性', '选项']):
                    for row in rows[1:]:  # 跳过标题行
                        if len(row) > 0 and row[0]:  # 第一列有内容
                            # 为每个表格行创建一个功能点
                            self.function_points.append({
                                'title': row[0],
                                'level': 5,
                                'context': '\n'.join(row),
                                'type': 'table',
                                'source': f'表格{table_idx+1}'
                            })

        # 4. 提取带编号的功能列表
        numbered_pattern = r'^(\d+[.、）)]\s+|\d+、)\s*(.+)'  # 匹配 1.、2.、3.、1）、2）、1、2、

        for i, para in enumerate(paragraphs):
            match = re.match(numbered_pattern, para['text'])
            if match:
                title = match.group(2)
                if len(title) > 5:  # 避免过短的列表项
                    context = self._get_context(paragraphs, i, 1)
                    self.function_points.append({
                        'title': title,
                        'level': 5,
                        'context': context,
                        'type': 'numbered',
                        'source': f'段落{i+1}'
                    })

        # 5. 提取功能描述段落（包含"功能"、"需求"等词的段落）
        func_keywords = ['功能', '需求', '要求', '条件', '特性', '支持', '实现']

        for i, para in enumerate(paragraphs):
            text = para['text']
            if any(keyword in text for keyword in func_keywords) and len(text) > 15:
                # 排除已提取的
                if not any(fp['title'] == text for fp in self.function_points):
                    context = self._get_context(paragraphs, i, 3)
                    self.function_points.append({
                        'title': text,
                        'level': 5,
                        'context': context,
                        'type': 'description',
                        'source': f'段落{i+1}'
                    })

        # 去重（基于标题）
        seen_titles = set()
        unique_points = []
        for fp in self.function_points:
            if fp['title'] not in seen_titles:
                seen_titles.add(fp['title'])
                unique_points.append(fp)

        self.function_points = unique_points
        logger.info(f"成功提取 {len(self.function_points)} 个功能点")
        return len(self.function_points)

    def _get_heading_level(self, paragraph):
        """获取标题级别"""
        style = paragraph.style.name.lower()
        if 'heading' in style:
            if '1' in style:
                return 1
            elif '2' in style:
                return 2
            elif '3' in style:
                return 3
            elif '4' in style:
                return 4
        return 0

    def _is_function_description(self, text):
        """判断是否为功能描述"""
        # 排除纯标题、目录、页码等
        if len(text) < 5:
            return False
        if text.startswith('目录') or text.startswith('第') or text.startswith('页'):
            return False
        if '图' in text and '表' in text and '编号' in text:
            return False
        # 包含功能动词
        function_verbs = ['增加', '修改', '删除', '支持', '验证', '实现', '提供', '允许', '配置', '设置', '开启', '关闭', '限制', '显示', '隐藏', '控制', '管理']
        return any(verb in text for verb in function_verbs)

    def _get_context(self, paragraphs, index, window=3):
        """获取上下文"""
        start = max(0, index - window)
        end = min(len(paragraphs), index + window + 1)
        context = []
        for i in range(start, end):
            if i != index and paragraphs[i]['text'].strip():
                context.append(paragraphs[i]['text'])
        return '\n'.join(context)

    def extract_tables(self):
        """提取所有表格数据"""
        logger.info("开始提取表格数据...")

        for table_idx, table in enumerate(self.doc.tables):
            rows = []
            for row in table.rows:
                row_data = [cell.text.strip() for cell in row.cells]
                if any(row_data):
                    rows.append(row_data)

            if len(rows) > 1:  # 有数据的表格
                self.tables_data.append({
                    'index': table_idx + 1,
                    'rows': rows,
                    'headers': rows[0] if rows else [],
                    'content': rows[1:] if len(rows) > 1 else []
                })
                logger.info(f"提取表格 {table_idx + 1}: {len(rows)} 行")

        logger.info(f"共提取 {len(self.tables_data)} 个表格")
        return len(self.tables_data)

    def generate_positive_test_cases(self):
        """为每个功能点生成多个正向测试用例"""
        logger.info("开始生成正向测试用例...")

        for fp in self.function_points:
            title = fp['title']

            # 为每个功能点生成3个正向测试用例

            # 1. 基本功能验证
            self._add_test_case(
                module="功能测试",
                title=f"验证{title}的基本功能",
                preconditions=f"1. 系统已部署\n2. {self._extract_feature_context(title)}已配置",
                steps=f"1. 进入{self._extract_feature_context(title)}相关界面\n2. 执行{title}操作\n3. 验证结果",
                priority="高",
                expected_result=f"1. {title}功能正常工作\n2. 无错误提示\n3. 数据正确更新",
                method="正向测试"
            )

            # 2. 多次操作验证
            self._add_test_case(
                module="功能测试",
                title=f"验证{title}的多次操作",
                preconditions=f"1. 系统已部署\n2. {self._extract_feature_context(title)}已配置",
                steps=f"1. 进入{self._extract_feature_context(title)}相关界面\n2. 执行{title}操作\n3. 再次执行{title}操作\n4. 验证状态一致性",
                priority="中",
                expected_result=f"1. 多次操作后功能正常\n2. 状态保持一致\n3. 无内存泄漏",
                method="正向测试"
            )

            # 3. 跨模块集成验证
            self._add_test_case(
                module="功能测试",
                title=f"验证{title}与其他模块的集成",
                preconditions=f"1. 系统已部署\n2. {self._extract_feature_context(title)}已配置\n3. 相关模块已启用",
                steps=f"1. 在其他模块中触发与{title}相关的事件\n2. 验证{title}的响应\n3. 检查数据同步",
                priority="中",
                expected_result=f"1. 集成功能正常\n2. 数据同步准确\n3. 无交叉影响",
                method="正向测试"
            )

    def generate_boundary_test_cases(self):
        """为数值型功能点生成边界值测试用例"""
        logger.info("开始生成边界值测试用例...")

        # 从表格中查找数值字段
        for table in self.tables_data:
            headers = table['headers']
            rows = table['content']

            # 检查表头是否包含数值字段
            numeric_keywords = ['数量', '数量', '级别', '等级', '次数', '时间', '秒', '分钟', '小时', '天', 'KB', 'MB', 'GB', 'px', '像素', '大小', '长度', '宽度', '高度']

            for col_idx, header in enumerate(headers):
                if any(keyword in header for keyword in numeric_keywords):
                    # 为这个数值列生成边界值测试
                    for row in rows:
                        if col_idx < len(row) and row[col_idx]:
                            value = row[col_idx]

                            # 尝试提取数字
                            numbers = re.findall(r'(\d+)', value)
                            if numbers:
                                num = int(numbers[0])

                                # 生成边界值测试用例
                                self._add_test_case(
                                    module="边界值测试",
                                    title=f"验证{header}为{num-1}时的边界值",
                                    preconditions=f"1. 系统已部署\n2. {header}可配置",
                                    steps=f"1. 设置{header}为{max(0, num-1)}\n2. 执行相关操作",
                                    priority="高",
                                    expected_result=f"1. 系统正常处理\n2. 无错误提示\n3. 功能正常",
                                    method="边界值测试"
                                )

                                self._add_test_case(
                                    module="边界值测试",
                                    title=f"验证{header}为{num}时的正常值",
                                    preconditions=f"1. 系统已部署\n2. {header}可配置",
                                    steps=f"1. 设置{header}为{num}\n2. 执行相关操作",
                                    priority="中",
                                    expected_result=f"1. 系统正常处理\n2. 功能按预期工作\n3. 无性能问题",
                                    method="边界值测试"
                                )

                                self._add_test_case(
                                    module="边界值测试",
                                    title=f"验证{header}为{num+1}时的边界值",
                                    preconditions=f"1. 系统已部署\n2. {header}可配置",
                                    steps=f"1. 设置{header}为{num+1}\n2. 执行相关操作",
                                    priority="中",
                                    expected_result=f"1. 系统正确处理或提示上限\n2. 无崩溃\n3. 有明确提示信息",
                                    method="边界值测试"
                                )

    def generate_exception_test_cases(self):
        """为功能点生成异常测试用例"""
        logger.info("开始生成异常测试用例...")

        # 基于功能点生成异常场景
        for fp in self.function_points:
            title = fp['title']

            # 检查是否涉及权限、数据、网络等异常场景
            if any(keyword in title for keyword in ['权限', '访问', '禁止', '限制', '安全', '加密', '认证', '授权']):
                self._add_test_case(
                    module="异常测试",
                    title=f"验证{title}在无权限时的表现",
                    preconditions=f"1. 系统已部署\n2. 当前用户无{title}权限",
                    steps=f"1. 尝试执行{title}操作\n2. 观察系统反应",
                    priority="高",
                    expected_result=f"1. 系统拒绝操作\n2. 显示权限不足提示\n3. 无崩溃",
                    method="错误推测"
                )

                self._add_test_case(
                    module="异常测试",
                    title=f"验证{title}在权限不足时的表现",
                    preconditions=f"1. 系统已部署\n2. 当前用户有部分{title}权限",
                    steps=f"1

path: bin/generate/generate_testcases_v2.py
content: #!/usr/bin/env python3
"""
增强版测试用例生成工具 - generate_testcases_v2.py

智能适配任意规模需求文档的测试用例生成工具

- 复杂需求：提取所有功能点、表格、规则，生成200+个详细测试用例
- 简单需求：精准提取核心功能，生成8-15个针对性用例
- 通用设计：无需修改代码，自动适应任何需求文档结构
"""

import sys
import re
import json
from pathlib import Path
from docx import Document
from typing import List, Dict, Any, Tuple
from datetime import datetime
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class AdvancedTestCaseGenerator:
    """高级测试用例生成器 - 智能适配任意需求文档"""

    def __init__(self, doc_path):
        """
        初始化测试用例生成器

        Args:
            doc_path: Word文档路径
        """
        self.doc_path = Path(doc_path)
        self.doc = None
        self.content = ""
        self.test_cases = []
        self.tc_counter = 1
        self.function_points = []  # 提取的所有功能点
        self.tables_data = []      # 提取的所有表格数据

        # 测试方法类型定义
        self.test_methods = [
            "正向测试", "边界值测试", "错误推测", "场景法",
            "UI测试", "兼容性测试", "性能测试", "安全测试", "异常测试"
        ]

    def load_document(self):
        """加载 Word 文档"""
        try:
            self.doc = Document(self.doc_path)
            logger.info(f"成功加载文档: {self.doc_path}")
            return True
        except Exception as e:
            logger.error(f"文档加载失败: {e}")
            return False

    def extract_function_points(self):
        """
        智能提取功能点

        识别方式：
        1. 标题层级（1-4级）
        2. 以功能动词开头的段落（增加、修改、删除、支持、验证）
        3. 表格中的功能项
        4. 带编号的功能列表
        """
        logger.info("开始智能提取功能点...")

        # 收集所有段落
        paragraphs = []
        for para in self.doc.paragraphs:
            if para.text.strip():
                paragraphs.append({
                    'text': para.text.strip(),
                    'style': para.style.name if para.style else '',
                    'level': self._get_heading_level(para)
                })

        # 1. 提取标题作为功能点（1-4级）
        for i, para in enumerate(paragraphs):
            if para['level'] > 0 and para['level'] <= 4:
                # 检查是否为有效功能描述
                if self._is_function_description(para['text']):
                    # 查找上下文
                    context = self._get_context(paragraphs, i, 3)
                    self.function_points.append({
                        'title': para['text'],
                        'level': para['level'],
                        'context': context,
                        'type': 'heading',
                        'source': f'段落{i+1}'
                    })

        # 2. 提取以功能动词开头的段落
        function_verbs = ['增加', '修改', '删除', '支持', '验证', '实现', '提供', '允许', '配置', '设置', '开启', '关闭', '限制']

        for i, para in enumerate(paragraphs):
            text = para['text']
            if any(text.startswith(verb) for verb in function_verbs) and len(text) > 5:
                # 排除标题（已处理）
                if para['level'] == 0:  # 非标题段落
                    context = self._get_context(paragraphs, i, 2)
                    self.function_points.append({
                        'title': text,
                        'level': 5,  # 自定义级别
                        'context': context,
                        'type': 'verb',
                        'source': f'段落{i+1}'
                    })

        # 3. 提取表格中的功能项
        for table_idx, table in enumerate(self.doc.tables):
            # 分析表格结构
            rows = []
            for row in table.rows:
                row_data = [cell.text.strip() for cell in row.cells]
                if any(row_data):  # 非空行
                    rows.append(row_data)

            if len(rows) > 1:  # 有数据的表格
                # 尝试识别列标题
                headers = rows[0]
                # 如果有'功能'、'模块'、'权限'等关键词，视为功能点表格
                if any(keyword in str(headers).lower() for keyword in ['功能', '模块', '权限', '菜单', '按钮', '字段']):
                    for row in rows[1:]:  # 跳过标题行
                        if len(row) > 0 and row[0]:  # 第一列有内容
                            self.function_points.append({
                                'title': row[0],
                                'level': 5,
                                'context': '\n'.join(row),
                                'type': 'table',
                                'source': f'表格{table_idx+1}'
                            })

        # 4. 提取带编号的功能列表
        numbered_pattern = r'^(\d+[.、）)]\s+|\d+、)\s*(.+)'  # 匹配 1.、2.、3.、1）、2）、1、2、

        for i, para in enumerate(paragraphs):
            match = re.match(numbered_pattern, para['text'])
            if match:
                title = match.group(2)
                if len(title) > 5:  # 避免过短的列表项
                    context = self._get_context(paragraphs, i, 1)
                    self.function_points.append({
                        'title': title,
                        'level': 5,
                        'context': context,
                        'type': 'numbered',
                        'source': f'段落{i+1}'
                    })

        # 去重（基于标题）
        seen_titles = set()
        unique_points = []
        for fp in self.function_points:
            if fp['title'] not in seen_titles:
                seen_titles.add(fp['title'])
                unique_points.append(fp)

        self.function_points = unique_points
        logger.info(f"成功提取 {len(self.function_points)} 个功能点")
        return len(self.function_points)

    def _get_heading_level(self, paragraph):
        """获取标题级别"""
        style = paragraph.style.name.lower()
        if 'heading' in style:
            if '1' in style:
                return 1
            elif '2' in style:
                return 2
            elif '3' in style:
                return 3
            elif '4' in style:
                return 4
        return 0

    def _is_function_description(self, text):
        """判断是否为功能描述"""
        # 排除纯标题、目录、页码等
        if len(text) < 5:
            return False
        if text.startswith('目录') or text.startswith('第') or text.startswith('页'):
            return False
        if '图' in text and '表' in text and '编号' in text:
            return False
        # 包含功能动词
        function_verbs = ['增加', '修改', '删除', '支持', '验证', '实现', '提供', '允许', '配置', '设置', '开启', '关闭', '限制', '显示', '隐藏']
        return any(verb in text for verb in function_verbs)

    def _get_context(self, paragraphs, index, window=3):
        """获取上下文"""
        start = max(0, index - window)
        end = min(len(paragraphs), index + window + 1)
        context = []
        for i in range(start, end):
            if i != index and paragraphs[i]['text'].strip():
                context.append(paragraphs[i]['text'])
        return '\n'.join(context)

    def extract_tables(self):
        """提取所有表格数据"""
        logger.info("开始提取表格数据...")

        for table_idx, table in enumerate(self.doc.tables):
            rows = []
            for row in table.rows:
                row_data = [cell.text.strip() for cell in row.cells]
                if any(row_data):
                    rows.append(row_data)

            if len(rows) > 1:  # 有数据的表格
                self.tables_data.append({
                    'index': table_idx + 1,
                    'rows': rows,
                    'headers': rows[0] if rows else [],
                    'content': rows[1:] if len(rows) > 1 else []
                })
                logger.info(f"提取表格 {table_idx + 1}: {len(rows)} 行")

        logger.info(f"共提取 {len(self.tables_data)} 个表格")
        return len(self.tables_data)

    def generate_positive_test_cases(self):
        """为每个功能点生成正向测试用例"""
        logger.info("开始生成正向测试用例...")

        for fp in self.function_points:
            title = fp['title']

            # 生成正向测试用例
            # 基于功能点生成具体测试用例
            # 例如：如果标题是"增加二级菜单指定业务属性"，就生成对应测试

            # 提取功能动词和对象
            verbs = ['增加', '修改', '删除', '支持', '验证', '实现', '提供', '允许', '配置', '设置', '开启', '关闭', '限制']
            verb = None
            obj = None

            for v in verbs:
                if v in title:
                    verb = v
                    obj = title.replace(v, '').strip().strip('：').strip('。').strip()
                    break

            if not verb:
                # 如果没有动词，就用功能点作为标题
                verb = "验证"
                obj = title

            # 生成具体测试用例
            self._add_test_case(
                module="功能测试",
                title=f"{verb}{obj}",
                preconditions=f"1. 系统已部署\n2. {obj}功能已启用",
                steps=f"1. 执行{obj}操作\n2. 验证结果",
                priority="高",
                expected_result=f"1. {obj}功能正常工作\n2. 无错误提示\n3. 界面显示正确",
                method="正向测试"
            )

    def generate_boundary_test_cases(self):
        """为数值型功能点生成边界值测试用例"""
        logger.info("开始生成边界值测试用例...")

        # 从表格中查找数值字段
        for table in self.tables_data:
            headers = table['headers']
            rows = table['content']

            # 检查表头是否包含数值字段
            numeric_keywords = ['数量', '数量', '级别', '等级', '次数', '时间', '秒', '分钟', '小时', '天', 'KB', 'MB', 'GB', 'px', '像素']

            for col_idx, header in enumerate(headers):
                if any(keyword in header for keyword in numeric_keywords):
                    # 为这个数值列生成边界值测试
                    for row in rows:
                        if col_idx < len(row) and row[col_idx]:
                            value = row[col_idx]

                            # 尝试提取数字
                            numbers = re.findall(r'(\d+)', value)
                            if numbers:
                                num = int(numbers[0])

                                # 生成边界值测试用例
                                self._add_test_case(
                                    module="边界值测试",
                                    title=f"验证{header}为{num-1}时的边界值",
                                    preconditions=f"1. 系统已部署\n2. {header}可配置",
                                    steps=f"1. 设置{header}为{max(0, num-1)}\n2. 执行相关操作",
                                    priority="高",
                                    expected_result=f"1. 系统正常处理\n2. 无错误提示\n3. 功能正常",
                                    method="边界值测试"
                                )

                                self._add_test_case(
                                    module="边界值测试",
                                    title=f"验证{header}为{num+1}时的边界值",
                                    preconditions=f"1. 系统已部署\n2. {header}可配置",
                                    steps=f"1. 设置{header}为{num+1}\n2. 执行相关操作",
                                    priority="中",
                                    expected_result=f"1. 系统正确处理或提示上限\n2. 无崩溃\n3. 有明确提示信息",
                                    method="边界值测试"
                                )

    def generate_exception_test_cases(self):
        """为功能点生成异常测试用例"""
        logger.info("开始生成异常测试用例...")

        # 基于功能点生成异常场景
        for fp in self.function_points:
            title = fp['title']

            # 检查是否涉及权限、数据、网络等异常场景
            if any(keyword in title for keyword in ['权限', '访问', '禁止', '限制', '安全', '加密']):
                self._add_test_case(
                    module="异常测试",
                    title=f"验证{title}在无权限时的表现",
                    preconditions=f"1. 系统已部署\n2. 当前用户无{title}权限",
                    steps=f"1. 尝试执行{title}操作\n2. 观察系统反应",
                    priority="高",
                    expected_result=f"1. 系统拒绝操作\n2. 显示权限不足提示\n3. 无崩溃",
                    method="错误推测"
                )

            if any(keyword in title for keyword in ['数据', '字段', '输入', '输出']):
                self._add_test_case(
                    module="异常测试",
                    title=f"验证{title}输入空值时的表现",
                    preconditions=f"1. 系统已部署\n2. {title}可输入数据",
                    steps=f"1. 清空{title}输入字段\n2. 提交操作",
                    priority="中",
                    expected_result=f"1. 系统提示输入不能为空\n2. 无崩溃\n3. 输入框高亮显示",
                    method="错误推测"
                )

            if any(keyword in title for keyword in ['网络', '连接', '超时', '失败']):
                self._add_test_case(
                    module="异常测试",
                    title=f"验证{title}在断网时的表现",
                    preconditions=f"1. 系统已部署\n2. 网络连接断开",
                    steps=f"1. 执行{title}操作\n2. 观察系统反应",
                    priority="高",
                    expected_result=f"1. 系统提示网络异常\n2. 无崩溃\n3. 网络恢复后自动重试",
                    method="错误推测"
                )

    def generate_scenario_test_cases(self):
        """基于业务流程生成场景测试用例"""
        logger.info("开始生成场景测试用例...")

        # 检查文档中是否有流程描述
        process_keywords = ['流程', '步骤', '顺序', '流程图', '交互', '时序', '工作流']

        for para in self.doc.paragraphs:
            text = para.text.strip()
            if any(keyword in text for keyword in process_keywords) and len(text) > 10:
                # 提取流程描述（截取前50字符）
                flow_desc = text[:50] + "..." if len(text) > 50 else text
                # 生成一个端到端场景测试
                self._add_test_case(
                    module="场景测试",
                    title=f"验证{flow_desc}完整流程",
                    preconditions=f"1. 系统已部署\n2. 用户已登录\n3. 所有前置条件满足",
                    steps=f"1. 进入相关功能模块\n2. 按照业务流程执行所有步骤\n3. 完成最终操作",
                    priority="高",
                    expected_result=f"1. 整个流程顺畅无卡顿\n2. 所有功能正常\n3. 数据一致且正确\n4. 用户体验完整",
                    method="场景法"
                )

    def generate_ui_test_cases(self):
        """为UI相关功能点生成UI测试用例"""
        logger.info("开始生成UI测试用例...")

        ui_keywords = ['界面', '页面', '按钮', '菜单', '图标', '显示', '布局', '样式', '颜色', '大小', '位置', '对齐', '字体']

        for fp in self.function_points:
            title = fp['title']
            if any(keyword in title for keyword in ui_keywords):
                # 生成UI测试用例
                self._add_test_case(
                    module="UI测试",
                    title=f"验证{title}的界面显示",
                    preconditions=f"1. 系统已部署\n2. {title}功能已启用",
                    steps=f"1. 进入{title}相关界面\n2. 检查所有元素显示\n3. 验证布局和样式",
                    priority="中",
                    expected_result=f"1. 所有元素清晰显示\n2. 布局无错位\n3. 样式符合设计规范\n4. 无兼容性问题",
                    method="UI测试"
                )

    def generate_compatibility_test_cases(self):
        """生成兼容性测试用例"""
        logger.info("开始生成兼容性测试用例...")

        # 基于文档内容生成兼容性测试
        compatibility_keywords = ['适配', '兼容', '多端', '多平台', '多浏览器', '多分辨率']

        for fp in self.function_points:
            title = fp['title']
            if any(keyword in title for keyword in compatibility_keywords):
                self._add_test_case(
                    module="兼容性测试",
                    title=f"验证{title}在不同设备/分辨率下的兼容性",
                    preconditions=f"1. 系统已部署\n2. 准备多种设备/分辨率",
                    steps=f"1. 在不同设备上测试{title}\n2. 在不同分辨率下测试{title}\n3. 在不同浏览器下测试{title}",
                    priority="中",
                    expected_result=f"1. 所有设备/分辨率下功能正常\n2. 界面自适应良好\n3. 无样式错位",
                    method="兼容性测试"
                )

    def generate_performance_test_cases(self):
        """生成性能测试用例"""
        logger.info("开始生成性能测试用例...")

        performance_keywords = ['性能', '响应', '速度', '并发', '负载', '延迟', '吞吐量', '效率']

        for fp in self.function_points:
            title = fp['title']
            if any(keyword in title for keyword in performance_keywords):
                self._add_test_case(
                    module="性能测试",
                    title=f"验证{title}的性能表现",
                    preconditions=f"1. 系统已部署\n2. 准备性能测试环境",
                    steps=f"1. 模拟大量并发请求\n2. 测量{title}响应时间\n3. 监控系统资源使用",
                    priority="低",
                    expected_result=f"1. 响应时间在可接受范围内（<3秒）\n2. 系统无性能瓶颈\n3. 资源使用正常",
                    method="性能测试"
                )

    def generate_security_test_cases(self):
        """生成安全测试用例"""
        logger.info("开始生成安全测试用例...")

        security_keywords = ['安全', '权限', '认证', '授权', '加密', '泄露', 'XSS', 'SQL', '注入', '攻击']

        for fp in self.function_points:
            title = fp['title']
            if any(keyword in title for keyword in security_keywords):
                self._add_test_case(
                    module="安全测试",
                    title=f"验证{title}的安全性",
                    preconditions=f"1. 系统已部署\n2. 已登录用户",
                    steps=f"1. 尝试越权访问{title}\n2. 输入恶意代码\n3. 测试数据泄露风险",
                    priority="高",
                    expected_result=f"1. 无越权访问\n2. 无XSS/SQL注入\n3. 数据加密传输\n4. 无敏感信息泄露",
                    method="安全测试"
                )

    def _add_test_case(self, module: str, title: str, preconditions: str,
                      steps: str, priority: str, expected_result: str, method: str = ""):
        """
        添加测试用例

        Args:
            module: 测试模块
            title: 用例标题
            preconditions: 前置条件
            steps: 测试步骤
            priority: 优先级
            expected_result: 预期结果
            method: 测试方法
        """
        self.test_cases.append({
            "id": f"TC{self.tc_counter:03d}",
            "module": module,
            "title": title,
            "preconditions": preconditions,
            "steps": steps,
            "priority": priority,
            "expected_result": expected_result,
            "method": method
        })
        self.tc_counter += 1

    def generate_test_cases(self):
        """生成所有测试用例"""
        if not self.load_document():
            return False

        # 提取功能点和表格
        self.extract_function_points()
        self.extract_tables()

        logger.info(f"总功能点数: {len(self.function_points)}")
        logger.info(f"总表格数: {len(self.tables_data)}")

        # 生成各种类型的测试用例
        self.generate_positive_test_cases()
        self.generate_boundary_test_cases()
        self.generate_exception_test_cases()
        self.generate_scenario_test_cases()
        self.generate_ui_test_cases()
        self.generate_compatibility_test_cases()
        self.generate_performance_test_cases()
        self.generate_security_test_cases()

        logger.info(f"总计生成测试用例: {len(self.test_cases)} 个")
        return True

    def save_to_markdown(self, output_path=None):
        """
        保存测试用例到Markdown文件

        Args:
            output_path: 输出文件路径，默认为文档同级目录

        Returns:
            str: 输出文件路径
        """
        if output_path is None:
            output_path = self.doc_path.parent / f"{self.doc_path.stem}_testcases.md"

        with open(output_path, 'w', encoding='utf-8') as f:
            # 写入标题
            f.write(f"# 测试用例\n\n")
            f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
            f.write(f"基于需求文档: {self.doc_path.name}\n\n")
            f.write(f"总用例数: {len(self.test_cases)}\n\n")
            f.write("---\n\n")

            # 按模块分组
            modules = {}
            for tc in self.test_cases:
                module = tc["module"]
                if module not in modules:
                    modules[module] = []
                modules[module].append(tc)

            # 写入各模块的测试用例
            for module, cases in modules.items():
                f.write(f"## {module}\n\n")

                for tc in cases:
                    f.write(f"### {tc['id']}: {tc['title']}\n\n")
                    f.write(f"**测试方法**: {tc.get('method', '')}\n\n")
                    f.write(f"**优先级**: {tc['priority']}\n\n")
                    f.write(f"**前置条件**:\n```\n{tc['preconditions']}\n```\n\n")
                    f.write(f"**测试步骤**:\n```\n{tc['steps']}\n```\n\n")
                    f.write(f"**预期结果**:\n```\n{tc['expected_result']}\n```\n\n")
                    f.write("---\n\n")

        logger.info(f"测试用例已保存到Markdown: {output_path}")
        return str(output_path)

    def save_to_json(self, output_path=None):
        """
        保存测试用例到JSON文件

        Args:
            output_path: 输出文件路径，默认为文档同级目录

        Returns:
            str: 输出文件路径
        """
        if output_path is None:
            output_path = self.doc_path.parent / f"{self.doc_path.stem}_testcases.json"

        data = {
            "generated_at": datetime.now().isoformat(),
            "source_document": str(self.doc_path),
            "total_cases": len(self.test_cases),
            "function_points": self.function_points,
            "tables_count": len(self.tables_data),
            "test_cases": self.test_cases
        }

        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

        logger.info(f"测试用例已保存到JSON: {output_path}")
        return str(output_path)


def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(
        description="高级测试用例生成工具 - 智能适配任意规模需求文档",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  python generate_testcases_v2.py demand/需求文档.docx
  python generate_testcases_v2.py demand/需求文档.docx --output testcases.md
        """
    )

    parser.add_argument("doc_file", help="需求文档路径（Word格式）")
    parser.add_argument("--output", help="输出文件路径（可选）")

    args = parser.parse_args()

    # 创建生成器
    generator = AdvancedTestCaseGenerator(args.doc_file)

    # 生成测试用例
    if generator.generate_test_cases():
        # 保存到Markdown
        md_path = generator.save_to_markdown(args.output)
        # 同时保存JSON
        json_path = generator.save_to_json()

        print(f"\n生成完成！")
        print(f"Markdown文件: {md_path}")
        print(f"JSON文件: {json_path}")
    else:
        print("\n生成失败！")
        sys.exit(1)


if __name__ == "__main__":
    main()

path: bin/wiki2doc.py
content: #!/usr/bin/env python3
"""
Confluence Wiki to Word Document Converter

Extracts content from Confluence wiki pages and saves as .docx files.
"""

import argparse
import os
import io
import re
import shutil
import time
from pathlib import Path
from urllib.parse import urljoin

from bs4 import BeautifulSoup
from docx import Document
from docx.shared import Inches, Pt
from PIL import Image
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError

# Configuration
BASE_URL = "http://10.225.1.76:8090"
LOGIN_URL = f"{BASE_URL}/login.action"
USERNAME = "yanghua"
[REDACTED_SECRET]"
OUTPUT_DIR = Path.home() / ".claude" / "skills" / "wiki2doc" / "demand"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

# Temporary directory for images
TEMP_DIR = OUTPUT_DIR / "temp_images"
TEMP_DIR.mkdir(exist_ok=True)


def extract_title(soup):
    """Extract page title from #title-text element."""
    title_elem = soup.select_one("#title-text")
    if title_elem:
        return title_elem.get_text(strip=True)
    return f"Untitled_{int(time.time())}"


def clean_filename(filename):
    """Clean filename to remove invalid characters."""
    # Replace invalid characters with underscore
    cleaned = re.sub(r'[\\/:*?"<>|]', '_', filename)
    # Limit length to 50 characters
    return cleaned[:50] if len(cleaned) > 50 else cleaned


def extract_table_with_merges(table_element):
    """Extract table data with merge information (rowspan and colspan)."""
    rows_data = []

    # Get all rows
    rows = table_element.select("tr")

    # Track which cells are occupied by rowspan
    rowspan_tracker = {}  # key: (row_idx, col_idx), value: remaining rows

    for row_idx, tr in enumerate(rows):
        row_data = []
        col_idx = 0

        # Get all cells in this row
        cells = tr.select("td, th")

        for cell in cells:
            # Skip columns that are occupied by rowspan from previous rows
            while (row_idx, col_idx) in rowspan_tracker:
                # This cell is occupied, add placeholder
                row_data.append({"text": "", "rowspan": 0, "colspan": 0, "occupied": True})
                col_idx += 1

            # Get cell properties
            text = cell.get_text(strip=True)
            rowspan = int(cell.get("rowspan", 1))
            colspan = int(cell.get("colspan", 1))

            # Add cell data
            row_data.append({
                "text": text,
                "rowspan": rowspan,
                "colspan": colspan,
                "occupied": False
            })

            # Mark cells that will be occupied by rowspan
            if rowspan > 1:
                for r in range(1, rowspan):
                    rowspan_tracker[(row_idx + r, col_idx)] = rowspan - r

            # Move to next column (account for colspan)
            col_idx += colspan

        # Fill remaining columns that might be occupied by rowspan
        while (row_idx, col_idx) in rowspan_tracker:
            row_data.append({"text": "", "rowspan": 0, "colspan": 0, "occupied": True})
            col_idx += 1

        rows_data.append(row_data)

    return rows_data


def is_hidden_by_css(element):
    """Check if element is hidden by CSS."""
    style = element.get("style", "").lower()
    if "display:none" in style or "display: none" in style:
        return True
    if "visibility:hidden" in style or "visibility: hidden" in style:
        return True
    if "opacity:0" in style or "opacity: 0" in style:
        return True
    return False


def extract_content(soup):
    """Extract text and image content from the main page content."""
    # Try to find the main content container
    content_div = soup.select_one("#page, #main-content, .page-body, .wiki-content")
    if not content_div:
        return []

    content_items = []

    # Walk through all elements in document order
    for element in content_div.descendants:
        if element.name == "p":
            text = element.get_text(strip=True)
            if text:
                content_items.append(("text", text))
        elif element.name == "h1":
            text = element.get_text(strip=True)
            if text:
                content_items.append(("heading1", text))
        elif element.name == "h2":
            text = element.get_text(strip=True)
            if text:
                content_items.append(("heading2", text))
        elif element.name == "h3":
            text = element.get_text(strip=True)
            if text:
                content_items.append(("heading3", text))
        elif element.name == "ul" or element.name == "ol":
            text = element.get_text(strip=True)
            if text:
                content_items.append(("list", text))
        elif element.name == "table":
            # Extract table data with merge information
            table_data = extract_table_with_merges(element)
            if table_data:
                content_items.append(("table", table_data))
        elif element.name == "pre" or element.name == "code":
            text = element.get_text(strip=True)
            if text:
                content_items.append(("code", text))
        elif element.name == "img":
            # Skip hidden images
            if is_hidden_by_css(element):
                continue

            src = element.get("src")
            if src:
                # Skip non-image formats
                if any(src.lower().endswith(ext) for ext in [".gif", ".svg", ".webp", ".mp4", ".avi", ".mov"]):
                    continue

                # Get alt text for accessibility
                alt = element.get("alt", "")
                content_items.append(("image", {"src": src, "alt": alt}))

    return content_items


def download_image(page, img_src, temp_dir):
    """Download image using Playwright and convert to JPG."""
    try:
        # Resolve relative URLs
        if not img_src.startswith("http"):
            img_src = urljoin(BASE_URL, img_src)

        # Generate unique filename
        img_hash = abs(hash(img_src))
        filename = f"img_{img_hash}.jpg"
        filepath = temp_dir / filename

        # Download image using Playwright's request context
        response = page.request.get(img_src)
        if response.ok:
            image_bytes = response.body()

            # Load image
            img = Image.open(io.BytesIO(image_bytes))

            # Convert to RGB if needed (for JPG)
            if img.mode in ("RGBA", "P"):
                img = img.convert("RGB")

            # Save as JPG
            img.save(filepath, "JPEG", quality=85)

            return filename
        else:
            print(f"Warning: Failed to download {img_src} (status: {response.status})")
            return None
    except Exception as e:
        print(f"Warning: Failed to process image {img_src}: {e}")
        return None


def create_word_document(title, content_items, image_dir):
    """Create a Word document with text and images."""
    doc = Document()

    # Add title
    doc.add_heading(title, level=0)

    # Process content items in order
    for item_type, item_data in content_items:
        if item_type == "text":
            doc.add_paragraph(item_data)
        elif item_type == "heading1":
            doc.add_heading(item_data, level=1)
        elif item_type == "heading2":
            doc.add_heading(item_data, level=2)
        elif item_type == "heading3":
            doc.add_heading(item_data, level=3)
        elif item_type == "list":
            # Add list items
            for line in item_data.split("\n"):
                if line.strip():
                    doc.add_paragraph(line.strip(), style="List Bullet")
        elif item_type == "code":
            # Add code block with monospace font
            para = doc.add_paragraph(item_data)
            for run in para.runs:
                run.font.name = "Courier New"
                run.font.size = Pt(9)
        elif item_type == "table":
            # Add table with merged cells support
            if item_data:
                # Calculate actual column count (accounting for colspan)
                max_cols = max(len(row) for row in item_data)
                table = doc.add_table(rows=len(item_data), cols=max_cols)
                table.style = "Light Grid Accent 1"

                # Fill table and handle merges
                for i, row_data in enumerate(item_data):
                    for j, cell_info in enumerate(row_data):
                        if isinstance(cell_info, dict):
                            # New format with merge information
                            if not cell_info.get("occupied", False):
                                cell = table.cell(i, j)
                                cell.text = cell_info.get("text", "")

                                # Handle colspan and rowspan
                                rowspan = cell_info.get("rowspan", 1)
                                colspan = cell_info.get("colspan", 1)

                                if rowspan > 1 or colspan > 1:
                                    # Merge cells
                                    try:
                                        end_row = min(i + rowspan - 1, len(item_data) - 1)
                                        end_col = min(j + colspan - 1, max_cols - 1)

                                        # Only merge if we have valid range
                                        if i <= end_row and j <= end_col:
                                            cell.merge(table.cell(end_row, end_col))
                                    except Exception as e:
                                        print(f"Warning: Failed to merge cells ({i},{j}): {e}")
                        else:
                            # Old format (simple string)
                            table.rows[i].cells[j].text = cell_info
        elif item_type == "image":
            # Add image
            img_filename = item_data.get("filename")
            if img_filename:
                img_path = image_dir / img_filename
                if img_path.exists():
                    try:
                        doc.add_picture(str(img_path), width=Inches(5))
                        # Add alt text as caption if available
                        alt_text = item_data.get("alt", "")
                        if alt_text:
                            doc.add_paragraph(alt_text, style="Caption")
                    except Exception as e:
                        print(f"Warning: Failed to add image to document: {e}")

    # Save document
    doc_path = OUTPUT_DIR / f"{clean_filename(title)}.docx"

    # Handle file name conflicts
    counter = 1
    while doc_path.exists():
        doc_path = OUTPUT_DIR / f"{clean_filename(title)}_{counter}.docx"
        counter += 1

    doc.save(doc_path)
    return doc_path


def process_single_url(browser, url):
    """Process a single wiki URL and generate Word document."""
    try:
        print(f"\nProcessing: {url}")

        # Create new page
        page = browser.new_page()

        # Navigate directly to target URL - Confluence will redirect to login if needed
        print(f"Navigating to: {url}")
        page.goto(url, wait_until="networkidle", timeout=30000)

        # Check if we are on the login page
        current_url = page.url.lower()
        if "login" in current_url or "login.action" in current_url:
            print("Detected login page, logging in...")

            # Fill login form
            page.fill('input[name="os_username"]', USERNAME)
            page.fill('input[name="os_password"]', PASSWORD)

            # Submit the login form
            page.evaluate('''() => {
                const form = document.querySelector('form[name="loginform"]');
                if (form) {
                    form.submit();
                }
            }''')

            # Wait for login to complete and redirect to target page
            page.wait_for_load_state("networkidle", timeout=15000)
            print("Login successful, redirected to target page")
        else:
            print("Page loaded (already authenticated or no login required)")

        # Wait for splitter-content to appear
        print("Waiting for #splitter-content element...")
        try:
            page.wait_for_selector("#splitter-content", timeout=10000)
            print("Found #splitter-content element")
        except:
            print("Warning: #splitter-content not found within timeout")
            # Save debug info
            debug_file = OUTPUT_DIR / f"debug_no_splitter_{int(time.time())}.html"
            with open(debug_file, 'w', encoding='utf-8') as f:
                f.write(page.content())
            print(f"Debug: Saved HTML to {debug_file}")

        # Get page HTML
        html_content = page.content()

        # Debug: save HTML to file for inspection (saved to OUTPUT_DIR, not temp)
        debug_file = OUTPUT_DIR / f"debug_{int(time.time())}.html"
        with open(debug_file, 'w', encoding='utf-8') as f:
            f.write(html_content)
        print(f"Debug: Saved page HTML to {debug_file}")

        # Parse HTML
        soup = BeautifulSoup(html_content, "html.parser")

        # Debug: check if common Confluence elements exist
        splitter = soup.select_one("#splitter-content")
        if not splitter:
            print("Debug: Checking for alternative content containers...")
            for selector in ['#page-content', '#main-content', '#wiki-content', '.wiki-content', '.page-content', '.page-body', '#content']:
                element = soup.select_one(selector)
                if element:
                    print(f"Found alternative: {selector}")
                    break

        # Extract title
        title = extract_title(soup)
        print(f"Title: {title}")

        # Extract content
        content_items = extract_content(soup)

        # Count items
        text_count = sum(1 for item_type, _ in content_items if item_type != "image")
        image_count = sum(1 for item_type, _ in content_items if item_type == "image")
        print(f"Extracted {text_count} text elements and {image_count} images")

        # Download images
        print(f"Downloading images...")
        image_counter = 0
        for i, (item_type, item_data) in enumerate(content_items):
            if item_type == "image":
                image_counter += 1
                img_src = item_data["src"]
                img_filename = download_image(page, img_src, TEMP_DIR)
                if img_filename:
                    # Update content_items with filename
                    content_items[i] = ("image", {**item_data, "filename": img_filename})
                    print(f"   Downloaded image {image_counter}/{image_count}")

        # Create Word document
        print(f"Generating Word document...")
        doc_path = create_word_document(title, content_items, TEMP_DIR)
        print(f"Saved to: {doc_path}")

        # Close page
        page.close()

        return True, doc_path

    except PlaywrightTimeoutError as e:
        print(f"Timeout error: {e}")
        return False, None
    except Exception as e:
        print(f"Error processing {url}: {e}")
        return False, None


def main():
    parser = argparse.ArgumentParser(
        description="Convert Confluence wiki pages to Word documents",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  Single URL:
    python wiki2doc.py http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052

  Batch mode (comma-separated):
    python wiki2doc.py --batch "url1,url2,url3"

  Full automation (generate test cases):
    python wiki2doc.py --auto http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052
        """
    )
    parser.add_argument("urls", nargs="*", help="One or more wiki URLs")
    parser.add_argument("--batch", action="store_true", help="Enable batch mode (comma-separated URLs)")
    parser.add_argument("--auto", action="store_true", help="Enable full automation workflow (generate test cases)")

    args = parser.parse_args()

    # Parse URLs
    urls = []
    if args.batch:
        if args.urls:
            urls = [url.strip() for url in args.urls[0].split(",")]
    else:
        urls = args.urls

    if not urls:
        parser.print_help()
        print("\n[ERROR] No URLs provided.")
        return

    print(f"Total URLs to process: {len(urls)}")

    if args.auto:
        # Full automation workflow
        from wiki2testcases import WorkflowOrchestrator
        for url in urls:
            orchestrator = WorkflowOrchestrator(url)
            if not orchestrator.run():
                print(f"\n[ERROR] Full automation failed for {url}")
                return
        return

    # Launch browser for normal operation
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)

        success_count = 0
        fail_count = 0

        for i, url in enumerate(urls, 1):
            print(f"\n{'='*60}")
            print(f"Progress: {i}/{len(urls)}")
            success, _ = process_single_url(browser, url)
            if success:
                success_count += 1
            else:
                fail_count += 1

        browser.close()

    # Clean up temporary images
    if TEMP_DIR.exists():
        shutil.rmtree(TEMP_DIR)

    print(f"\n{'='*60}")
    print(f"Completed! Success: {success_count}, Failed: {fail_count}")
    print(f"Output directory: {OUTPUT_DIR}")


if __name__ == "__main__":
    main()

path: bin/wiki2testcases.py
content: #!/usr/bin/env python3
"""
完整的自动化工作流 - wiki2testcases.py

将Wiki页面转换为测试用例Excel文件的完整流程:
1. 从Wiki提取内容生成Word文档
2. 分析需求检测问题
3. 生成测试用例（Markdown格式）
4. 转换为Excel格式
"""

import sys
import os
import subprocess
from pathlib import Path
from datetime import datetime


class WorkflowOrchestrator:
    """工作流编排器"""

    def __init__(self, wiki_url):
        """
        初始化工作流编排器

        Args:
            wiki_url: Wiki页面URL
        """
        self.wiki_url = wiki_url
        self.base_dir = Path(__file__).parent.parent  # wiki2doc目录
        self.bin_dir = Path(__file__).parent
        self.demand_dir = self.base_dir / "demand"
        self.demand_dir.mkdir(exist_ok=True)

        # 步骤输出文件
        self.doc_file = None
        self.analysis_report = None
        self.testcase_md = None
        self.testcase_excel = None

    def log(self, message, level="INFO"):
        """
        打印日志

        Args:
            message: 日志消息
            level: 日志级别
        """
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{timestamp}] [{level}] {message}")

    def step1_extract_from_wiki(self):
        """
        步骤1: 从Wiki提取内容并生成Word文档

        Returns:
            bool: 是否成功
        """
        self.log("\n" + "="*60)
        self.log("步骤1: 从Wiki提取内容并生成Word文档")
        self.log("="*60)

        try:
            # 调用wiki2doc.py
            wiki2doc_script = self.bin_dir / "wiki2doc.py"
            if not wiki2doc_script.exists():
                self.log(f"找不到wiki2doc.py脚本: {wiki2doc_script}", "ERROR")
                return False

            cmd = [sys.executable, str(wiki2doc_script), self.wiki_url]
            self.log(f"执行命令: {' '.join(cmd)}")

            result = subprocess.run(cmd, cwd=str(self.base_dir), capture_output=True, text=True)

            if result.returncode != 0:
                self.log(f"Wiki提取失败: {result.stderr}", "ERROR")
                return False

            # 查找生成的Word文档
            doc_files = list(self.demand_dir.glob("*.docx"))
            if doc_files:
                # 按修改时间排序，取最新的
                self.doc_file = max(doc_files, key=lambda p: p.stat().st_mtime)
                self.log(f"[OK] 成功生成Word文档: {self.doc_file}")
                return True
            else:
                self.log("未找到生成的Word文档", "ERROR")
                return False

        except Exception as e:
            self.log(f"步骤1执行失败: {e}", "ERROR")
            return False

    def step2_analyze_requirements(self):
        """
        步骤2: 分析需求文档

        Returns:
            bool: 是否成功
        """
        self.log("\n" + "="*60)
        self.log("步骤2: 分析需求文档")
        self.log("="*60)

        if not self.doc_file or not self.doc_file.exists():
            self.log("Word文档不存在，跳过此步骤", "WARNING")
            return True  # 不阻止后续步骤

        try:
            # 调用analyze_requirements.py
            analyze_script = self.bin_dir / "analyze" / "analyze_requirements.py"
            if not analyze_script.exists():
                self.log(f"找不到分析脚本: {analyze_script}", "ERROR")
                return False

            cmd = [sys.executable, str(analyze_script), str(self.doc_file)]
            self.log(f"执行命令: {' '.join(cmd)}")

            result = subprocess.run(cmd, cwd=str(self.base_dir), capture_output=True, text=True)

            if result.returncode != 0:
                self.log(f"需求分析失败: {result.stderr}", "ERROR")
                return False

            # 查找生成的分析报告
            report_files = list(self.demand_dir.glob("*_analysis_report.txt"))
            if report_files:
                self.analysis_report = max(report_files, key=lambda p: p.stat().st_mtime)
                self.log(f"[OK] 成功生成分析报告: {self.analysis_report}")
                return True
            else:
                self.log("未找到分析报告", "WARNING")
                return True  # 不阻止后续步骤

        except Exception as e:
            self.log(f"步骤2执行失败: {e}", "ERROR")
            return False

    def step3_generate_test_cases(self):
        """
        步骤3: 生成测试用例

        Returns:
            bool: 是否成功
        """
        self.log("\n" + "="*60)
        self.log("步骤3: 生成测试用例")
        self.log("="*60)

        if not self.doc_file or not self.doc_file.exists():
            self.log("Word文档不存在，无法生成测试用例", "ERROR")
            return False

        try:
            # 调用generate_testcases.py
            generate_script = self.bin_dir / "generate" / "generate_testcases.py"
            if not generate_script.exists():
                self.log(f"找不到生成脚本: {generate_script}", "ERROR")
                return False

            cmd = [sys.executable, str(generate_script), str(self.doc_file)]
            self.log(f"执行命令: {' '.join(cmd)}")

            result = subprocess.run(cmd, cwd=str(self.base_dir), capture_output=True, text=True)

            if result.returncode != 0:
                self.log(f"测试用例生成失败: {result.stderr}", "ERROR")
                return False

            # 查找生成的Markdown文件
            md_files = list(self.demand_dir.glob("*_testcases.md"))
            if md_files:
                self.testcase_md = max(md_files, key=lambda p: p.stat().st_mtime)
                self.log(f"[OK] 成功生成测试用例Markdown: {self.testcase_md}")
                return True
            else:
                self.log("未找到测试用例Markdown文件", "ERROR")
                return False

        except Exception as e:
            self.log(f"步骤3执行失败: {e}", "ERROR")
            return False

    def step4_convert_to_excel(self):
        """
        步骤4: 转换为Excel格式

        Returns:
            bool: 是否成功
        """
        self.log("\n" + "="*60)
        self.log("步骤4: 转换为Excel格式")
        self.log("="*60)

        if not self.testcase_md or not self.testcase_md.exists():
            self.log("测试用例Markdown文件不存在", "ERROR")
            return False

        try:
            # 调用md2excel.py
            convert_script = self.bin_dir / "convert" / "md2excel.py"
            if not convert_script.exists():
                self.log(f"找不到转换脚本: {convert_script}", "ERROR")
                return False

            cmd = [sys.executable, str(convert_script), str(self.testcase_md)]
            self.log(f"执行命令: {' '.join(cmd)}")

            result = subprocess.run(cmd, cwd=str(self.base_dir), capture_output=True, text=True)

            if result.returncode != 0:
                self.log(f"Excel转换失败: {result.stderr}", "ERROR")
                return False

            # 查找生成的Excel文件
            excel_files = list(self.demand_dir.glob("*_testcases.xlsx"))
            if excel_files:
                self.testcase_excel = max(excel_files, key=lambda p: p.stat().st_mtime)
                self.log(f"[OK] 成功生成Excel文件: {self.testcase_excel}")
                return True
            else:
                # 可能是md同名xlsx
                excel_files = list(self.demand_dir.glob("*.xlsx"))
                if excel_files:
                    self.testcase_excel = max(excel_files, key=lambda p: p.stat().st_mtime)
                    self.log(f"[OK] 成功生成Excel文件: {self.testcase_excel}")
                    return True
                else:
                    self.log("未找到Excel文件", "ERROR")
                    return False

        except Exception as e:
            self.log(f"步骤4执行失败: {e}", "ERROR")
            return False

    def run(self):
        """
        运行完整工作流

        Returns:
            bool: 是否成功
        """
        self.log("开始执行完整工作流...")
        self.log(f"Wiki URL: {self.wiki_url}")
        self.log(f"工作目录: {self.base_dir}")

        # 执行各个步骤
        steps = [
            ("步骤1: Wiki提取", self.step1_extract_from_wiki),
            ("步骤2: 需求分析", self.step2_analyze_requirements),
            ("步骤3: 测试用例生成", self.step3_generate_test_cases),
            ("步骤4: Excel转换", self.step4_convert_to_excel)
        ]

        for step_name, step_func in steps:
            if not step_func():
                self.log(f"{step_name} 失败，工作流终止", "ERROR")
                return False

        # 打印总结
        self.log("\n" + "="*60)
        self.log("工作流执行完成！")
        self.log("="*60)

        if self.doc_file:
            self.log(f"[OK] Word文档: {self.doc_file}")
        if self.analysis_report:
            self.log(f"[OK] 分析报告: {self.analysis_report}")
        if self.testcase_md:
            self.log(f"[OK] 测试用例MD: {self.testcase_md}")
        if self.testcase_excel:
            self.log(f"[OK] 测试用例Excel: {self.testcase_excel}")

        self.log(f"\n所有文件保存在: {self.demand_dir}")

        return True


def main():
    """主函数"""
    import argparse

    parser = argparse.ArgumentParser(
        description="Wiki到测试用例的完整自动化工作流",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
完整工作流:
  1. 从Wiki提取内容生成Word文档
  2. 分析需求检测遗漏点、矛盾点
  3. 生成测试用例（Markdown格式）
  4. 转换为Excel格式

示例:
  python wiki2testcases.py http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052
        """
    )

    parser.add_argument("wiki_url", help="Wiki页面URL")

    args = parser.parse_args()

    # 创建工作流编排器
    orchestrator = WorkflowOrchestrator(args.wiki_url)

    # 运行工作流
    if orchestrator.run():
        print("\n[OK] 工作流执行成功！")
        sys.exit(0)
    else:
        print("\n[FAILED] 工作流执行失败！")
        sys.exit(1)


if __name__ == "__main__":
    main()


path: examples.md
content: # Wiki2Doc 使用示例

本文档提供实际使用场景的示例。

## 场景一：快速生成测试用例（推荐）

**需求：** 从Wiki需求页面一键生成完整的测试用例Excel

**命令：**
```bash
/skill wiki2doc --auto http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052
```

**执行过程：**
1. ✓ 提取Wiki内容生成Word文档
2. ✓ 分析需求检测问题
3. ✓ 自动生成测试用例
4. ✓ 转换为Excel格式

**输出文件：**
- `Pad端适配需求.docx` - 需求Word文档
- `Pad端适配需求_analysis_report.txt` - 需求分析报告
- `Pad端适配需求_testcases.md` - 测试用例Markdown
- `Pad端适配需求_testcases.xlsx` - 测试用例Excel（最终交付）

**耗时：** 约 1-2 分钟

---

## 场景二：仅提取Wiki内容

**需求：** 只需要将Wiki内容保存为Word文档

**命令：**
```bash
/skill wiki2doc http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052
```

**输出文件：**
- `需求标题.docx` - 包含文本和图片的Word文档

**适用场景：**
- 需求归档
- 离线查看
- 内容审阅

---

## 场景三：批量处理多个Wiki页面

**需求：** 一次处理多个Wiki页面

**命令：**
```bash
/skill wiki2doc --batch "http://10.225.1.76:8090/page1,http://10.225.1.76:8090/page2,http://10.225.1.76:8090/page3"
```

**输出：**
每个页面生成独立的Word文档

**适用场景：**
- 批量归档
- 批量导出
- 批量备份

---

## 场景四：独立分析需求文档

**需求：** 已有Word文档，需要分析需求质量

**命令：**
```bash
python bin/analyze/analyze_requirements.py demand/需求文档.docx
```

**输出：**
- JSON格式报告（机器可读）
- TXT格式报告（人类可读）

**报告内容：**
```
【遗漏点】
1. 功能缺少测试 (严重程度: high)
   描述: 文档中描述了功能模块，但缺少相应的测试要点
   建议: 建议为每个功能模块添加具体的测试要点

【矛盾点】
1. 数值矛盾 (严重程度: high)
   描述: 关于菜单数量的描述存在不同数值: ['9', '10']
   建议: 请确认正确的数值

【不明确点】
1. 模糊描述 (严重程度: medium)
   描述: 使用模糊词汇'适当'，没有具体的参数范围
   建议: 建议将'适当'替换为具体的数值
```

**适用场景：**
- 需求评审前的预检查
- 需求质量改进
- 发现潜在问题

---

## 场景五：为现有文档生成测试用例

**需求：** 已有需求文档，需要生成测试用例

**命令：**
```bash
python bin/generate/generate_testcases.py demand/需求文档.docx
```

**输出：**
- Markdown格式测试用例
- JSON格式测试用例

**生成的测试类型：**
- 正向测试（功能验证）
- 边界值测试
- 错误推测测试
- 场景测试
- UI测试
- 兼容性测试
- 性能测试

**适用场景：**
- 快速生成测试用例框架
- 补充测试用例
- 测试设计参考

---

## 场景六：Markdown转Excel

**需求：** 已有Markdown格式的测试用例，需要转为Excel

**命令：**
```bash
python bin/convert/md2excel.py demand/测试用例.md
```

**输出：**
- 格式化的Excel文件

**Excel特点：**
- 专业表格样式
- 自动列宽调整
- 表头高亮显示
- 边框和对齐优化
- 自动换行

**适用场景：**
- 测试用例格式转换
- 导入测试管理系统
- 团队共享

---

## 场景七：完整工作流（分步执行）

**需求：** 想要控制每个步骤，逐步执行

**步骤1：提取Wiki**
```bash
/skill wiki2doc http://10.225.1.76:8090/pages/viewpage.action?pageId=12345678
```

**步骤2：分析需求**
```bash
python bin/analyze/analyze_requirements.py demand/需求文档.docx
```
→ 查看分析报告，改进需求文档

**步骤3：生成测试用例**
```bash
python bin/generate/generate_testcases.py demand/需求文档.docx
```
→ 审阅Markdown文件，补充用例

**步骤4：转换Excel**
```bash
python bin/convert/md2excel.py demand/测试用例.md
```
→ 得到最终Excel文件

**优点：**
- 每步可控
- 可中间检查
- 可手动修改

---

## 实际案例：Pad端适配需求

### 输入
Wiki页面：`http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052`

### 命令
```bash
/skill wiki2doc --auto http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052
```

### 执行日志
```
[2026-03-06 14:30:15] [INFO] 开始执行完整工作流...
[2026-03-06 14:30:15] [INFO] Wiki URL: http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052

============================================================
步骤1: 从Wiki提取内容并生成Word文档
============================================================
[2026-03-06 14:30:15] [INFO] 执行命令: python bin/wiki2doc.py ...
成功加载文档: Pad端适配需求.docx
✓ 成功生成Word文档: demand/Pad端适配需求.docx

============================================================
步骤2: 分析需求文档
============================================================
[2026-03-06 14:30:45] [INFO] 执行命令: python bin/analyze/analyze_requirements.py ...
提取文本内容: 5234 字符
发现遗漏点: 3 个
发现矛盾点: 1 个
发现不明确点: 2 个
发现不完整点: 1 个
✓ 成功生成分析报告: demand/Pad端适配需求_analysis_report.txt

============================================================
步骤3: 生成测试用例
============================================================
[2026-03-06 14:31:00] [INFO] 执行命令: python bin/generate/generate_testcases.py ...
提取到 12 个功能特性
生成正向测试用例...
生成边界值测试用例...
生成异常测试用例...
生成场景测试用例...
生成UI测试用例...
生成兼容性测试用例...
生成性能测试用例...
总计生成测试用例: 35 个
✓ 成功生成测试用例Markdown: demand/Pad端适配需求_testcases.md

============================================================
步骤4: 转换为Excel格式
============================================================
[2026-03-06 14:31:15] [INFO] 执行命令: python bin/convert/md2excel.py ...
提取到 35 个测试用例
Excel文件已生成: demand/Pad端适配需求_testcases.xlsx
总计测试用例: 35 条
✓ 成功生成Excel文件: demand/Pad端适配需求_testcases.xlsx

============================================================
工作流执行完成！
============================================================
✓ Word文档: demand/Pad端适配需求.docx
✓ 分析报告: demand/Pad端适配需求_analysis_report.txt
✓ 测试用例MD: demand/Pad端适配需求_testcases.md
✓ 测试用例Excel: demand/Pad端适配需求_testcases.xlsx

所有文件保存在: C:\Users\yanghua1\.claude\skills\wiki2doc\demand
```

### 输出结果

**1. Word文档**
- 文件：`Pad端适配需求.docx`
- 内容：完整的Wiki内容，包含文本和图片
- 大小：约 2.3 MB

**2. 分析报告**
- 文件：`Pad端适配需求_analysis_report.txt`
- 发现问题：7个（3个遗漏点、1个矛盾点、2个不明确点、1个不完整点）
- 建议：针对每个问题的具体改进建议

**3. 测试用例Markdown**
- 文件：`Pad端适配需求_testcases.md`
- 用例数：35个
- 分类：8个模块

**4. 测试用例Excel**
- 文件：`Pad端适配需求_testcases.xlsx`
- 格式：专业表格样式
- 列：编号、模块、标题、前置条件、步骤、优先级、预期结果

### 总耗时
约 1分30秒

---

## 常见问题排查

### 问题1：提示"找不到元素"
**原因：** Wiki页面结构与预期不同

**解决：**
```bash
# 检查页面结构
python bin/wiki2doc.py --debug http://...
```

### 问题2：图片下载失败
**原因：** 图片权限或链接失效

**解决：** 跳过失败图片，不影响其他内容

### 问题3：生成的用例不够详细
**原因：** 需求文档内容较少

**解决：**
1. 完善需求文档
2. 手动补充测试用例
3. 调整生成策略

---

## 进阶技巧

### 技巧1：自定义配置

创建配置文件 `~/.wiki2doc/config.json`：
```json
{
  "confluence_url": "http://your-wiki.com",
  "username": "your-username",
  "password": "your-password",
  "output_dir": "/custom/output/path"
}
```

### 技巧2：批量脚本

创建批处理脚本 `batch_process.sh`：
```bash
#!/bin/bash
urls=(
  "http://10.225.1.76:8090/page1"
  "http://10.225.1.76:8090/page2"
  "http://10.225.1.76:8090/page3"
)

for url in "${urls[@]}"; do
  echo "Processing: $url"
  /skill wiki2doc --auto "$url"
  echo "---"
done
```

### 技巧3：自动化测试用例管理

将生成的Excel导入测试管理系统：
1. 导出Excel
2. 使用测试管理系统的导入功能
3. 映射字段对应关系
4. 批量导入

---

## 最佳实践建议

1. **需求质量保证**
   - 在生成测试用例前，先运行需求分析
   - 根据分析报告改进需求文档
   - 确保需求完整、清晰、无矛盾

2. **测试用例优化**
   - 自动生成后进行人工审核
   - 删除重复或冗余的用例
   - 补充业务特定的测试场景
   - 调整优先级

3. **团队协作**
   - 将生成的文档共享给团队
   - 定期更新测试用例
   - 建立评审机制

4. **版本管理**
   - 保存历史版本的测试用例
   - 记录变更历史
   - 追踪需求变更

---

**祝您使用愉快！**


path: README.md
content: # Wiki2Doc 工具集完整文档

> 将 Confluence Wiki 页面自动化转换为测试用例 Excel 的完整解决方案

## 目录

- [项目概述](#项目概述)
- [功能特性](#功能特性)
- [快速开始](#快速开始)
- [详细使用说明](#详细使用说明)
- [测试设计方法](#测试设计方法)
- [常见问题](#常见问题)
- [更新日志](#更新日志)

## 项目概述

Wiki2Doc 是一个完整的自动化测试工具集，可以从 Confluence Wiki 页面自动提取需求内容，分析需求质量，生成结构化测试用例，并输出为专业的 Excel 格式。

### 核心价值

- **自动化**：一键完成从Wiki到Excel的完整转换
- **智能化**：自动检测需求遗漏点、矛盾点和不明确点
- **专业化**：支持多种测试设计方法（边界值、场景法、错误推测等）
- **模块化**：各工具可独立使用，也可组合使用
- **标准化**：输出标准化的测试用例格式

### 工作流程

```
Wiki URL
    ↓
[步骤1] 提取内容 → Word文档 (.docx)
    ↓
[步骤2] 需求分析 → 分析报告 (.txt/.json)
    ↓
[步骤3] 生成用例 → Markdown文件 (.md)
    ↓
[步骤4] 格式转换 → Excel文件 (.xlsx)
```

## 功能特性

### ✅ Wiki内容提取
- 自动登录 Confluence（支持账号密码）
- 提取页面标题和完整内容
- 支持文本、图片、表格、代码块等多种格式
- 过滤 CSS 隐藏元素
- 图片自动转换为 JPG
- 保持原始文档顺序

### ✅ 需求分析
- **遗漏点检测**：识别缺失的测试要点、数据校验、UI规格等
- **矛盾点检测**：发现数值矛盾、逻辑矛盾、描述矛盾
- **不明确点检测**：识别模糊描述、缩写不明确等
- **不完整点检测**：发现句子不完整、列表缺失等
- 生成详细的分析报告

### ✅ 测试用例生成
支持多种测试设计方法：

1. **正向测试**：基于功能特性的正常流程测试
2. **边界值测试**：基于数值边界的测试
3. **错误推测**：基于经验的异常场景测试
4. **场景法**：完整的业务流程测试
5. **UI测试**：界面显示和适配测试
6. **兼容性测试**：多环境兼容测试
7. **性能测试**：性能相关测试

### ✅ 格式转换
- Markdown → Excel 自动转换
- 专业的表格格式化
- 自适应列宽和行高
- 表头样式优化
- 自动换行和边框

## 快速开始

### 环境准备

```bash
# 1. 进入项目目录
cd ~/.claude/skills/wiki2doc

# 2. 创建虚拟环境（推荐）
python -m venv .venv
source .venv/bin/activate  # Linux/Mac
# 或
.venv\Scripts\activate  # Windows

# 3. 安装依赖
pip install -r requirements.txt

# 4. 安装 Playwright 浏览器（首次使用）
playwright install chromium
```

### 一键生成测试用例

```bash
# 完整自动化流程
/skill wiki2doc --auto http://10.225.1.76:8090/pages/viewpage.action?pageId=12345678
```

该命令将自动完成所有步骤，最终在 `demand/` 目录生成：
- Word文档
- 需求分析报告
- 测试用例Markdown
- 测试用例Excel

## 详细使用说明

### 模式一：完整自动化流程

**推荐使用**，适合快速生成测试用例。

```bash
/skill wiki2doc --auto <Wiki_URL>
```

**示例：**
```bash
/skill wiki2doc --auto http://10.225.1.76:8090/pages/viewpage.action?pageId=34556052
```

**执行过程：**
1. 提取Wiki内容并生成Word文档
2. 分析需求并生成分析报告
3. 自动生成测试用例（Markdown格式）
4. 转换为Excel格式

**输出文件：**
- `需求标题.docx` - Word文档
- `需求标题_analysis_report.txt` - 分析报告
- `需求标题_testcases.md` - 测试用例Markdown
- `需求标题_testcases.xlsx` - 测试用例Excel

### 模式二：独立工具使用

#### 1. Wiki内容提取

```bash
# 单个页面
python bin/wiki2doc.py http://10.225.1.76:8090/pages/viewpage.action?pageId=12345678

# 批量处理
python bin/wiki2doc.py --batch "url1,url2,url3"
```

#### 2. 需求分析

```bash
python bin/analyze/analyze_requirements.py demand/需求文档.docx
```

**输出：**
- JSON格式报告（机器可读）
- TXT格式报告（人类可读）

**分析内容：**
- 遗漏点：缺少测试要点、数据校验等
- 矛盾点：数值矛盾、逻辑矛盾
- 不明确点：模糊描述、缩写不明确
- 不完整点：句子不完整、列表缺失

#### 3. 测试用例生成

```bash
python bin/generate/generate_testcases.py demand/需求文档.docx
```

**输出：**
- Markdown格式测试用例
- JSON格式测试用例

**生成策略：**
- 自动提取功能特性
- 应用多种测试设计方法
- 生成结构化测试用例
- 包含完整的测试信息

#### 4. Markdown转Excel

```bash
python bin/convert/md2excel.py demand/测试用例.md
```

**输出：**
- 格式化的Excel文件
- 专业的表格样式
- 自适应布局

**Excel格式：**
- 用例编号
- 用例模块
- 用例标题
- 前置条件
- 测试步骤
- 优先级
- 预期结果

## 测试设计方法

### 1. 正向测试（功能测试）
- 验证功能正常工作
- 测试核心业务流程
- 验证界面显示正确

### 2. 边界值测试
- 最小值、最小值-1
- 最大值、最大值+1
- 空值、特殊值

### 3. 错误推测法
- 网络异常
- 权限不足
- 数据异常
- 并发冲突
- 资源耗尽

### 4. 场景法
- 基本流程
- 备选流程
- 异常流程
- 端到端流程

### 5. UI测试
- 界面显示
- 样式正确性
- 布局适配
- 响应式设计

### 6. 兼容性测试
- 浏览器兼容
- 分辨率适配
- 系统版本兼容
- 设备兼容

### 7. 性能测试
- 页面加载性能
- 并发性能
- 大数据量性能
- 资源消耗

## 配置说明

### Confluence配置

默认配置在 `bin/wiki2doc.py` 中：

```python
BASE_URL = "http://10.225.1.76:8090"
USERNAME = "yanghua"
[REDACTED_SECRET]"
```

**修改方式：**

编辑 `bin/wiki2doc.py` 文件，修改相关配置。

**环境变量方式（推荐）：**

```bash
export CONFLUENCE_URL="http://your-wiki-url"
export CONFLUENCE_USERNAME="your-username"
export CONFLUENCE_PASSWORD="your-password"
```

### 输出目录配置

默认输出目录：`~/.claude/skills/wiki2doc/demand/`

可通过环境变量修改：
```bash
export OUTPUT_DIR="/path/to/output"
```

## 文件结构

```
wiki2doc/
├── bin/                          # 工具脚本目录
│   ├── wiki2doc.py              # Wiki提取工具
│   ├── wiki2testcases.py        # 完整工作流脚本
│   ├── analyze/                 # 需求分析模块
│   │   └── analyze_requirements.py
│   ├── generate/                # 测试用例生成模块
│   │   └── generate_testcases.py
│   └── convert/                 # 格式转换模块
│       └── md2excel.py
├── demand/                       # 输出目录
├── .venv/                       # 虚拟环境
├── requirements.txt             # 依赖列表
├── skill.md                     # 技能说明
└── README.md                    # 本文档
```

## 常见问题

### 1. 登录失败

**问题：** 无法登录Confluence

**解决方案：**
- 检查网络连接
- 确认账号密码正确
- 检查Confluence地址是否正确
- 尝试手动登录验证账号状态

### 2. 页面加载超时

**问题：** 提示"Timeout waiting for selector"

**解决方案：**
- 检查网络速度
- 增加超时时间（修改脚本中的timeout参数）
- 检查页面是否存在指定元素

### 3. 图片下载失败

**问题：** 部分图片未下载成功

**解决方案：**
- 检查图片链接是否有效
- 确认有权限访问图片
- 部分图片失败不影响其他内容

### 4. 生成的测试用例不完整

**问题：** 测试用例数量较少或不够详细

**解决方案：**
- 确保需求文档内容充分
- 手动补充特殊场景的测试用例
- 调整生成策略参数

### 5. Excel格式问题

**问题：** Excel显示异常

**解决方案：**
- 使用Microsoft Excel 2016及以上版本
- 检查文件是否完整下载
- 尝试使用WPS或其他办公软件打开

### 6. 编码问题

**问题：** 中文显示乱码

**解决方案：**
- 确保文件编码为UTF-8
- 在Excel中导入时选择UTF-8编码
- 使用记事本打开MD文件验证编码

## 最佳实践

### 1. 需求文档质量
- 确保Wiki页面结构清晰
- 使用标题和列表组织内容
- 包含详细的功能描述
- 明确测试要点

### 2. 测试用例优化
- 自动生成后进行人工审核
- 补充业务特定的测试场景
- 调整优先级排序
- 删除冗余测试用例

### 3. 团队协作
- 将生成的Excel导入测试管理系统
- 定期更新需求文档
- 建立测试用例评审机制
- 保存历史版本

### 4. 持续改进
- 收集测试反馈
- 优化生成策略
- 补充新的测试方法
- 完善测试模板

## 更新日志

### v2.0.0 (2026-03-06)
- ✨ 新增需求分析功能
- ✨ 新增测试用例自动生成
- ✨ 新增Markdown转Excel工具
- ✨ 新增完整自动化工作流
- 🎨 重构为模块化架构
- 📝 完善文档和使用说明

### v1.0.0 (2026-02-28)
- ✅ Wiki内容提取功能
- ✅ Word文档生成
- ✅ 图片处理和转换
- ✅ 批量处理支持

## 贡献指南

欢迎贡献代码、报告问题或提出建议！

### 开发环境设置
```bash
git clone <repo-url>
cd wiki2doc
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

### 提交代码
1. Fork 项目
2. 创建特性分支
3. 提交更改
4. 推送到分支
5. 创建 Pull Request

## 许可证

本项目仅供内部使用。

## 联系方式

- 项目维护者：Claude Code
- 技术支持：通过Issue反馈问题
- 功能建议：通过Issue提交建议

---

**Made with ❤️ by Claude Code**


path: requirements.txt
content: playwright>=1.43.0
beautifulsoup4>=4.12.3
python-docx>=1.0.0
requests>=2.31.0
Pillow>=10.3.0
lxml>=5.2.1
pandas>=2.0.0
openpyxl>=3.1.0


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)