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.
- kdtest_pw-2.0.0/LICENSE +21 -0
- kdtest_pw-2.0.0/MANIFEST.in +17 -0
- kdtest_pw-2.0.0/PKG-INFO +169 -0
- kdtest_pw-2.0.0/README.md +130 -0
- kdtest_pw-2.0.0/kdtest_pw/__init__.py +50 -0
- kdtest_pw-2.0.0/kdtest_pw/action/__init__.py +7 -0
- kdtest_pw-2.0.0/kdtest_pw/action/base_keyword.py +292 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/__init__.py +23 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_cascader.py +263 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_datepicker.py +324 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_dialog.py +317 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_form.py +443 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_menu.py +456 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_select.py +268 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_table.py +442 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_tree.py +364 -0
- kdtest_pw-2.0.0/kdtest_pw/action/element_plus/el_upload.py +313 -0
- kdtest_pw-2.0.0/kdtest_pw/action/key_retrieval.py +311 -0
- kdtest_pw-2.0.0/kdtest_pw/action/page_action.py +1129 -0
- kdtest_pw-2.0.0/kdtest_pw/api/__init__.py +6 -0
- kdtest_pw-2.0.0/kdtest_pw/api/api_keyword.py +251 -0
- kdtest_pw-2.0.0/kdtest_pw/api/request_handler.py +232 -0
- kdtest_pw-2.0.0/kdtest_pw/cases/__init__.py +6 -0
- kdtest_pw-2.0.0/kdtest_pw/cases/case_collector.py +182 -0
- kdtest_pw-2.0.0/kdtest_pw/cases/case_executor.py +359 -0
- kdtest_pw-2.0.0/kdtest_pw/cases/read/__init__.py +6 -0
- kdtest_pw-2.0.0/kdtest_pw/cases/read/cell_handler.py +305 -0
- kdtest_pw-2.0.0/kdtest_pw/cases/read/excel_reader.py +223 -0
- kdtest_pw-2.0.0/kdtest_pw/cli/__init__.py +5 -0
- kdtest_pw-2.0.0/kdtest_pw/cli/run.py +318 -0
- kdtest_pw-2.0.0/kdtest_pw/common.py +106 -0
- kdtest_pw-2.0.0/kdtest_pw/core/__init__.py +7 -0
- kdtest_pw-2.0.0/kdtest_pw/core/browser_manager.py +196 -0
- kdtest_pw-2.0.0/kdtest_pw/core/config_loader.py +235 -0
- kdtest_pw-2.0.0/kdtest_pw/core/page_context.py +228 -0
- kdtest_pw-2.0.0/kdtest_pw/data/__init__.py +5 -0
- kdtest_pw-2.0.0/kdtest_pw/data/init_data.py +105 -0
- kdtest_pw-2.0.0/kdtest_pw/data/static/elementData.yaml +59 -0
- kdtest_pw-2.0.0/kdtest_pw/data/static/parameters.json +24 -0
- kdtest_pw-2.0.0/kdtest_pw/plugins/__init__.py +6 -0
- kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/__init__.py +5 -0
- kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/elementData/elementData.yaml +144 -0
- kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/element_plus_plugin.py +237 -0
- kdtest_pw-2.0.0/kdtest_pw/plugins/element_plus_plugin/my.ini +23 -0
- kdtest_pw-2.0.0/kdtest_pw/plugins/plugin_base.py +180 -0
- kdtest_pw-2.0.0/kdtest_pw/plugins/plugin_loader.py +260 -0
- kdtest_pw-2.0.0/kdtest_pw/product.py +5 -0
- kdtest_pw-2.0.0/kdtest_pw/reference.py +99 -0
- kdtest_pw-2.0.0/kdtest_pw/utils/__init__.py +13 -0
- kdtest_pw-2.0.0/kdtest_pw/utils/built_in_function.py +376 -0
- kdtest_pw-2.0.0/kdtest_pw/utils/decorator.py +211 -0
- kdtest_pw-2.0.0/kdtest_pw/utils/log/__init__.py +6 -0
- kdtest_pw-2.0.0/kdtest_pw/utils/log/html_report.py +336 -0
- kdtest_pw-2.0.0/kdtest_pw/utils/log/logger.py +123 -0
- kdtest_pw-2.0.0/kdtest_pw/utils/public_script.py +366 -0
- kdtest_pw-2.0.0/kdtest_pw.egg-info/PKG-INFO +169 -0
- kdtest_pw-2.0.0/kdtest_pw.egg-info/SOURCES.txt +62 -0
- kdtest_pw-2.0.0/kdtest_pw.egg-info/dependency_links.txt +1 -0
- kdtest_pw-2.0.0/kdtest_pw.egg-info/entry_points.txt +2 -0
- kdtest_pw-2.0.0/kdtest_pw.egg-info/requires.txt +9 -0
- kdtest_pw-2.0.0/kdtest_pw.egg-info/top_level.txt +1 -0
- kdtest_pw-2.0.0/pyproject.toml +63 -0
- kdtest_pw-2.0.0/setup.cfg +4 -0
- kdtest_pw-2.0.0/setup.py +66 -0
kdtest_pw-2.0.0/LICENSE
ADDED
|
@@ -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*
|
kdtest_pw-2.0.0/PKG-INFO
ADDED
|
@@ -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,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)
|