pytest-dsl 0.3.0__tar.gz → 0.4.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.
- {pytest_dsl-0.3.0/pytest_dsl.egg-info → pytest_dsl-0.4.0}/PKG-INFO +29 -3
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/README.md +26 -1
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pyproject.toml +5 -4
- pytest_dsl-0.4.0/pytest_dsl/cli.py +143 -0
- pytest_dsl-0.4.0/pytest_dsl/keywords/system_keywords.py +343 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0/pytest_dsl.egg-info}/PKG-INFO +29 -3
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl.egg-info/requires.txt +1 -0
- pytest_dsl-0.3.0/pytest_dsl/cli.py +0 -80
- pytest_dsl-0.3.0/pytest_dsl/keywords/system_keywords.py +0 -17
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/LICENSE +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/MANIFEST.in +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/__init__.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/conftest_adapter.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/__init__.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/auth_provider.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/auto_decorator.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/auto_directory.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/context.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/custom_keyword_manager.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/dsl_executor.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/dsl_executor_utils.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/global_context.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/http_client.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/http_request.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/keyword_manager.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/lexer.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/parser.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/parsetab.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/plugin_discovery.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/utils.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/variable_utils.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/yaml_loader.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/core/yaml_vars.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/docs/custom_keywords.md +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/__init__.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/assert/assertion_example.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/assert/boolean_test.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/assert/expression_test.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/custom/test_advanced_keywords.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/custom/test_custom_keywords.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/custom/test_default_values.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/__init__.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/builtin_auth_test.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/file_reference_test.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/http_advanced.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/http_example.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/http_length_test.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/http_retry_assertions.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/http_with_yaml.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/new_retry_test.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/retry_assertions_only.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/retry_config_only.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/retry_debug.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/retry_with_fix.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/simple_retry.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/vars.yaml +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/quickstart/api_basics.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/quickstart/assertions.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/quickstart/loops.auto +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/test_assert.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/test_custom_keyword.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/test_http.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/test_quickstart.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/keywords/__init__.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/keywords/assertion_keywords.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/keywords/global_keywords.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/keywords/http_keywords.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/main_adapter.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/parsetab.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/plugin.py +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl.egg-info/SOURCES.txt +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl.egg-info/dependency_links.txt +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl.egg-info/entry_points.txt +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl.egg-info/top_level.txt +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/setup.cfg +0 -0
- {pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pytest-dsl
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: A DSL testing framework based on pytest
|
5
5
|
Author: Chen Shuanglin
|
6
6
|
License: MIT
|
@@ -8,13 +8,13 @@ Project-URL: Homepage, https://github.com/felix-1991/pytest-dsl
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/felix-1991/pytest-dsl/issues
|
9
9
|
Classifier: Framework :: Pytest
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
12
11
|
Classifier: Programming Language :: Python :: 3.9
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
16
16
|
Classifier: Operating System :: OS Independent
|
17
|
-
Requires-Python: >=3.
|
17
|
+
Requires-Python: >=3.9
|
18
18
|
Description-Content-Type: text/markdown
|
19
19
|
License-File: LICENSE
|
20
20
|
Requires-Dist: pytest>=7.0.0
|
@@ -26,6 +26,7 @@ Requires-Dist: jsonpath-ng>=1.5.0
|
|
26
26
|
Requires-Dist: requests>=2.28.0
|
27
27
|
Requires-Dist: lxml>=4.9.0
|
28
28
|
Requires-Dist: jsonschema>=4.17.0
|
29
|
+
Requires-Dist: pytz>=2023.3
|
29
30
|
Dynamic: license-file
|
30
31
|
|
31
32
|
# pytest-dsl: 强大的关键字驱动测试自动化框架
|
@@ -330,6 +331,31 @@ pytest test_api.py
|
|
330
331
|
pytest -v --alluredir=./reports
|
331
332
|
```
|
332
333
|
|
334
|
+
### 使用Allure生成和查看报告
|
335
|
+
|
336
|
+
pytest-dsl已与Allure报告框架集成,可以生成美观、交互式的测试报告。
|
337
|
+
|
338
|
+
```bash
|
339
|
+
# 运行测试并生成Allure报告数据
|
340
|
+
pytest --alluredir=./allure-results
|
341
|
+
|
342
|
+
# 生成HTML报告并启动本地服务器查看
|
343
|
+
allure serve ./allure-results
|
344
|
+
|
345
|
+
# 或生成HTML报告到指定目录
|
346
|
+
allure generate ./allure-results -o ./allure-report
|
347
|
+
# 然后可以打开 ./allure-report/index.html 查看报告
|
348
|
+
```
|
349
|
+
|
350
|
+
Allure报告会自动包含以下信息:
|
351
|
+
- 测试步骤和执行状态
|
352
|
+
- HTTP请求和响应详情
|
353
|
+
- 断言结果和失败原因
|
354
|
+
- 测试执行时间和性能数据
|
355
|
+
- 测试标签和分类信息
|
356
|
+
|
357
|
+
通过Allure报告,您可以更直观地分析测试结果,快速定位问题。
|
358
|
+
|
333
359
|
## 更多功能
|
334
360
|
|
335
361
|
### 断言重试功能
|
@@ -300,6 +300,31 @@ pytest test_api.py
|
|
300
300
|
pytest -v --alluredir=./reports
|
301
301
|
```
|
302
302
|
|
303
|
+
### 使用Allure生成和查看报告
|
304
|
+
|
305
|
+
pytest-dsl已与Allure报告框架集成,可以生成美观、交互式的测试报告。
|
306
|
+
|
307
|
+
```bash
|
308
|
+
# 运行测试并生成Allure报告数据
|
309
|
+
pytest --alluredir=./allure-results
|
310
|
+
|
311
|
+
# 生成HTML报告并启动本地服务器查看
|
312
|
+
allure serve ./allure-results
|
313
|
+
|
314
|
+
# 或生成HTML报告到指定目录
|
315
|
+
allure generate ./allure-results -o ./allure-report
|
316
|
+
# 然后可以打开 ./allure-report/index.html 查看报告
|
317
|
+
```
|
318
|
+
|
319
|
+
Allure报告会自动包含以下信息:
|
320
|
+
- 测试步骤和执行状态
|
321
|
+
- HTTP请求和响应详情
|
322
|
+
- 断言结果和失败原因
|
323
|
+
- 测试执行时间和性能数据
|
324
|
+
- 测试标签和分类信息
|
325
|
+
|
326
|
+
通过Allure报告,您可以更直观地分析测试结果,快速定位问题。
|
327
|
+
|
303
328
|
## 更多功能
|
304
329
|
|
305
330
|
### 断言重试功能
|
@@ -415,4 +440,4 @@ MIT License
|
|
415
440
|
|
416
441
|
---
|
417
442
|
|
418
|
-
开始使用pytest-dsl,释放测试自动化的无限可能!
|
443
|
+
开始使用pytest-dsl,释放测试自动化的无限可能!
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "pytest-dsl"
|
7
|
-
version = "0.
|
7
|
+
version = "0.4.0"
|
8
8
|
description = "A DSL testing framework based on pytest"
|
9
9
|
readme = "README.md"
|
10
|
-
requires-python = ">=3.
|
10
|
+
requires-python = ">=3.9"
|
11
11
|
license = {text = "MIT"}
|
12
12
|
authors = [
|
13
13
|
{name = "Chen Shuanglin"}
|
@@ -15,10 +15,10 @@ authors = [
|
|
15
15
|
classifiers = [
|
16
16
|
"Framework :: Pytest",
|
17
17
|
"Programming Language :: Python :: 3",
|
18
|
-
"Programming Language :: Python :: 3.8",
|
19
18
|
"Programming Language :: Python :: 3.9",
|
20
19
|
"Programming Language :: Python :: 3.10",
|
21
20
|
"Programming Language :: Python :: 3.11",
|
21
|
+
"Programming Language :: Python :: 3.12",
|
22
22
|
"License :: OSI Approved :: MIT License",
|
23
23
|
"Operating System :: OS Independent",
|
24
24
|
]
|
@@ -32,6 +32,7 @@ dependencies = [
|
|
32
32
|
"requests>=2.28.0",
|
33
33
|
"lxml>=4.9.0",
|
34
34
|
"jsonschema>=4.17.0",
|
35
|
+
"pytz>=2023.3",
|
35
36
|
]
|
36
37
|
|
37
38
|
[project.entry-points.pytest11]
|
@@ -42,4 +43,4 @@ pytest-dsl = "pytest_dsl.cli:main"
|
|
42
43
|
|
43
44
|
[project.urls]
|
44
45
|
"Homepage" = "https://github.com/felix-1991/pytest-dsl"
|
45
|
-
"Bug Tracker" = "https://github.com/felix-1991/pytest-dsl/issues"
|
46
|
+
"Bug Tracker" = "https://github.com/felix-1991/pytest-dsl/issues"
|
@@ -0,0 +1,143 @@
|
|
1
|
+
"""
|
2
|
+
pytest-dsl命令行入口
|
3
|
+
|
4
|
+
提供独立的命令行工具,用于执行DSL文件。
|
5
|
+
"""
|
6
|
+
|
7
|
+
import sys
|
8
|
+
import argparse
|
9
|
+
import pytest
|
10
|
+
import os
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
from pytest_dsl.core.lexer import get_lexer
|
14
|
+
from pytest_dsl.core.parser import get_parser
|
15
|
+
from pytest_dsl.core.dsl_executor import DSLExecutor
|
16
|
+
from pytest_dsl.core.yaml_vars import yaml_vars
|
17
|
+
from pytest_dsl.core.auto_directory import SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
|
18
|
+
|
19
|
+
|
20
|
+
def read_file(filename):
|
21
|
+
"""读取 DSL 文件内容"""
|
22
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
23
|
+
return f.read()
|
24
|
+
|
25
|
+
|
26
|
+
def parse_args():
|
27
|
+
"""解析命令行参数"""
|
28
|
+
parser = argparse.ArgumentParser(description='执行DSL测试文件')
|
29
|
+
parser.add_argument('path', help='要执行的DSL文件路径或包含DSL文件的目录')
|
30
|
+
parser.add_argument('--yaml-vars', action='append', default=[],
|
31
|
+
help='YAML变量文件路径,可以指定多个文件 (例如: --yaml-vars vars1.yaml --yaml-vars vars2.yaml)')
|
32
|
+
parser.add_argument('--yaml-vars-dir', default=None,
|
33
|
+
help='YAML变量文件目录路径,将加载该目录下所有.yaml文件')
|
34
|
+
|
35
|
+
return parser.parse_args()
|
36
|
+
|
37
|
+
|
38
|
+
def load_yaml_variables(args):
|
39
|
+
"""从命令行参数加载YAML变量"""
|
40
|
+
# 加载单个YAML文件
|
41
|
+
if args.yaml_vars:
|
42
|
+
yaml_vars.load_yaml_files(args.yaml_vars)
|
43
|
+
print(f"已加载YAML变量文件: {', '.join(args.yaml_vars)}")
|
44
|
+
|
45
|
+
# 加载目录中的YAML文件
|
46
|
+
if args.yaml_vars_dir:
|
47
|
+
yaml_vars_dir = args.yaml_vars_dir
|
48
|
+
try:
|
49
|
+
yaml_vars.load_from_directory(yaml_vars_dir)
|
50
|
+
print(f"已加载YAML变量目录: {yaml_vars_dir}")
|
51
|
+
loaded_files = yaml_vars.get_loaded_files()
|
52
|
+
if loaded_files:
|
53
|
+
dir_files = [f for f in loaded_files if Path(f).parent == Path(yaml_vars_dir)]
|
54
|
+
if dir_files:
|
55
|
+
print(f"目录中加载的文件: {', '.join(dir_files)}")
|
56
|
+
except NotADirectoryError:
|
57
|
+
print(f"YAML变量目录不存在: {yaml_vars_dir}")
|
58
|
+
sys.exit(1)
|
59
|
+
|
60
|
+
|
61
|
+
def execute_dsl_file(file_path, lexer, parser, executor):
|
62
|
+
"""执行单个DSL文件"""
|
63
|
+
try:
|
64
|
+
print(f"执行文件: {file_path}")
|
65
|
+
dsl_code = read_file(file_path)
|
66
|
+
ast = parser.parse(dsl_code, lexer=lexer)
|
67
|
+
executor.execute(ast)
|
68
|
+
return True
|
69
|
+
except Exception as e:
|
70
|
+
print(f"执行失败 {file_path}: {e}")
|
71
|
+
return False
|
72
|
+
|
73
|
+
|
74
|
+
def find_dsl_files(directory):
|
75
|
+
"""查找目录中的所有DSL文件"""
|
76
|
+
dsl_files = []
|
77
|
+
for root, _, files in os.walk(directory):
|
78
|
+
for file in files:
|
79
|
+
if file.endswith(('.dsl', '.auto')) and file not in [SETUP_FILE_NAME, TEARDOWN_FILE_NAME]:
|
80
|
+
dsl_files.append(os.path.join(root, file))
|
81
|
+
return dsl_files
|
82
|
+
|
83
|
+
|
84
|
+
def main():
|
85
|
+
"""命令行入口点"""
|
86
|
+
args = parse_args()
|
87
|
+
path = args.path
|
88
|
+
|
89
|
+
# 加载YAML变量
|
90
|
+
load_yaml_variables(args)
|
91
|
+
|
92
|
+
lexer = get_lexer()
|
93
|
+
parser = get_parser()
|
94
|
+
executor = DSLExecutor()
|
95
|
+
|
96
|
+
# 检查路径是文件还是目录
|
97
|
+
if os.path.isfile(path):
|
98
|
+
# 执行单个文件
|
99
|
+
success = execute_dsl_file(path, lexer, parser, executor)
|
100
|
+
if not success:
|
101
|
+
sys.exit(1)
|
102
|
+
elif os.path.isdir(path):
|
103
|
+
# 执行目录中的所有DSL文件
|
104
|
+
print(f"执行目录: {path}")
|
105
|
+
|
106
|
+
# 先执行目录的setup文件(如果存在)
|
107
|
+
setup_file = os.path.join(path, SETUP_FILE_NAME)
|
108
|
+
if os.path.exists(setup_file):
|
109
|
+
execute_hook_file(Path(setup_file), True, path)
|
110
|
+
|
111
|
+
# 查找并执行所有DSL文件
|
112
|
+
dsl_files = find_dsl_files(path)
|
113
|
+
if not dsl_files:
|
114
|
+
print(f"目录中没有找到DSL文件: {path}")
|
115
|
+
sys.exit(1)
|
116
|
+
|
117
|
+
print(f"找到 {len(dsl_files)} 个DSL文件")
|
118
|
+
|
119
|
+
# 执行所有DSL文件
|
120
|
+
failures = 0
|
121
|
+
for file_path in dsl_files:
|
122
|
+
success = execute_dsl_file(file_path, lexer, parser, executor)
|
123
|
+
if not success:
|
124
|
+
failures += 1
|
125
|
+
|
126
|
+
# 最后执行目录的teardown文件(如果存在)
|
127
|
+
teardown_file = os.path.join(path, TEARDOWN_FILE_NAME)
|
128
|
+
if os.path.exists(teardown_file):
|
129
|
+
execute_hook_file(Path(teardown_file), False, path)
|
130
|
+
|
131
|
+
# 如果有失败的测试,返回非零退出码
|
132
|
+
if failures > 0:
|
133
|
+
print(f"总计 {failures}/{len(dsl_files)} 个测试失败")
|
134
|
+
sys.exit(1)
|
135
|
+
else:
|
136
|
+
print(f"所有 {len(dsl_files)} 个测试成功完成")
|
137
|
+
else:
|
138
|
+
print(f"路径不存在: {path}")
|
139
|
+
sys.exit(1)
|
140
|
+
|
141
|
+
|
142
|
+
if __name__ == '__main__':
|
143
|
+
main()
|
@@ -0,0 +1,343 @@
|
|
1
|
+
import allure
|
2
|
+
import time
|
3
|
+
import random
|
4
|
+
import string
|
5
|
+
import subprocess
|
6
|
+
import datetime
|
7
|
+
import logging
|
8
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
9
|
+
|
10
|
+
|
11
|
+
@keyword_manager.register('打印', [
|
12
|
+
{'name': '内容', 'mapping': 'content', 'description': '要打印的文本内容'}
|
13
|
+
])
|
14
|
+
def print_content(**kwargs):
|
15
|
+
content = kwargs.get('content')
|
16
|
+
print(f"内容: {content}")
|
17
|
+
|
18
|
+
|
19
|
+
@keyword_manager.register('返回结果', [
|
20
|
+
{'name': '结果', 'mapping': 'result', 'description': '要返回的结果值'}
|
21
|
+
])
|
22
|
+
def return_result(**kwargs):
|
23
|
+
return kwargs.get('result')
|
24
|
+
|
25
|
+
|
26
|
+
@keyword_manager.register('等待', [
|
27
|
+
{'name': '秒数', 'mapping': 'seconds', 'description': '等待的秒数,可以是小数'}
|
28
|
+
])
|
29
|
+
def wait_seconds(**kwargs):
|
30
|
+
"""等待指定的秒数
|
31
|
+
|
32
|
+
Args:
|
33
|
+
seconds: 等待的秒数,可以是小数表示毫秒级等待
|
34
|
+
"""
|
35
|
+
seconds = float(kwargs.get('seconds', 0))
|
36
|
+
with allure.step(f"等待 {seconds} 秒"):
|
37
|
+
time.sleep(seconds)
|
38
|
+
return True
|
39
|
+
|
40
|
+
|
41
|
+
@keyword_manager.register('获取当前时间', [
|
42
|
+
{'name': '格式', 'mapping': 'format', 'description': '时间格式,例如 "%Y-%m-%d %H:%M:%S",默认返回时间戳'},
|
43
|
+
{'name': '时区', 'mapping': 'timezone', 'description': '时区,例如 "Asia/Shanghai",默认为本地时区'}
|
44
|
+
])
|
45
|
+
def get_current_time(**kwargs):
|
46
|
+
"""获取当前时间
|
47
|
+
|
48
|
+
Args:
|
49
|
+
format: 时间格式,如果不提供则返回时间戳
|
50
|
+
timezone: 时区,默认为本地时区
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
str: 格式化的时间字符串或时间戳
|
54
|
+
"""
|
55
|
+
time_format = kwargs.get('format')
|
56
|
+
timezone = kwargs.get('timezone')
|
57
|
+
|
58
|
+
# 获取当前时间
|
59
|
+
if timezone:
|
60
|
+
import pytz
|
61
|
+
try:
|
62
|
+
tz = pytz.timezone(timezone)
|
63
|
+
current_time = datetime.datetime.now(tz)
|
64
|
+
except Exception as e:
|
65
|
+
allure.attach(
|
66
|
+
f"时区设置异常: {str(e)}",
|
67
|
+
name="时区设置异常",
|
68
|
+
attachment_type=allure.attachment_type.TEXT
|
69
|
+
)
|
70
|
+
current_time = datetime.datetime.now()
|
71
|
+
else:
|
72
|
+
current_time = datetime.datetime.now()
|
73
|
+
|
74
|
+
# 格式化时间
|
75
|
+
if time_format:
|
76
|
+
try:
|
77
|
+
result = current_time.strftime(time_format)
|
78
|
+
except Exception as e:
|
79
|
+
allure.attach(
|
80
|
+
f"时间格式化异常: {str(e)}",
|
81
|
+
name="时间格式化异常",
|
82
|
+
attachment_type=allure.attachment_type.TEXT
|
83
|
+
)
|
84
|
+
result = str(current_time)
|
85
|
+
else:
|
86
|
+
# 返回时间戳
|
87
|
+
result = str(int(current_time.timestamp()))
|
88
|
+
|
89
|
+
return result
|
90
|
+
|
91
|
+
|
92
|
+
@keyword_manager.register('生成随机字符串', [
|
93
|
+
{'name': '长度', 'mapping': 'length', 'description': '随机字符串的长度,默认为8'},
|
94
|
+
{'name': '类型', 'mapping': 'type',
|
95
|
+
'description': '字符类型:字母(letters)、数字(digits)、字母数字(alphanumeric)、全部(all),默认为字母数字'}
|
96
|
+
])
|
97
|
+
def generate_random_string(**kwargs):
|
98
|
+
"""生成随机字符串
|
99
|
+
|
100
|
+
Args:
|
101
|
+
length: 随机字符串的长度
|
102
|
+
type: 字符类型:字母、数字、字母数字、全部
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
str: 生成的随机字符串
|
106
|
+
"""
|
107
|
+
length = int(kwargs.get('length', 8))
|
108
|
+
char_type = kwargs.get('type', 'alphanumeric').lower()
|
109
|
+
|
110
|
+
# 根据类型选择字符集
|
111
|
+
if char_type == 'letters':
|
112
|
+
chars = string.ascii_letters
|
113
|
+
elif char_type == 'digits':
|
114
|
+
chars = string.digits
|
115
|
+
elif char_type == 'alphanumeric':
|
116
|
+
chars = string.ascii_letters + string.digits
|
117
|
+
elif char_type == 'all':
|
118
|
+
chars = string.ascii_letters + string.digits + string.punctuation
|
119
|
+
else:
|
120
|
+
# 默认使用字母数字
|
121
|
+
chars = string.ascii_letters + string.digits
|
122
|
+
|
123
|
+
# 生成随机字符串
|
124
|
+
result = ''.join(random.choice(chars) for _ in range(length))
|
125
|
+
|
126
|
+
with allure.step(f"生成随机字符串: 长度={length}, 类型={char_type}"):
|
127
|
+
allure.attach(
|
128
|
+
f"生成的随机字符串: {result}",
|
129
|
+
name="随机字符串",
|
130
|
+
attachment_type=allure.attachment_type.TEXT
|
131
|
+
)
|
132
|
+
|
133
|
+
return result
|
134
|
+
|
135
|
+
|
136
|
+
@keyword_manager.register('生成随机数', [
|
137
|
+
{'name': '最小值', 'mapping': 'min', 'description': '随机数的最小值,默认为0'},
|
138
|
+
{'name': '最大值', 'mapping': 'max', 'description': '随机数的最大值,默认为100'},
|
139
|
+
{'name': '小数位数', 'mapping': 'decimals', 'description': '小数位数,默认为0(整数)'}
|
140
|
+
])
|
141
|
+
def generate_random_number(**kwargs):
|
142
|
+
"""生成随机数
|
143
|
+
|
144
|
+
Args:
|
145
|
+
min: 随机数的最小值
|
146
|
+
max: 随机数的最大值
|
147
|
+
decimals: 小数位数,0表示整数
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
int/float: 生成的随机数
|
151
|
+
"""
|
152
|
+
min_value = float(kwargs.get('min', 0))
|
153
|
+
max_value = float(kwargs.get('max', 100))
|
154
|
+
decimals = int(kwargs.get('decimals', 0))
|
155
|
+
|
156
|
+
if decimals <= 0:
|
157
|
+
# 生成整数
|
158
|
+
result = random.randint(int(min_value), int(max_value))
|
159
|
+
else:
|
160
|
+
# 生成浮点数
|
161
|
+
result = round(random.uniform(min_value, max_value), decimals)
|
162
|
+
|
163
|
+
with allure.step(f"生成随机数: 范围=[{min_value}, {max_value}], 小数位数={decimals}"):
|
164
|
+
allure.attach(
|
165
|
+
f"生成的随机数: {result}",
|
166
|
+
name="随机数",
|
167
|
+
attachment_type=allure.attachment_type.TEXT
|
168
|
+
)
|
169
|
+
|
170
|
+
return result
|
171
|
+
|
172
|
+
|
173
|
+
@keyword_manager.register('字符串操作', [
|
174
|
+
{'name': '操作', 'mapping': 'operation',
|
175
|
+
'description': '操作类型:拼接(concat)、替换(replace)、分割(split)、大写(upper)、小写(lower)、去空格(strip)'},
|
176
|
+
{'name': '字符串', 'mapping': 'string', 'description': '要操作的字符串'},
|
177
|
+
{'name': '参数1', 'mapping': 'param1', 'description': '操作参数1,根据操作类型不同而不同'},
|
178
|
+
{'name': '参数2', 'mapping': 'param2', 'description': '操作参数2,根据操作类型不同而不同'}
|
179
|
+
])
|
180
|
+
def string_operation(**kwargs):
|
181
|
+
"""字符串操作
|
182
|
+
|
183
|
+
Args:
|
184
|
+
operation: 操作类型
|
185
|
+
string: 要操作的字符串
|
186
|
+
param1: 操作参数1
|
187
|
+
param2: 操作参数2
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
str: 操作结果
|
191
|
+
"""
|
192
|
+
operation = kwargs.get('operation', '').lower()
|
193
|
+
string = str(kwargs.get('string', ''))
|
194
|
+
param1 = kwargs.get('param1', '')
|
195
|
+
param2 = kwargs.get('param2', '')
|
196
|
+
|
197
|
+
result = string
|
198
|
+
|
199
|
+
if operation == 'concat':
|
200
|
+
# 拼接字符串
|
201
|
+
result = string + str(param1)
|
202
|
+
elif operation == 'replace':
|
203
|
+
# 替换字符串
|
204
|
+
result = string.replace(str(param1), str(param2))
|
205
|
+
elif operation == 'split':
|
206
|
+
# 分割字符串
|
207
|
+
result = string.split(str(param1))
|
208
|
+
if param2 and param2.isdigit():
|
209
|
+
# 如果提供了索引,返回指定位置的元素
|
210
|
+
index = int(param2)
|
211
|
+
if 0 <= index < len(result):
|
212
|
+
result = result[index]
|
213
|
+
elif operation == 'upper':
|
214
|
+
# 转大写
|
215
|
+
result = string.upper()
|
216
|
+
elif operation == 'lower':
|
217
|
+
# 转小写
|
218
|
+
result = string.lower()
|
219
|
+
elif operation == 'strip':
|
220
|
+
# 去空格
|
221
|
+
result = string.strip()
|
222
|
+
else:
|
223
|
+
# 未知操作,返回原字符串
|
224
|
+
allure.attach(
|
225
|
+
f"未知的字符串操作: {operation}",
|
226
|
+
name="字符串操作错误",
|
227
|
+
attachment_type=allure.attachment_type.TEXT
|
228
|
+
)
|
229
|
+
|
230
|
+
with allure.step(f"字符串操作: {operation}"):
|
231
|
+
allure.attach(
|
232
|
+
f"原字符串: {string}\n操作: {operation}\n参数1: {param1}\n参数2: {param2}\n结果: {result}",
|
233
|
+
name="字符串操作结果",
|
234
|
+
attachment_type=allure.attachment_type.TEXT
|
235
|
+
)
|
236
|
+
|
237
|
+
return result
|
238
|
+
|
239
|
+
|
240
|
+
@keyword_manager.register('日志', [
|
241
|
+
{'name': '级别', 'mapping': 'level',
|
242
|
+
'description': '日志级别:DEBUG, INFO, WARNING, ERROR, CRITICAL,默认为INFO'},
|
243
|
+
{'name': '消息', 'mapping': 'message', 'description': '日志消息内容'}
|
244
|
+
])
|
245
|
+
def log_message(**kwargs):
|
246
|
+
"""记录日志
|
247
|
+
|
248
|
+
Args:
|
249
|
+
level: 日志级别
|
250
|
+
message: 日志消息内容
|
251
|
+
"""
|
252
|
+
level = kwargs.get('level', 'INFO').upper()
|
253
|
+
message = kwargs.get('message', '')
|
254
|
+
|
255
|
+
# 获取日志级别
|
256
|
+
log_level = getattr(logging, level, logging.INFO)
|
257
|
+
|
258
|
+
# 记录日志
|
259
|
+
logging.log(log_level, message)
|
260
|
+
|
261
|
+
with allure.step(f"记录日志: [{level}] {message}"):
|
262
|
+
allure.attach(
|
263
|
+
f"日志级别: {level}\n日志消息: {message}",
|
264
|
+
name="日志记录",
|
265
|
+
attachment_type=allure.attachment_type.TEXT
|
266
|
+
)
|
267
|
+
|
268
|
+
return True
|
269
|
+
|
270
|
+
|
271
|
+
@keyword_manager.register('执行命令', [
|
272
|
+
{'name': '命令', 'mapping': 'command', 'description': '要执行的系统命令'},
|
273
|
+
{'name': '超时', 'mapping': 'timeout', 'description': '命令执行超时时间(秒),默认为60秒'},
|
274
|
+
{'name': '捕获输出', 'mapping': 'capture_output', 'description': '是否捕获命令输出,默认为True'}
|
275
|
+
])
|
276
|
+
def execute_command(**kwargs):
|
277
|
+
"""执行系统命令
|
278
|
+
|
279
|
+
Args:
|
280
|
+
command: 要执行的系统命令
|
281
|
+
timeout: 命令执行超时时间(秒)
|
282
|
+
capture_output: 是否捕获命令输出
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
dict: 包含返回码、标准输出和标准错误的字典
|
286
|
+
"""
|
287
|
+
command = kwargs.get('command', '')
|
288
|
+
timeout = float(kwargs.get('timeout', 60))
|
289
|
+
capture_output = kwargs.get('capture_output', True)
|
290
|
+
|
291
|
+
with allure.step(f"执行命令: {command}"):
|
292
|
+
try:
|
293
|
+
# 执行命令
|
294
|
+
result = subprocess.run(
|
295
|
+
command,
|
296
|
+
shell=True,
|
297
|
+
timeout=timeout,
|
298
|
+
capture_output=capture_output,
|
299
|
+
text=True
|
300
|
+
)
|
301
|
+
|
302
|
+
# 构建结果字典
|
303
|
+
command_result = {
|
304
|
+
'returncode': result.returncode,
|
305
|
+
'stdout': result.stdout if capture_output else '',
|
306
|
+
'stderr': result.stderr if capture_output else ''
|
307
|
+
}
|
308
|
+
|
309
|
+
# 记录执行结果
|
310
|
+
allure.attach(
|
311
|
+
f"命令: {command}\n返回码: {result.returncode}\n"
|
312
|
+
f"标准输出: {result.stdout if capture_output else '未捕获'}\n"
|
313
|
+
f"标准错误: {result.stderr if capture_output else '未捕获'}",
|
314
|
+
name="命令执行结果",
|
315
|
+
attachment_type=allure.attachment_type.TEXT
|
316
|
+
)
|
317
|
+
|
318
|
+
return command_result
|
319
|
+
|
320
|
+
except subprocess.TimeoutExpired:
|
321
|
+
# 命令执行超时
|
322
|
+
allure.attach(
|
323
|
+
f"命令执行超时: {command} (超时: {timeout}秒)",
|
324
|
+
name="命令执行超时",
|
325
|
+
attachment_type=allure.attachment_type.TEXT
|
326
|
+
)
|
327
|
+
return {
|
328
|
+
'returncode': -1,
|
329
|
+
'stdout': '',
|
330
|
+
'stderr': f'Command timed out after {timeout} seconds'
|
331
|
+
}
|
332
|
+
except Exception as e:
|
333
|
+
# 其他异常
|
334
|
+
allure.attach(
|
335
|
+
f"命令执行异常: {str(e)}",
|
336
|
+
name="命令执行异常",
|
337
|
+
attachment_type=allure.attachment_type.TEXT
|
338
|
+
)
|
339
|
+
return {
|
340
|
+
'returncode': -1,
|
341
|
+
'stdout': '',
|
342
|
+
'stderr': str(e)
|
343
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pytest-dsl
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: A DSL testing framework based on pytest
|
5
5
|
Author: Chen Shuanglin
|
6
6
|
License: MIT
|
@@ -8,13 +8,13 @@ Project-URL: Homepage, https://github.com/felix-1991/pytest-dsl
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/felix-1991/pytest-dsl/issues
|
9
9
|
Classifier: Framework :: Pytest
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
12
11
|
Classifier: Programming Language :: Python :: 3.9
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
16
16
|
Classifier: Operating System :: OS Independent
|
17
|
-
Requires-Python: >=3.
|
17
|
+
Requires-Python: >=3.9
|
18
18
|
Description-Content-Type: text/markdown
|
19
19
|
License-File: LICENSE
|
20
20
|
Requires-Dist: pytest>=7.0.0
|
@@ -26,6 +26,7 @@ Requires-Dist: jsonpath-ng>=1.5.0
|
|
26
26
|
Requires-Dist: requests>=2.28.0
|
27
27
|
Requires-Dist: lxml>=4.9.0
|
28
28
|
Requires-Dist: jsonschema>=4.17.0
|
29
|
+
Requires-Dist: pytz>=2023.3
|
29
30
|
Dynamic: license-file
|
30
31
|
|
31
32
|
# pytest-dsl: 强大的关键字驱动测试自动化框架
|
@@ -330,6 +331,31 @@ pytest test_api.py
|
|
330
331
|
pytest -v --alluredir=./reports
|
331
332
|
```
|
332
333
|
|
334
|
+
### 使用Allure生成和查看报告
|
335
|
+
|
336
|
+
pytest-dsl已与Allure报告框架集成,可以生成美观、交互式的测试报告。
|
337
|
+
|
338
|
+
```bash
|
339
|
+
# 运行测试并生成Allure报告数据
|
340
|
+
pytest --alluredir=./allure-results
|
341
|
+
|
342
|
+
# 生成HTML报告并启动本地服务器查看
|
343
|
+
allure serve ./allure-results
|
344
|
+
|
345
|
+
# 或生成HTML报告到指定目录
|
346
|
+
allure generate ./allure-results -o ./allure-report
|
347
|
+
# 然后可以打开 ./allure-report/index.html 查看报告
|
348
|
+
```
|
349
|
+
|
350
|
+
Allure报告会自动包含以下信息:
|
351
|
+
- 测试步骤和执行状态
|
352
|
+
- HTTP请求和响应详情
|
353
|
+
- 断言结果和失败原因
|
354
|
+
- 测试执行时间和性能数据
|
355
|
+
- 测试标签和分类信息
|
356
|
+
|
357
|
+
通过Allure报告,您可以更直观地分析测试结果,快速定位问题。
|
358
|
+
|
333
359
|
## 更多功能
|
334
360
|
|
335
361
|
### 断言重试功能
|
@@ -1,80 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
pytest-dsl命令行入口
|
3
|
-
|
4
|
-
提供独立的命令行工具,用于执行DSL文件。
|
5
|
-
"""
|
6
|
-
|
7
|
-
import sys
|
8
|
-
import argparse
|
9
|
-
import pytest
|
10
|
-
from pathlib import Path
|
11
|
-
|
12
|
-
from pytest_dsl.core.lexer import get_lexer
|
13
|
-
from pytest_dsl.core.parser import get_parser
|
14
|
-
from pytest_dsl.core.dsl_executor import DSLExecutor
|
15
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
16
|
-
|
17
|
-
|
18
|
-
def read_file(filename):
|
19
|
-
"""读取 DSL 文件内容"""
|
20
|
-
with open(filename, 'r', encoding='utf-8') as f:
|
21
|
-
return f.read()
|
22
|
-
|
23
|
-
|
24
|
-
def parse_args():
|
25
|
-
"""解析命令行参数"""
|
26
|
-
parser = argparse.ArgumentParser(description='执行DSL测试文件')
|
27
|
-
parser.add_argument('dsl_file', help='要执行的DSL文件路径')
|
28
|
-
parser.add_argument('--yaml-vars', action='append', default=[],
|
29
|
-
help='YAML变量文件路径,可以指定多个文件 (例如: --yaml-vars vars1.yaml --yaml-vars vars2.yaml)')
|
30
|
-
parser.add_argument('--yaml-vars-dir', default=None,
|
31
|
-
help='YAML变量文件目录路径,将加载该目录下所有.yaml文件')
|
32
|
-
|
33
|
-
return parser.parse_args()
|
34
|
-
|
35
|
-
|
36
|
-
def load_yaml_variables(args):
|
37
|
-
"""从命令行参数加载YAML变量"""
|
38
|
-
# 加载单个YAML文件
|
39
|
-
if args.yaml_vars:
|
40
|
-
yaml_vars.load_yaml_files(args.yaml_vars)
|
41
|
-
print(f"已加载YAML变量文件: {', '.join(args.yaml_vars)}")
|
42
|
-
|
43
|
-
# 加载目录中的YAML文件
|
44
|
-
if args.yaml_vars_dir:
|
45
|
-
yaml_vars_dir = args.yaml_vars_dir
|
46
|
-
try:
|
47
|
-
yaml_vars.load_from_directory(yaml_vars_dir)
|
48
|
-
print(f"已加载YAML变量目录: {yaml_vars_dir}")
|
49
|
-
loaded_files = yaml_vars.get_loaded_files()
|
50
|
-
if loaded_files:
|
51
|
-
dir_files = [f for f in loaded_files if Path(f).parent == Path(yaml_vars_dir)]
|
52
|
-
if dir_files:
|
53
|
-
print(f"目录中加载的文件: {', '.join(dir_files)}")
|
54
|
-
except NotADirectoryError:
|
55
|
-
print(f"YAML变量目录不存在: {yaml_vars_dir}")
|
56
|
-
sys.exit(1)
|
57
|
-
|
58
|
-
|
59
|
-
def main():
|
60
|
-
"""命令行入口点"""
|
61
|
-
args = parse_args()
|
62
|
-
|
63
|
-
# 加载YAML变量
|
64
|
-
load_yaml_variables(args)
|
65
|
-
|
66
|
-
lexer = get_lexer()
|
67
|
-
parser = get_parser()
|
68
|
-
executor = DSLExecutor()
|
69
|
-
|
70
|
-
try:
|
71
|
-
dsl_code = read_file(args.dsl_file)
|
72
|
-
ast = parser.parse(dsl_code, lexer=lexer)
|
73
|
-
executor.execute(ast)
|
74
|
-
except Exception as e:
|
75
|
-
print(f"执行失败: {e}")
|
76
|
-
sys.exit(1)
|
77
|
-
|
78
|
-
|
79
|
-
if __name__ == '__main__':
|
80
|
-
main()
|
@@ -1,17 +0,0 @@
|
|
1
|
-
import allure
|
2
|
-
from pytest_dsl.core.keyword_manager import keyword_manager
|
3
|
-
|
4
|
-
|
5
|
-
@keyword_manager.register('打印', [
|
6
|
-
{'name': '内容', 'mapping': 'content', 'description': '要打印的文本内容'}
|
7
|
-
])
|
8
|
-
def print_content(**kwargs):
|
9
|
-
content = kwargs.get('content')
|
10
|
-
print(f"内容: {content}")
|
11
|
-
|
12
|
-
|
13
|
-
@keyword_manager.register('返回结果', [
|
14
|
-
{'name': '结果', 'mapping': 'result', 'description': '要返回的结果值'}
|
15
|
-
])
|
16
|
-
def return_result(**kwargs):
|
17
|
-
return kwargs.get('result')
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/custom/test_advanced_keywords.auto
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{pytest_dsl-0.3.0 → pytest_dsl-0.4.0}/pytest_dsl/examples/http/http_retry_assertions_enhanced.auto
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|