pytest-dsl 0.15.0__py3-none-any.whl → 0.15.1__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.
pytest_dsl/__init__.py CHANGED
@@ -54,6 +54,13 @@ from pytest_dsl.core.keyword_loader import (
54
54
  group_keywords_by_source, scan_project_custom_keywords
55
55
  )
56
56
 
57
+ # 关键字工具
58
+ from pytest_dsl.core.keyword_utils import (
59
+ KeywordInfo, KeywordListOptions, KeywordFormatter, KeywordLister,
60
+ keyword_lister, list_keywords, get_keyword_info, search_keywords,
61
+ generate_html_report
62
+ )
63
+
57
64
  # 便捷导入的别名
58
65
  Executor = DSLExecutor
59
66
  Validator = DSLValidator
@@ -77,6 +84,11 @@ __all__ = [
77
84
  'load_all_keywords', 'categorize_keyword', 'get_keyword_source_info',
78
85
  'group_keywords_by_source', 'scan_project_custom_keywords',
79
86
 
87
+ # 关键字工具
88
+ 'KeywordInfo', 'KeywordListOptions', 'KeywordFormatter', 'KeywordLister',
89
+ 'keyword_lister', 'list_keywords', 'get_keyword_info', 'search_keywords',
90
+ 'generate_html_report',
91
+
80
92
  # Hook系统
81
93
  'hookimpl', 'hookspec', 'DSLHookSpecs',
82
94
  'hook_manager', 'DSLHookManager', 'HookManager',
pytest_dsl/cli.py CHANGED
@@ -16,11 +16,8 @@ from pytest_dsl.core.yaml_loader import load_yaml_variables_from_args
16
16
  from pytest_dsl.core.auto_directory import (
17
17
  SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
18
18
  )
19
- from pytest_dsl.core.keyword_loader import (
20
- load_all_keywords, categorize_keyword, get_keyword_source_info,
21
- group_keywords_by_source, scan_project_custom_keywords
22
- )
23
- from pytest_dsl.core.keyword_manager import keyword_manager
19
+ from pytest_dsl.core.keyword_loader import load_all_keywords
20
+ from pytest_dsl.core.keyword_utils import list_keywords as utils_list_keywords
24
21
 
25
22
 
26
23
  def read_file(filename):
@@ -139,431 +136,25 @@ def parse_args():
139
136
  return args
140
137
 
141
138
 
142
-
143
-
144
-
145
- def format_keyword_info_text(keyword_name, keyword_info, show_category=True,
146
- project_custom_keywords=None):
147
- """格式化关键字信息为文本格式"""
148
- lines = []
149
-
150
- # 关键字名称和类别
151
- category = categorize_keyword(
152
- keyword_name, keyword_info, project_custom_keywords
153
- )
154
- category_names = {
155
- 'builtin': '内置',
156
- 'custom': '自定义',
157
- 'project_custom': '项目自定义',
158
- 'remote': '远程'
159
- }
160
-
161
- if show_category:
162
- category_display = category_names.get(category, '未知')
163
- lines.append(f"关键字: {keyword_name} [{category_display}]")
164
- else:
165
- lines.append(f"关键字: {keyword_name}")
166
-
167
- # 远程关键字特殊标识
168
- if keyword_info.get('remote', False):
169
- alias = keyword_info.get('alias', '未知')
170
- original_name = keyword_info.get('original_name', keyword_name)
171
- lines.append(f" 远程服务器: {alias}")
172
- lines.append(f" 原始名称: {original_name}")
173
-
174
- # 项目自定义关键字特殊标识
175
- if category == 'project_custom' and project_custom_keywords:
176
- custom_info = project_custom_keywords[keyword_name]
177
- lines.append(f" 文件位置: {custom_info['file']}")
178
-
179
- # 对于项目自定义关键字,使用从AST中提取的参数信息
180
- custom_parameters = custom_info.get('parameters', [])
181
- if custom_parameters:
182
- lines.append(" 参数:")
183
- for param_info in custom_parameters:
184
- param_name = param_info['name']
185
- param_mapping = param_info.get('mapping', '')
186
- param_desc = param_info.get('description', '')
187
- param_default = param_info.get('default', None)
188
-
189
- # 构建参数描述
190
- param_parts = []
191
- if param_mapping and param_mapping != param_name:
192
- param_parts.append(f"{param_name} ({param_mapping})")
193
- else:
194
- param_parts.append(param_name)
195
-
196
- param_parts.append(f": {param_desc}")
197
-
198
- # 添加默认值信息
199
- if param_default is not None:
200
- param_parts.append(f" (默认值: {param_default})")
201
-
202
- lines.append(f" {''.join(param_parts)}")
203
- else:
204
- lines.append(" 参数: 无")
205
- else:
206
- # 参数信息(对于其他类型的关键字)
207
- parameters = keyword_info.get('parameters', [])
208
- if parameters:
209
- lines.append(" 参数:")
210
- for param in parameters:
211
- param_name = getattr(param, 'name', str(param))
212
- param_mapping = getattr(param, 'mapping', '')
213
- param_desc = getattr(param, 'description', '')
214
- param_default = getattr(param, 'default', None)
215
-
216
- # 构建参数描述
217
- param_info = []
218
- if param_mapping and param_mapping != param_name:
219
- param_info.append(f"{param_name} ({param_mapping})")
220
- else:
221
- param_info.append(param_name)
222
-
223
- param_info.append(f": {param_desc}")
224
-
225
- # 添加默认值信息
226
- if param_default is not None:
227
- param_info.append(f" (默认值: {param_default})")
228
-
229
- lines.append(f" {''.join(param_info)}")
230
- else:
231
- lines.append(" 参数: 无")
232
-
233
- # 函数文档
234
- func = keyword_info.get('func')
235
- if func and hasattr(func, '__doc__') and func.__doc__:
236
- lines.append(f" 说明: {func.__doc__.strip()}")
237
-
238
- return '\n'.join(lines)
239
-
240
-
241
- def format_keyword_info_json(keyword_name, keyword_info,
242
- project_custom_keywords=None):
243
- """格式化关键字信息为JSON格式"""
244
- category = categorize_keyword(
245
- keyword_name, keyword_info, project_custom_keywords
246
- )
247
- source_info = get_keyword_source_info(keyword_info)
248
-
249
- keyword_data = {
250
- 'name': keyword_name,
251
- 'category': category,
252
- 'source_info': source_info,
253
- 'parameters': []
254
- }
255
-
256
- # 远程关键字特殊信息
257
- if keyword_info.get('remote', False):
258
- keyword_data['remote'] = {
259
- 'alias': keyword_info.get('alias', ''),
260
- 'original_name': keyword_info.get('original_name', keyword_name)
261
- }
262
-
263
- # 项目自定义关键字特殊信息
264
- if category == 'project_custom' and project_custom_keywords:
265
- custom_info = project_custom_keywords[keyword_name]
266
- keyword_data['file_location'] = custom_info['file']
267
-
268
- # 对于项目自定义关键字,使用从AST中提取的参数信息
269
- for param_info in custom_info.get('parameters', []):
270
- keyword_data['parameters'].append(param_info)
271
- else:
272
- # 参数信息(对于其他类型的关键字)
273
- parameters = keyword_info.get('parameters', [])
274
- for param in parameters:
275
- param_data = {
276
- 'name': getattr(param, 'name', str(param)),
277
- 'mapping': getattr(param, 'mapping', ''),
278
- 'description': getattr(param, 'description', '')
279
- }
280
-
281
- # 添加默认值信息
282
- param_default = getattr(param, 'default', None)
283
- if param_default is not None:
284
- param_data['default'] = param_default
285
-
286
- keyword_data['parameters'].append(param_data)
287
-
288
- # 函数文档
289
- func = keyword_info.get('func')
290
- if func and hasattr(func, '__doc__') and func.__doc__:
291
- keyword_data['documentation'] = func.__doc__.strip()
292
-
293
- return keyword_data
294
-
295
-
296
- def generate_html_report(keywords_data, output_file):
297
- """生成HTML格式的关键字报告"""
298
- from jinja2 import Environment, FileSystemLoader, select_autoescape
299
- import os
300
-
301
- # 准备数据
302
- summary = keywords_data['summary']
303
- keywords = keywords_data['keywords']
304
-
305
- # 按类别分组
306
- categories = {}
307
- for keyword in keywords:
308
- category = keyword['category']
309
- if category not in categories:
310
- categories[category] = []
311
- categories[category].append(keyword)
312
-
313
- # 按来源分组(用于更详细的分组视图)
314
- source_groups = {}
315
- for keyword in keywords:
316
- source_info = keyword.get('source_info', {})
317
- category = keyword['category']
318
- source_name = source_info.get('name', '未知来源')
319
-
320
- # 构建分组键
321
- if category == 'plugin':
322
- group_key = f"插件 - {source_name}"
323
- elif category == 'builtin':
324
- group_key = "内置关键字"
325
- elif category == 'project_custom':
326
- group_key = f"项目自定义 - {keyword.get('file_location', source_name)}"
327
- elif category == 'remote':
328
- group_key = f"远程 - {source_name}"
329
- else:
330
- group_key = f"自定义 - {source_name}"
331
-
332
- if group_key not in source_groups:
333
- source_groups[group_key] = []
334
- source_groups[group_key].append(keyword)
335
-
336
- # 按位置分组(用于全部关键字视图,保持向后兼容)
337
- location_groups = {}
338
- for keyword in keywords:
339
- # 优先使用file_location,然后使用source_info中的name
340
- location = keyword.get('file_location')
341
- if not location:
342
- source_info = keyword.get('source_info', {})
343
- location = source_info.get('name', '内置/插件')
344
-
345
- if location not in location_groups:
346
- location_groups[location] = []
347
- location_groups[location].append(keyword)
348
-
349
- # 类别名称映射
350
- category_names = {
351
- 'builtin': '内置',
352
- 'plugin': '插件',
353
- 'custom': '自定义',
354
- 'project_custom': '项目自定义',
355
- 'remote': '远程'
356
- }
357
-
358
- # 设置Jinja2环境
359
- template_dir = os.path.join(os.path.dirname(__file__), 'templates')
360
-
361
- env = Environment(
362
- loader=FileSystemLoader(template_dir),
363
- autoescape=select_autoescape(['html', 'xml'])
364
- )
365
-
366
- # 加载模板
367
- template = env.get_template('keywords_report.html')
368
-
369
- # 渲染模板
370
- html_content = template.render(
371
- summary=summary,
372
- keywords=keywords,
373
- categories=categories,
374
- source_groups=source_groups,
375
- location_groups=location_groups,
376
- category_names=category_names
377
- )
378
-
379
- # 写入文件
380
- with open(output_file, 'w', encoding='utf-8') as f:
381
- f.write(html_content)
382
-
383
- print(f"HTML报告已生成: {output_file}")
384
-
385
-
386
139
  def list_keywords(output_format='json', name_filter=None,
387
140
  category_filter='all', output_file=None,
388
141
  include_remote=False):
389
- """罗列所有关键字信息"""
390
- import json
391
-
142
+ """罗列所有关键字信息(简化版,调用统一的工具函数)"""
392
143
  print("正在加载关键字...")
393
- project_custom_keywords = load_all_keywords(include_remote=include_remote)
394
-
395
- # 获取所有注册的关键字
396
- all_keywords = keyword_manager._keywords
397
-
398
- if not all_keywords:
399
- print("未发现任何关键字")
400
- return
401
-
402
- # 过滤关键字
403
- filtered_keywords = {}
404
144
 
405
- for name, info in all_keywords.items():
406
- # 名称过滤
407
- if name_filter and name_filter.lower() not in name.lower():
408
- continue
409
-
410
- # 远程关键字过滤
411
- if not include_remote and info.get('remote', False):
412
- continue
413
-
414
- # 类别过滤
415
- if category_filter != 'all':
416
- keyword_category = categorize_keyword(
417
- name, info, project_custom_keywords
418
- )
419
- if keyword_category != category_filter:
420
- continue
421
-
422
- filtered_keywords[name] = info
423
-
424
- if not filtered_keywords:
425
- if name_filter:
426
- print(f"未找到包含 '{name_filter}' 的关键字")
427
- else:
428
- print(f"未找到 {category_filter} 类别的关键字")
429
- return
430
-
431
- # 输出统计信息
432
- total_count = len(filtered_keywords)
433
- category_counts = {}
434
- source_counts = {}
435
-
436
- for name, info in filtered_keywords.items():
437
- cat = categorize_keyword(name, info, project_custom_keywords)
438
- category_counts[cat] = category_counts.get(cat, 0) + 1
439
-
440
- # 统计各来源的关键字数量
441
- source_info = get_keyword_source_info(info)
442
- source_name = source_info['name']
443
- if cat == 'project_custom' and project_custom_keywords:
444
- custom_info = project_custom_keywords[name]
445
- source_name = custom_info['file']
446
-
447
- source_key = f"{cat}:{source_name}"
448
- source_counts[source_key] = source_counts.get(source_key, 0) + 1
449
-
450
- if output_format == 'text':
451
- print(f"\n找到 {total_count} 个关键字:")
452
- for cat, count in category_counts.items():
453
- cat_names = {
454
- 'builtin': '内置', 'plugin': '插件', 'custom': '自定义',
455
- 'project_custom': '项目自定义', 'remote': '远程'
456
- }
457
- print(f" {cat_names.get(cat, cat)}: {count} 个")
458
- print("-" * 60)
459
-
460
- # 按类别和来源分组显示
461
- grouped = group_keywords_by_source(
462
- filtered_keywords, project_custom_keywords
145
+ # 使用统一的工具函数
146
+ try:
147
+ utils_list_keywords(
148
+ output_format=output_format,
149
+ name_filter=name_filter,
150
+ category_filter=category_filter,
151
+ include_remote=include_remote,
152
+ output_file=output_file,
153
+ print_summary=True
463
154
  )
464
-
465
- for category in [
466
- 'builtin', 'plugin', 'custom', 'project_custom', 'remote'
467
- ]:
468
- if category not in grouped or not grouped[category]:
469
- continue
470
-
471
- cat_names = {
472
- 'builtin': '内置关键字',
473
- 'plugin': '插件关键字',
474
- 'custom': '自定义关键字',
475
- 'project_custom': '项目自定义关键字',
476
- 'remote': '远程关键字'
477
- }
478
- print(f"\n=== {cat_names[category]} ===")
479
-
480
- for source_name, keyword_list in grouped[category].items():
481
- if len(grouped[category]) > 1: # 如果有多个来源,显示来源名
482
- print(f"\n--- {source_name} ---")
483
-
484
- for keyword_data in keyword_list:
485
- name = keyword_data['name']
486
- info = keyword_data['info']
487
- print()
488
- print(format_keyword_info_text(
489
- name, info, show_category=False,
490
- project_custom_keywords=project_custom_keywords
491
- ))
492
-
493
- elif output_format == 'json':
494
- keywords_data = {
495
- 'summary': {
496
- 'total_count': total_count,
497
- 'category_counts': category_counts,
498
- 'source_counts': source_counts
499
- },
500
- 'keywords': []
501
- }
502
-
503
- for name in sorted(filtered_keywords.keys()):
504
- info = filtered_keywords[name]
505
- keyword_data = format_keyword_info_json(
506
- name, info, project_custom_keywords
507
- )
508
- keywords_data['keywords'].append(keyword_data)
509
-
510
- json_output = json.dumps(keywords_data, ensure_ascii=False, indent=2)
511
-
512
- # 确定输出文件名
513
- if output_file is None:
514
- output_file = 'keywords.json'
515
-
516
- # 写入到文件
517
- try:
518
- with open(output_file, 'w', encoding='utf-8') as f:
519
- f.write(json_output)
520
- print(f"关键字信息已保存到文件: {output_file}")
521
- print(f"共 {total_count} 个关键字")
522
- for cat, count in category_counts.items():
523
- cat_names = {
524
- 'builtin': '内置', 'plugin': '插件', 'custom': '自定义',
525
- 'project_custom': '项目自定义', 'remote': '远程'
526
- }
527
- print(f" {cat_names.get(cat, cat)}: {count} 个")
528
- except Exception as e:
529
- print(f"保存文件失败: {e}")
530
- # 如果写入文件失败,则回退到打印
531
- print(json_output)
532
-
533
- elif output_format == 'html':
534
- keywords_data = {
535
- 'summary': {
536
- 'total_count': total_count,
537
- 'category_counts': category_counts,
538
- 'source_counts': source_counts
539
- },
540
- 'keywords': []
541
- }
542
-
543
- for name in sorted(filtered_keywords.keys()):
544
- info = filtered_keywords[name]
545
- keyword_data = format_keyword_info_json(
546
- name, info, project_custom_keywords
547
- )
548
- keywords_data['keywords'].append(keyword_data)
549
-
550
- # 确定输出文件名
551
- if output_file is None:
552
- output_file = 'keywords.html'
553
-
554
- # 生成HTML报告
555
- try:
556
- generate_html_report(keywords_data, output_file)
557
- print(f"共 {total_count} 个关键字")
558
- for cat, count in category_counts.items():
559
- cat_names = {
560
- 'builtin': '内置', 'plugin': '插件', 'custom': '自定义',
561
- 'project_custom': '项目自定义', 'remote': '远程'
562
- }
563
- print(f" {cat_names.get(cat, cat)}: {count} 个")
564
- except Exception as e:
565
- print(f"生成HTML报告失败: {e}")
566
- raise
155
+ except Exception as e:
156
+ print(f"列出关键字失败: {e}")
157
+ raise
567
158
 
568
159
 
569
160
  def load_yaml_variables(args):
@@ -776,8 +367,5 @@ def main_list_keywords():
776
367
  )
777
368
 
778
369
 
779
-
780
-
781
-
782
370
  if __name__ == '__main__':
783
371
  main()
@@ -321,8 +321,6 @@ class DSLExecutor:
321
321
  def _handle_start(self, node):
322
322
  """处理开始节点"""
323
323
  try:
324
- # 清空上下文,确保每个测试用例都有一个新的上下文
325
- self.test_context.clear()
326
324
  metadata = {}
327
325
  teardown_node = None
328
326
 
@@ -722,6 +720,7 @@ class DSLExecutor:
722
720
  kwargs['step_name'] = keyword_name # 内层步骤只显示关键字名称
723
721
  # 避免KeywordManager重复记录,由DSL执行器统一记录
724
722
  kwargs['skip_logging'] = True
723
+
725
724
  result = keyword_manager.execute(keyword_name, **kwargs)
726
725
 
727
726
  # 执行成功后记录关键字信息,包含行号
@@ -733,9 +732,26 @@ class DSLExecutor:
733
732
 
734
733
  return result
735
734
  except Exception as e:
736
- # 记录关键字执行失败,包含行号信息
735
+ # 记录关键字执行失败,包含行号信息和更详细的错误信息
736
+ error_details = (
737
+ f"关键字: {keyword_name}\n"
738
+ f"错误: {str(e)}\n"
739
+ f"错误类型: {type(e).__name__}"
740
+ f"{line_info}"
741
+ )
742
+
743
+ # 如果异常中包含了内部行号信息,提取并显示
744
+ if hasattr(e, 'args') and e.args:
745
+ error_msg = str(e.args[0])
746
+ # 尝试从错误消息中提取行号信息
747
+ import re
748
+ line_match = re.search(r'行(\d+)', error_msg)
749
+ if line_match:
750
+ inner_line = int(line_match.group(1))
751
+ error_details += f"\n内部错误行号: {inner_line}"
752
+
737
753
  allure.attach(
738
- f"关键字: {keyword_name}\n错误: {str(e)}{line_info}",
754
+ error_details,
739
755
  name="关键字调用失败",
740
756
  attachment_type=allure.attachment_type.TEXT
741
757
  )
@@ -0,0 +1,609 @@
1
+ """
2
+ pytest-dsl关键字工具
3
+
4
+ 提供统一的关键字列表查看、格式化和导出功能,供CLI和其他程序使用。
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from typing import Dict, Any, Optional, Union, List
10
+
11
+ from pytest_dsl.core.keyword_loader import (
12
+ load_all_keywords, categorize_keyword, get_keyword_source_info,
13
+ group_keywords_by_source
14
+ )
15
+ from pytest_dsl.core.keyword_manager import keyword_manager
16
+
17
+
18
+ class KeywordInfo:
19
+ """关键字信息类"""
20
+
21
+ def __init__(self, name: str, info: Dict[str, Any],
22
+ project_custom_keywords: Optional[Dict[str, Any]] = None):
23
+ self.name = name
24
+ self.info = info
25
+ self.project_custom_keywords = project_custom_keywords
26
+ self._category = None
27
+ self._source_info = None
28
+
29
+ @property
30
+ def category(self) -> str:
31
+ """获取关键字类别"""
32
+ if self._category is None:
33
+ self._category = categorize_keyword(
34
+ self.name, self.info, self.project_custom_keywords
35
+ )
36
+ return self._category
37
+
38
+ @property
39
+ def source_info(self) -> Dict[str, Any]:
40
+ """获取来源信息"""
41
+ if self._source_info is None:
42
+ self._source_info = get_keyword_source_info(self.info)
43
+ return self._source_info
44
+
45
+ @property
46
+ def parameters(self) -> List[Dict[str, Any]]:
47
+ """获取参数信息"""
48
+ if (self.category == 'project_custom' and
49
+ self.project_custom_keywords and
50
+ self.name in self.project_custom_keywords):
51
+ return self.project_custom_keywords[self.name].get('parameters', [])
52
+
53
+ # 对于其他类型的关键字
54
+ parameters = self.info.get('parameters', [])
55
+ param_list = []
56
+ for param in parameters:
57
+ param_data = {
58
+ 'name': getattr(param, 'name', str(param)),
59
+ 'mapping': getattr(param, 'mapping', ''),
60
+ 'description': getattr(param, 'description', '')
61
+ }
62
+
63
+ # 添加默认值信息
64
+ param_default = getattr(param, 'default', None)
65
+ if param_default is not None:
66
+ param_data['default'] = param_default
67
+
68
+ param_list.append(param_data)
69
+
70
+ return param_list
71
+
72
+ @property
73
+ def documentation(self) -> str:
74
+ """获取文档信息"""
75
+ func = self.info.get('func')
76
+ if func and hasattr(func, '__doc__') and func.__doc__:
77
+ return func.__doc__.strip()
78
+ return ""
79
+
80
+ @property
81
+ def file_location(self) -> Optional[str]:
82
+ """获取文件位置(仅适用于项目自定义关键字)"""
83
+ if (self.category == 'project_custom' and
84
+ self.project_custom_keywords and
85
+ self.name in self.project_custom_keywords):
86
+ return self.project_custom_keywords[self.name]['file']
87
+ return None
88
+
89
+ @property
90
+ def remote_info(self) -> Optional[Dict[str, str]]:
91
+ """获取远程关键字信息"""
92
+ if self.info.get('remote', False):
93
+ return {
94
+ 'alias': self.info.get('alias', ''),
95
+ 'original_name': self.info.get('original_name', self.name)
96
+ }
97
+ return None
98
+
99
+
100
+ class KeywordListOptions:
101
+ """关键字列表选项"""
102
+
103
+ def __init__(self,
104
+ output_format: str = 'json',
105
+ name_filter: Optional[str] = None,
106
+ category_filter: str = 'all',
107
+ include_remote: bool = False,
108
+ output_file: Optional[str] = None):
109
+ self.output_format = output_format
110
+ self.name_filter = name_filter
111
+ self.category_filter = category_filter
112
+ self.include_remote = include_remote
113
+ self.output_file = output_file
114
+
115
+ def should_include_keyword(self, keyword_info: KeywordInfo) -> bool:
116
+ """判断是否应该包含此关键字"""
117
+ # 名称过滤
118
+ if (self.name_filter and
119
+ self.name_filter.lower() not in keyword_info.name.lower()):
120
+ return False
121
+
122
+ # 远程关键字过滤
123
+ if (not self.include_remote and
124
+ keyword_info.info.get('remote', False)):
125
+ return False
126
+
127
+ # 类别过滤
128
+ if (self.category_filter != 'all' and
129
+ keyword_info.category != self.category_filter):
130
+ return False
131
+
132
+ return True
133
+
134
+
135
+ class KeywordFormatter:
136
+ """关键字格式化器"""
137
+
138
+ def __init__(self):
139
+ self.category_names = {
140
+ 'builtin': '内置',
141
+ 'plugin': '插件',
142
+ 'custom': '自定义',
143
+ 'project_custom': '项目自定义',
144
+ 'remote': '远程'
145
+ }
146
+
147
+ def format_text(self, keyword_info: KeywordInfo,
148
+ show_category: bool = True) -> str:
149
+ """格式化为文本格式"""
150
+ lines = []
151
+
152
+ # 关键字名称和类别
153
+ if show_category:
154
+ category_display = self.category_names.get(
155
+ keyword_info.category, '未知'
156
+ )
157
+ lines.append(f"关键字: {keyword_info.name} [{category_display}]")
158
+ else:
159
+ lines.append(f"关键字: {keyword_info.name}")
160
+
161
+ # 远程关键字特殊信息
162
+ if keyword_info.remote_info:
163
+ remote = keyword_info.remote_info
164
+ lines.append(f" 远程服务器: {remote['alias']}")
165
+ lines.append(f" 原始名称: {remote['original_name']}")
166
+
167
+ # 项目自定义关键字文件位置
168
+ if keyword_info.file_location:
169
+ lines.append(f" 文件位置: {keyword_info.file_location}")
170
+
171
+ # 参数信息
172
+ parameters = keyword_info.parameters
173
+ if parameters:
174
+ lines.append(" 参数:")
175
+ for param_info in parameters:
176
+ param_name = param_info['name']
177
+ param_mapping = param_info.get('mapping', '')
178
+ param_desc = param_info.get('description', '')
179
+ param_default = param_info.get('default', None)
180
+
181
+ # 构建参数描述
182
+ param_parts = []
183
+ if param_mapping and param_mapping != param_name:
184
+ param_parts.append(f"{param_name} ({param_mapping})")
185
+ else:
186
+ param_parts.append(param_name)
187
+
188
+ param_parts.append(f": {param_desc}")
189
+
190
+ # 添加默认值信息
191
+ if param_default is not None:
192
+ param_parts.append(f" (默认值: {param_default})")
193
+
194
+ lines.append(f" {''.join(param_parts)}")
195
+ else:
196
+ lines.append(" 参数: 无")
197
+
198
+ # 函数文档
199
+ if keyword_info.documentation:
200
+ lines.append(f" 说明: {keyword_info.documentation}")
201
+
202
+ return '\n'.join(lines)
203
+
204
+ def format_json(self, keyword_info: KeywordInfo) -> Dict[str, Any]:
205
+ """格式化为JSON格式"""
206
+ keyword_data = {
207
+ 'name': keyword_info.name,
208
+ 'category': keyword_info.category,
209
+ 'source_info': keyword_info.source_info,
210
+ 'parameters': keyword_info.parameters
211
+ }
212
+
213
+ # 远程关键字特殊信息
214
+ if keyword_info.remote_info:
215
+ keyword_data['remote'] = keyword_info.remote_info
216
+
217
+ # 项目自定义关键字文件位置
218
+ if keyword_info.file_location:
219
+ keyword_data['file_location'] = keyword_info.file_location
220
+
221
+ # 函数文档
222
+ if keyword_info.documentation:
223
+ keyword_data['documentation'] = keyword_info.documentation
224
+
225
+ return keyword_data
226
+
227
+
228
+ class KeywordLister:
229
+ """关键字列表器"""
230
+
231
+ def __init__(self):
232
+ self.formatter = KeywordFormatter()
233
+ self._project_custom_keywords = None
234
+
235
+ def get_keywords(self, options: KeywordListOptions) -> List[KeywordInfo]:
236
+ """获取关键字列表
237
+
238
+ Args:
239
+ options: 列表选项
240
+
241
+ Returns:
242
+ 符合条件的关键字信息列表
243
+ """
244
+ # 加载关键字
245
+ if self._project_custom_keywords is None:
246
+ self._project_custom_keywords = load_all_keywords(
247
+ include_remote=options.include_remote
248
+ )
249
+
250
+ # 获取所有注册的关键字
251
+ all_keywords = keyword_manager._keywords
252
+
253
+ if not all_keywords:
254
+ return []
255
+
256
+ # 过滤关键字
257
+ filtered_keywords = []
258
+ for name, info in all_keywords.items():
259
+ keyword_info = KeywordInfo(
260
+ name, info, self._project_custom_keywords
261
+ )
262
+
263
+ if options.should_include_keyword(keyword_info):
264
+ filtered_keywords.append(keyword_info)
265
+
266
+ return filtered_keywords
267
+
268
+ def get_keywords_summary(self, keywords: List[KeywordInfo]) -> Dict[str, Any]:
269
+ """获取关键字统计摘要
270
+
271
+ Args:
272
+ keywords: 关键字列表
273
+
274
+ Returns:
275
+ 统计摘要信息
276
+ """
277
+ total_count = len(keywords)
278
+ category_counts = {}
279
+ source_counts = {}
280
+
281
+ for keyword_info in keywords:
282
+ # 类别统计
283
+ cat = keyword_info.category
284
+ category_counts[cat] = category_counts.get(cat, 0) + 1
285
+
286
+ # 来源统计
287
+ source_name = keyword_info.source_info['name']
288
+ if keyword_info.file_location:
289
+ source_name = keyword_info.file_location
290
+
291
+ source_key = f"{cat}:{source_name}"
292
+ source_counts[source_key] = source_counts.get(source_key, 0) + 1
293
+
294
+ return {
295
+ 'total_count': total_count,
296
+ 'category_counts': category_counts,
297
+ 'source_counts': source_counts
298
+ }
299
+
300
+ def list_keywords_text(self, options: KeywordListOptions) -> str:
301
+ """以文本格式列出关键字"""
302
+ keywords = self.get_keywords(options)
303
+ summary = self.get_keywords_summary(keywords)
304
+
305
+ if not keywords:
306
+ if options.name_filter:
307
+ return f"未找到包含 '{options.name_filter}' 的关键字"
308
+ else:
309
+ return f"未找到 {options.category_filter} 类别的关键字"
310
+
311
+ lines = []
312
+
313
+ # 统计信息
314
+ lines.append(f"找到 {summary['total_count']} 个关键字:")
315
+ for cat, count in summary['category_counts'].items():
316
+ cat_display = self.formatter.category_names.get(cat, cat)
317
+ lines.append(f" {cat_display}: {count} 个")
318
+ lines.append("-" * 60)
319
+
320
+ # 按类别和来源分组显示
321
+ all_keywords_dict = {kw.name: kw.info for kw in keywords}
322
+ grouped = group_keywords_by_source(
323
+ all_keywords_dict, self._project_custom_keywords
324
+ )
325
+
326
+ for category in ['builtin', 'plugin', 'custom', 'project_custom', 'remote']:
327
+ if category not in grouped or not grouped[category]:
328
+ continue
329
+
330
+ cat_names = {
331
+ 'builtin': '内置关键字',
332
+ 'plugin': '插件关键字',
333
+ 'custom': '自定义关键字',
334
+ 'project_custom': '项目自定义关键字',
335
+ 'remote': '远程关键字'
336
+ }
337
+ lines.append(f"\n=== {cat_names[category]} ===")
338
+
339
+ for source_name, keyword_list in grouped[category].items():
340
+ if len(grouped[category]) > 1: # 如果有多个来源,显示来源名
341
+ lines.append(f"\n--- {source_name} ---")
342
+
343
+ for keyword_data in keyword_list:
344
+ name = keyword_data['name']
345
+ keyword_info = next(
346
+ kw for kw in keywords if kw.name == name)
347
+ lines.append("")
348
+ lines.append(self.formatter.format_text(
349
+ keyword_info, show_category=False
350
+ ))
351
+
352
+ return '\n'.join(lines)
353
+
354
+ def list_keywords_json(self, options: KeywordListOptions) -> Dict[str, Any]:
355
+ """以JSON格式列出关键字"""
356
+ keywords = self.get_keywords(options)
357
+ summary = self.get_keywords_summary(keywords)
358
+
359
+ keywords_data = {
360
+ 'summary': summary,
361
+ 'keywords': []
362
+ }
363
+
364
+ # 按名称排序
365
+ keywords.sort(key=lambda x: x.name)
366
+
367
+ for keyword_info in keywords:
368
+ keyword_data = self.formatter.format_json(keyword_info)
369
+ keywords_data['keywords'].append(keyword_data)
370
+
371
+ return keywords_data
372
+
373
+ def list_keywords(self, options: KeywordListOptions) -> Union[str, Dict[str, Any]]:
374
+ """列出关键字(根据格式返回不同类型)
375
+
376
+ Args:
377
+ options: 列表选项
378
+
379
+ Returns:
380
+ 文本格式返回str,JSON格式返回dict,HTML格式返回dict
381
+ """
382
+ if options.output_format == 'text':
383
+ return self.list_keywords_text(options)
384
+ elif options.output_format in ['json', 'html']:
385
+ return self.list_keywords_json(options)
386
+ else:
387
+ raise ValueError(f"不支持的输出格式: {options.output_format}")
388
+
389
+
390
+ def generate_html_report(keywords_data: Dict[str, Any], output_file: str):
391
+ """生成HTML格式的关键字报告
392
+
393
+ Args:
394
+ keywords_data: 关键字数据(JSON格式)
395
+ output_file: 输出文件路径
396
+ """
397
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
398
+
399
+ # 准备数据
400
+ summary = keywords_data['summary']
401
+ keywords = keywords_data['keywords']
402
+
403
+ # 按类别分组
404
+ categories = {}
405
+ for keyword in keywords:
406
+ category = keyword['category']
407
+ if category not in categories:
408
+ categories[category] = []
409
+ categories[category].append(keyword)
410
+
411
+ # 按来源分组
412
+ source_groups = {}
413
+ for keyword in keywords:
414
+ source_info = keyword.get('source_info', {})
415
+ category = keyword['category']
416
+ source_name = source_info.get('name', '未知来源')
417
+
418
+ # 构建分组键
419
+ if category == 'plugin':
420
+ group_key = f"插件 - {source_name}"
421
+ elif category == 'builtin':
422
+ group_key = "内置关键字"
423
+ elif category == 'project_custom':
424
+ group_key = f"项目自定义 - {keyword.get('file_location', source_name)}"
425
+ elif category == 'remote':
426
+ group_key = f"远程 - {source_name}"
427
+ else:
428
+ group_key = f"自定义 - {source_name}"
429
+
430
+ if group_key not in source_groups:
431
+ source_groups[group_key] = []
432
+ source_groups[group_key].append(keyword)
433
+
434
+ # 类别名称映射
435
+ category_names = {
436
+ 'builtin': '内置',
437
+ 'plugin': '插件',
438
+ 'custom': '自定义',
439
+ 'project_custom': '项目自定义',
440
+ 'remote': '远程'
441
+ }
442
+
443
+ # 设置Jinja2环境
444
+ template_dir = os.path.join(os.path.dirname(__file__), '..', 'templates')
445
+
446
+ env = Environment(
447
+ loader=FileSystemLoader(template_dir),
448
+ autoescape=select_autoescape(['html', 'xml'])
449
+ )
450
+
451
+ # 加载模板
452
+ template = env.get_template('keywords_report.html')
453
+
454
+ # 渲染模板
455
+ html_content = template.render(
456
+ summary=summary,
457
+ keywords=keywords,
458
+ categories=categories,
459
+ source_groups=source_groups,
460
+ category_names=category_names
461
+ )
462
+
463
+ # 写入文件
464
+ with open(output_file, 'w', encoding='utf-8') as f:
465
+ f.write(html_content)
466
+
467
+
468
+ # 创建全局实例
469
+ keyword_lister = KeywordLister()
470
+
471
+
472
+ # 便捷函数
473
+ def list_keywords(output_format: str = 'json',
474
+ name_filter: Optional[str] = None,
475
+ category_filter: str = 'all',
476
+ include_remote: bool = False,
477
+ output_file: Optional[str] = None,
478
+ print_summary: bool = True) -> Union[str, Dict[str, Any], None]:
479
+ """列出关键字的便捷函数
480
+
481
+ Args:
482
+ output_format: 输出格式 ('text', 'json', 'html')
483
+ name_filter: 名称过滤器(支持部分匹配)
484
+ category_filter: 类别过滤器
485
+ include_remote: 是否包含远程关键字
486
+ output_file: 输出文件路径(可选)
487
+ print_summary: 是否打印摘要信息
488
+
489
+ Returns:
490
+ 根据输出格式返回相应的数据,如果输出到文件则返回None
491
+ """
492
+ options = KeywordListOptions(
493
+ output_format=output_format,
494
+ name_filter=name_filter,
495
+ category_filter=category_filter,
496
+ include_remote=include_remote,
497
+ output_file=output_file
498
+ )
499
+
500
+ # 获取数据
501
+ result = keyword_lister.list_keywords(options)
502
+
503
+ if isinstance(result, str):
504
+ # 文本格式
505
+ if output_file:
506
+ with open(output_file, 'w', encoding='utf-8') as f:
507
+ f.write(result)
508
+ if print_summary:
509
+ print(f"关键字信息已保存到文件: {output_file}")
510
+ return None
511
+ else:
512
+ return result
513
+
514
+ elif isinstance(result, dict):
515
+ # JSON或HTML格式
516
+ if output_format == 'json':
517
+ json_output = json.dumps(result, ensure_ascii=False, indent=2)
518
+
519
+ if output_file:
520
+ with open(output_file, 'w', encoding='utf-8') as f:
521
+ f.write(json_output)
522
+ if print_summary:
523
+ _print_json_summary(result, output_file)
524
+ return None
525
+ else:
526
+ return result
527
+
528
+ elif output_format == 'html':
529
+ if not output_file:
530
+ output_file = 'keywords.html'
531
+
532
+ try:
533
+ generate_html_report(result, output_file)
534
+ if print_summary:
535
+ _print_json_summary(result, output_file, is_html=True)
536
+ return None
537
+ except Exception as e:
538
+ if print_summary:
539
+ print(f"生成HTML报告失败: {e}")
540
+ raise
541
+
542
+ return result
543
+
544
+
545
+ def _print_json_summary(keywords_data: Dict[str, Any],
546
+ output_file: str, is_html: bool = False):
547
+ """打印JSON数据的摘要信息"""
548
+ summary = keywords_data['summary']
549
+ total_count = summary['total_count']
550
+ category_counts = summary['category_counts']
551
+
552
+ if is_html:
553
+ print(f"HTML报告已生成: {output_file}")
554
+ else:
555
+ print(f"关键字信息已保存到文件: {output_file}")
556
+
557
+ print(f"共 {total_count} 个关键字")
558
+
559
+ category_names = {
560
+ 'builtin': '内置',
561
+ 'plugin': '插件',
562
+ 'custom': '自定义',
563
+ 'project_custom': '项目自定义',
564
+ 'remote': '远程'
565
+ }
566
+
567
+ for cat, count in category_counts.items():
568
+ cat_display = category_names.get(cat, cat)
569
+ print(f" {cat_display}: {count} 个")
570
+
571
+
572
+ def get_keyword_info(keyword_name: str,
573
+ include_remote: bool = False) -> Optional[KeywordInfo]:
574
+ """获取单个关键字的详细信息
575
+
576
+ Args:
577
+ keyword_name: 关键字名称
578
+ include_remote: 是否包含远程关键字
579
+
580
+ Returns:
581
+ 关键字信息对象,如果未找到则返回None
582
+ """
583
+ # 加载关键字
584
+ project_custom_keywords = load_all_keywords(include_remote=include_remote)
585
+
586
+ # 获取关键字信息
587
+ keyword_info = keyword_manager.get_keyword_info(keyword_name)
588
+ if not keyword_info:
589
+ return None
590
+
591
+ return KeywordInfo(keyword_name, keyword_info, project_custom_keywords)
592
+
593
+
594
+ def search_keywords(pattern: str,
595
+ include_remote: bool = False) -> List[KeywordInfo]:
596
+ """搜索匹配模式的关键字
597
+
598
+ Args:
599
+ pattern: 搜索模式(支持部分匹配)
600
+ include_remote: 是否包含远程关键字
601
+
602
+ Returns:
603
+ 匹配的关键字信息列表
604
+ """
605
+ options = KeywordListOptions(
606
+ name_filter=pattern,
607
+ include_remote=include_remote
608
+ )
609
+ return keyword_lister.get_keywords(options)
pytest_dsl/core/lexer.py CHANGED
@@ -90,6 +90,7 @@ t_PLACEHOLDER = (r'\$\{[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*'
90
90
  # 添加管道符的正则表达式定义
91
91
  t_PIPE = r'\|'
92
92
 
93
+
93
94
  # 添加@remote关键字的token规则
94
95
  def t_REMOTE_KEYWORD(t):
95
96
  r'@remote'
@@ -111,7 +112,14 @@ def t_STRING(t):
111
112
  r"""(\'\'\'[\s\S]*?\'\'\'|\"\"\"[\s\S]*?\"\"\"|'[^']*'|\"[^\"]*\")"""
112
113
  # 处理单引号和双引号的多行/单行字符串
113
114
  if t.value.startswith("'''") or t.value.startswith('"""'):
115
+ # 对于多行字符串,需要正确更新行号
116
+ original_value = t.value
114
117
  t.value = t.value[3:-3] # 去掉三引号
118
+
119
+ # 计算多行字符串包含的换行符数量,更新词法分析器的行号
120
+ newlines = original_value.count('\n')
121
+ if newlines > 0:
122
+ t.lexer.lineno += newlines
115
123
  else:
116
124
  t.value = t.value[1:-1] # 去掉单引号或双引号
117
125
  return t
pytest_dsl/core/parser.py CHANGED
@@ -198,7 +198,12 @@ def p_expr_atom(p):
198
198
  elif isinstance(p[1], Node):
199
199
  p[0] = p[1]
200
200
  else:
201
- p[0] = Node('Expression', value=p[1])
201
+ # 为基本表达式设置行号信息
202
+ expr_line = getattr(p.slice[1], 'lineno', None)
203
+ expr_node = Node('Expression', value=p[1])
204
+ if expr_line is not None:
205
+ expr_node.set_position(expr_line)
206
+ p[0] = expr_node
202
207
 
203
208
 
204
209
  def p_boolean_expr(p):
@@ -265,8 +270,29 @@ def p_keyword_call(p):
265
270
  line_number = getattr(p.slice[1], 'lineno', None)
266
271
 
267
272
  if len(p) == 6:
268
- p[0] = Node('KeywordCall', [p[5]], p[2], line_number=line_number)
273
+ # 对于有参数的关键字调用,尝试获取更精确的行号
274
+ # 优先使用关键字名称的行号,其次是左括号的行号
275
+ keyword_line = getattr(p.slice[2], 'lineno', None)
276
+ if keyword_line is not None:
277
+ line_number = keyword_line
278
+
279
+ keyword_node = Node('KeywordCall', [p[5]], p[2],
280
+ line_number=line_number)
281
+
282
+ # 为参数列表中的每个参数也设置行号信息(如果可用)
283
+ if p[5] and isinstance(p[5], list):
284
+ for param in p[5]:
285
+ if (hasattr(param, 'set_position') and
286
+ not hasattr(param, 'line_number')):
287
+ # 如果参数没有行号,使用关键字的行号作为默认值
288
+ param.set_position(line_number)
289
+
290
+ p[0] = keyword_node
269
291
  else:
292
+ # 对于无参数的关键字调用,也优先使用关键字名称的行号
293
+ keyword_line = getattr(p.slice[2], 'lineno', None)
294
+ if keyword_line is not None:
295
+ line_number = keyword_line
270
296
  p[0] = Node('KeywordCall', [[]], p[2], line_number=line_number)
271
297
 
272
298
 
@@ -286,7 +312,15 @@ def p_parameter_items(p):
286
312
 
287
313
  def p_parameter_item(p):
288
314
  '''parameter_item : ID COLON expression'''
289
- p[0] = Node('ParameterItem', value=p[1], children=[p[3]])
315
+ # 获取参数名的行号
316
+ param_line = getattr(p.slice[1], 'lineno', None)
317
+ param_node = Node('ParameterItem', value=p[1], children=[p[3]])
318
+
319
+ # 设置参数节点的行号
320
+ if param_line is not None:
321
+ param_node.set_position(param_line)
322
+
323
+ p[0] = param_node
290
324
 
291
325
 
292
326
  def p_teardown(p):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-dsl
3
- Version: 0.15.0
3
+ Version: 0.15.1
4
4
  Summary: A DSL testing framework based on pytest
5
5
  Author: Chen Shuanglin
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
- pytest_dsl/__init__.py,sha256=VZz4wtSOB6NkY8jkO69YM8xVfD0y0v6xwob-ljkATnE,4817
2
- pytest_dsl/cli.py,sha256=pkom2w-7AWEDVH-khQGfUDs3NTeiG9pmWLk04gKD-gM,27289
1
+ pytest_dsl/__init__.py,sha256=QAn8612I9Fn7SnnuaU34ZiAImZ2sHnuVaLuHi3S_3p4,5254
2
+ pytest_dsl/cli.py,sha256=bH2z5-Onqpir1ymB-DB-tu-fuI1nWHcwVo_3ve9Hgco,12845
3
3
  pytest_dsl/conftest_adapter.py,sha256=cevEb0oEZKTZfUrGe1-CmkFByxKhUtjuurBJP7kpLc0,149
4
4
  pytest_dsl/main_adapter.py,sha256=pUIPN_EzY3JCDlYK7yF_OeLDVqni8vtG15G7gVzPJXg,181
5
5
  pytest_dsl/plugin.py,sha256=CEwi-ci2rMevaAl9PwBw2WKXWRbXuHI1IkkDV0I0VIo,2224
@@ -9,7 +9,7 @@ pytest_dsl/core/auto_decorator.py,sha256=9Mga-GB4AzV5nkB6zpfaq8IuHa0KOH8LlFvnWyH
9
9
  pytest_dsl/core/auto_directory.py,sha256=egyTnVxtGs4P75EIivRauLRPJfN9aZpoGVvp_Ma72AM,2714
10
10
  pytest_dsl/core/context.py,sha256=fXpVQon666Lz_PCexjkWMBBr-Xcd1SkDMp2vHar6eIs,664
11
11
  pytest_dsl/core/custom_keyword_manager.py,sha256=F9aSbth4x4-5nHb0N5uG04CpgwGnQv4RDGeRKR2WuIk,16001
12
- pytest_dsl/core/dsl_executor.py,sha256=DQVPLCh0_Z1PQtMdU_We5Pv1im_CDEwLI0q-9DkzmIQ,47992
12
+ pytest_dsl/core/dsl_executor.py,sha256=aDK7rXbE5xLg4DcQ5VMbexSBuh4YGMDrVqtXENFCbsw,48638
13
13
  pytest_dsl/core/dsl_executor_utils.py,sha256=ZJLSYSsiKHqg53d3Bl--ZF8m9abd5kpqdevWl31KEZg,2276
14
14
  pytest_dsl/core/execution_tracker.py,sha256=Pwcxraxt_xkOouq32KBqola-OVfnbaomCoMTyUIqoN4,9476
15
15
  pytest_dsl/core/global_context.py,sha256=NcEcS2V61MT70tgAsGsFWQq0P3mKjtHQr1rgT3yTcyY,3535
@@ -21,8 +21,9 @@ pytest_dsl/core/http_client.py,sha256=hdx8gI4JCmq1-96pbiKeyKzSQUzPAi08cRNmljiPQm
21
21
  pytest_dsl/core/http_request.py,sha256=6e-gTztH3wu2eSW27Nc0uPmyWjB6oBwndx8Vqnu5uyg,60030
22
22
  pytest_dsl/core/keyword_loader.py,sha256=3GQ4w5Zf2XdkoJ85uYXh9YB93_8L8OAb7vvuKE3-gVA,13864
23
23
  pytest_dsl/core/keyword_manager.py,sha256=5WZWwlYk74kGHh1T6WjCuVd8eelq2-UEXvDIe4U7rEI,7730
24
- pytest_dsl/core/lexer.py,sha256=WaLzt9IhtHiA90Fg2WGgfVztveCUhtgxzANBaEiy-F8,4347
25
- pytest_dsl/core/parser.py,sha256=iDxZ6MHa6EtDUieWDBYP36f2QYPBxKnIdoWgTrpulkI,14471
24
+ pytest_dsl/core/keyword_utils.py,sha256=1zIKzbA8Lhifc97skzN4oJV-2Cljzf9aVSutwjU7LaA,19847
25
+ pytest_dsl/core/lexer.py,sha256=o_EJIadfhgyCImI73Y9ybqlBE9AisgA6nOhxpXNlaMw,4648
26
+ pytest_dsl/core/parser.py,sha256=LCyFNwrIO0dEup7HubnCkceAPqY7Qt5aoSKQddqG7Os,15898
26
27
  pytest_dsl/core/parsetab.py,sha256=o4XbFKwpsi3fYmfI_F6u5NSM61Qp6gTx-Sfh1jDINxI,31767
27
28
  pytest_dsl/core/plugin_discovery.py,sha256=3pt3EXJ7EPF0rkUlyDZMVHkIiTy2vicdIIQJkrHXZjY,8305
28
29
  pytest_dsl/core/utils.py,sha256=q0AMdw5HO33JvlA3UJeQ7IXh1Z_8K1QlwiM1_yiITrU,5350
@@ -72,9 +73,9 @@ pytest_dsl/remote/keyword_client.py,sha256=BL80MOaLroUi0v-9sLtkJ55g1R0Iw9SE1k11I
72
73
  pytest_dsl/remote/keyword_server.py,sha256=vGIE3Bhh461xX_u1U-Cf5nrWL2GQFYdtQdcMWfFIYgE,22320
73
74
  pytest_dsl/remote/variable_bridge.py,sha256=dv-d3Gq9ttvvrXM1fdlLtoSOPB6vRp0_GBOwX4wvcy8,7121
74
75
  pytest_dsl/templates/keywords_report.html,sha256=7x84iq6hi08nf1iQ95jZ3izcAUPx6JFm0_8xS85CYws,31241
75
- pytest_dsl-0.15.0.dist-info/licenses/LICENSE,sha256=Rguy8cb9sYhK6cmrBdXvwh94rKVDh2tVZEWptsHIsVM,1071
76
- pytest_dsl-0.15.0.dist-info/METADATA,sha256=C0m-LwNfCj8xXE_CzU1rIUVCuUsxQkeodkC7IQxxDSk,29655
77
- pytest_dsl-0.15.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
- pytest_dsl-0.15.0.dist-info/entry_points.txt,sha256=PLOBbH02OGY1XR1JDKIZB1Em87loUvbgMRWaag-5FhY,204
79
- pytest_dsl-0.15.0.dist-info/top_level.txt,sha256=4CrSx4uNqxj7NvK6k1y2JZrSrJSzi-UvPZdqpUhumWM,11
80
- pytest_dsl-0.15.0.dist-info/RECORD,,
76
+ pytest_dsl-0.15.1.dist-info/licenses/LICENSE,sha256=Rguy8cb9sYhK6cmrBdXvwh94rKVDh2tVZEWptsHIsVM,1071
77
+ pytest_dsl-0.15.1.dist-info/METADATA,sha256=tBZyIhox0tvZCCVK2aaP2EIUNyawij9coLH0NFn8PUo,29655
78
+ pytest_dsl-0.15.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
+ pytest_dsl-0.15.1.dist-info/entry_points.txt,sha256=PLOBbH02OGY1XR1JDKIZB1Em87loUvbgMRWaag-5FhY,204
80
+ pytest_dsl-0.15.1.dist-info/top_level.txt,sha256=4CrSx4uNqxj7NvK6k1y2JZrSrJSzi-UvPZdqpUhumWM,11
81
+ pytest_dsl-0.15.1.dist-info/RECORD,,