python自动化测试框架

import time
import datetime
from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from playwright.sync_api import sync_playwright, expect


# ========================= Word 报告生成模块 =========================
def set_cell_background(cell, fill_hex):
    """设置 Word 单元格背景颜色"""
    tcPr = cell._element.get_or_add_tcPr()
    shd = OxmlElement('w:shd')
    shd.set(qn('w:val'), 'clear')
    shd.set(qn('w:color'), 'auto')
    shd.set(qn('w:fill'), fill_hex)
    tcPr.append(shd)


def generate_word_report(results, report_path="自动化测试结果报告.docx"):
    """生成自动化测试报告 Word 文档"""
    doc = Document()

    # 1. 报告大标题
    title = doc.add_heading("XXXX Web UI 自动化测试报告", level=0)
    title.alignment = WD_ALIGN_PARAGRAPH.CENTER

    # 2. 统计数据
    total = len(results)
    passed = sum(1 for r in results if r["status"] == "PASS")
    failed = total - passed
    pass_rate = f"{(passed / total * 100):.1f}%" if total > 0 else "0%"

    # 3. 基本信息
    p_meta = doc.add_paragraph()
    p_meta.add_run(f"执行时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n").font.size = Pt(10)
    p_meta.add_run(f"测试环境:https://stageassp.XXXXX.com/asm/#/\n").font.size = Pt(10)
    p_meta.add_run(f"执行机制:单步异常捕获并自动跳过\n").font.size = Pt(10)

    # 4. 执行概览卡片
    doc.add_heading("一、 测试执行概览", level=1)
    summary_table = doc.add_table(rows=2, cols=4)
    summary_table.alignment = WD_TABLE_ALIGNMENT.CENTER

    headers = ["执行总数", "成功用例 (PASS)", "失败用例 (FAIL)", "测试通过率"]
    for i, h in enumerate(headers):
        cell = summary_table.cell(0, i)
        cell.text = h
        cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
        cell.paragraphs[0].runs[0].font.bold = True
        cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 255, 255)
        set_cell_background(cell, "1F4E79")

    values = [str(total), str(passed), str(failed), pass_rate]
    text_colors = ["333333", "2E7D32", "C62828" if failed > 0 else "333333", "1F4E79"]
    for i, v in enumerate(values):
        cell = summary_table.cell(1, i)
        cell.text = v
        cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
        run = cell.paragraphs[0].runs[0]
        run.font.bold = True
        run.font.size = Pt(13)
        r, g, b = int(text_colors[i][0:2], 16), int(text_colors[i][2:4], 16), int(text_colors[i][4:6], 16)
        run.font.color.rgb = RGBColor(r, g, b)
        set_cell_background(cell, "F2F4F7")

    doc.add_paragraph("")

    # 5. 详细明细表
    doc.add_heading("二、 用例执行明细", level=1)
    detail_table = doc.add_table(rows=1, cols=6)
    detail_table.alignment = WD_TABLE_ALIGNMENT.CENTER

    d_headers = ["序号", "测试步骤 / 用例名称", "预期结果", "实际执行结果", "耗时(秒)", "测试状态"]
    widths = [Inches(0.6), Inches(1.8), Inches(1.5), Inches(1.8), Inches(0.8), Inches(0.9)]

    for i, h in enumerate(d_headers):
        cell = detail_table.cell(0, i)
        cell.text = h
        cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
        cell.paragraphs[0].runs[0].font.bold = True
        cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 255, 255)
        set_cell_background(cell, "2E5B88")
        cell.width = widths[i]

    for idx, r in enumerate(results, start=1):
        row = detail_table.add_row()
        vals = [str(idx), r["name"], r["expect"], r["actual"], f"{r['duration']:.2f}", r["status"]]
        for col_idx, val in enumerate(vals):
            cell = row.cells[col_idx]
            cell.text = val
            cell.width = widths[col_idx]
            p = cell.paragraphs[0]
            if col_idx in [0, 4, 5]:
                p.alignment = WD_ALIGN_PARAGRAPH.CENTER

            # 状态列高亮
            if col_idx == 5:
                run = p.runs[0]
                run.font.bold = True
                if val == "PASS":
                    run.font.color.rgb = RGBColor(46, 125, 50)
                    set_cell_background(cell, "E8F5E9")
                else:
                    run.font.color.rgb = RGBColor(198, 40, 40)
                    set_cell_background(cell, "FFEBEE")

    doc.save(report_path)
    print(f"\n📄 [Word报告生成完毕] -> {report_path}")


# ========================= 自动化测试调度器 =========================
def run_automation_suite():
    results = []

    def record_step(page, name, expect_desc, func):
        """
        核心异常处理与用例记录:
        - 成功:记录为 PASS
        - 失败:捕获异常,记录为 FAIL,打印错误信息,【不中断程序,安全跳过继续执行下一步】
        """
        t0 = time.time()
        print(f"\n👉 正在执行:{name} ...")
        try:
            actual_msg = func(page) or "执行正常完成"
            duration = time.time() - t0
            results.append({
                "name": name,
                "expect": expect_desc,
                "actual": str(actual_msg),
                "duration": duration,
                "status": "PASS"
            })
            print(f"   ✅ [PASS] 耗时: {duration:.2f}s | {actual_msg}")
            return True
        except Exception as e:
            duration = time.time() - t0
            err_line = str(e).split("\n")[0][:120]  # 截取主要错误原因
            results.append({
                "name": name,
                "expect": expect_desc,
                "actual": f"执行失败: {err_line}",
                "duration": duration,
                "status": "FAIL"
            })
            print(f"   ❌ [FAIL] 耗时: {duration:.2f}s | 错误: {err_line}")
            print("   ⚠️  [已触发容错] 自动跳过当前用例,继续执行下一项...")

            # 页面状态容错恢复:按 Escape 键尝试关闭可能卡住的下拉菜单或遮罩层
            try:
                page.keyboard.press("Escape")
                page.wait_for_timeout(500)
            except Exception:
                pass
            return False

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False, slow_mo=200)
        page = browser.new_page()

        # ================= 各用例独立定义 =================
        def tc_01_login(pg):
            pg.goto("https://stageassp.xxxx.com/asm/#/")
            pg.wait_for_load_state("domcontentloaded")
            pg.get_by_placeholder("请输入账号名称").fill("xxxx")
            pg.get_by_placeholder("请输入您的密码").fill("xxxx")
            pg.get_by_role("button", name="登 录").click()
            expect(pg.get_by_text("登录成功")).to_be_visible(timeout=8000)
            pg.wait_for_load_state("networkidle")
            return "弹出【登录成功】提示,进入系统首页"

        def tc_02_nav_order_list(pg):
            pg.get_by_text("工单管理").click()
            pg.wait_for_timeout(500)
            pg.get_by_text("工单列表").click()
            pg.wait_for_load_state("networkidle")
            return "成功进入工单列表页面"



        # ================= 用例注册调度列表 =================
        # 💡 如果要添加新用例,直接在这下面添加一行元组即可!
        test_cases = [
            ("TC01 - 系统登录", "弹出登录成功并跳转主页", tc_01_login),
            ("TC02 - 导航到工单列表", "成功展开菜单并进入列表页", tc_02_nav_order_list),

            # 如需扩展:
            # ("TC05 - 新用例名称", "预期结果描述", 自定义用例函数名),
        ]

        # 逐个执行(失败跳过不中断)
        for name, expect_desc, func in test_cases:
            record_step(page, name, expect_desc, func)

        page.wait_for_timeout(2000)
        browser.close()

        # 生成 Word 报告
        generate_word_report(results, report_path="自动化测试结果报告.docx")


if __name__ == "__main__":
    run_automation_suite()

增加测试用例的方法

1.增加测试函数定义如:

   def tc_03_logout(pg):

2 在test case里增加用例定义

        test_cases = [
            ("TC01 - 系统登录", "弹出登录成功并跳转主页", tc_01_login),
            ("TC02 - 导航到工单列表", "成功展开菜单并进入列表页", tc_02_nav_order_list),

            # 如需扩展:
            # ("TC05 - 新用例名称", "预期结果描述", 自定义用例函数名),
posted @ 2026-09-01 17:37  金一九  阅读(5)  评论(0)    收藏  举报