kdtest-pw 2.0.0__tar.gz

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 (64) hide show
  1. kdtest_pw-2.0.0/LICENSE +21 -0
  2. kdtest_pw-2.0.0/MANIFEST.in +17 -0
  3. kdtest_pw-2.0.0/PKG-INFO +169 -0
  4. kdtest_pw-2.0.0/README.md +130 -0
  5. kdtest_pw-2.0.0/kdtest_pw/__init__.py +50 -0
  6. kdtest_pw-2.0.0/kdtest_pw/action/__init__.py +7 -0
  7. kdtest_pw-2.0.0/kdtest_pw/action/base_keyword.py +292 -0
  8. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/__init__.py +23 -0
  9. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_cascader.py +263 -0
  10. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_datepicker.py +324 -0
  11. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_dialog.py +317 -0
  12. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_form.py +443 -0
  13. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_menu.py +456 -0
  14. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_select.py +268 -0
  15. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_table.py +442 -0
  16. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_tree.py +364 -0
  17. kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_upload.py +313 -0
  18. kdtest_pw-2.0.0/kdtest_pw/action/key_retrieval.py +311 -0
  19. kdtest_pw-2.0.0/kdtest_pw/action/page_action.py +1129 -0
  20. kdtest_pw-2.0.0/kdtest_pw/api/__init__.py +6 -0
  21. kdtest_pw-2.0.0/kdtest_pw/api/api_keyword.py +251 -0
  22. kdtest_pw-2.0.0/kdtest_pw/api/request_handler.py +232 -0
  23. kdtest_pw-2.0.0/kdtest_pw/cases/__init__.py +6 -0
  24. kdtest_pw-2.0.0/kdtest_pw/cases/case_collector.py +182 -0
  25. kdtest_pw-2.0.0/kdtest_pw/cases/case_executor.py +359 -0
  26. kdtest_pw-2.0.0/kdtest_pw/cases/read/__init__.py +6 -0
  27. kdtest_pw-2.0.0/kdtest_pw/cases/read/cell_handler.py +305 -0
  28. kdtest_pw-2.0.0/kdtest_pw/cases/read/excel_reader.py +223 -0
  29. kdtest_pw-2.0.0/kdtest_pw/cli/__init__.py +5 -0
  30. kdtest_pw-2.0.0/kdtest_pw/cli/run.py +318 -0
  31. kdtest_pw-2.0.0/kdtest_pw/common.py +106 -0
  32. kdtest_pw-2.0.0/kdtest_pw/core/__init__.py +7 -0
  33. kdtest_pw-2.0.0/kdtest_pw/core/browser_manager.py +196 -0
  34. kdtest_pw-2.0.0/kdtest_pw/core/config_loader.py +235 -0
  35. kdtest_pw-2.0.0/kdtest_pw/core/page_context.py +228 -0
  36. kdtest_pw-2.0.0/kdtest_pw/data/__init__.py +5 -0
  37. kdtest_pw-2.0.0/kdtest_pw/data/init_data.py +105 -0
  38. kdtest_pw-2.0.0/kdtest_pw/data/static/elementData.yaml +59 -0
  39. kdtest_pw-2.0.0/kdtest_pw/data/static/parameters.json +24 -0
  40. kdtest_pw-2.0.0/kdtest_pw/plugins/__init__.py +6 -0
  41. kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/__init__.py +5 -0
  42. kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/elementData/elementData.yaml +144 -0
  43. kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/element_plus_plugin.py +237 -0
  44. kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/my.ini +23 -0
  45. kdtest_pw-2.0.0/kdtest_pw/plugins/plugin_base.py +180 -0
  46. kdtest_pw-2.0.0/kdtest_pw/plugins/plugin_loader.py +260 -0
  47. kdtest_pw-2.0.0/kdtest_pw/product.py +5 -0
  48. kdtest_pw-2.0.0/kdtest_pw/reference.py +99 -0
  49. kdtest_pw-2.0.0/kdtest_pw/utils/__init__.py +13 -0
  50. kdtest_pw-2.0.0/kdtest_pw/utils/built_in_function.py +376 -0
  51. kdtest_pw-2.0.0/kdtest_pw/utils/decorator.py +211 -0
  52. kdtest_pw-2.0.0/kdtest_pw/utils/log/__init__.py +6 -0
  53. kdtest_pw-2.0.0/kdtest_pw/utils/log/html_report.py +336 -0
  54. kdtest_pw-2.0.0/kdtest_pw/utils/log/logger.py +123 -0
  55. kdtest_pw-2.0.0/kdtest_pw/utils/public_script.py +366 -0
  56. kdtest_pw-2.0.0/kdtest_pw.egg-info/PKG-INFO +169 -0
  57. kdtest_pw-2.0.0/kdtest_pw.egg-info/SOURCES.txt +62 -0
  58. kdtest_pw-2.0.0/kdtest_pw.egg-info/dependency_links.txt +1 -0
  59. kdtest_pw-2.0.0/kdtest_pw.egg-info/entry_points.txt +2 -0
  60. kdtest_pw-2.0.0/kdtest_pw.egg-info/requires.txt +9 -0
  61. kdtest_pw-2.0.0/kdtest_pw.egg-info/top_level.txt +1 -0
  62. kdtest_pw-2.0.0/pyproject.toml +63 -0
  63. kdtest_pw-2.0.0/setup.cfg +4 -0
  64. kdtest_pw-2.0.0/setup.py +66 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 KDTest Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ # 包含配置文件
2
+ include kdtest_pw/data/static/*.json
3
+ include kdtest_pw/data/static/*.yaml
4
+ include kdtest_pw/data/static/*.xlsx
5
+
6
+ # 包含插件配置
7
+ include kdtest_pw/plugins/element_plus_plugin/*.ini
8
+ include kdtest_pw/plugins/element_plus_plugin/elementData/*.yaml
9
+
10
+ # 包含文档
11
+ include README.md
12
+ include LICENSE
13
+
14
+ # 排除缓存
15
+ global-exclude __pycache__
16
+ global-exclude *.py[cod]
17
+ global-exclude .git*
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: kdtest-pw
3
+ Version: 2.0.0
4
+ Summary: KDTest Playwright Edition - 关键字驱动测试框架,专为 Element Plus/Vue3 优化
5
+ Home-page: https://github.com/kdtest/kdtest-pw
6
+ Author: KDTest Team
7
+ Author-email: KDTest Team <kdtest@example.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/kdtest/kdtest-pw
10
+ Project-URL: Documentation, https://github.com/kdtest/kdtest-pw#readme
11
+ Project-URL: Repository, https://github.com/kdtest/kdtest-pw
12
+ Keywords: testing,automation,playwright,keyword-driven,element-plus,vue3
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: playwright>=1.40.0
28
+ Requires-Dist: openpyxl>=3.1.0
29
+ Requires-Dist: PyYAML>=6.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
32
+ Requires-Dist: pytest-playwright>=0.4.0; extra == "dev"
33
+ Requires-Dist: build; extra == "dev"
34
+ Requires-Dist: twine; extra == "dev"
35
+ Dynamic: author
36
+ Dynamic: home-page
37
+ Dynamic: license-file
38
+ Dynamic: requires-python
39
+
40
+ # KDTest-Playwright
41
+
42
+ 基于 Playwright 的关键字驱动测试框架,专为 Element Plus/Vue3 优化。
43
+
44
+ ## 安装
45
+
46
+ ```bash
47
+ # 从 PyPI 安装
48
+ pip install kdtest-pw
49
+
50
+ # 安装 Playwright 浏览器
51
+ playwright install chromium
52
+ ```
53
+
54
+ ## 快速开始
55
+
56
+ ### 1. 创建配置文件 `parameters.json`
57
+
58
+ ```json
59
+ {
60
+ "testCaseFile": [
61
+ {
62
+ "caseFilePath": "testCases.xlsx",
63
+ "caseItem": ["Sheet1"]
64
+ }
65
+ ],
66
+ "browser": "chromium",
67
+ "url": "http://localhost:8080",
68
+ "headless": false,
69
+ "timeout": 30000
70
+ }
71
+ ```
72
+
73
+ ### 2. 创建 Excel 测试用例
74
+
75
+ | 用例ID | 用例名称 | 描述 | 关键字 | 定位信息 | 操作值 |
76
+ |--------|----------|------|--------|----------|--------|
77
+ | TC001 | 登录测试 | | | | |
78
+ | | | 打开网站 | driver_get | | http://localhost |
79
+ | | | 输入用户名 | input_text | xpath | //input[@placeholder='账号'] | admin |
80
+ | | | 点击登录 | click_btn | css | .el-button--primary | |
81
+
82
+ ### 3. 运行测试
83
+
84
+ ```bash
85
+ # 命令行运行
86
+ kdtest-pw -c parameters.json
87
+
88
+ # 或在 Python 中使用
89
+ from kdtest_pw.cli import KDTestRunner
90
+
91
+ runner = KDTestRunner("parameters.json")
92
+ runner.setup()
93
+ runner.run()
94
+ runner.teardown()
95
+ ```
96
+
97
+ ## 核心关键字
98
+
99
+ ### 浏览器操作
100
+ - `driver_get` - 导航到 URL
101
+ - `driver_back` - 后退
102
+ - `driver_refresh` - 刷新
103
+ - `driver_close` - 关闭页面
104
+
105
+ ### 输入操作
106
+ - `input_text` - 输入文本(清除后输入)
107
+ - `input_append` - 追加输入
108
+ - `input_clear` - 清除输入
109
+
110
+ ### 点击操作
111
+ - `click_btn` - 点击元素
112
+ - `double_click` - 双击
113
+ - `right_click` - 右键点击
114
+ - `hover` - 鼠标悬停
115
+
116
+ ### 断言
117
+ - `text_assert` - 文本断言
118
+ - `title_assert` - 标题断言
119
+ - `element_visible_assert` - 可见性断言
120
+ - `element_count_assert` - 数量断言
121
+
122
+ ### Element Plus 组件
123
+ - `el_select` - 下拉选择
124
+ - `el_datepicker` - 日期选择
125
+ - `el_dialog_confirm` - 对话框确认
126
+ - `el_table_click_row` - 表格行点击
127
+ - `el_tree_click_node` - 树节点点击
128
+ - `el_cascader` - 级联选择
129
+ - `el_upload` - 文件上传
130
+ - `el_form_input` - 表单输入
131
+
132
+ ### 菜单导航
133
+ - `menu_load_data` - 加载菜单配置
134
+ - `menu_navigate` - 导航到菜单
135
+ - `menu_switch_iframe` - 切换 iframe
136
+ - `menu_exit_iframe` - 退出 iframe
137
+
138
+ ## 变量和内置函数
139
+
140
+ ### 变量引用
141
+ ```
142
+ ${varName} # 从缓存获取
143
+ ${self.param} # 自定义参数
144
+ ${env.NAME} # 环境变量
145
+ ```
146
+
147
+ ### 内置函数
148
+ ```
149
+ @today() # 今天日期
150
+ @now() # 当前时间
151
+ @random(1,100) # 随机数
152
+ @random_phone() # 随机手机号
153
+ @uuid() # UUID
154
+ ```
155
+
156
+ ## 与 kdtest (Selenium) 的区别
157
+
158
+ | 特性 | kdtest (Selenium) | kdtest-pw (Playwright) |
159
+ |------|-------------------|------------------------|
160
+ | 浏览器驱动 | 需要下载 WebDriver | 内置浏览器 |
161
+ | 等待机制 | 显式/隐式等待 | 自动等待 |
162
+ | Element Plus | 需要额外处理 | 原生支持 |
163
+ | 多标签页 | 需要切换句柄 | 原生支持 |
164
+ | 录制回放 | 无 | 支持 Trace |
165
+ | 视频录制 | 无 | 原生支持 |
166
+
167
+ ## License
168
+
169
+ MIT
@@ -0,0 +1,130 @@
1
+ # KDTest-Playwright
2
+
3
+ 基于 Playwright 的关键字驱动测试框架,专为 Element Plus/Vue3 优化。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ # 从 PyPI 安装
9
+ pip install kdtest-pw
10
+
11
+ # 安装 Playwright 浏览器
12
+ playwright install chromium
13
+ ```
14
+
15
+ ## 快速开始
16
+
17
+ ### 1. 创建配置文件 `parameters.json`
18
+
19
+ ```json
20
+ {
21
+ "testCaseFile": [
22
+ {
23
+ "caseFilePath": "testCases.xlsx",
24
+ "caseItem": ["Sheet1"]
25
+ }
26
+ ],
27
+ "browser": "chromium",
28
+ "url": "http://localhost:8080",
29
+ "headless": false,
30
+ "timeout": 30000
31
+ }
32
+ ```
33
+
34
+ ### 2. 创建 Excel 测试用例
35
+
36
+ | 用例ID | 用例名称 | 描述 | 关键字 | 定位信息 | 操作值 |
37
+ |--------|----------|------|--------|----------|--------|
38
+ | TC001 | 登录测试 | | | | |
39
+ | | | 打开网站 | driver_get | | http://localhost |
40
+ | | | 输入用户名 | input_text | xpath | //input[@placeholder='账号'] | admin |
41
+ | | | 点击登录 | click_btn | css | .el-button--primary | |
42
+
43
+ ### 3. 运行测试
44
+
45
+ ```bash
46
+ # 命令行运行
47
+ kdtest-pw -c parameters.json
48
+
49
+ # 或在 Python 中使用
50
+ from kdtest_pw.cli import KDTestRunner
51
+
52
+ runner = KDTestRunner("parameters.json")
53
+ runner.setup()
54
+ runner.run()
55
+ runner.teardown()
56
+ ```
57
+
58
+ ## 核心关键字
59
+
60
+ ### 浏览器操作
61
+ - `driver_get` - 导航到 URL
62
+ - `driver_back` - 后退
63
+ - `driver_refresh` - 刷新
64
+ - `driver_close` - 关闭页面
65
+
66
+ ### 输入操作
67
+ - `input_text` - 输入文本(清除后输入)
68
+ - `input_append` - 追加输入
69
+ - `input_clear` - 清除输入
70
+
71
+ ### 点击操作
72
+ - `click_btn` - 点击元素
73
+ - `double_click` - 双击
74
+ - `right_click` - 右键点击
75
+ - `hover` - 鼠标悬停
76
+
77
+ ### 断言
78
+ - `text_assert` - 文本断言
79
+ - `title_assert` - 标题断言
80
+ - `element_visible_assert` - 可见性断言
81
+ - `element_count_assert` - 数量断言
82
+
83
+ ### Element Plus 组件
84
+ - `el_select` - 下拉选择
85
+ - `el_datepicker` - 日期选择
86
+ - `el_dialog_confirm` - 对话框确认
87
+ - `el_table_click_row` - 表格行点击
88
+ - `el_tree_click_node` - 树节点点击
89
+ - `el_cascader` - 级联选择
90
+ - `el_upload` - 文件上传
91
+ - `el_form_input` - 表单输入
92
+
93
+ ### 菜单导航
94
+ - `menu_load_data` - 加载菜单配置
95
+ - `menu_navigate` - 导航到菜单
96
+ - `menu_switch_iframe` - 切换 iframe
97
+ - `menu_exit_iframe` - 退出 iframe
98
+
99
+ ## 变量和内置函数
100
+
101
+ ### 变量引用
102
+ ```
103
+ ${varName} # 从缓存获取
104
+ ${self.param} # 自定义参数
105
+ ${env.NAME} # 环境变量
106
+ ```
107
+
108
+ ### 内置函数
109
+ ```
110
+ @today() # 今天日期
111
+ @now() # 当前时间
112
+ @random(1,100) # 随机数
113
+ @random_phone() # 随机手机号
114
+ @uuid() # UUID
115
+ ```
116
+
117
+ ## 与 kdtest (Selenium) 的区别
118
+
119
+ | 特性 | kdtest (Selenium) | kdtest-pw (Playwright) |
120
+ |------|-------------------|------------------------|
121
+ | 浏览器驱动 | 需要下载 WebDriver | 内置浏览器 |
122
+ | 等待机制 | 显式/隐式等待 | 自动等待 |
123
+ | Element Plus | 需要额外处理 | 原生支持 |
124
+ | 多标签页 | 需要切换句柄 | 原生支持 |
125
+ | 录制回放 | 无 | 支持 Trace |
126
+ | 视频录制 | 无 | 原生支持 |
127
+
128
+ ## License
129
+
130
+ MIT
@@ -0,0 +1,50 @@
1
+ """KDTest-Playwright 关键字驱动测试框架
2
+
3
+ 基于 Playwright 的关键字驱动测试框架,专为 Element Plus/Vue3 优化。
4
+ """
5
+
6
+ from .product import __version__, __author__, __description__
7
+ from .reference import (
8
+ GSTORE,
9
+ GSDSTORE,
10
+ MODULEDATA,
11
+ PRIVATEDATA,
12
+ CASESDATA,
13
+ INFO,
14
+ get_global,
15
+ set_global,
16
+ get_element_value,
17
+ set_element_value,
18
+ clear_element_values,
19
+ )
20
+ from .common import (
21
+ DEFAULT_CONFIG,
22
+ BROWSER_TYPES,
23
+ LOCATOR_TYPES,
24
+ KEY_MAP,
25
+ )
26
+
27
+ __all__ = [
28
+ # 版本信息
29
+ '__version__',
30
+ '__author__',
31
+ '__description__',
32
+ # 全局存储
33
+ 'GSTORE',
34
+ 'GSDSTORE',
35
+ 'MODULEDATA',
36
+ 'PRIVATEDATA',
37
+ 'CASESDATA',
38
+ # 工具函数
39
+ 'INFO',
40
+ 'get_global',
41
+ 'set_global',
42
+ 'get_element_value',
43
+ 'set_element_value',
44
+ 'clear_element_values',
45
+ # 常量
46
+ 'DEFAULT_CONFIG',
47
+ 'BROWSER_TYPES',
48
+ 'LOCATOR_TYPES',
49
+ 'KEY_MAP',
50
+ ]
@@ -0,0 +1,7 @@
1
+ """动作模块 - 关键字实现"""
2
+
3
+ from .base_keyword import BaseKeyword
4
+ from .page_action import KeyWordTest
5
+ from .key_retrieval import KeyRetrieval
6
+
7
+ __all__ = ['BaseKeyword', 'KeyWordTest', 'KeyRetrieval']
@@ -0,0 +1,292 @@
1
+ """基础关键字类 - 元素定位和通用操作"""
2
+
3
+ from playwright.sync_api import Page, Locator, FrameLocator
4
+ from typing import Optional, Union, Literal, List
5
+
6
+ from ..common import LOCATOR_TYPES
7
+ from ..reference import INFO
8
+
9
+ LocatorType = Literal[
10
+ 'id', 'name', 'class_name', 'class', 'css', 'xpath',
11
+ 'text', 'role', 'data_testid', 'placeholder', 'link_text', 'partial_link_text'
12
+ ]
13
+
14
+
15
+ class BaseKeyword:
16
+ """基础关键字类
17
+
18
+ 提供元素定位和通用操作方法,是所有关键字类的基类。
19
+ """
20
+
21
+ def __init__(self, page: Page):
22
+ """初始化基础关键字
23
+
24
+ Args:
25
+ page: Playwright Page 实例
26
+ """
27
+ self.page = page
28
+ self._current_frame: Optional[FrameLocator] = None
29
+ self._frame_stack: List[FrameLocator] = []
30
+
31
+ def locator(
32
+ self,
33
+ targeting: LocatorType,
34
+ element: str,
35
+ index: Optional[Union[int, str]] = None,
36
+ parent: Optional[Locator] = None
37
+ ) -> Locator:
38
+ """统一元素定位方法
39
+
40
+ Args:
41
+ targeting: 定位类型 (id, name, class_name, css, xpath, text, role, data_testid, placeholder)
42
+ element: 定位表达式
43
+ index: 索引 (数字、'first'、'last'),None 表示不使用索引
44
+ parent: 父元素定位器,用于链式定位
45
+
46
+ Returns:
47
+ Locator: Playwright Locator 实例
48
+ """
49
+ # 确定基础定位上下文
50
+ base = parent or self._current_frame or self.page
51
+
52
+ # 标准化定位类型
53
+ targeting = LOCATOR_TYPES.get(targeting, targeting)
54
+
55
+ # 根据定位类型创建 Locator
56
+ locator_map = {
57
+ 'id': lambda: base.locator(f'#{element}'),
58
+ 'name': lambda: base.locator(f'[name="{element}"]'),
59
+ 'class_name': lambda: base.locator(f'.{element}'),
60
+ 'css': lambda: base.locator(element),
61
+ 'xpath': lambda: base.locator(f'xpath={element}'),
62
+ 'text': lambda: base.get_by_text(element, exact=False),
63
+ 'role': lambda: base.get_by_role(element),
64
+ 'data_testid': lambda: base.get_by_test_id(element),
65
+ 'placeholder': lambda: base.get_by_placeholder(element),
66
+ }
67
+
68
+ loc = locator_map.get(targeting, lambda: base.locator(element))()
69
+
70
+ # 处理索引
71
+ if index is not None:
72
+ if index == 'first' or index == 0:
73
+ loc = loc.first
74
+ elif index == 'last' or index == -1:
75
+ loc = loc.last
76
+ elif isinstance(index, int):
77
+ loc = loc.nth(index)
78
+ elif isinstance(index, str) and index.isdigit():
79
+ loc = loc.nth(int(index))
80
+
81
+ return loc
82
+
83
+ def locator_by_expression(self, expression: str, index: Optional[Union[int, str]] = None) -> Locator:
84
+ """通过表达式定位元素
85
+
86
+ 支持格式:
87
+ - "xpath=//div" 或 "xpath://div"
88
+ - "css=.class" 或 "css:.class"
89
+ - "text=文本"
90
+ - "id=elementId"
91
+ - 纯 CSS 选择器
92
+
93
+ Args:
94
+ expression: 定位表达式
95
+ index: 索引
96
+
97
+ Returns:
98
+ Locator: Playwright Locator 实例
99
+ """
100
+ base = self._current_frame or self.page
101
+
102
+ # 解析表达式
103
+ if '=' in expression:
104
+ parts = expression.split('=', 1)
105
+ prefix = parts[0].lower().rstrip(':')
106
+ value = parts[1]
107
+
108
+ if prefix == 'xpath':
109
+ loc = base.locator(f'xpath={value}')
110
+ elif prefix == 'css':
111
+ loc = base.locator(value)
112
+ elif prefix == 'text':
113
+ loc = base.get_by_text(value)
114
+ elif prefix == 'id':
115
+ loc = base.locator(f'#{value}')
116
+ elif prefix == 'name':
117
+ loc = base.locator(f'[name="{value}"]')
118
+ elif prefix == 'class':
119
+ loc = base.locator(f'.{value}')
120
+ else:
121
+ loc = base.locator(expression)
122
+ else:
123
+ # 纯 CSS 选择器
124
+ loc = base.locator(expression)
125
+
126
+ # 处理索引
127
+ if index is not None:
128
+ if index == 'first' or index == 0:
129
+ loc = loc.first
130
+ elif index == 'last' or index == -1:
131
+ loc = loc.last
132
+ elif isinstance(index, int):
133
+ loc = loc.nth(index)
134
+
135
+ return loc
136
+
137
+ def switch_frame(self, targeting: str, element: str) -> None:
138
+ """切换到 iframe
139
+
140
+ Args:
141
+ targeting: 定位类型
142
+ element: 定位表达式
143
+ """
144
+ if targeting == 'xpath':
145
+ frame_locator = self.page.frame_locator(f'xpath={element}')
146
+ elif targeting in ('css', 'selector'):
147
+ frame_locator = self.page.frame_locator(element)
148
+ elif targeting == 'name':
149
+ frame_locator = self.page.frame_locator(f'[name="{element}"]')
150
+ elif targeting == 'id':
151
+ frame_locator = self.page.frame_locator(f'#{element}')
152
+ else:
153
+ frame_locator = self.page.frame_locator(element)
154
+
155
+ self._frame_stack.append(self._current_frame)
156
+ self._current_frame = frame_locator
157
+ INFO(f"切换到 iframe: {element}")
158
+
159
+ def switch_frame_parent(self) -> None:
160
+ """切换到父级 frame"""
161
+ if self._frame_stack:
162
+ self._current_frame = self._frame_stack.pop()
163
+ else:
164
+ self._current_frame = None
165
+ INFO("切换到父级 frame")
166
+
167
+ def frame_default(self) -> None:
168
+ """返回主文档"""
169
+ self._current_frame = None
170
+ self._frame_stack.clear()
171
+ INFO("切换到主文档")
172
+
173
+ def wait_for_loading(self, timeout: int = 30000) -> None:
174
+ """等待页面加载完成
175
+
176
+ Args:
177
+ timeout: 超时时间 (毫秒)
178
+ """
179
+ self.page.wait_for_load_state('networkidle', timeout=timeout)
180
+
181
+ def wait_for_dom_ready(self, timeout: int = 30000) -> None:
182
+ """等待 DOM 加载完成
183
+
184
+ Args:
185
+ timeout: 超时时间 (毫秒)
186
+ """
187
+ self.page.wait_for_load_state('domcontentloaded', timeout=timeout)
188
+
189
+ def wait_for_element_plus_loading(self, timeout: int = 30000) -> None:
190
+ """等待 Element Plus 的 v-loading 消失
191
+
192
+ Args:
193
+ timeout: 超时时间 (毫秒)
194
+ """
195
+ loading = self.page.locator('.el-loading-mask')
196
+ try:
197
+ if loading.count() > 0:
198
+ loading.first.wait_for(state='hidden', timeout=timeout)
199
+ except Exception:
200
+ pass # loading 可能已经消失
201
+
202
+ def wait_for_element(
203
+ self,
204
+ targeting: str,
205
+ element: str,
206
+ state: str = 'visible',
207
+ timeout: int = 30000
208
+ ) -> None:
209
+ """等待元素状态
210
+
211
+ Args:
212
+ targeting: 定位类型
213
+ element: 定位表达式
214
+ state: 等待状态 (visible, hidden, attached, detached)
215
+ timeout: 超时时间 (毫秒)
216
+ """
217
+ loc = self.locator(targeting, element)
218
+ loc.wait_for(state=state, timeout=timeout)
219
+
220
+ def is_element_visible(self, targeting: str, element: str, index: Optional[int] = None) -> bool:
221
+ """检查元素是否可见
222
+
223
+ Args:
224
+ targeting: 定位类型
225
+ element: 定位表达式
226
+ index: 索引
227
+
228
+ Returns:
229
+ bool: 元素是否可见
230
+ """
231
+ loc = self.locator(targeting, element, index)
232
+ return loc.is_visible()
233
+
234
+ def is_element_enabled(self, targeting: str, element: str, index: Optional[int] = None) -> bool:
235
+ """检查元素是否可用
236
+
237
+ Args:
238
+ targeting: 定位类型
239
+ element: 定位表达式
240
+ index: 索引
241
+
242
+ Returns:
243
+ bool: 元素是否可用
244
+ """
245
+ loc = self.locator(targeting, element, index)
246
+ return loc.is_enabled()
247
+
248
+ def get_element_count(self, targeting: str, element: str) -> int:
249
+ """获取元素数量
250
+
251
+ Args:
252
+ targeting: 定位类型
253
+ element: 定位表达式
254
+
255
+ Returns:
256
+ int: 元素数量
257
+ """
258
+ loc = self.locator(targeting, element)
259
+ return loc.count()
260
+
261
+ def execute_script(self, script: str, *args) -> any:
262
+ """执行 JavaScript
263
+
264
+ Args:
265
+ script: JavaScript 代码
266
+ *args: 脚本参数
267
+
268
+ Returns:
269
+ 脚本执行结果
270
+ """
271
+ return self.page.evaluate(script, args if args else None)
272
+
273
+ def execute_script_on_element(
274
+ self,
275
+ targeting: str,
276
+ element: str,
277
+ script: str,
278
+ index: Optional[int] = None
279
+ ) -> any:
280
+ """在元素上执行 JavaScript
281
+
282
+ Args:
283
+ targeting: 定位类型
284
+ element: 定位表达式
285
+ script: JavaScript 代码 (使用 el 引用元素)
286
+ index: 索引
287
+
288
+ Returns:
289
+ 脚本执行结果
290
+ """
291
+ loc = self.locator(targeting, element, index)
292
+ return loc.evaluate(script)