kdtest-pw 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. kdtest_pw/__init__.py +50 -0
  2. kdtest_pw/action/__init__.py +7 -0
  3. kdtest_pw/action/base_keyword.py +292 -0
  4. kdtest_pw/action/element_plus/__init__.py +23 -0
  5. kdtest_pw/action/element_plus/el_cascader.py +263 -0
  6. kdtest_pw/action/element_plus/el_datepicker.py +324 -0
  7. kdtest_pw/action/element_plus/el_dialog.py +317 -0
  8. kdtest_pw/action/element_plus/el_form.py +443 -0
  9. kdtest_pw/action/element_plus/el_menu.py +456 -0
  10. kdtest_pw/action/element_plus/el_select.py +268 -0
  11. kdtest_pw/action/element_plus/el_table.py +442 -0
  12. kdtest_pw/action/element_plus/el_tree.py +364 -0
  13. kdtest_pw/action/element_plus/el_upload.py +313 -0
  14. kdtest_pw/action/key_retrieval.py +311 -0
  15. kdtest_pw/action/page_action.py +1129 -0
  16. kdtest_pw/api/__init__.py +6 -0
  17. kdtest_pw/api/api_keyword.py +251 -0
  18. kdtest_pw/api/request_handler.py +232 -0
  19. kdtest_pw/cases/__init__.py +6 -0
  20. kdtest_pw/cases/case_collector.py +182 -0
  21. kdtest_pw/cases/case_executor.py +359 -0
  22. kdtest_pw/cases/read/__init__.py +6 -0
  23. kdtest_pw/cases/read/cell_handler.py +305 -0
  24. kdtest_pw/cases/read/excel_reader.py +223 -0
  25. kdtest_pw/cli/__init__.py +5 -0
  26. kdtest_pw/cli/run.py +318 -0
  27. kdtest_pw/common.py +106 -0
  28. kdtest_pw/core/__init__.py +7 -0
  29. kdtest_pw/core/browser_manager.py +196 -0
  30. kdtest_pw/core/config_loader.py +235 -0
  31. kdtest_pw/core/page_context.py +228 -0
  32. kdtest_pw/data/__init__.py +5 -0
  33. kdtest_pw/data/init_data.py +105 -0
  34. kdtest_pw/data/static/elementData.yaml +59 -0
  35. kdtest_pw/data/static/parameters.json +24 -0
  36. kdtest_pw/plugins/__init__.py +6 -0
  37. kdtest_pw/plugins/element_plus_plugin/__init__.py +5 -0
  38. kdtest_pw/plugins/element_plus_plugin/elementData/elementData.yaml +144 -0
  39. kdtest_pw/plugins/element_plus_plugin/element_plus_plugin.py +237 -0
  40. kdtest_pw/plugins/element_plus_plugin/my.ini +23 -0
  41. kdtest_pw/plugins/plugin_base.py +180 -0
  42. kdtest_pw/plugins/plugin_loader.py +260 -0
  43. kdtest_pw/product.py +5 -0
  44. kdtest_pw/reference.py +99 -0
  45. kdtest_pw/utils/__init__.py +13 -0
  46. kdtest_pw/utils/built_in_function.py +376 -0
  47. kdtest_pw/utils/decorator.py +211 -0
  48. kdtest_pw/utils/log/__init__.py +6 -0
  49. kdtest_pw/utils/log/html_report.py +336 -0
  50. kdtest_pw/utils/log/logger.py +123 -0
  51. kdtest_pw/utils/public_script.py +366 -0
  52. kdtest_pw-2.0.0.dist-info/METADATA +169 -0
  53. kdtest_pw-2.0.0.dist-info/RECORD +57 -0
  54. kdtest_pw-2.0.0.dist-info/WHEEL +5 -0
  55. kdtest_pw-2.0.0.dist-info/entry_points.txt +2 -0
  56. kdtest_pw-2.0.0.dist-info/licenses/LICENSE +21 -0
  57. kdtest_pw-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,223 @@
1
+ """Excel 测试用例解析器"""
2
+
3
+ from typing import List, Dict, Any, Optional, Tuple
4
+ from pathlib import Path
5
+
6
+ from ...reference import INFO
7
+ from ...common import EXCEL_COLUMNS
8
+
9
+
10
+ class ExcelReader:
11
+ """Excel 测试用例读取器
12
+
13
+ 兼容原 kdtest Excel 格式,解析测试用例文件。
14
+ """
15
+
16
+ def __init__(self, file_path: str):
17
+ """初始化 Excel 读取器
18
+
19
+ Args:
20
+ file_path: Excel 文件路径
21
+ """
22
+ self._file_path = Path(file_path)
23
+ self._workbook = None
24
+ self._sheets: List[str] = []
25
+
26
+ if not self._file_path.exists():
27
+ raise FileNotFoundError(f"Excel 文件不存在: {file_path}")
28
+
29
+ self._load_workbook()
30
+
31
+ def _load_workbook(self) -> None:
32
+ """加载工作簿"""
33
+ try:
34
+ import openpyxl
35
+ self._workbook = openpyxl.load_workbook(str(self._file_path), data_only=True)
36
+ self._sheets = self._workbook.sheetnames
37
+ INFO(f"加载 Excel: {self._file_path.name}, Sheets: {self._sheets}")
38
+ except ImportError:
39
+ raise ImportError("请安装 openpyxl: pip install openpyxl")
40
+
41
+ @property
42
+ def sheet_names(self) -> List[str]:
43
+ """获取所有 Sheet 名称"""
44
+ return self._sheets.copy()
45
+
46
+ def read_sheet(self, sheet_name: str) -> List[Dict[str, Any]]:
47
+ """读取指定 Sheet 的测试用例
48
+
49
+ Args:
50
+ sheet_name: Sheet 名称
51
+
52
+ Returns:
53
+ List[Dict]: 测试用例列表
54
+ """
55
+ if sheet_name not in self._sheets:
56
+ raise ValueError(f"Sheet 不存在: {sheet_name}")
57
+
58
+ sheet = self._workbook[sheet_name]
59
+ cases = []
60
+ current_case = None
61
+ steps = []
62
+
63
+ for row_idx, row in enumerate(sheet.iter_rows(min_row=1, values_only=True), start=1):
64
+ # 跳过空行
65
+ if not any(row):
66
+ continue
67
+
68
+ # 获取各列值
69
+ case_id = self._get_cell_value(row, 0)
70
+ case_name = self._get_cell_value(row, 1)
71
+ description = self._get_cell_value(row, 2)
72
+ keyword = self._get_cell_value(row, 3)
73
+
74
+ # 获取定位信息 (E-N 列,索引 4-13)
75
+ locator_info = []
76
+ for i in range(4, 14):
77
+ val = self._get_cell_value(row, i)
78
+ if val:
79
+ locator_info.append(val)
80
+
81
+ # 获取操作值 (O 列,索引 14)
82
+ value = self._get_cell_value(row, 14)
83
+
84
+ # 判断是否是用例开始行
85
+ if case_id or case_name:
86
+ # 保存上一个用例
87
+ if current_case and steps:
88
+ current_case['steps'] = steps
89
+ cases.append(current_case)
90
+
91
+ # 开始新用例
92
+ current_case = {
93
+ 'id': case_id or f"case_{len(cases) + 1}",
94
+ 'name': case_name or f"用例{len(cases) + 1}",
95
+ 'sheet': sheet_name,
96
+ 'row': row_idx,
97
+ 'steps': []
98
+ }
99
+ steps = []
100
+
101
+ # 如果当前行有关键字,也作为第一个步骤
102
+ if keyword:
103
+ steps.append({
104
+ 'row': row_idx,
105
+ 'description': description,
106
+ 'keyword': keyword,
107
+ 'locator': locator_info,
108
+ 'value': value
109
+ })
110
+ elif keyword:
111
+ # 步骤行
112
+ steps.append({
113
+ 'row': row_idx,
114
+ 'description': description,
115
+ 'keyword': keyword,
116
+ 'locator': locator_info,
117
+ 'value': value
118
+ })
119
+
120
+ # 保存最后一个用例
121
+ if current_case and steps:
122
+ current_case['steps'] = steps
123
+ cases.append(current_case)
124
+
125
+ INFO(f"读取 {sheet_name}: {len(cases)} 个用例")
126
+ return cases
127
+
128
+ def read_all_sheets(self, include_sheets: List[str] = None) -> Dict[str, List[Dict[str, Any]]]:
129
+ """读取所有或指定 Sheet 的测试用例
130
+
131
+ Args:
132
+ include_sheets: 要包含的 Sheet 列表,None 表示全部
133
+
134
+ Returns:
135
+ Dict[sheet_name, cases]: Sheet 名称到用例列表的映射
136
+ """
137
+ result = {}
138
+ sheets_to_read = include_sheets or self._sheets
139
+
140
+ for sheet_name in sheets_to_read:
141
+ if sheet_name in self._sheets:
142
+ result[sheet_name] = self.read_sheet(sheet_name)
143
+
144
+ return result
145
+
146
+ def _get_cell_value(self, row: tuple, index: int) -> Optional[str]:
147
+ """获取单元格值
148
+
149
+ Args:
150
+ row: 行数据
151
+ index: 列索引
152
+
153
+ Returns:
154
+ 单元格值(字符串)
155
+ """
156
+ if index >= len(row):
157
+ return None
158
+
159
+ value = row[index]
160
+ if value is None:
161
+ return None
162
+
163
+ return str(value).strip()
164
+
165
+ def get_case_count(self, sheet_name: str = None) -> int:
166
+ """获取用例数量
167
+
168
+ Args:
169
+ sheet_name: Sheet 名称,None 表示全部
170
+
171
+ Returns:
172
+ int: 用例数量
173
+ """
174
+ if sheet_name:
175
+ return len(self.read_sheet(sheet_name))
176
+
177
+ total = 0
178
+ for sheet in self._sheets:
179
+ total += len(self.read_sheet(sheet))
180
+ return total
181
+
182
+ def close(self) -> None:
183
+ """关闭工作簿"""
184
+ if self._workbook:
185
+ self._workbook.close()
186
+ self._workbook = None
187
+
188
+ def __enter__(self) -> 'ExcelReader':
189
+ return self
190
+
191
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
192
+ self.close()
193
+
194
+
195
+ class TestCase:
196
+ """测试用例数据类"""
197
+
198
+ def __init__(self, data: Dict[str, Any]):
199
+ self.id = data.get('id', '')
200
+ self.name = data.get('name', '')
201
+ self.sheet = data.get('sheet', '')
202
+ self.row = data.get('row', 0)
203
+ self.steps: List['TestStep'] = []
204
+
205
+ for step_data in data.get('steps', []):
206
+ self.steps.append(TestStep(step_data))
207
+
208
+ def __repr__(self) -> str:
209
+ return f"TestCase(id={self.id}, name={self.name}, steps={len(self.steps)})"
210
+
211
+
212
+ class TestStep:
213
+ """测试步骤数据类"""
214
+
215
+ def __init__(self, data: Dict[str, Any]):
216
+ self.row = data.get('row', 0)
217
+ self.description = data.get('description', '')
218
+ self.keyword = data.get('keyword', '')
219
+ self.locator = data.get('locator', [])
220
+ self.value = data.get('value')
221
+
222
+ def __repr__(self) -> str:
223
+ return f"TestStep(keyword={self.keyword}, value={self.value})"
@@ -0,0 +1,5 @@
1
+ """CLI 模块"""
2
+
3
+ from .run import main, KDTestRunner
4
+
5
+ __all__ = ['main', 'KDTestRunner']
kdtest_pw/cli/run.py ADDED
@@ -0,0 +1,318 @@
1
+ """CLI 入口 - 命令行运行测试"""
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Optional, List
7
+
8
+ from ..product import __version__
9
+ from ..core.browser_manager import BrowserManager
10
+ from ..core.config_loader import ConfigLoader
11
+ from ..action.key_retrieval import KeyRetrieval
12
+ from ..cases.case_collector import CaseCollector
13
+ from ..cases.case_executor import CaseExecutor
14
+ from ..cases.read.cell_handler import CellHandler
15
+ from ..plugins.plugin_loader import PluginLoader
16
+ from ..reference import GSTORE, GSDSTORE, INFO
17
+ from ..utils.log.html_report import HtmlReporter
18
+
19
+
20
+ class KDTestRunner:
21
+ """KDTest Playwright 测试运行器"""
22
+
23
+ def __init__(self, config_path: Optional[str] = None):
24
+ """初始化运行器
25
+
26
+ Args:
27
+ config_path: 配置文件路径
28
+ """
29
+ self._config_loader: Optional[ConfigLoader] = None
30
+ self._browser_manager: Optional[BrowserManager] = None
31
+ self._key_retrieval: Optional[KeyRetrieval] = None
32
+ self._plugin_loader: Optional[PluginLoader] = None
33
+ self._case_collector: Optional[CaseCollector] = None
34
+ self._case_executor: Optional[CaseExecutor] = None
35
+ self._reporter: Optional[HtmlReporter] = None
36
+
37
+ if config_path:
38
+ self.load_config(config_path)
39
+
40
+ def load_config(self, config_path: str) -> None:
41
+ """加载配置文件
42
+
43
+ Args:
44
+ config_path: 配置文件路径
45
+ """
46
+ self._config_loader = ConfigLoader(config_path)
47
+ INFO(f"配置已加载: {config_path}")
48
+
49
+ def setup(self) -> None:
50
+ """初始化测试环境"""
51
+ if not self._config_loader:
52
+ raise RuntimeError("请先加载配置文件")
53
+
54
+ config = self._config_loader.config
55
+
56
+ # 初始化浏览器管理器
57
+ self._browser_manager = BrowserManager()
58
+
59
+ # 启动浏览器
60
+ page = self._browser_manager.launch(
61
+ browser_type=self._config_loader.browser,
62
+ headless=self._config_loader.headless,
63
+ slow_mo=self._config_loader.slow_mo,
64
+ trace=self._config_loader.trace,
65
+ video=self._config_loader.video,
66
+ timeout=self._config_loader.timeout,
67
+ )
68
+
69
+ # 初始化关键字分发器
70
+ self._key_retrieval = KeyRetrieval(page)
71
+
72
+ # 初始化插件加载器
73
+ self._plugin_loader = PluginLoader(page)
74
+ self._plugin_loader.load_builtin_plugins()
75
+
76
+ # 合并插件关键字和元素数据
77
+ self._key_retrieval.register_plugin_keywords(
78
+ self._plugin_loader.get_all_keywords()
79
+ )
80
+ self._key_retrieval.set_element_data(
81
+ self._plugin_loader.get_all_element_data()
82
+ )
83
+
84
+ # 初始化单元格处理器
85
+ cell_handler = CellHandler(
86
+ self._config_loader.self_defined_parameters
87
+ )
88
+
89
+ # 初始化用例执行器
90
+ self._case_executor = CaseExecutor(
91
+ key_retrieval=self._key_retrieval,
92
+ cell_handler=cell_handler,
93
+ retry_count=self._config_loader.retry_count if self._config_loader.retry_switch else 0,
94
+ screenshot_on_fail=True,
95
+ )
96
+
97
+ # 初始化用例收集器
98
+ self._case_collector = CaseCollector(config)
99
+
100
+ # 初始化报告生成器
101
+ self._reporter = HtmlReporter()
102
+
103
+ # 导航到起始 URL
104
+ if self._config_loader.url:
105
+ page.goto(self._config_loader.url)
106
+
107
+ INFO("测试环境初始化完成")
108
+
109
+ def run(
110
+ self,
111
+ include_sheets: List[str] = None,
112
+ exclude_sheets: List[str] = None,
113
+ case_ids: List[str] = None,
114
+ case_pattern: str = None,
115
+ ) -> dict:
116
+ """运行测试
117
+
118
+ Args:
119
+ include_sheets: 只执行这些 Sheet
120
+ exclude_sheets: 排除这些 Sheet
121
+ case_ids: 只执行这些用例 ID
122
+ case_pattern: 用例名称匹配模式
123
+
124
+ Returns:
125
+ dict: 执行摘要
126
+ """
127
+ # 收集用例
128
+ all_cases = self._case_collector.collect()
129
+
130
+ # 过滤用例
131
+ cases = self._case_collector.filter_cases(
132
+ include_sheets=include_sheets,
133
+ exclude_sheets=exclude_sheets,
134
+ include_ids=case_ids,
135
+ name_pattern=case_pattern,
136
+ )
137
+
138
+ INFO(f"过滤后用例数: {len(cases)}")
139
+
140
+ # 执行用例
141
+ results = self._case_executor.execute_cases(cases)
142
+
143
+ # 生成报告
144
+ if self._reporter:
145
+ report_path = self._reporter.generate(results)
146
+ INFO(f"测试报告: {report_path}")
147
+
148
+ return self._case_executor.summary
149
+
150
+ def teardown(self, save_trace: str = None) -> None:
151
+ """清理测试环境
152
+
153
+ Args:
154
+ save_trace: trace 文件保存路径
155
+ """
156
+ # 卸载插件
157
+ if self._plugin_loader:
158
+ self._plugin_loader.unload_all()
159
+
160
+ # 关闭浏览器
161
+ if self._browser_manager:
162
+ trace_path = save_trace or (
163
+ 'result/trace/trace.zip' if self._config_loader and self._config_loader.trace else None
164
+ )
165
+ self._browser_manager.close(save_trace=trace_path)
166
+
167
+ INFO("测试环境已清理")
168
+
169
+ def __enter__(self) -> 'KDTestRunner':
170
+ self.setup()
171
+ return self
172
+
173
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
174
+ self.teardown()
175
+
176
+
177
+ def parse_args() -> argparse.Namespace:
178
+ """解析命令行参数"""
179
+ parser = argparse.ArgumentParser(
180
+ prog='kdtest-pw',
181
+ description='KDTest Playwright - 关键字驱动测试框架',
182
+ )
183
+
184
+ parser.add_argument(
185
+ '-v', '--version',
186
+ action='version',
187
+ version=f'kdtest-pw {__version__}'
188
+ )
189
+
190
+ parser.add_argument(
191
+ '-c', '--config',
192
+ type=str,
193
+ default='data/parameters.json',
194
+ help='配置文件路径 (默认: data/parameters.json)'
195
+ )
196
+
197
+ parser.add_argument(
198
+ '-s', '--sheets',
199
+ type=str,
200
+ nargs='+',
201
+ help='只执行指定的 Sheet'
202
+ )
203
+
204
+ parser.add_argument(
205
+ '-x', '--exclude',
206
+ type=str,
207
+ nargs='+',
208
+ help='排除指定的 Sheet'
209
+ )
210
+
211
+ parser.add_argument(
212
+ '-i', '--ids',
213
+ type=str,
214
+ nargs='+',
215
+ help='只执行指定的用例 ID'
216
+ )
217
+
218
+ parser.add_argument(
219
+ '-p', '--pattern',
220
+ type=str,
221
+ help='用例名称匹配模式 (支持通配符)'
222
+ )
223
+
224
+ parser.add_argument(
225
+ '--browser',
226
+ type=str,
227
+ choices=['chromium', 'firefox', 'webkit'],
228
+ help='覆盖配置中的浏览器类型'
229
+ )
230
+
231
+ parser.add_argument(
232
+ '--headless',
233
+ action='store_true',
234
+ help='无头模式运行'
235
+ )
236
+
237
+ parser.add_argument(
238
+ '--trace',
239
+ action='store_true',
240
+ help='开启 trace 录制'
241
+ )
242
+
243
+ parser.add_argument(
244
+ '--video',
245
+ action='store_true',
246
+ help='开启视频录制'
247
+ )
248
+
249
+ parser.add_argument(
250
+ '--slow-mo',
251
+ type=int,
252
+ help='操作延迟 (毫秒)'
253
+ )
254
+
255
+ return parser.parse_args()
256
+
257
+
258
+ def main() -> int:
259
+ """主入口函数"""
260
+ args = parse_args()
261
+
262
+ # 检查配置文件
263
+ config_path = Path(args.config)
264
+ if not config_path.exists():
265
+ print(f"错误: 配置文件不存在: {config_path}")
266
+ return 1
267
+
268
+ runner = KDTestRunner(str(config_path))
269
+
270
+ # 覆盖配置
271
+ if args.browser:
272
+ runner._config_loader.set('browser', args.browser)
273
+ if args.headless:
274
+ runner._config_loader.set('headless', True)
275
+ if args.trace:
276
+ runner._config_loader.set('trace', True)
277
+ if args.video:
278
+ runner._config_loader.set('video', True)
279
+ if args.slow_mo:
280
+ runner._config_loader.set('slowMo', args.slow_mo)
281
+
282
+ try:
283
+ runner.setup()
284
+ summary = runner.run(
285
+ include_sheets=args.sheets,
286
+ exclude_sheets=args.exclude,
287
+ case_ids=args.ids,
288
+ case_pattern=args.pattern,
289
+ )
290
+
291
+ # 输出摘要
292
+ print("\n" + "=" * 50)
293
+ print("测试执行摘要")
294
+ print("=" * 50)
295
+ print(f"总用例数: {summary['total']}")
296
+ print(f"通过: {summary['passed']}")
297
+ print(f"失败: {summary['failed']}")
298
+ print(f"跳过: {summary['skipped']}")
299
+ print(f"通过率: {summary['pass_rate']:.1f}%")
300
+ print(f"执行时间: {summary['duration']:.2f}s")
301
+ print("=" * 50)
302
+
303
+ return 0 if summary['failed'] == 0 else 1
304
+
305
+ except KeyboardInterrupt:
306
+ print("\n测试被中断")
307
+ return 130
308
+ except Exception as e:
309
+ print(f"执行错误: {e}")
310
+ import traceback
311
+ traceback.print_exc()
312
+ return 1
313
+ finally:
314
+ runner.teardown()
315
+
316
+
317
+ if __name__ == '__main__':
318
+ sys.exit(main())
kdtest_pw/common.py ADDED
@@ -0,0 +1,106 @@
1
+ """常量和数据存储模块"""
2
+
3
+ from typing import Dict, Any, List
4
+ from pathlib import Path
5
+
6
+ # 项目根目录
7
+ PROJECT_ROOT = Path(__file__).parent
8
+
9
+ # 默认配置
10
+ DEFAULT_CONFIG: Dict[str, Any] = {
11
+ 'browser': 'chromium',
12
+ 'headless': False,
13
+ 'slowMo': 0,
14
+ 'timeout': 30000,
15
+ 'trace': False,
16
+ 'video': False,
17
+ 'retrySwitch': False,
18
+ 'retryCount': 1,
19
+ 'parallel': False,
20
+ }
21
+
22
+ # 支持的浏览器类型
23
+ BROWSER_TYPES: List[str] = ['chromium', 'firefox', 'webkit']
24
+
25
+ # 定位器类型映射
26
+ LOCATOR_TYPES: Dict[str, str] = {
27
+ 'id': 'id',
28
+ 'name': 'name',
29
+ 'class': 'class_name',
30
+ 'class_name': 'class_name',
31
+ 'css': 'css',
32
+ 'xpath': 'xpath',
33
+ 'text': 'text',
34
+ 'role': 'role',
35
+ 'data_testid': 'data_testid',
36
+ 'placeholder': 'placeholder',
37
+ 'link_text': 'text',
38
+ 'partial_link_text': 'text',
39
+ }
40
+
41
+ # 键盘键映射 (Selenium Keys -> Playwright)
42
+ KEY_MAP: Dict[str, str] = {
43
+ 'Keys.ENTER': 'Enter',
44
+ 'Keys.TAB': 'Tab',
45
+ 'Keys.BACK_SPACE': 'Backspace',
46
+ 'Keys.BACKSPACE': 'Backspace',
47
+ 'Keys.ESCAPE': 'Escape',
48
+ 'Keys.DELETE': 'Delete',
49
+ 'Keys.ARROW_UP': 'ArrowUp',
50
+ 'Keys.ARROW_DOWN': 'ArrowDown',
51
+ 'Keys.ARROW_LEFT': 'ArrowLeft',
52
+ 'Keys.ARROW_RIGHT': 'ArrowRight',
53
+ 'Keys.HOME': 'Home',
54
+ 'Keys.END': 'End',
55
+ 'Keys.PAGE_UP': 'PageUp',
56
+ 'Keys.PAGE_DOWN': 'PageDown',
57
+ 'Keys.SPACE': 'Space',
58
+ 'Keys.CONTROL': 'Control',
59
+ 'Keys.ALT': 'Alt',
60
+ 'Keys.SHIFT': 'Shift',
61
+ 'Keys.F1': 'F1',
62
+ 'Keys.F2': 'F2',
63
+ 'Keys.F3': 'F3',
64
+ 'Keys.F4': 'F4',
65
+ 'Keys.F5': 'F5',
66
+ 'Keys.F6': 'F6',
67
+ 'Keys.F7': 'F7',
68
+ 'Keys.F8': 'F8',
69
+ 'Keys.F9': 'F9',
70
+ 'Keys.F10': 'F10',
71
+ 'Keys.F11': 'F11',
72
+ 'Keys.F12': 'F12',
73
+ # 组合键
74
+ 'Keys.CONTROL+a': 'Control+a',
75
+ 'Keys.CONTROL+c': 'Control+c',
76
+ 'Keys.CONTROL+v': 'Control+v',
77
+ 'Keys.CONTROL+x': 'Control+x',
78
+ 'Keys.CONTROL+z': 'Control+z',
79
+ 'Keys.CONTROL+s': 'Control+s',
80
+ }
81
+
82
+ # 断言模式
83
+ ASSERT_MODES: List[str] = ['equals', 'contains', 'regex', 'gt', 'lt', 'gte', 'lte']
84
+
85
+ # 元素等待状态
86
+ WAIT_STATES: List[str] = ['visible', 'hidden', 'attached', 'detached']
87
+
88
+ # 结果目录
89
+ RESULT_DIRS: Dict[str, str] = {
90
+ 'report': 'result/report',
91
+ 'log': 'result/log',
92
+ 'trace': 'result/trace',
93
+ 'video': 'result/video',
94
+ 'screenshot': 'result/screenshot',
95
+ }
96
+
97
+ # Excel列映射
98
+ EXCEL_COLUMNS: Dict[str, int] = {
99
+ 'case_id': 0, # A列 - 用例ID
100
+ 'case_name': 1, # B列 - 用例名称
101
+ 'description': 2, # C列 - 步骤描述
102
+ 'keyword': 3, # D列 - 关键字
103
+ 'locator_start': 4, # E列开始 - 定位信息
104
+ 'locator_end': 13, # N列结束 - 定位信息
105
+ 'value': 14, # O列 - 操作值
106
+ }
@@ -0,0 +1,7 @@
1
+ """核心模块"""
2
+
3
+ from .browser_manager import BrowserManager
4
+ from .config_loader import ConfigLoader
5
+ from .page_context import PageContext
6
+
7
+ __all__ = ['BrowserManager', 'ConfigLoader', 'PageContext']