Kea2-python 0.2.3__tar.gz → 0.2.4__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.
Potentially problematic release.
This version of Kea2-python might be problematic. Click here for more details.
- {kea2_python-0.2.3 → kea2_python-0.2.4}/Kea2_python.egg-info/PKG-INFO +4 -5
- {kea2_python-0.2.3 → kea2_python-0.2.4}/PKG-INFO +4 -5
- {kea2_python-0.2.3 → kea2_python-0.2.4}/README.md +3 -4
- kea2_python-0.2.4/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/bug_report_generator.py +188 -80
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/cli.py +42 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/fastbotManager.py +1 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/keaUtils.py +2 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/kea_launcher.py +2 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/logWatcher.py +13 -1
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/templates/bug_report_template.html +429 -22
- {kea2_python-0.2.3 → kea2_python-0.2.4}/pyproject.toml +1 -1
- kea2_python-0.2.3/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/Kea2_python.egg-info/SOURCES.txt +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/Kea2_python.egg-info/dependency_links.txt +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/Kea2_python.egg-info/entry_points.txt +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/Kea2_python.egg-info/requires.txt +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/Kea2_python.egg-info/top_level.txt +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/LICENSE +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/__init__.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/absDriver.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/adbUtils.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot-thirdpart.jar +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/abl.strings +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/awl.strings +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/max.config +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/max.strings +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_configs/widget.block.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/framework.jar +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/kea2-thirdpart.jar +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/quicktest.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/resultSyncer.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/u2Driver.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/utils.py +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/setup.cfg +0 -0
- {kea2_python-0.2.3 → kea2_python-0.2.4}/tests/test_u2Selector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A python library for supporting and customizing automated UI testing for mobile apps
|
|
5
5
|
Author-email: Xixian Liang <xixian@stu.ecnu.edu.cn>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -18,22 +18,21 @@ Dynamic: license-file
|
|
|
18
18
|
[](https://pepy.tech/projects/kea2-python)
|
|
19
19
|

|
|
20
20
|
|
|
21
|
-
|
|
22
21
|
<div>
|
|
23
|
-
<img src="https://github.com/user-attachments/assets/
|
|
22
|
+
<img src="https://github.com/user-attachments/assets/84e47b87-2dd2-4d7e-91d1-e8c1d1db0cf4" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
24
23
|
</div>
|
|
25
24
|
|
|
26
25
|
### Github repo link
|
|
27
26
|
[https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
28
27
|
|
|
29
|
-
### [点击此处:查看中文文档](README_cn.md)
|
|
28
|
+
### [点击此处:查看中文文档](README_cn.md)
|
|
30
29
|
|
|
31
30
|
## About
|
|
32
31
|
|
|
33
32
|
Kea2 is an easy-to-use tool for fuzzing mobile apps. Its key *novelty* is able to fuse automated UI testing with scripts (usually written by human), thus empowering automated UI testing with human intelligence for effectively finding *crashing bugs* as well as *non-crashing functional (logic) bugs*.
|
|
34
33
|
|
|
35
34
|
Kea2 is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android), *an industrial-strength automated UI testing tool*, and [uiautomator2](https://github.com/openatx/uiautomator2), *an easy-to-use and stable Android automation library*.
|
|
36
|
-
Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
|
|
35
|
+
Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
|
|
37
36
|
|
|
38
37
|
## Novelty & Important features
|
|
39
38
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A python library for supporting and customizing automated UI testing for mobile apps
|
|
5
5
|
Author-email: Xixian Liang <xixian@stu.ecnu.edu.cn>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -18,22 +18,21 @@ Dynamic: license-file
|
|
|
18
18
|
[](https://pepy.tech/projects/kea2-python)
|
|
19
19
|

|
|
20
20
|
|
|
21
|
-
|
|
22
21
|
<div>
|
|
23
|
-
<img src="https://github.com/user-attachments/assets/
|
|
22
|
+
<img src="https://github.com/user-attachments/assets/84e47b87-2dd2-4d7e-91d1-e8c1d1db0cf4" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
24
23
|
</div>
|
|
25
24
|
|
|
26
25
|
### Github repo link
|
|
27
26
|
[https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
28
27
|
|
|
29
|
-
### [点击此处:查看中文文档](README_cn.md)
|
|
28
|
+
### [点击此处:查看中文文档](README_cn.md)
|
|
30
29
|
|
|
31
30
|
## About
|
|
32
31
|
|
|
33
32
|
Kea2 is an easy-to-use tool for fuzzing mobile apps. Its key *novelty* is able to fuse automated UI testing with scripts (usually written by human), thus empowering automated UI testing with human intelligence for effectively finding *crashing bugs* as well as *non-crashing functional (logic) bugs*.
|
|
34
33
|
|
|
35
34
|
Kea2 is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android), *an industrial-strength automated UI testing tool*, and [uiautomator2](https://github.com/openatx/uiautomator2), *an easy-to-use and stable Android automation library*.
|
|
36
|
-
Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
|
|
35
|
+
Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
|
|
37
36
|
|
|
38
37
|
## Novelty & Important features
|
|
39
38
|
|
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
[](https://pepy.tech/projects/kea2-python)
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
<div>
|
|
9
|
-
<img src="https://github.com/user-attachments/assets/
|
|
8
|
+
<img src="https://github.com/user-attachments/assets/84e47b87-2dd2-4d7e-91d1-e8c1d1db0cf4" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
10
9
|
</div>
|
|
11
10
|
|
|
12
11
|
### Github repo link
|
|
13
12
|
[https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
14
13
|
|
|
15
|
-
### [点击此处:查看中文文档](README_cn.md)
|
|
14
|
+
### [点击此处:查看中文文档](README_cn.md)
|
|
16
15
|
|
|
17
16
|
## About
|
|
18
17
|
|
|
19
18
|
Kea2 is an easy-to-use tool for fuzzing mobile apps. Its key *novelty* is able to fuse automated UI testing with scripts (usually written by human), thus empowering automated UI testing with human intelligence for effectively finding *crashing bugs* as well as *non-crashing functional (logic) bugs*.
|
|
20
19
|
|
|
21
20
|
Kea2 is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android), *an industrial-strength automated UI testing tool*, and [uiautomator2](https://github.com/openatx/uiautomator2), *an easy-to-use and stable Android automation library*.
|
|
22
|
-
Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
|
|
21
|
+
Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
|
|
23
22
|
|
|
24
23
|
## Novelty & Important features
|
|
25
24
|
|
|
Binary file
|
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Dict, TypedDict, List, Deque, NewType, Union
|
|
5
|
+
from typing import Dict, TypedDict, List, Deque, NewType, Union, Optional
|
|
6
6
|
from collections import deque
|
|
7
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
8
|
|
|
@@ -28,12 +28,13 @@ class StepData(TypedDict):
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class CovData(TypedDict):
|
|
31
|
-
stepsCount: int
|
|
31
|
+
stepsCount: int
|
|
32
32
|
coverage: float
|
|
33
33
|
totalActivitiesCount: int
|
|
34
34
|
testedActivitiesCount: int
|
|
35
35
|
totalActivities: List[str]
|
|
36
36
|
testedActivities: List[str]
|
|
37
|
+
activityCountHistory: Dict[str, int]
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
class ReportData(TypedDict):
|
|
@@ -53,6 +54,8 @@ class ReportData(TypedDict):
|
|
|
53
54
|
property_error_details: Dict[str, List[Dict]] # Support multiple errors per property
|
|
54
55
|
screenshot_info: Dict
|
|
55
56
|
coverage_trend: List
|
|
57
|
+
property_execution_trend: List # Track executed properties count over steps
|
|
58
|
+
activity_count_history: Dict[str, int] # Activity traversal count from final coverage data
|
|
56
59
|
|
|
57
60
|
|
|
58
61
|
class PropertyExecResult(TypedDict):
|
|
@@ -62,6 +65,49 @@ class PropertyExecResult(TypedDict):
|
|
|
62
65
|
error: int
|
|
63
66
|
|
|
64
67
|
|
|
68
|
+
@dataclass
|
|
69
|
+
class PropertyExecInfo:
|
|
70
|
+
"""Class representing property execution information from property_exec_info file"""
|
|
71
|
+
prop_name: str
|
|
72
|
+
state: str # start, pass, fail, error
|
|
73
|
+
traceback: str
|
|
74
|
+
start_steps_count: int
|
|
75
|
+
occurrence_count: int = 1
|
|
76
|
+
short_description: str = ""
|
|
77
|
+
start_steps_count_list: List[int] = None
|
|
78
|
+
|
|
79
|
+
def __post_init__(self):
|
|
80
|
+
if self.start_steps_count_list is None:
|
|
81
|
+
self.start_steps_count_list = [self.start_steps_count]
|
|
82
|
+
if not self.short_description and self.traceback:
|
|
83
|
+
self.short_description = self._extract_error_summary(self.traceback)
|
|
84
|
+
|
|
85
|
+
def _extract_error_summary(self, traceback: str) -> str:
|
|
86
|
+
"""Extract a short error summary from the full traceback"""
|
|
87
|
+
try:
|
|
88
|
+
lines = traceback.strip().split('\n')
|
|
89
|
+
for line in reversed(lines):
|
|
90
|
+
line = line.strip()
|
|
91
|
+
if line and not line.startswith(' '):
|
|
92
|
+
return line
|
|
93
|
+
return "Unknown error"
|
|
94
|
+
except Exception:
|
|
95
|
+
return "Error parsing traceback"
|
|
96
|
+
|
|
97
|
+
def get_error_hash(self) -> int:
|
|
98
|
+
"""Generate hash key for error deduplication"""
|
|
99
|
+
return hash((self.state, self.traceback))
|
|
100
|
+
|
|
101
|
+
def is_error_state(self) -> bool:
|
|
102
|
+
"""Check if this is an error or fail state"""
|
|
103
|
+
return self.state in ["fail", "error"]
|
|
104
|
+
|
|
105
|
+
def add_occurrence(self, start_steps_count: int):
|
|
106
|
+
"""Add another occurrence of the same error"""
|
|
107
|
+
self.occurrence_count += 1
|
|
108
|
+
self.start_steps_count_list.append(start_steps_count)
|
|
109
|
+
|
|
110
|
+
|
|
65
111
|
PropertyName = NewType("PropertyName", str)
|
|
66
112
|
TestResult = NewType("TestResult", Dict[PropertyName, PropertyExecResult])
|
|
67
113
|
|
|
@@ -239,11 +285,15 @@ class BugReportGenerator:
|
|
|
239
285
|
"property_stats": [],
|
|
240
286
|
"property_error_details": {},
|
|
241
287
|
"screenshot_info": {},
|
|
242
|
-
"coverage_trend": []
|
|
288
|
+
"coverage_trend": [],
|
|
289
|
+
"property_execution_trend": [],
|
|
290
|
+
"activity_count_history": {}
|
|
243
291
|
}
|
|
244
292
|
|
|
245
293
|
# Parse steps.log file to get test step numbers and screenshot mappings
|
|
246
294
|
property_violations = {} # Store multiple violation records for each property
|
|
295
|
+
executed_properties_by_step = {} # Track executed properties at each step: {step_count: set()}
|
|
296
|
+
executed_properties = set() # Track unique executed properties
|
|
247
297
|
|
|
248
298
|
if not self.data_path.steps_log.exists():
|
|
249
299
|
logger.error(f"{self.data_path.steps_log} not exists")
|
|
@@ -279,11 +329,18 @@ class BugReportGenerator:
|
|
|
279
329
|
if screenshot and screenshot not in data["screenshot_info"]:
|
|
280
330
|
self._add_screenshot_info(step_data, step_index, data)
|
|
281
331
|
|
|
282
|
-
# Process ScriptInfo for property violations
|
|
332
|
+
# Process ScriptInfo for property violations and execution tracking
|
|
283
333
|
if step_type == "ScriptInfo":
|
|
284
334
|
try:
|
|
285
335
|
property_name = info.get("propName", "")
|
|
286
336
|
state = info.get("state", "")
|
|
337
|
+
|
|
338
|
+
# Track executed properties (properties that have been started)
|
|
339
|
+
if property_name and state == "start":
|
|
340
|
+
executed_properties.add(property_name)
|
|
341
|
+
# Record the monkey steps count for this property execution
|
|
342
|
+
executed_properties_by_step[monkey_events_count] = executed_properties.copy()
|
|
343
|
+
|
|
287
344
|
current_property, current_test = self._process_script_info(
|
|
288
345
|
property_name, state, step_index, screenshot,
|
|
289
346
|
current_property, current_test, property_violations
|
|
@@ -334,6 +391,10 @@ class BugReportGenerator:
|
|
|
334
391
|
data["tested_activities"] = final_trend["testedActivities"]
|
|
335
392
|
data["total_activities_count"] = final_trend["totalActivitiesCount"]
|
|
336
393
|
data["tested_activities_count"] = final_trend["testedActivitiesCount"]
|
|
394
|
+
data["activity_count_history"] = final_trend["activityCountHistory"]
|
|
395
|
+
|
|
396
|
+
# Generate property execution trend aligned with coverage trend
|
|
397
|
+
data["property_execution_trend"] = self._generate_property_execution_trend(executed_properties_by_step)
|
|
337
398
|
|
|
338
399
|
# Generate Property Violations list
|
|
339
400
|
self._generate_property_violations_list(property_violations, data)
|
|
@@ -373,16 +434,16 @@ class BugReportGenerator:
|
|
|
373
434
|
|
|
374
435
|
def _mark_screenshot_interaction(self, step_type: str, screenshot_name: str, action_type: str, position: Union[List, tuple]) -> bool:
|
|
375
436
|
"""
|
|
376
|
-
|
|
437
|
+
Mark interaction on screenshot with colored rectangle
|
|
377
438
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
439
|
+
Args:
|
|
440
|
+
step_type (str): Type of the step (Monkey or Script)
|
|
441
|
+
screenshot_name (str): Name of the screenshot file
|
|
442
|
+
action_type (str): Type of action (CLICK/LONG_CLICK/SCROLL for Monkey, click/setText/swipe for Script)
|
|
443
|
+
position: Position coordinates or parameters (format varies by action type)
|
|
383
444
|
|
|
384
|
-
|
|
385
|
-
|
|
445
|
+
Returns:
|
|
446
|
+
bool: True if marking was successful, False otherwise
|
|
386
447
|
"""
|
|
387
448
|
screenshot_path: Path = self.data_path.screenshots_dir / screenshot_name
|
|
388
449
|
if not screenshot_path.exists():
|
|
@@ -492,7 +553,10 @@ class BugReportGenerator:
|
|
|
492
553
|
'property_stats': data["property_stats"],
|
|
493
554
|
'property_error_details': data["property_error_details"],
|
|
494
555
|
'coverage_data': coverage_trend_json,
|
|
495
|
-
'take_screenshots': self.take_screenshots # Pass screenshot setting to template
|
|
556
|
+
'take_screenshots': self.take_screenshots, # Pass screenshot setting to template
|
|
557
|
+
'property_execution_trend': data["property_execution_trend"],
|
|
558
|
+
'property_execution_data': json.dumps(data["property_execution_trend"]),
|
|
559
|
+
'activity_count_history': data["activity_count_history"]
|
|
496
560
|
}
|
|
497
561
|
|
|
498
562
|
# Check if template exists, if not create it
|
|
@@ -522,8 +586,10 @@ class BugReportGenerator:
|
|
|
522
586
|
caption = ""
|
|
523
587
|
|
|
524
588
|
if step_data["Type"] == "Monkey":
|
|
525
|
-
# Extract 'act' attribute for Monkey type and
|
|
526
|
-
|
|
589
|
+
# Extract 'act' attribute for Monkey type and add MonkeyStepsCount
|
|
590
|
+
monkey_steps_count = step_data.get('MonkeyStepsCount', 'N/A')
|
|
591
|
+
action = step_data['Info'].get('act', 'N/A')
|
|
592
|
+
caption = f"Monkey Step {monkey_steps_count}: {action}"
|
|
527
593
|
elif step_data["Type"] == "Script":
|
|
528
594
|
# Extract 'method' attribute for Script type
|
|
529
595
|
caption = f"{step_data['Info'].get('method', 'N/A')}"
|
|
@@ -630,90 +696,132 @@ class BugReportGenerator:
|
|
|
630
696
|
Returns:
|
|
631
697
|
Dict[str, List[Dict]]: Mapping of property names to their error tracebacks with context
|
|
632
698
|
"""
|
|
633
|
-
error_details = {}
|
|
634
|
-
|
|
635
699
|
if not self.data_path.property_exec_info.exists():
|
|
636
700
|
logger.warning(f"Property exec info file {self.data_path.property_exec_info} not found")
|
|
637
|
-
return
|
|
701
|
+
return {}
|
|
638
702
|
|
|
639
703
|
try:
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
for line_number, line in enumerate(f, 1):
|
|
645
|
-
line = line.strip()
|
|
646
|
-
if not line:
|
|
647
|
-
continue
|
|
648
|
-
|
|
649
|
-
try:
|
|
650
|
-
exec_info = json.loads(line)
|
|
651
|
-
prop_name = exec_info.get("propName", "")
|
|
652
|
-
state = exec_info.get("state", "")
|
|
653
|
-
tb = exec_info.get("tb", "")
|
|
654
|
-
|
|
655
|
-
# Only process error details for failed or error states
|
|
656
|
-
if prop_name and state in ["fail", "error"] and tb:
|
|
657
|
-
if prop_name not in error_hash_map:
|
|
658
|
-
error_hash_map[prop_name] = {}
|
|
659
|
-
|
|
660
|
-
# Create hash key for this specific error (state + traceback)
|
|
661
|
-
error_hash = hash((state, tb))
|
|
662
|
-
|
|
663
|
-
if error_hash in error_hash_map[prop_name]:
|
|
664
|
-
# Error already exists, increment count
|
|
665
|
-
error_hash_map[prop_name][error_hash]["occurrence_count"] += 1
|
|
666
|
-
else:
|
|
667
|
-
# New error, create entry
|
|
668
|
-
short_desc = self._extract_error_summary(tb)
|
|
669
|
-
error_hash_map[prop_name][error_hash] = {
|
|
670
|
-
"state": state,
|
|
671
|
-
"traceback": tb,
|
|
672
|
-
"occurrence_count": 1,
|
|
673
|
-
"short_description": short_desc
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
except json.JSONDecodeError as e:
|
|
677
|
-
logger.warning(f"Failed to parse property exec info line {line_number}: {line[:100]}... Error: {e}")
|
|
678
|
-
continue
|
|
679
|
-
|
|
680
|
-
# Convert hash map to list format for template compatibility
|
|
681
|
-
for prop_name, hash_dict in error_hash_map.items():
|
|
682
|
-
error_details[prop_name] = list(hash_dict.values())
|
|
683
|
-
# Sort by occurrence count (descending) to show most frequent errors first
|
|
684
|
-
error_details[prop_name].sort(key=lambda x: x["occurrence_count"], reverse=True)
|
|
685
|
-
|
|
704
|
+
property_exec_infos = self._parse_property_exec_infos()
|
|
705
|
+
return self._group_errors_by_property(property_exec_infos)
|
|
706
|
+
|
|
686
707
|
except Exception as e:
|
|
687
708
|
logger.error(f"Error reading property exec info file: {e}")
|
|
709
|
+
return {}
|
|
710
|
+
|
|
711
|
+
def _parse_property_exec_infos(self) -> List[PropertyExecInfo]:
|
|
712
|
+
"""Parse property execution info from file"""
|
|
713
|
+
exec_infos = []
|
|
714
|
+
|
|
715
|
+
with open(self.data_path.property_exec_info, "r", encoding="utf-8") as f:
|
|
716
|
+
for line_number, line in enumerate(f, 1):
|
|
717
|
+
line = line.strip()
|
|
718
|
+
if not line:
|
|
719
|
+
continue
|
|
720
|
+
|
|
721
|
+
try:
|
|
722
|
+
exec_info_data = json.loads(line)
|
|
723
|
+
prop_name = exec_info_data.get("propName", "")
|
|
724
|
+
state = exec_info_data.get("state", "")
|
|
725
|
+
tb = exec_info_data.get("tb", "")
|
|
726
|
+
start_steps_count = exec_info_data.get("startStepsCount", 0)
|
|
727
|
+
|
|
728
|
+
exec_info = PropertyExecInfo(
|
|
729
|
+
prop_name=prop_name,
|
|
730
|
+
state=state,
|
|
731
|
+
traceback=tb,
|
|
732
|
+
start_steps_count=start_steps_count
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
if exec_info.is_error_state() and prop_name and tb:
|
|
736
|
+
exec_infos.append(exec_info)
|
|
737
|
+
|
|
738
|
+
except json.JSONDecodeError as e:
|
|
739
|
+
logger.warning(f"Failed to parse property exec info line {line_number}: {line[:100]}... Error: {e}")
|
|
740
|
+
continue
|
|
741
|
+
|
|
742
|
+
return exec_infos
|
|
743
|
+
|
|
744
|
+
def _group_errors_by_property(self, exec_infos: List[PropertyExecInfo]) -> Dict[str, List[Dict]]:
|
|
745
|
+
"""Group errors by property name and deduplicate"""
|
|
746
|
+
error_details = {}
|
|
747
|
+
|
|
748
|
+
for exec_info in exec_infos:
|
|
749
|
+
prop_name = exec_info.prop_name
|
|
688
750
|
|
|
689
|
-
|
|
751
|
+
if prop_name not in error_details:
|
|
752
|
+
error_details[prop_name] = {}
|
|
753
|
+
|
|
754
|
+
error_hash = exec_info.get_error_hash()
|
|
755
|
+
|
|
756
|
+
if error_hash in error_details[prop_name]:
|
|
757
|
+
# Error already exists, add occurrence
|
|
758
|
+
error_details[prop_name][error_hash].add_occurrence(exec_info.start_steps_count)
|
|
759
|
+
else:
|
|
760
|
+
# New error, create entry
|
|
761
|
+
error_details[prop_name][error_hash] = exec_info
|
|
762
|
+
|
|
763
|
+
# Convert to template-compatible format
|
|
764
|
+
result = {}
|
|
765
|
+
for prop_name, hash_dict in error_details.items():
|
|
766
|
+
result[prop_name] = []
|
|
767
|
+
for exec_info in hash_dict.values():
|
|
768
|
+
result[prop_name].append({
|
|
769
|
+
"state": exec_info.state,
|
|
770
|
+
"traceback": exec_info.traceback,
|
|
771
|
+
"occurrence_count": exec_info.occurrence_count,
|
|
772
|
+
"short_description": exec_info.short_description,
|
|
773
|
+
"startStepsCountList": exec_info.start_steps_count_list
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
# Sort by earliest startStepsCount, then by occurrence count (descending)
|
|
777
|
+
result[prop_name].sort(key=lambda x: (min(x["startStepsCountList"]), -x["occurrence_count"]))
|
|
778
|
+
|
|
779
|
+
return result
|
|
690
780
|
|
|
691
|
-
def
|
|
781
|
+
def _generate_property_execution_trend(self, executed_properties_by_step: Dict[int, set]) -> List[Dict]:
|
|
692
782
|
"""
|
|
693
|
-
|
|
783
|
+
Generate property execution trend aligned with coverage trend
|
|
694
784
|
|
|
695
785
|
Args:
|
|
696
|
-
|
|
786
|
+
executed_properties_by_step: Dictionary containing executed properties at each step
|
|
697
787
|
|
|
698
788
|
Returns:
|
|
699
|
-
|
|
789
|
+
List[Dict]: Property execution trend data aligned with coverage trend
|
|
700
790
|
"""
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
791
|
+
property_execution_trend = []
|
|
792
|
+
|
|
793
|
+
# Get step points from coverage trend to ensure alignment
|
|
794
|
+
coverage_step_points = []
|
|
795
|
+
if self.cov_trend:
|
|
796
|
+
coverage_step_points = [cov_data["stepsCount"] for cov_data in self.cov_trend]
|
|
797
|
+
|
|
798
|
+
# If no coverage data, use property execution data points
|
|
799
|
+
if not coverage_step_points and executed_properties_by_step:
|
|
800
|
+
coverage_step_points = sorted(executed_properties_by_step.keys())
|
|
801
|
+
|
|
802
|
+
# Generate property execution data for each coverage step point
|
|
803
|
+
for step_count in coverage_step_points:
|
|
804
|
+
# Find the latest executed properties count up to this step
|
|
805
|
+
executed_count = 0
|
|
806
|
+
latest_step = 0
|
|
807
|
+
|
|
808
|
+
for exec_step in executed_properties_by_step.keys():
|
|
809
|
+
if exec_step <= step_count and exec_step >= latest_step:
|
|
810
|
+
latest_step = exec_step
|
|
811
|
+
executed_count = len(executed_properties_by_step[exec_step])
|
|
812
|
+
|
|
813
|
+
property_execution_trend.append({
|
|
814
|
+
"stepsCount": step_count,
|
|
815
|
+
"executedPropertiesCount": executed_count
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
return property_execution_trend
|
|
711
819
|
|
|
712
820
|
|
|
713
821
|
if __name__ == "__main__":
|
|
714
822
|
print("Generating bug report")
|
|
715
823
|
# OUTPUT_PATH = "<Your output path>"
|
|
716
|
-
OUTPUT_PATH = "P:/Python/Kea2/output/
|
|
824
|
+
OUTPUT_PATH = "P:/Python/Kea2/output/res_2025070814_4842540549"
|
|
717
825
|
|
|
718
826
|
report_generator = BugReportGenerator()
|
|
719
827
|
report_path = report_generator.generate_report(OUTPUT_PATH)
|
|
@@ -46,6 +46,34 @@ def cmd_load_configs(args):
|
|
|
46
46
|
pass
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
def cmd_report(args):
|
|
50
|
+
from .bug_report_generator import BugReportGenerator
|
|
51
|
+
try:
|
|
52
|
+
report_dir = args.path
|
|
53
|
+
if not report_dir:
|
|
54
|
+
logger.error("Report directory path is required. Use -p to specify the path.")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
report_path = Path(report_dir)
|
|
58
|
+
if not report_path.exists():
|
|
59
|
+
logger.error(f"Report directory does not exist: {report_dir}")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
logger.debug(f"Generating test report from directory: {report_dir}")
|
|
63
|
+
|
|
64
|
+
generator = BugReportGenerator()
|
|
65
|
+
report_file = generator.generate_report(report_path)
|
|
66
|
+
|
|
67
|
+
if report_file:
|
|
68
|
+
logger.debug(f"Test report generated successfully: {report_file}")
|
|
69
|
+
print(f"Report saved to: {report_file}", flush=True)
|
|
70
|
+
else:
|
|
71
|
+
logger.error("Failed to generate test report")
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Error generating test report: {e}")
|
|
75
|
+
|
|
76
|
+
|
|
49
77
|
def cmd_run(args):
|
|
50
78
|
base_dir = getProjectRoot()
|
|
51
79
|
if base_dir is None:
|
|
@@ -60,6 +88,20 @@ _commands = [
|
|
|
60
88
|
action=cmd_init,
|
|
61
89
|
command="init",
|
|
62
90
|
help="init the Kea2 project in current directory",
|
|
91
|
+
),
|
|
92
|
+
dict(
|
|
93
|
+
action=cmd_report,
|
|
94
|
+
command="report",
|
|
95
|
+
help="generate test report from existing test results",
|
|
96
|
+
flags=[
|
|
97
|
+
dict(
|
|
98
|
+
name=["report_dir"],
|
|
99
|
+
args=["-p", "--path"],
|
|
100
|
+
type=str,
|
|
101
|
+
required=True,
|
|
102
|
+
help="Path to the directory containing test results"
|
|
103
|
+
)
|
|
104
|
+
]
|
|
63
105
|
)
|
|
64
106
|
]
|
|
65
107
|
|
|
@@ -181,6 +181,7 @@ class FastbotManager:
|
|
|
181
181
|
"--running-minutes", f"{self.options.running_mins}",
|
|
182
182
|
"--throttle", f"{self.options.throttle}",
|
|
183
183
|
"--bugreport",
|
|
184
|
+
"--output-directory", f"{self.options.device_output_root}/output_{self.options.log_stamp}",
|
|
184
185
|
]
|
|
185
186
|
|
|
186
187
|
if self.options.profile_period:
|
|
@@ -164,6 +164,8 @@ class Options:
|
|
|
164
164
|
f"char: `{char}` is illegal in --log-stamp. current stamp: {self.log_stamp}"
|
|
165
165
|
)
|
|
166
166
|
STAMP = self.log_stamp
|
|
167
|
+
|
|
168
|
+
self.log_stamp = STAMP
|
|
167
169
|
|
|
168
170
|
self.output_dir = Path(self.output_dir).absolute() / f"res_{STAMP}"
|
|
169
171
|
LOGFILE = f"fastbot_{STAMP}.log"
|
|
@@ -170,6 +170,8 @@ def driver_info_logger(args):
|
|
|
170
170
|
print(" log_stamp:", args.log_stamp, flush=True)
|
|
171
171
|
if args.take_screenshots:
|
|
172
172
|
print(" take_screenshots:", args.take_screenshots, flush=True)
|
|
173
|
+
if args.max_step:
|
|
174
|
+
print(" max_step:", args.max_step, flush=True)
|
|
173
175
|
|
|
174
176
|
|
|
175
177
|
def parse_args(argv: List):
|
|
@@ -75,7 +75,19 @@ class LogWatcher:
|
|
|
75
75
|
self.end_flag = True
|
|
76
76
|
if self.t:
|
|
77
77
|
self.t.join()
|
|
78
|
+
|
|
79
|
+
if not self.statistic_printed:
|
|
80
|
+
self._parse_whole_log()
|
|
81
|
+
|
|
82
|
+
def _parse_whole_log(self):
|
|
83
|
+
logger.warning(
|
|
84
|
+
"LogWatcher closed without reading the statistics, parsing the whole log now."
|
|
85
|
+
)
|
|
86
|
+
with open(self.log_file, "r", encoding="utf-8") as fp:
|
|
87
|
+
content = fp.read()
|
|
88
|
+
self.parse_log(content)
|
|
78
89
|
|
|
79
90
|
|
|
80
91
|
if __name__ == "__main__":
|
|
81
|
-
LogWatcher(
|
|
92
|
+
# LogWatcher()
|
|
93
|
+
pass
|
|
@@ -24,6 +24,47 @@
|
|
|
24
24
|
line-height: 1.6;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/* Custom container width - wider than Bootstrap default */
|
|
28
|
+
.container {
|
|
29
|
+
max-width: 98% !important;
|
|
30
|
+
width: 98% !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@media (min-width: 1200px) {
|
|
34
|
+
.container {
|
|
35
|
+
max-width: 1800px !important;
|
|
36
|
+
width: 95% !important;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (min-width: 1400px) {
|
|
41
|
+
.container {
|
|
42
|
+
max-width: 2000px !important;
|
|
43
|
+
width: 92% !important;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@media (min-width: 1600px) {
|
|
48
|
+
.container {
|
|
49
|
+
max-width: 2200px !important;
|
|
50
|
+
width: 90% !important;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@media (min-width: 1800px) {
|
|
55
|
+
.container {
|
|
56
|
+
max-width: 2400px !important;
|
|
57
|
+
width: 88% !important;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@media (min-width: 2000px) {
|
|
62
|
+
.container {
|
|
63
|
+
max-width: 2600px !important;
|
|
64
|
+
width: 85% !important;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
27
68
|
.header {
|
|
28
69
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
|
29
70
|
color: white;
|
|
@@ -81,9 +122,10 @@
|
|
|
81
122
|
|
|
82
123
|
.screenshot-item {
|
|
83
124
|
flex: 0 0 auto;
|
|
84
|
-
width:
|
|
125
|
+
width: 300px;
|
|
85
126
|
position: relative;
|
|
86
127
|
transition: transform 0.2s;
|
|
128
|
+
margin-bottom: 10px;
|
|
87
129
|
}
|
|
88
130
|
|
|
89
131
|
.screenshot-item:hover {
|
|
@@ -91,30 +133,55 @@
|
|
|
91
133
|
}
|
|
92
134
|
|
|
93
135
|
.screenshot-img {
|
|
94
|
-
width:
|
|
136
|
+
width: 300px;
|
|
95
137
|
height: 400px;
|
|
96
138
|
object-fit: contain;
|
|
97
139
|
border-radius: 8px;
|
|
98
140
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
|
|
141
|
+
margin-bottom: 8px;
|
|
99
142
|
}
|
|
100
143
|
|
|
101
144
|
.screenshot-caption {
|
|
102
|
-
font-size:
|
|
145
|
+
font-size: 13px;
|
|
103
146
|
color: #555;
|
|
104
|
-
padding: 8px
|
|
105
|
-
text-overflow: ellipsis;
|
|
106
|
-
overflow: hidden;
|
|
147
|
+
padding: 12px 8px;
|
|
107
148
|
font-weight: 500;
|
|
108
149
|
text-align: center;
|
|
109
150
|
background-color: white;
|
|
110
|
-
border-radius:
|
|
151
|
+
border-radius: 8px;
|
|
111
152
|
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.05);
|
|
153
|
+
line-height: 1.5;
|
|
154
|
+
white-space: normal;
|
|
155
|
+
word-wrap: break-word;
|
|
156
|
+
min-height: 50px;
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-direction: column;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
align-items: center;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.screenshot-caption .step-number {
|
|
164
|
+
display: block;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
color: var(--primary-color);
|
|
167
|
+
font-size: 12px;
|
|
168
|
+
margin-bottom: 4px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.screenshot-caption .step-action {
|
|
172
|
+
display: block;
|
|
173
|
+
font-size: 13px;
|
|
174
|
+
color: #666;
|
|
175
|
+
font-weight: 400;
|
|
176
|
+
line-height: 1.3;
|
|
112
177
|
}
|
|
113
178
|
|
|
114
179
|
.table-custom {
|
|
115
180
|
border-radius: 10px;
|
|
116
181
|
overflow: hidden;
|
|
117
182
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
|
183
|
+
width: 100%;
|
|
184
|
+
table-layout: auto;
|
|
118
185
|
}
|
|
119
186
|
|
|
120
187
|
.table-custom thead {
|
|
@@ -124,12 +191,53 @@
|
|
|
124
191
|
|
|
125
192
|
.table-custom th {
|
|
126
193
|
font-weight: 600;
|
|
127
|
-
padding: 15px
|
|
194
|
+
padding: 15px 12px;
|
|
195
|
+
white-space: nowrap;
|
|
196
|
+
text-align: center;
|
|
128
197
|
}
|
|
129
198
|
|
|
130
199
|
.table-custom td {
|
|
131
|
-
padding: 15px
|
|
200
|
+
padding: 15px 12px;
|
|
132
201
|
vertical-align: middle;
|
|
202
|
+
text-align: center;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Specific column widths for property statistics table */
|
|
206
|
+
.table-custom th:nth-child(1), .table-custom td:nth-child(1) { /* Index */
|
|
207
|
+
width: 8%;
|
|
208
|
+
min-width: 60px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.table-custom th:nth-child(2), .table-custom td:nth-child(2) { /* Property Name */
|
|
212
|
+
width: 25%;
|
|
213
|
+
min-width: 200px;
|
|
214
|
+
text-align: left;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.table-custom th:nth-child(3), .table-custom td:nth-child(3) { /* Precondition Satisfied */
|
|
218
|
+
width: 12%;
|
|
219
|
+
min-width: 100px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.table-custom th:nth-child(4), .table-custom td:nth-child(4) { /* Executed */
|
|
223
|
+
width: 10%;
|
|
224
|
+
min-width: 80px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.table-custom th:nth-child(5), .table-custom td:nth-child(5) { /* Fails */
|
|
228
|
+
width: 10%;
|
|
229
|
+
min-width: 80px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.table-custom th:nth-child(6), .table-custom td:nth-child(6) { /* Errors */
|
|
233
|
+
width: 10%;
|
|
234
|
+
min-width: 80px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.table-custom th:nth-child(7), .table-custom td:nth-child(7) { /* Error Details */
|
|
238
|
+
width: 25%;
|
|
239
|
+
min-width: 200px;
|
|
240
|
+
text-align: left;
|
|
133
241
|
}
|
|
134
242
|
|
|
135
243
|
.table-custom tbody tr:nth-of-type(odd) {
|
|
@@ -279,6 +387,28 @@
|
|
|
279
387
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
280
388
|
transition: all 0.2s;
|
|
281
389
|
line-height: 1.5;
|
|
390
|
+
display: flex;
|
|
391
|
+
align-items: center;
|
|
392
|
+
justify-content: space-between;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.activity-item .activity-content {
|
|
396
|
+
display: flex;
|
|
397
|
+
align-items: center;
|
|
398
|
+
flex: 1;
|
|
399
|
+
min-width: 0;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.activity-item .activity-name {
|
|
403
|
+
flex: 1;
|
|
404
|
+
word-break: break-all;
|
|
405
|
+
margin-right: 10px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.activity-item .traversal-badge {
|
|
409
|
+
flex-shrink: 0;
|
|
410
|
+
font-size: 0.85rem;
|
|
411
|
+
font-weight: 500;
|
|
282
412
|
}
|
|
283
413
|
|
|
284
414
|
.activity-item:hover {
|
|
@@ -382,17 +512,64 @@
|
|
|
382
512
|
}
|
|
383
513
|
|
|
384
514
|
@media (max-width: 768px) {
|
|
515
|
+
.container {
|
|
516
|
+
max-width: 98% !important;
|
|
517
|
+
width: 98% !important;
|
|
518
|
+
padding-left: 10px !important;
|
|
519
|
+
padding-right: 10px !important;
|
|
520
|
+
}
|
|
521
|
+
|
|
385
522
|
.stat-value {
|
|
386
523
|
font-size: 1.5rem;
|
|
387
524
|
}
|
|
388
525
|
|
|
389
526
|
.screenshot-item {
|
|
390
|
-
width:
|
|
527
|
+
width: 280px;
|
|
391
528
|
}
|
|
392
529
|
|
|
393
530
|
.screenshot-img {
|
|
394
|
-
width:
|
|
395
|
-
height:
|
|
531
|
+
width: 280px;
|
|
532
|
+
height: 400px;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.table-custom {
|
|
536
|
+
font-size: 0.9rem;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.table-custom th, .table-custom td {
|
|
540
|
+
padding: 10px 6px;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.badge-custom {
|
|
544
|
+
font-size: 0.8rem;
|
|
545
|
+
padding: 4px 8px;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@media (max-width: 576px) {
|
|
550
|
+
.container {
|
|
551
|
+
max-width: 100% !important;
|
|
552
|
+
width: 100% !important;
|
|
553
|
+
padding-left: 5px !important;
|
|
554
|
+
padding-right: 5px !important;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.screenshot-item {
|
|
558
|
+
width: 260px;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.screenshot-img {
|
|
562
|
+
width: 260px;
|
|
563
|
+
height: 380px;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.table-custom {
|
|
567
|
+
font-size: 0.8rem;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.table-custom th, .table-custom td {
|
|
571
|
+
padding: 8px 4px;
|
|
572
|
+
white-space: normal;
|
|
396
573
|
}
|
|
397
574
|
}
|
|
398
575
|
</style>
|
|
@@ -469,6 +646,14 @@
|
|
|
469
646
|
</div>
|
|
470
647
|
</div>
|
|
471
648
|
|
|
649
|
+
<!-- Property Execution Trend Chart -->
|
|
650
|
+
<div class="section-block">
|
|
651
|
+
<h2 class="section-title">Property Execution Trend</h2>
|
|
652
|
+
<div class="chart-container">
|
|
653
|
+
<canvas id="propertyExecutionChart"></canvas>
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
656
|
+
|
|
472
657
|
<!-- Tested Activities List -->
|
|
473
658
|
<div class="section-block">
|
|
474
659
|
<h2 class="section-title">Activities Coverage</h2>
|
|
@@ -477,10 +662,18 @@
|
|
|
477
662
|
<div class="card-header bg-primary text-white">
|
|
478
663
|
<div class="d-flex justify-content-between align-items-center">
|
|
479
664
|
<span><i class="bi bi-app"></i> Activities Coverage Overview</span>
|
|
480
|
-
<span class="badge bg-light text-dark">Coverage: {{ coverage_percent }}%</span>
|
|
665
|
+
<span class="badge bg-light text-dark" style="font-size: 1.1em; font-weight: 600;">Coverage: {{ coverage_percent }}%</span>
|
|
481
666
|
</div>
|
|
482
667
|
</div>
|
|
483
668
|
<div class="card-body">
|
|
669
|
+
<div class="alert alert-info mb-3" style="border-left: 4px solid #17a2b8; background-color: #f8f9fa;">
|
|
670
|
+
<small class="text-muted">
|
|
671
|
+
<i class="bi bi-info-circle me-1"></i>
|
|
672
|
+
<strong>Traversal Count Explanation:</strong>
|
|
673
|
+
The number after the <i class="bi bi-eye"></i> icon indicates how many times each Activity was visited during testing.
|
|
674
|
+
</small>
|
|
675
|
+
</div>
|
|
676
|
+
|
|
484
677
|
<!-- Navigation Tabs -->
|
|
485
678
|
<ul class="nav nav-tabs mb-3" id="activitiesTabs" role="tablist">
|
|
486
679
|
<li class="nav-item" role="presentation">
|
|
@@ -517,8 +710,15 @@
|
|
|
517
710
|
<div id="tested-activities-container">
|
|
518
711
|
{% for activity in tested_activities %}
|
|
519
712
|
<div class="activity-item tested-activity" data-page="1">
|
|
520
|
-
<
|
|
521
|
-
|
|
713
|
+
<div class="activity-content">
|
|
714
|
+
<i class="bi bi-check-circle-fill text-success me-2"></i>
|
|
715
|
+
<span class="activity-name">{{ activity }}</span>
|
|
716
|
+
</div>
|
|
717
|
+
{% if activity in activity_count_history %}
|
|
718
|
+
<span class="badge bg-info text-white traversal-badge">
|
|
719
|
+
<i class="bi bi-eye"></i> {{ activity_count_history[activity] }} times
|
|
720
|
+
</span>
|
|
721
|
+
{% endif %}
|
|
522
722
|
</div>
|
|
523
723
|
{% endfor %}
|
|
524
724
|
</div>
|
|
@@ -565,12 +765,19 @@
|
|
|
565
765
|
<div id="all-activities-container">
|
|
566
766
|
{% for activity in total_activities %}
|
|
567
767
|
<div class="activity-item all-activity" data-page="1">
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
768
|
+
<div class="activity-content">
|
|
769
|
+
{% if activity in tested_activities %}
|
|
770
|
+
<i class="bi bi-check-circle-fill text-success me-2"></i>
|
|
771
|
+
{% else %}
|
|
772
|
+
<i class="bi bi-dash-circle text-secondary me-2"></i>
|
|
773
|
+
{% endif %}
|
|
774
|
+
<span class="activity-name">{{ activity }}</span>
|
|
775
|
+
</div>
|
|
776
|
+
{% if activity in activity_count_history %}
|
|
777
|
+
<span class="badge bg-info text-white traversal-badge">
|
|
778
|
+
<i class="bi bi-eye"></i> {{ activity_count_history[activity] }} times
|
|
779
|
+
</span>
|
|
572
780
|
{% endif %}
|
|
573
|
-
{{ activity }}
|
|
574
781
|
</div>
|
|
575
782
|
{% endfor %}
|
|
576
783
|
</div>
|
|
@@ -730,6 +937,11 @@
|
|
|
730
937
|
Occurred {{ error_list[0].occurrence_count }} times
|
|
731
938
|
</span>
|
|
732
939
|
{% endif %}
|
|
940
|
+
{% if error_list[0].startStepsCountList is defined and error_list[0].startStepsCountList|length > 0 %}
|
|
941
|
+
<span class="badge bg-secondary ms-2">
|
|
942
|
+
<i class="bi bi-step-forward"></i> Monkey Steps: {{ error_list[0].startStepsCountList|join(', ') }}
|
|
943
|
+
</span>
|
|
944
|
+
{% endif %}
|
|
733
945
|
</div>
|
|
734
946
|
{% if error_list[0].short_description %}
|
|
735
947
|
<div class="mb-2">
|
|
@@ -759,6 +971,9 @@
|
|
|
759
971
|
<span class="badge bg-{{ 'danger' if error.state == 'fail' else 'warning' }}">
|
|
760
972
|
{{ error.state|upper }} #{{ loop.index }}
|
|
761
973
|
{% if error.occurrence_count > 1 %} ({{ error.occurrence_count }}x){% endif %}
|
|
974
|
+
{% if error.startStepsCountList is defined and error.startStepsCountList|length > 0 %}
|
|
975
|
+
@{% if error.startStepsCountList|length == 1 %}{{ error.startStepsCountList[0] }}{% else %}{{ error.startStepsCountList[0] }}-{{ error.startStepsCountList[-1] }}{% endif %}
|
|
976
|
+
{% endif %}
|
|
762
977
|
</span>
|
|
763
978
|
{% endfor %}
|
|
764
979
|
</div>
|
|
@@ -801,14 +1016,19 @@
|
|
|
801
1016
|
{{ error.occurrence_count }} occurrences
|
|
802
1017
|
</span>
|
|
803
1018
|
{% endif %}
|
|
1019
|
+
{% if error.startStepsCountList is defined and error.startStepsCountList|length > 0 %}
|
|
1020
|
+
<span class="badge bg-secondary ms-2">
|
|
1021
|
+
<i class="bi bi-step-forward"></i> Monkey Steps: {{ error.startStepsCountList|join(', ') }}
|
|
1022
|
+
</span>
|
|
1023
|
+
{% endif %}
|
|
804
1024
|
</div>
|
|
805
1025
|
{% if error.short_description %}
|
|
806
1026
|
<div class="mb-2">
|
|
807
1027
|
<strong>Error:</strong> <code>{{ error.short_description }}</code>
|
|
808
1028
|
</div>
|
|
809
1029
|
{% endif %}
|
|
810
|
-
<details
|
|
811
|
-
<summary class="btn btn-sm btn-outline-secondary mb-2">
|
|
1030
|
+
<details>
|
|
1031
|
+
<summary class="btn btn-sm btn-outline-secondary mb-2">Show Full Traceback</summary>
|
|
812
1032
|
<pre class="text-danger mb-0" style="font-size: 0.85rem; white-space: pre-wrap;">{{ error.traceback }}</pre>
|
|
813
1033
|
</details>
|
|
814
1034
|
</div>
|
|
@@ -1019,6 +1239,143 @@
|
|
|
1019
1239
|
}
|
|
1020
1240
|
});
|
|
1021
1241
|
|
|
1242
|
+
// Draw property execution trend chart
|
|
1243
|
+
var propertyExecutionData = {{ property_execution_data|safe }};
|
|
1244
|
+
console.log("Property execution data points:", propertyExecutionData.length);
|
|
1245
|
+
|
|
1246
|
+
// Ensure we have valid data
|
|
1247
|
+
if (propertyExecutionData.length === 0) {
|
|
1248
|
+
propertyExecutionData = [{"stepsCount": 0, "executedPropertiesCount": 0}];
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
propertyExecutionData.sort(function(a, b) {
|
|
1252
|
+
return a.stepsCount - b.stepsCount;
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
var propSteps = propertyExecutionData.map(function(item) { return item.stepsCount; });
|
|
1256
|
+
var executedProps = propertyExecutionData.map(function(item) { return item.executedPropertiesCount; });
|
|
1257
|
+
|
|
1258
|
+
// Add zero starting point if needed
|
|
1259
|
+
if (propSteps.length > 0 && propSteps[0] > 0) {
|
|
1260
|
+
propSteps.unshift(0);
|
|
1261
|
+
executedProps.unshift(0);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
console.log("Property execution steps:", propSteps);
|
|
1265
|
+
console.log("Executed properties values:", executedProps);
|
|
1266
|
+
|
|
1267
|
+
var propCtx = document.getElementById('propertyExecutionChart').getContext('2d');
|
|
1268
|
+
var propChart = new Chart(propCtx, {
|
|
1269
|
+
type: 'line',
|
|
1270
|
+
data: {
|
|
1271
|
+
labels: propSteps,
|
|
1272
|
+
datasets: [
|
|
1273
|
+
{
|
|
1274
|
+
label: 'Executed Properties',
|
|
1275
|
+
data: executedProps.map((value, index) => ({x: propSteps[index], y: value})),
|
|
1276
|
+
borderColor: '#e74c3c',
|
|
1277
|
+
backgroundColor: 'rgba(231, 76, 60, 0.1)',
|
|
1278
|
+
borderWidth: 3,
|
|
1279
|
+
fill: true,
|
|
1280
|
+
tension: 0.4,
|
|
1281
|
+
pointRadius: 4,
|
|
1282
|
+
pointHoverRadius: 6
|
|
1283
|
+
}
|
|
1284
|
+
]
|
|
1285
|
+
},
|
|
1286
|
+
options: {
|
|
1287
|
+
responsive: true,
|
|
1288
|
+
maintainAspectRatio: false,
|
|
1289
|
+
aspectRatio: 2,
|
|
1290
|
+
plugins: {
|
|
1291
|
+
legend: {
|
|
1292
|
+
position: 'top',
|
|
1293
|
+
labels: {
|
|
1294
|
+
boxWidth: 15,
|
|
1295
|
+
usePointStyle: true,
|
|
1296
|
+
pointStyle: 'circle'
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
tooltip: {
|
|
1300
|
+
mode: 'index',
|
|
1301
|
+
intersect: false,
|
|
1302
|
+
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
1303
|
+
titleColor: '#fff',
|
|
1304
|
+
bodyColor: '#fff',
|
|
1305
|
+
borderColor: 'rgba(255, 255, 255, 0.2)',
|
|
1306
|
+
borderWidth: 1,
|
|
1307
|
+
padding: 10,
|
|
1308
|
+
displayColors: true,
|
|
1309
|
+
callbacks: {
|
|
1310
|
+
label: function(context) {
|
|
1311
|
+
return 'Executed Properties: ' + context.parsed.y;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
scales: {
|
|
1317
|
+
x: {
|
|
1318
|
+
type: 'linear',
|
|
1319
|
+
beginAtZero: true,
|
|
1320
|
+
max: propSteps.length > 0 ? Math.max(...propSteps) : 10,
|
|
1321
|
+
grid: {
|
|
1322
|
+
display: false
|
|
1323
|
+
},
|
|
1324
|
+
title: {
|
|
1325
|
+
display: true,
|
|
1326
|
+
text: 'Steps Count',
|
|
1327
|
+
font: {
|
|
1328
|
+
size: 14
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
ticks: {
|
|
1332
|
+
stepSize: propSteps.length > 0 ? Math.max(1, Math.ceil(Math.max(...propSteps) / 10)) : 1,
|
|
1333
|
+
callback: function(value) {
|
|
1334
|
+
if (Number.isInteger(value) && value <= (propSteps.length > 0 ? Math.max(...propSteps) : 10)) {
|
|
1335
|
+
return value;
|
|
1336
|
+
}
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
y: {
|
|
1342
|
+
beginAtZero: true,
|
|
1343
|
+
title: {
|
|
1344
|
+
display: true,
|
|
1345
|
+
text: 'Executed Properties',
|
|
1346
|
+
font: {
|
|
1347
|
+
size: 14
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
grid: {
|
|
1351
|
+
borderDash: [5, 5]
|
|
1352
|
+
},
|
|
1353
|
+
ticks: {
|
|
1354
|
+
stepSize: 1,
|
|
1355
|
+
callback: function(value) {
|
|
1356
|
+
if (Number.isInteger(value)) {
|
|
1357
|
+
return value;
|
|
1358
|
+
}
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
1364
|
+
interaction: {
|
|
1365
|
+
mode: 'index',
|
|
1366
|
+
intersect: false
|
|
1367
|
+
},
|
|
1368
|
+
hover: {
|
|
1369
|
+
mode: 'index',
|
|
1370
|
+
intersect: false
|
|
1371
|
+
},
|
|
1372
|
+
animation: {
|
|
1373
|
+
duration: 1000,
|
|
1374
|
+
easing: 'easeOutQuart'
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1022
1379
|
// Initialize pagination for Activities lists
|
|
1023
1380
|
initPagination('tested-activities-container', 'tested-activity', 'tested-pagination', 'tested-page-size');
|
|
1024
1381
|
initPagination('all-activities-container', 'all-activity', 'all-pagination', 'all-page-size');
|
|
@@ -1241,6 +1598,56 @@
|
|
|
1241
1598
|
}
|
|
1242
1599
|
});
|
|
1243
1600
|
}
|
|
1601
|
+
|
|
1602
|
+
// Beautify screenshot captions
|
|
1603
|
+
function beautifyScreenshotCaptions() {
|
|
1604
|
+
const captions = document.querySelectorAll('.screenshot-caption');
|
|
1605
|
+
captions.forEach(function(caption) {
|
|
1606
|
+
const originalText = caption.textContent.trim();
|
|
1607
|
+
|
|
1608
|
+
// Handle Monkey Step format: "1. Monkey Step 6: SCROLL_TOP_DOWN"
|
|
1609
|
+
const monkeyStepMatch = originalText.match(/^(\d+)\.\s*Monkey Step (\d+):\s*(.+)$/);
|
|
1610
|
+
if (monkeyStepMatch) {
|
|
1611
|
+
const stepIndex = monkeyStepMatch[1];
|
|
1612
|
+
const monkeyStep = monkeyStepMatch[2];
|
|
1613
|
+
const action = monkeyStepMatch[3];
|
|
1614
|
+
|
|
1615
|
+
caption.innerHTML = `
|
|
1616
|
+
<div class="step-number">${stepIndex}. Monkey Step ${monkeyStep}</div>
|
|
1617
|
+
<div class="step-action">${action}</div>
|
|
1618
|
+
`;
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// Handle Script format: "1. click" or similar
|
|
1623
|
+
const scriptMatch = originalText.match(/^(\d+)\.\s*(.+)$/);
|
|
1624
|
+
if (scriptMatch) {
|
|
1625
|
+
const stepIndex = scriptMatch[1];
|
|
1626
|
+
const action = scriptMatch[2];
|
|
1627
|
+
|
|
1628
|
+
// Check if it's a property info
|
|
1629
|
+
if (action.includes(':')) {
|
|
1630
|
+
const parts = action.split(':');
|
|
1631
|
+
if (parts.length === 2) {
|
|
1632
|
+
caption.innerHTML = `
|
|
1633
|
+
<div class="step-number">${stepIndex}. ${parts[0].trim()}</div>
|
|
1634
|
+
<div class="step-action">${parts[1].trim()}</div>
|
|
1635
|
+
`;
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// Simple action
|
|
1641
|
+
caption.innerHTML = `
|
|
1642
|
+
<div class="step-number">${stepIndex}. Script Step</div>
|
|
1643
|
+
<div class="step-action">${action}</div>
|
|
1644
|
+
`;
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Call beautify function after DOM is ready
|
|
1650
|
+
beautifyScreenshotCaptions();
|
|
1244
1651
|
});
|
|
1245
1652
|
</script>
|
|
1246
1653
|
</body>
|
|
Binary file
|
|
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
|
{kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so
RENAMED
|
File without changes
|
{kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so
RENAMED
|
File without changes
|
|
File without changes
|
{kea2_python-0.2.3 → kea2_python-0.2.4}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so
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
|