diff --git a/conftest.py b/conftest.py index 3ede409..8ec07cc 100644 --- a/conftest.py +++ b/conftest.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: conftest @date: 2026/1/16 10:52 @desc: diff --git a/core/__init__.py b/core/__init__.py index 0f7f039..8d593dd 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: __init__.py @date: 2026/1/16 10:49 @desc: diff --git a/core/modules.py b/core/modules.py index 424bb70..7f0a602 100644 --- a/core/modules.py +++ b/core/modules.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: modules @date: 2026/1/20 11:54 @desc: diff --git a/core/run_appium.py b/core/run_appium.py index f6b1017..4c0467e 100644 --- a/core/run_appium.py +++ b/core/run_appium.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: test @date: 2026/1/12 10:21 @desc: diff --git a/core/settings.py b/core/settings.py index 7358861..b379e75 100644 --- a/core/settings.py +++ b/core/settings.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: settings @date: 2026/1/19 16:54 @desc: diff --git a/main.py b/main.py index 0a93281..e53d6c3 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,14 @@ +#!/usr/bin/env python +# coding=utf-8 + +""" +@author: CNWei,ChenWei +@Software: PyCharm +@contact: t6g888@163.com +@file: main +@date: 2026/1/13 16:54 +@desc: +""" import os import shutil import subprocess diff --git a/pyproject.toml b/pyproject.toml index 005b738..3d87057 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,8 @@ dependencies = [ "appium-python-client>=5.2.4", "loguru>=0.7.3", "pytest>=8.3.5", + "PyYAML>=6.0.1", + "openpyxl>=3.1.2", ] [[tool.uv.index]] diff --git a/test_cases/conftest.py b/test_cases/conftest.py index a51ec1d..1637105 100644 --- a/test_cases/conftest.py +++ b/test_cases/conftest.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: conftest @date: 2026/1/19 14:08 @desc: diff --git a/test_cases/test_keyword_sample.py b/test_cases/test_keyword_sample.py new file mode 100644 index 0000000..382a7a0 --- /dev/null +++ b/test_cases/test_keyword_sample.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# coding=utf-8 + +""" +@author: CNWei,ChenWei +@Software: PyCharm +@contact: t6g888@163.com +@file: test_keyword_sample +@date: 2026/1/23 17:48 +@desc: +""" + +import pytest +import logging +from core.driver import CoreDriver +from utils.data_loader import DataLoader +from core.settings import BASE_DIR + +logger = logging.getLogger(__name__) + +# 假设数据文件路径 +DATA_FILE = BASE_DIR / "test_cases" / "data" / "login_flow.xlsx" + + +# 或者 "login_flow.yaml" —— 代码不需要改动,只需要改文件名 + +class TestKeywordDriven: + + def run_step(self, driver: CoreDriver, step: dict): + """ + 核心执行引擎:反射调用 CoreDriver 的方法 + """ + action_name = step.get("action") + if not action_name: + return # 跳过无效行 + + # 1. 获取 CoreDriver 中对应的方法 + if not hasattr(driver, action_name): + raise ValueError(f"CoreDriver 中未定义方法: {action_name}") + + func = getattr(driver, action_name) + + # 2. 准备参数 + # 你的 CoreDriver 方法签名通常是 (by, value, [args], timeout) + # 我们从 step 字典中提取这些参数 + kwargs = {} + if "by" in step and step["by"]: kwargs["by"] = step["by"] + if "value" in step and step["value"]: kwargs["value"] = step["value"] + + # 处理特殊参数,比如 input 方法需要的 text,或者 assert_text 需要的 expected_text + # 这里做一个简单的映射,或者在 Excel 表头直接写对应参数名 + if "args" in step and step["args"]: + # 假设 input 的第三个参数是 text,这里简单处理,实际可根据 func.__code__.co_varnames 动态匹配 + if action_name == "input": + kwargs["text"] = str(step["args"]) # 确保 Excel 数字转字符串 + elif action_name == "assert_text": + kwargs["expected_text"] = str(step["args"]) + elif action_name == "explicit_wait": + # 支持你封装的 resolve_wait_method + # 此时 method 参数就是 args 列的内容,例如 "toast_visible:成功" + kwargs["method"] = step["args"] + + logger.info(f"执行步骤 [{step.get('desc', '无描述')}]: {action_name} {kwargs}") + + # 3. 执行调用 + func(**kwargs) + + def test_execute_from_file(self, driver): + """ + 主测试入口 + """ + # 1. 加载数据 (自动识别 Excel/YAML) + # 注意:实际使用时建议把 load 放在 pytest.mark.parametrize 里 + # 这里为了演示逻辑写在函数内 + if not DATA_FILE.exists(): + pytest.skip("数据文件不存在") + + steps = DataLoader.load(DATA_FILE) + + # 2. 遍历执行 + for step in steps: + self.run_step(driver, step) diff --git a/tests/test_logger.py b/tests/test_logger.py index f9a57e7..de8bfb5 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: test @date: 2026/1/14 10:12 @desc: diff --git a/utils/__init__.py b/utils/__init__.py index b27e721..b4bb252 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: __init__.py @date: 2026/1/16 09:06 @desc: diff --git a/utils/data_loader.py b/utils/data_loader.py new file mode 100644 index 0000000..bcc6165 --- /dev/null +++ b/utils/data_loader.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# coding=utf-8 + +""" +@author: CNWei,ChenWei +@Software: PyCharm +@contact: t6g888@163.com +@file: data_loader.py +@date: 2026/1/23 13:55 +@desc: +""" +# !/usr/bin/env python +# coding=utf-8 + +""" +@author: CNWei +@desc: 数据驱动加载器 (Adapter Pattern 实现) + 负责将 YAML, JSON, Excel 统一转换为 Python List[Dict] 格式 +""" +import json +import logging +from pathlib import Path +from typing import List, Dict, Any, Union + +import yaml + +# 尝试导入 openpyxl,如果未安装则在运行时报错提示 +try: + import openpyxl +except ImportError: + openpyxl = None + +logger = logging.getLogger(__name__) + + +class DataLoader: + """ + 数据加载适配器 + 统一输出格式: List[Dict] + """ + + @staticmethod + def load(file_path: Union[str, Path]) -> List[Dict[str, Any]]: + """ + 入口方法:根据文件后缀分发处理逻辑 + """ + path = Path(file_path) + if not path.exists(): + raise FileNotFoundError(f"测试数据文件未找到: {path}") + + suffix = path.suffix.lower() + + logger.info(f"正在加载测试数据: {path.name}") + + if suffix in ['.yaml', '.yml']: + return DataLoader._load_yaml(path) + elif suffix == '.json': + return DataLoader._load_json(path) + elif suffix in ['.xlsx', '.xls']: + return DataLoader._load_excel(path) + else: + raise ValueError(f"不支持的文件格式: {suffix}。仅支持 yaml, json, xlsx") + + @staticmethod + def _load_yaml(path: Path) -> List[Dict]: + with open(path, 'r', encoding='utf-8') as f: + # safe_load 防止代码注入风险 + data = yaml.safe_load(f) + # 归一化:如果根节点是字典,转为单元素列表;如果是列表则直接返回 + return data if isinstance(data, list) else [data] + + @staticmethod + def _load_json(path: Path) -> List[Dict]: + with open(path, 'r', encoding='utf-8') as f: + data = json.load(f) + return data if isinstance(data, list) else [data] + + @staticmethod + def _load_excel(path: Path) -> List[Dict]: + """ + Excel 解析规则: + 1. 第一行默认为表头 (Keys) + 2. 后续行为数据 (Values) + 3. 自动过滤全空行 + """ + if openpyxl is None: + raise ImportError("检测到 .xlsx 文件,但未安装 openpyxl。请执行: pip install openpyxl") + + wb = openpyxl.load_workbook(path, read_only=True, data_only=True) + # 默认读取第一个 Sheet + ws = wb.active + + # 获取所有行 + rows = list(ws.rows) + if not rows: + return [] + + # 解析表头 (第一行) + headers = [cell.value for cell in rows[0] if cell.value is not None] + + result = [] + # 解析数据 (从第二行开始) + for row in rows[1:]: + # 提取当前行的数据 + values = [cell.value for cell in row] + + # 如果整行都是 None,跳过 + if not any(values): + continue + + # 组装字典: {header: value} + row_dict = {} + for i, header in enumerate(headers): + # 防止越界 (有些行可能数据列数少于表头) + val = values[i] if i < len(values) else None + # 转换处理:Excel 的数字可能需要转为字符串,视业务需求而定 + # 这里保持原样,由后续逻辑处理类型 + row_dict[header] = val + + result.append(row_dict) + + wb.close() + return result + + +if __name__ == "__main__": + # 调试代码 + # 假设有一个 test.yaml + # print(DataLoader.load("test.yaml")) + pass \ No newline at end of file diff --git a/utils/finder.py b/utils/finder.py index e1e7cda..6bd16c5 100644 --- a/utils/finder.py +++ b/utils/finder.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: locator_utils @date: 2026/1/20 15:40 @desc: diff --git a/utils/logger.py b/utils/logger.py index 192a823..bc9b919 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -4,7 +4,7 @@ """ @author: CNWei,ChenWei @Software: PyCharm -@contact: t6g888@163.com,chenwei@zygj.com +@contact: t6g888@163.com @file: logger @date: 2026/1/15 11:30 @desc: