Kea2-python 1.0.4__tar.gz → 1.0.5__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.
- {kea2_python-1.0.4 → kea2_python-1.0.5}/Kea2_python.egg-info/PKG-INFO +4 -3
- {kea2_python-1.0.4 → kea2_python-1.0.5}/PKG-INFO +4 -3
- {kea2_python-1.0.4 → kea2_python-1.0.5}/README.md +3 -2
- kea2_python-1.0.5/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/fastbotManager.py +8 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/keaUtils.py +21 -6
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/kea_launcher.py +12 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/bug_report_generator.py +33 -5
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/mixin.py +23 -9
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/templates/bug_report_template.html +208 -12
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/resultSyncer.py +9 -13
- {kea2_python-1.0.4 → kea2_python-1.0.5}/pyproject.toml +1 -1
- kea2_python-1.0.5/tests/test_xpath.py +67 -0
- kea2_python-1.0.4/kea2/assets/monkeyq.jar +0 -0
- kea2_python-1.0.4/tests/test_xpath.py +0 -36
- {kea2_python-1.0.4 → kea2_python-1.0.5}/Kea2_python.egg-info/SOURCES.txt +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/Kea2_python.egg-info/dependency_links.txt +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/Kea2_python.egg-info/entry_points.txt +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/Kea2_python.egg-info/requires.txt +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/Kea2_python.egg-info/top_level.txt +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/LICENSE +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/__init__.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/absDriver.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/adbUtils.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/config_version.json +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot-thirdpart.jar +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/abl.strings +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/awl.strings +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/max.config +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/max.strings +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/teardown.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_configs/widget.block.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/framework.jar +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/kea2-thirdpart.jar +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/quicktest.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/cli.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/kea2_api.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/logWatcher.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/mixin.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/__init__.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/report_merger.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/templates/merged_bug_report_template.html +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/utils.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/u2Driver.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/utils.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/version_manager.py +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/setup.cfg +0 -0
- {kea2_python-1.0.4 → kea2_python-1.0.5}/tests/test_u2Selector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
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
|
|
@@ -21,9 +21,10 @@ Dynamic: license-file
|
|
|
21
21
|
[](https://deepwiki.com/ecnusse/Kea2)
|
|
22
22
|
|
|
23
23
|
<div>
|
|
24
|
-
<img src="https://github.com/user-attachments/assets/
|
|
24
|
+
<img src="https://github.com/user-attachments/assets/8d9f8750-1e10-411b-a49f-7d8367bbe9fe" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
+
|
|
27
28
|
Please contact Xixian Liang at [xixian@stu.ecnu.edu.cn](xixian@stu.ecnu.edu.cn) with your Wechat ID / QR code to be invited to the WeChat discussion group. Of course, we are also ready on GitHub to answer your questions/feedback.
|
|
28
29
|
|
|
29
30
|
#### Github repo [https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
@@ -77,7 +78,7 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
|
|
|
77
78
|
|
|
78
79
|
Kea2 (and its idea) has been used/integrated by
|
|
79
80
|
|
|
80
|
-
- [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app. OPay uses Kea2 for regression testing on POS machines and mobile devices.
|
|
81
|
+
- [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app (2 millions of active users daily). OPay uses Kea2 for regression testing on POS machines and mobile devices.
|
|
81
82
|
|
|
82
83
|
- [WeChat's iExplorer]() --- WeChat's in-house testing platform (coming with an interactive UI-based tool to ease writing scripts)
|
|
83
84
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
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
|
|
@@ -21,9 +21,10 @@ Dynamic: license-file
|
|
|
21
21
|
[](https://deepwiki.com/ecnusse/Kea2)
|
|
22
22
|
|
|
23
23
|
<div>
|
|
24
|
-
<img src="https://github.com/user-attachments/assets/
|
|
24
|
+
<img src="https://github.com/user-attachments/assets/8d9f8750-1e10-411b-a49f-7d8367bbe9fe" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
+
|
|
27
28
|
Please contact Xixian Liang at [xixian@stu.ecnu.edu.cn](xixian@stu.ecnu.edu.cn) with your Wechat ID / QR code to be invited to the WeChat discussion group. Of course, we are also ready on GitHub to answer your questions/feedback.
|
|
28
29
|
|
|
29
30
|
#### Github repo [https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
@@ -77,7 +78,7 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
|
|
|
77
78
|
|
|
78
79
|
Kea2 (and its idea) has been used/integrated by
|
|
79
80
|
|
|
80
|
-
- [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app. OPay uses Kea2 for regression testing on POS machines and mobile devices.
|
|
81
|
+
- [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app (2 millions of active users daily). OPay uses Kea2 for regression testing on POS machines and mobile devices.
|
|
81
82
|
|
|
82
83
|
- [WeChat's iExplorer]() --- WeChat's in-house testing platform (coming with an interactive UI-based tool to ease writing scripts)
|
|
83
84
|
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
[](https://deepwiki.com/ecnusse/Kea2)
|
|
7
7
|
|
|
8
8
|
<div>
|
|
9
|
-
<img src="https://github.com/user-attachments/assets/
|
|
9
|
+
<img src="https://github.com/user-attachments/assets/8d9f8750-1e10-411b-a49f-7d8367bbe9fe" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
Please contact Xixian Liang at [xixian@stu.ecnu.edu.cn](xixian@stu.ecnu.edu.cn) with your Wechat ID / QR code to be invited to the WeChat discussion group. Of course, we are also ready on GitHub to answer your questions/feedback.
|
|
13
14
|
|
|
14
15
|
#### Github repo [https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
@@ -62,7 +63,7 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
|
|
|
62
63
|
|
|
63
64
|
Kea2 (and its idea) has been used/integrated by
|
|
64
65
|
|
|
65
|
-
- [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app. OPay uses Kea2 for regression testing on POS machines and mobile devices.
|
|
66
|
+
- [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app (2 millions of active users daily). OPay uses Kea2 for regression testing on POS machines and mobile devices.
|
|
66
67
|
|
|
67
68
|
- [WeChat's iExplorer]() --- WeChat's in-house testing platform (coming with an interactive UI-based tool to ease writing scripts)
|
|
68
69
|
|
|
Binary file
|
|
@@ -129,6 +129,14 @@ class FastbotManager:
|
|
|
129
129
|
path="/dumpHierarchy",
|
|
130
130
|
)
|
|
131
131
|
return r.json()['result']
|
|
132
|
+
|
|
133
|
+
@retry(Exception, tries=2, delay=2)
|
|
134
|
+
def sendInfo(self, info: str):
|
|
135
|
+
r = self.request(
|
|
136
|
+
method="POST",
|
|
137
|
+
path="/sendInfo",
|
|
138
|
+
data=info
|
|
139
|
+
)
|
|
132
140
|
|
|
133
141
|
@property
|
|
134
142
|
def device_output_dir(self):
|
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
from collections import deque
|
|
9
9
|
from copy import deepcopy
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from time import perf_counter
|
|
11
|
+
from time import perf_counter, sleep
|
|
12
12
|
from typing import Callable, Any, Deque, Dict, List, Literal, NewType, Tuple, Union
|
|
13
13
|
from contextvars import ContextVar
|
|
14
14
|
from unittest import TextTestRunner, registerResult, TestSuite, TestCase, TextTestResult, defaultTestLoader, SkipTest
|
|
@@ -155,6 +155,8 @@ class Options:
|
|
|
155
155
|
act_blacklist_file: str = None
|
|
156
156
|
# propertytest sub-commands args (eg. discover -s xxx -p xxx)
|
|
157
157
|
propertytest_args: List[str] = None
|
|
158
|
+
# period (N steps) to restart the app under test
|
|
159
|
+
restart_app_period: int = None
|
|
158
160
|
# unittest sub-commands args (Feat 4)
|
|
159
161
|
unittest_args: List[str] = None
|
|
160
162
|
# Extra args (directly passed to fastbot)
|
|
@@ -468,21 +470,30 @@ class KeaTestRunner(TextTestRunner, KeaOptionSetter):
|
|
|
468
470
|
start_time = perf_counter()
|
|
469
471
|
fb_is_running = True
|
|
470
472
|
self.stepsCount = 0
|
|
473
|
+
|
|
471
474
|
while self.stepsCount < self.options.maxStep:
|
|
472
475
|
if self.shouldStop(start_time):
|
|
473
476
|
logger.info("Exploration time up (--running-minutes).")
|
|
474
477
|
break
|
|
478
|
+
|
|
479
|
+
if self.options.restart_app_period and self.stepsCount and self.stepsCount % self.options.restart_app_period == 0:
|
|
480
|
+
self.stepsCount += 1
|
|
481
|
+
logger.info(f"Sending monkeyEvent {self._monkey_event_count}")
|
|
482
|
+
logger.info("Kill all test apps to restart the app under test.")
|
|
483
|
+
for app in self.options.packageNames:
|
|
484
|
+
logger.info(f"Stopping app: {app}")
|
|
485
|
+
self.scriptDriver.app_stop(app)
|
|
486
|
+
sleep(3)
|
|
487
|
+
fb.sendInfo("kill_apps")
|
|
488
|
+
continue
|
|
489
|
+
|
|
475
490
|
try:
|
|
476
491
|
if fb.executed_prop:
|
|
477
492
|
fb.executed_prop = False
|
|
478
493
|
xml_raw = fb.dumpHierarchy()
|
|
479
494
|
else:
|
|
480
495
|
self.stepsCount += 1
|
|
481
|
-
logger.info("Sending monkeyEvent {}"
|
|
482
|
-
f"({self.stepsCount} / {self.options.maxStep})" if self.options.maxStep != float("inf")
|
|
483
|
-
else f"({self.stepsCount})"
|
|
484
|
-
)
|
|
485
|
-
)
|
|
496
|
+
logger.info(f"Sending monkeyEvent {self._monkey_event_count}")
|
|
486
497
|
xml_raw = fb.stepMonkey(self._monkeyStepInfo)
|
|
487
498
|
propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
|
|
488
499
|
except u2.HTTPError:
|
|
@@ -559,6 +570,10 @@ class KeaTestRunner(TextTestRunner, KeaOptionSetter):
|
|
|
559
570
|
r = self._get_block_widgets()
|
|
560
571
|
r["steps_count"] = self.stepsCount
|
|
561
572
|
return r
|
|
573
|
+
|
|
574
|
+
@property
|
|
575
|
+
def _monkey_event_count(self):
|
|
576
|
+
return f"({self.stepsCount} / {self.options.maxStep})" if self.options.maxStep != float("inf") else f"({self.stepsCount})"
|
|
562
577
|
|
|
563
578
|
def _get_block_widgets(self):
|
|
564
579
|
block_dict = self._getBlockedWidgets()
|
|
@@ -158,6 +158,15 @@ def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.Argument
|
|
|
158
158
|
help="Activity BlackList File. The activities listed in the file will be avoided during testing.",
|
|
159
159
|
)
|
|
160
160
|
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
"--restart-app-period",
|
|
163
|
+
dest="restart_app_period",
|
|
164
|
+
type=int,
|
|
165
|
+
required=False,
|
|
166
|
+
default=0,
|
|
167
|
+
help="The period (in the numbers of monkey events) to restart the app under test. 0 means no restart.",
|
|
168
|
+
)
|
|
169
|
+
|
|
161
170
|
parser.add_argument(
|
|
162
171
|
"extra",
|
|
163
172
|
nargs=argparse.REMAINDER,
|
|
@@ -200,6 +209,8 @@ def driver_info_logger(args):
|
|
|
200
209
|
print(" post_failure_screenshots:", args.post_failure_screenshots, flush=True)
|
|
201
210
|
if args.max_step:
|
|
202
211
|
print(" max_step:", args.max_step, flush=True)
|
|
212
|
+
if args.restart_app_period > 0:
|
|
213
|
+
print(" restart_app_period:", args.restart_app_period, flush=True)
|
|
203
214
|
|
|
204
215
|
|
|
205
216
|
def parse_args(argv: List):
|
|
@@ -270,6 +281,7 @@ def run(args=None):
|
|
|
270
281
|
device_output_root=args.device_output_root,
|
|
271
282
|
act_whitelist_file=args.act_whitelist_file,
|
|
272
283
|
act_blacklist_file=args.act_blacklist_file,
|
|
284
|
+
restart_app_period=args.restart_app_period,
|
|
273
285
|
propertytest_args=args.propertytest_args,
|
|
274
286
|
unittest_args=args.unittest_args,
|
|
275
287
|
extra_args=args.extra,
|
|
@@ -59,6 +59,7 @@ class ReportData(TypedDict):
|
|
|
59
59
|
activity_count_history: Dict[str, int] # Activity traversal count from final coverage data
|
|
60
60
|
crash_events: List[Dict] # Crash events from crash-dump.log
|
|
61
61
|
anr_events: List[Dict] # ANR events from crash-dump.log
|
|
62
|
+
kill_apps_events: List[Dict] # kill_apps info events from steps.log
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
class PropertyExecResult(TypedDict):
|
|
@@ -250,6 +251,7 @@ class BugReportGenerator(CrashAnrMixin, PathParserMixin, ScreenshotsMixin):
|
|
|
250
251
|
"activity_count_history": {},
|
|
251
252
|
"crash_events": [],
|
|
252
253
|
"anr_events": [],
|
|
254
|
+
"kill_apps_events": [],
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
# Parse steps.log file to get test step numbers and screenshot mappings
|
|
@@ -283,6 +285,25 @@ class BugReportGenerator(CrashAnrMixin, PathParserMixin, ScreenshotsMixin):
|
|
|
283
285
|
if step_type == "Monkey" or step_type == "Fuzz":
|
|
284
286
|
monkey_events_count += 1
|
|
285
287
|
|
|
288
|
+
# Record restart-app marker events (no screenshot expected)
|
|
289
|
+
if step_type == "Monkey" and info == "kill_apps":
|
|
290
|
+
monkey_steps_count = step_data.get("MonkeyStepsCount", "N/A")
|
|
291
|
+
caption = f"Monkey Step {monkey_steps_count}: restart app"
|
|
292
|
+
|
|
293
|
+
data["kill_apps_events"].append({
|
|
294
|
+
"step_index": step_index,
|
|
295
|
+
"monkey_steps_count": monkey_steps_count,
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
# Show this info event in the Test Screenshots timeline
|
|
299
|
+
self.screenshots.append({
|
|
300
|
+
"id": step_index,
|
|
301
|
+
"path": "",
|
|
302
|
+
"caption": f"{step_index}. {caption}",
|
|
303
|
+
"kind": "info",
|
|
304
|
+
"info": "kill_apps",
|
|
305
|
+
})
|
|
306
|
+
|
|
286
307
|
# If screenshots are enabled, mark the screenshot
|
|
287
308
|
if self.take_screenshots and step_data["Screenshot"]:
|
|
288
309
|
executor.submit(self._mark_screenshot, step_data)
|
|
@@ -390,8 +411,12 @@ class BugReportGenerator(CrashAnrMixin, PathParserMixin, ScreenshotsMixin):
|
|
|
390
411
|
|
|
391
412
|
def _parse_step_data(self, raw_step_info: str) -> StepData:
|
|
392
413
|
step_data: StepData = json.loads(raw_step_info)
|
|
393
|
-
if step_data
|
|
394
|
-
|
|
414
|
+
if step_data.get("Type") in {"Monkey", "Script", "ScriptInfo"}:
|
|
415
|
+
info = step_data.get("Info")
|
|
416
|
+
if isinstance(info, str):
|
|
417
|
+
stripped = info.strip()
|
|
418
|
+
if stripped and stripped[0] in "{[":
|
|
419
|
+
step_data["Info"] = json.loads(stripped)
|
|
395
420
|
return step_data
|
|
396
421
|
|
|
397
422
|
|
|
@@ -443,7 +468,8 @@ class BugReportGenerator(CrashAnrMixin, PathParserMixin, ScreenshotsMixin):
|
|
|
443
468
|
'anr_events': data["anr_events"],
|
|
444
469
|
'triggered_crash_count': len(data["crash_events"]),
|
|
445
470
|
'triggered_anr_count': len(data["anr_events"]),
|
|
446
|
-
'property_stats_summary': data["property_stats_summary"]
|
|
471
|
+
'property_stats_summary': data["property_stats_summary"],
|
|
472
|
+
'kill_apps_events': data.get("kill_apps_events", []),
|
|
447
473
|
}
|
|
448
474
|
|
|
449
475
|
# Check if template exists, if not create it
|
|
@@ -498,7 +524,8 @@ class BugReportGenerator(CrashAnrMixin, PathParserMixin, ScreenshotsMixin):
|
|
|
498
524
|
"start": current_test["start"],
|
|
499
525
|
"end": current_test["end"],
|
|
500
526
|
"screenshot_start": current_test["screenshot_start"],
|
|
501
|
-
"screenshot_end": screenshot
|
|
527
|
+
"screenshot_end": screenshot,
|
|
528
|
+
"state": state
|
|
502
529
|
})
|
|
503
530
|
|
|
504
531
|
# Reset current test
|
|
@@ -524,7 +551,8 @@ class BugReportGenerator(CrashAnrMixin, PathParserMixin, ScreenshotsMixin):
|
|
|
524
551
|
data["property_violations"].append({
|
|
525
552
|
"index": index,
|
|
526
553
|
"property_name": property_name,
|
|
527
|
-
"interaction_pages": [start_step, end_step]
|
|
554
|
+
"interaction_pages": [start_step, end_step],
|
|
555
|
+
"state": violation.get("state", "fail")
|
|
528
556
|
})
|
|
529
557
|
index += 1
|
|
530
558
|
|
|
@@ -311,16 +311,19 @@ class ScreenshotsMixin:
|
|
|
311
311
|
screenshot_name = step_data["Screenshot"]
|
|
312
312
|
if not screenshot_name:
|
|
313
313
|
return
|
|
314
|
+
info = step_data.get("Info")
|
|
315
|
+
if not isinstance(info, dict):
|
|
316
|
+
return
|
|
314
317
|
|
|
315
318
|
if step_type == "Monkey":
|
|
316
|
-
act =
|
|
317
|
-
pos =
|
|
319
|
+
act = info.get("act")
|
|
320
|
+
pos = info.get("pos")
|
|
318
321
|
if act in ["CLICK", "LONG_CLICK"] or act.startswith("SCROLL"):
|
|
319
322
|
self._mark_screenshot_interaction(step_type, screenshot_name, act, pos)
|
|
320
323
|
|
|
321
324
|
elif step_type == "Script":
|
|
322
|
-
act =
|
|
323
|
-
pos =
|
|
325
|
+
act = info.get("method")
|
|
326
|
+
pos = info.get("params")
|
|
324
327
|
if act in ["click", "setText", "swipe"]:
|
|
325
328
|
self._mark_screenshot_interaction(step_type, screenshot_name, act, pos)
|
|
326
329
|
|
|
@@ -425,19 +428,30 @@ class ScreenshotsMixin:
|
|
|
425
428
|
data: Data dictionary to update
|
|
426
429
|
"""
|
|
427
430
|
caption = ""
|
|
431
|
+
info = step_data.get("Info")
|
|
428
432
|
|
|
429
433
|
if step_data["Type"] == "Monkey":
|
|
430
434
|
# Extract 'act' attribute for Monkey type and add MonkeyStepsCount
|
|
431
435
|
monkey_steps_count = step_data.get('MonkeyStepsCount', 'N/A')
|
|
432
|
-
|
|
436
|
+
if isinstance(info, dict):
|
|
437
|
+
action = info.get('act', 'N/A')
|
|
438
|
+
else:
|
|
439
|
+
action = str(info) if info else 'N/A'
|
|
433
440
|
caption = f"Monkey Step {monkey_steps_count}: {action}"
|
|
434
441
|
elif step_data["Type"] == "Script":
|
|
435
442
|
# Extract 'method' attribute for Script type
|
|
436
|
-
|
|
443
|
+
if isinstance(info, dict):
|
|
444
|
+
caption = f"{info.get('method', 'N/A')}"
|
|
445
|
+
else:
|
|
446
|
+
caption = str(info) if info else "N/A"
|
|
437
447
|
elif step_data["Type"] == "ScriptInfo":
|
|
438
448
|
# Extract 'propName' and 'state' attributes for ScriptInfo type
|
|
439
|
-
|
|
440
|
-
|
|
449
|
+
if isinstance(info, dict):
|
|
450
|
+
prop_name = info.get('propName', '')
|
|
451
|
+
state = info.get('state', 'N/A')
|
|
452
|
+
else:
|
|
453
|
+
prop_name = ''
|
|
454
|
+
state = str(info) if info else 'N/A'
|
|
441
455
|
caption = f"{prop_name}: {state}" if prop_name else f"{state}"
|
|
442
456
|
elif step_data["Type"] == "Fuzz":
|
|
443
457
|
monkey_steps_count = step_data.get('MonkeyStepsCount', 'N/A')
|
|
@@ -465,4 +479,4 @@ class ScreenshotsMixin:
|
|
|
465
479
|
'id': step_index,
|
|
466
480
|
'path': relative_screenshot_path, # Now using string path
|
|
467
481
|
'caption': f"{step_index}. {caption}"
|
|
468
|
-
})
|
|
482
|
+
})
|
|
@@ -140,6 +140,107 @@
|
|
|
140
140
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
|
|
141
141
|
margin-bottom: 8px;
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
.screenshot-placeholder {
|
|
145
|
+
width: 300px;
|
|
146
|
+
height: 400px;
|
|
147
|
+
border-radius: 8px;
|
|
148
|
+
background: linear-gradient(180deg, rgba(52, 152, 219, 0.08), rgba(255, 255, 255, 0.98));
|
|
149
|
+
border: 1px solid rgba(52, 152, 219, 0.18);
|
|
150
|
+
box-shadow: 0 8px 24px rgba(44, 62, 80, 0.08);
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: center;
|
|
154
|
+
margin-bottom: 8px;
|
|
155
|
+
color: #6c757d;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.screenshot-placeholder::before {
|
|
159
|
+
content: "";
|
|
160
|
+
position: absolute;
|
|
161
|
+
inset: 10px;
|
|
162
|
+
border-radius: 10px;
|
|
163
|
+
border: 1px dashed rgba(52, 152, 219, 0.25);
|
|
164
|
+
pointer-events: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.screenshot-item .screenshot-placeholder {
|
|
168
|
+
position: relative;
|
|
169
|
+
overflow: hidden;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.screenshot-placeholder::after {
|
|
173
|
+
content: "";
|
|
174
|
+
position: absolute;
|
|
175
|
+
inset: 0;
|
|
176
|
+
background:
|
|
177
|
+
radial-gradient(circle at 20% 25%, rgba(46, 204, 113, 0.18), transparent 42%),
|
|
178
|
+
radial-gradient(circle at 80% 70%, rgba(52, 152, 219, 0.16), transparent 48%);
|
|
179
|
+
pointer-events: none;
|
|
180
|
+
opacity: 0.9;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.screenshot-placeholder .placeholder-content {
|
|
184
|
+
position: relative;
|
|
185
|
+
z-index: 1;
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 12px;
|
|
190
|
+
text-align: center;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.screenshot-placeholder .placeholder-icon {
|
|
194
|
+
width: 92px;
|
|
195
|
+
height: 92px;
|
|
196
|
+
border-radius: 999px;
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: center;
|
|
200
|
+
box-shadow: 0 16px 32px rgba(52, 152, 219, 0.22);
|
|
201
|
+
border: 1px solid rgba(52, 152, 219, 0.22);
|
|
202
|
+
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.9), rgba(52, 152, 219, 0.10));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.screenshot-placeholder .placeholder-icon i {
|
|
206
|
+
font-size: 40px;
|
|
207
|
+
color: var(--primary-color);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.screenshot-placeholder .placeholder-icon.info i {
|
|
211
|
+
color: #6c757d;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.screenshot-placeholder .placeholder-icon.reboot {
|
|
215
|
+
background: conic-gradient(from 210deg, rgba(46, 204, 113, 0.22), rgba(52, 152, 219, 0.22), rgba(46, 204, 113, 0.22));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.screenshot-placeholder .placeholder-text {
|
|
219
|
+
font-size: 13px;
|
|
220
|
+
font-weight: 600;
|
|
221
|
+
letter-spacing: 0.2px;
|
|
222
|
+
color: rgba(44, 62, 80, 0.78);
|
|
223
|
+
padding: 6px 10px;
|
|
224
|
+
border-radius: 999px;
|
|
225
|
+
background: rgba(255, 255, 255, 0.72);
|
|
226
|
+
border: 1px solid rgba(52, 152, 219, 0.14);
|
|
227
|
+
box-shadow: 0 6px 18px rgba(44, 62, 80, 0.06);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.screenshot-item:hover .screenshot-placeholder {
|
|
231
|
+
border-color: rgba(52, 152, 219, 0.28);
|
|
232
|
+
box-shadow: 0 12px 30px rgba(44, 62, 80, 0.10);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.screenshot-item:hover .placeholder-icon.reboot i {
|
|
236
|
+
animation: reboot-spin 1.4s ease-in-out infinite;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@keyframes reboot-spin {
|
|
240
|
+
0% { transform: rotate(0deg); }
|
|
241
|
+
60% { transform: rotate(220deg); }
|
|
242
|
+
100% { transform: rotate(360deg); }
|
|
243
|
+
}
|
|
143
244
|
|
|
144
245
|
.screenshot-caption {
|
|
145
246
|
font-size: 13px;
|
|
@@ -524,6 +625,10 @@
|
|
|
524
625
|
border-radius: 0 0 8px 8px;
|
|
525
626
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
526
627
|
}
|
|
628
|
+
|
|
629
|
+
.pagination-container.pagination-violations {
|
|
630
|
+
padding: 10px 16px;
|
|
631
|
+
}
|
|
527
632
|
|
|
528
633
|
.activity-item {
|
|
529
634
|
padding: 10px 15px;
|
|
@@ -1435,20 +1540,58 @@
|
|
|
1435
1540
|
|
|
1436
1541
|
<!-- Screenshots Section -->
|
|
1437
1542
|
{% if take_screenshots %}
|
|
1438
|
-
{% if screenshots %}
|
|
1543
|
+
{% if screenshots or kill_apps_events %}
|
|
1439
1544
|
<div class="section-block">
|
|
1440
1545
|
<h2 class="section-title">Test Screenshots</h2>
|
|
1441
1546
|
<div class="card">
|
|
1442
1547
|
<div class="card-body">
|
|
1443
1548
|
<div class="screenshots-container" id="screenshots">
|
|
1444
|
-
{%
|
|
1445
|
-
|
|
1446
|
-
<
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1549
|
+
{% if screenshots %}
|
|
1550
|
+
{% for screenshot in screenshots %}
|
|
1551
|
+
<div class="screenshot-item">
|
|
1552
|
+
{% if screenshot.path %}
|
|
1553
|
+
<a href="{{ screenshot.path }}" target="_blank">
|
|
1554
|
+
<img src="{{ screenshot.path }}" class="screenshot-img" id="{{ screenshot.id }}">
|
|
1555
|
+
</a>
|
|
1556
|
+
{% else %}
|
|
1557
|
+
<div class="screenshot-placeholder" id="{{ screenshot.id }}">
|
|
1558
|
+
{% if screenshot.info == "kill_apps" %}
|
|
1559
|
+
<div class="placeholder-content">
|
|
1560
|
+
<span class="placeholder-icon reboot">
|
|
1561
|
+
<i class="bi bi-bootstrap-reboot"></i>
|
|
1562
|
+
</span>
|
|
1563
|
+
<div class="placeholder-text">Restart app</div>
|
|
1564
|
+
</div>
|
|
1565
|
+
{% else %}
|
|
1566
|
+
<div class="placeholder-content">
|
|
1567
|
+
<span class="placeholder-icon info">
|
|
1568
|
+
<i class="bi bi-info-circle"></i>
|
|
1569
|
+
</span>
|
|
1570
|
+
<div class="placeholder-text">Info event</div>
|
|
1571
|
+
</div>
|
|
1572
|
+
{% endif %}
|
|
1573
|
+
</div>
|
|
1574
|
+
{% endif %}
|
|
1575
|
+
<div class="screenshot-caption">{{ screenshot.caption }}</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
{% endfor %}
|
|
1578
|
+
{% else %}
|
|
1579
|
+
{% for ev in kill_apps_events %}
|
|
1580
|
+
<div class="screenshot-item">
|
|
1581
|
+
<div class="screenshot-placeholder" id="{{ ev.step_index }}">
|
|
1582
|
+
<div class="placeholder-content">
|
|
1583
|
+
<span class="placeholder-icon reboot">
|
|
1584
|
+
<i class="bi bi-bootstrap-reboot"></i>
|
|
1585
|
+
</span>
|
|
1586
|
+
<div class="placeholder-text">Restart app</div>
|
|
1587
|
+
</div>
|
|
1588
|
+
</div>
|
|
1589
|
+
<div class="screenshot-caption">
|
|
1590
|
+
{{ ev.step_index }}. Monkey Step {{ ev.monkey_steps_count }}: restart app
|
|
1591
|
+
</div>
|
|
1592
|
+
</div>
|
|
1593
|
+
{% endfor %}
|
|
1594
|
+
{% endif %}
|
|
1452
1595
|
</div>
|
|
1453
1596
|
</div>
|
|
1454
1597
|
</div>
|
|
@@ -1630,7 +1773,7 @@
|
|
|
1630
1773
|
</thead>
|
|
1631
1774
|
<tbody id="property-violations-container">
|
|
1632
1775
|
{% for violation in property_violations %}
|
|
1633
|
-
<tr class="property-violation-row" data-page="1">
|
|
1776
|
+
<tr class="property-violation-row" data-page="1" data-status="{{ violation.state|default('fail') }}">
|
|
1634
1777
|
<td>{{ violation.index }}</td>
|
|
1635
1778
|
<td><span class="badge bg-light text-dark badge-custom">{{ violation.property_name }}</span></td>
|
|
1636
1779
|
<td>
|
|
@@ -1645,8 +1788,8 @@
|
|
|
1645
1788
|
</table>
|
|
1646
1789
|
|
|
1647
1790
|
<!-- Pagination for Property Violations -->
|
|
1648
|
-
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
1649
|
-
<div class="d-flex align-items-center">
|
|
1791
|
+
<div class="d-flex justify-content-between align-items-center mt-3 pagination-container pagination-violations">
|
|
1792
|
+
<div class="d-flex align-items-center gap-3">
|
|
1650
1793
|
<label for="violations-page-size" class="form-label me-2 mb-0">Show:</label>
|
|
1651
1794
|
<select class="form-select form-select-sm" id="violations-page-size" style="width: auto;">
|
|
1652
1795
|
<option value="5">5</option>
|
|
@@ -1655,6 +1798,11 @@
|
|
|
1655
1798
|
<option value="50">50</option>
|
|
1656
1799
|
<option value="100">100</option>
|
|
1657
1800
|
</select>
|
|
1801
|
+
<label for="violations-status-filter" class="form-label mb-0">Status:</label>
|
|
1802
|
+
<select class="form-select form-select-sm" id="violations-status-filter" style="width: auto;">
|
|
1803
|
+
<option value="all" selected>All</option>
|
|
1804
|
+
<option value="fail">Fail only</option>
|
|
1805
|
+
</select>
|
|
1658
1806
|
</div>
|
|
1659
1807
|
<nav aria-label="Property Violations Pagination">
|
|
1660
1808
|
<ul class="pagination pagination-sm mb-0" id="violations-pagination">
|
|
@@ -2232,6 +2380,7 @@
|
|
|
2232
2380
|
|
|
2233
2381
|
// Initialize pagination for Property tables
|
|
2234
2382
|
initPagination('property-violations-container', 'property-violation-row', 'violations-pagination', 'violations-page-size');
|
|
2383
|
+
initViolationFilter();
|
|
2235
2384
|
initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
|
|
2236
2385
|
|
|
2237
2386
|
// Initialize sorting for Property Checking Statistics
|
|
@@ -2402,6 +2551,10 @@
|
|
|
2402
2551
|
const allItems = Array.from(container.getElementsByClassName(itemClass));
|
|
2403
2552
|
const paginationElement = document.getElementById(paginationId);
|
|
2404
2553
|
|
|
2554
|
+
if (containerId === 'property-stats-container') {
|
|
2555
|
+
closePropertyErrorDetails(container);
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2405
2558
|
console.log('goToPage called with:', {pageNumber, containerId, itemClass, itemsPerPage, paginationId});
|
|
2406
2559
|
console.log('Total items found:', allItems.length);
|
|
2407
2560
|
|
|
@@ -2946,6 +3099,18 @@
|
|
|
2946
3099
|
}, 3000);
|
|
2947
3100
|
}
|
|
2948
3101
|
});
|
|
3102
|
+
|
|
3103
|
+
// Close opened Property Checking Statistics error details when pagination changes
|
|
3104
|
+
function closePropertyErrorDetails(container) {
|
|
3105
|
+
if (!container) return;
|
|
3106
|
+
|
|
3107
|
+
const openDetails = container.querySelectorAll('.property-detail-row.show');
|
|
3108
|
+
openDetails.forEach(function(detailRow) {
|
|
3109
|
+
const collapseInstance = bootstrap.Collapse.getInstance(detailRow) ||
|
|
3110
|
+
new bootstrap.Collapse(detailRow, { toggle: false });
|
|
3111
|
+
collapseInstance.hide();
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
2949
3114
|
|
|
2950
3115
|
// Global activity search functions
|
|
2951
3116
|
function performActivitySearch(searchInput) {
|
|
@@ -3140,6 +3305,33 @@
|
|
|
3140
3305
|
}
|
|
3141
3306
|
}
|
|
3142
3307
|
}
|
|
3308
|
+
|
|
3309
|
+
function initViolationFilter() {
|
|
3310
|
+
const filterSelect = document.getElementById('violations-status-filter');
|
|
3311
|
+
const container = document.getElementById('property-violations-container');
|
|
3312
|
+
|
|
3313
|
+
if (!filterSelect || !container) return;
|
|
3314
|
+
|
|
3315
|
+
filterSelect.addEventListener('change', function() {
|
|
3316
|
+
const filterValue = this.value;
|
|
3317
|
+
const rows = Array.from(container.getElementsByClassName('property-violation-row'));
|
|
3318
|
+
|
|
3319
|
+
rows.forEach(function(row) {
|
|
3320
|
+
const status = row.getAttribute('data-status') || 'fail';
|
|
3321
|
+
if (filterValue === 'all') {
|
|
3322
|
+
row.removeAttribute('data-search-visible');
|
|
3323
|
+
} else {
|
|
3324
|
+
row.setAttribute('data-search-visible', status === filterValue ? 'true' : 'false');
|
|
3325
|
+
}
|
|
3326
|
+
});
|
|
3327
|
+
|
|
3328
|
+
initPagination('property-violations-container', 'property-violation-row', 'violations-pagination', 'violations-page-size');
|
|
3329
|
+
|
|
3330
|
+
const currentPageSize = document.getElementById('violations-page-size') ?
|
|
3331
|
+
parseInt(document.getElementById('violations-page-size').value) : 10;
|
|
3332
|
+
goToPage(1, 'property-violations-container', 'property-violation-row', currentPageSize, 'violations-pagination');
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
3143
3335
|
|
|
3144
3336
|
// Global pagination and navigation functions
|
|
3145
3337
|
function goToPage(pageNumber, containerId, itemClass, itemsPerPage, paginationId) {
|
|
@@ -3147,6 +3339,10 @@
|
|
|
3147
3339
|
const allItems = Array.from(container.getElementsByClassName(itemClass));
|
|
3148
3340
|
const paginationElement = document.getElementById(paginationId);
|
|
3149
3341
|
|
|
3342
|
+
if (containerId === 'property-stats-container') {
|
|
3343
|
+
closePropertyErrorDetails(container);
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3150
3346
|
// Get items that should be visible based on search filter
|
|
3151
3347
|
const filteredItems = allItems.filter(item => {
|
|
3152
3348
|
const searchVisible = item.getAttribute('data-search-visible');
|
|
@@ -3,7 +3,7 @@ import threading
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
from .adbUtils import ADBDevice
|
|
6
|
-
from .utils import getLogger
|
|
6
|
+
from .utils import getLogger, catchException, timer
|
|
7
7
|
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
if TYPE_CHECKING:
|
|
@@ -34,36 +34,32 @@ class ResultSyncer:
|
|
|
34
34
|
"""Thread function that waits for sync event and then syncs data"""
|
|
35
35
|
while self.running:
|
|
36
36
|
# Wait for sync event with a timeout to periodically check if still running
|
|
37
|
-
if self.sync_event.wait(timeout=
|
|
37
|
+
if self.sync_event.wait(timeout=1):
|
|
38
38
|
self._sync_device_data()
|
|
39
39
|
self.sync_event.clear()
|
|
40
40
|
|
|
41
|
+
@timer("Data Sync cost %cost_time seconds")
|
|
41
42
|
def close(self):
|
|
42
43
|
self.running = False
|
|
43
44
|
self.sync_event.set()
|
|
44
45
|
if self.thread and self.thread.is_alive():
|
|
46
|
+
logger.info("Syncing result data from device. Please wait...")
|
|
45
47
|
self.thread.join(timeout=10)
|
|
46
48
|
self._sync_device_data()
|
|
47
49
|
try:
|
|
48
50
|
logger.debug(f"Removing device output directory: {self.device_output_dir}")
|
|
49
51
|
remove_device_dir = ["rm", "-rf", self.device_output_dir]
|
|
50
|
-
# adb_shell(remove_device_dir)
|
|
51
52
|
self.dev.shell(remove_device_dir)
|
|
52
53
|
except Exception as e:
|
|
53
54
|
logger.error(f"Error removing device output directory: {e}", flush=True)
|
|
54
55
|
|
|
56
|
+
@catchException("Error during device data sync.")
|
|
55
57
|
def _sync_device_data(self):
|
|
56
58
|
"""
|
|
57
59
|
Sync the device data to the local directory.
|
|
58
60
|
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
self.dev.sync.pull_dir(self.device_output_dir, self.output_dir, exist_ok=True)
|
|
63
|
-
# pull_file(self.device_output_dir, str(self.output_dir))
|
|
61
|
+
logger.debug("Syncing data")
|
|
62
|
+
self.dev.sync.pull_dir(self.device_output_dir, self.output_dir, exist_ok=True)
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# adb_shell(remove_pulled_screenshots)
|
|
68
|
-
except Exception as e:
|
|
69
|
-
logger.error(f"Error in data sync: {e}")
|
|
64
|
+
remove_pulled_screenshots = ["find", self.device_output_dir, "-name", '"*.png"', "-delete"]
|
|
65
|
+
self.dev.shell(remove_pulled_screenshots)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from kea2.u2Driver import U2StaticChecker, U2StaticDevice
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
XML_PATH = Path(__file__).parent / "xpath_test.xml"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class U2StaticCheckerForTest(U2StaticChecker):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.d = U2StaticDevice(script_driver=None)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_static_checker():
|
|
15
|
+
xml = ""
|
|
16
|
+
with open(XML_PATH, "r", encoding="utf-8") as f:
|
|
17
|
+
xml = f.read()
|
|
18
|
+
d = U2StaticCheckerForTest()
|
|
19
|
+
return d.getInstance(xml)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestXPath(unittest.TestCase):
|
|
23
|
+
|
|
24
|
+
def setUp(self):
|
|
25
|
+
self.d = get_static_checker()
|
|
26
|
+
|
|
27
|
+
def test_basic_xpath(self):
|
|
28
|
+
assert self.d.xpath("""//*[@text="Hrgshsjs"]""").exists
|
|
29
|
+
assert self.d.xpath("""//android.widget.TextView[@text="hehzhe"]""").exists
|
|
30
|
+
assert self.d.xpath(
|
|
31
|
+
"""(//*[@resource-id="it.feio.android.omninotes.alpha:id/category_marker"])[3]"""
|
|
32
|
+
).exists
|
|
33
|
+
|
|
34
|
+
assert self.d.xpath('@com.android.systemui:id/clock').exists
|
|
35
|
+
|
|
36
|
+
assert self.d.xpath('//android.widget.TextView[@text="hehzhe"]')\
|
|
37
|
+
.parent_exists('//androidx.recyclerview.widget.RecyclerView')
|
|
38
|
+
|
|
39
|
+
assert (self.d.xpath('100') &
|
|
40
|
+
self.d.xpath('@com.android.systemui:id/battery_inside_percent')).exists
|
|
41
|
+
|
|
42
|
+
assert (self.d.xpath('100') | self.d.xpath('2:14')).exists # |
|
|
43
|
+
|
|
44
|
+
assert self.d.xpath('//android.widget.TextView[@text="Notes"]')\
|
|
45
|
+
.parent_exists('@it.feio.android.omninotes.alpha:id/toolbar') # parent_exists
|
|
46
|
+
|
|
47
|
+
assert self.d.xpath('@it.feio.android.omninotes.alpha:id/fab')\
|
|
48
|
+
.child('/android.widget.ImageButton').exists # child
|
|
49
|
+
|
|
50
|
+
assert self.d.xpath('//androidx.drawerlayout.widget.DrawerLayout[@resource-id="it.feio.android.omninotes.alpha:id/drawer_layout"]' +
|
|
51
|
+
'//android.view.ViewGroup[@resource-id="it.feio.android.omninotes.alpha:id/toolbar"]').exists
|
|
52
|
+
|
|
53
|
+
assert self.d.xpath('(//android.view.View[@resource-id="it.feio.android.omninotes.alpha:id/category_marker"])[3]')\
|
|
54
|
+
.parent_exists('//androidx.recyclerview.widget.RecyclerView[@resource-id="it.feio.android.omninotes.alpha:id/list"]')
|
|
55
|
+
|
|
56
|
+
assert self.d.xpath('//androidx.drawerlayout.widget.DrawerLayout[@resource-id="it.feio.android.omninotes.alpha:id/drawer_layout"]' +
|
|
57
|
+
'//android.view.ViewGroup[@resource-id="it.feio.android.omninotes.alpha:id/toolbar"]').exists
|
|
58
|
+
|
|
59
|
+
# parent_exists
|
|
60
|
+
node = (self.d.xpath('@com.android.systemui:id/battery_inside_percent') |
|
|
61
|
+
self.d.xpath('@com.android.systemui:id/clock'))
|
|
62
|
+
assert node & self.d.xpath('//android.widget.TextView')
|
|
63
|
+
assert node.parent_exists('@com.android.systemui:id/status_bar') # parent_exists
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
unittest.main()
|
|
Binary file
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from kea2.u2Driver import U2StaticChecker, U2StaticDevice
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
XML_PATH = Path(__file__).parent / "xpath_test.xml"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class U2StaticCheckerForTest(U2StaticChecker):
|
|
10
|
-
def __init__(self):
|
|
11
|
-
self.d = U2StaticDevice(script_driver=None)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_static_checker():
|
|
15
|
-
xml = ""
|
|
16
|
-
with open(XML_PATH, "r", encoding="utf-8") as f:
|
|
17
|
-
xml = f.read()
|
|
18
|
-
d = U2StaticCheckerForTest()
|
|
19
|
-
return d.getInstance(xml)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class TestXPath(unittest.TestCase):
|
|
23
|
-
|
|
24
|
-
def setUp(self):
|
|
25
|
-
self.d = get_static_checker()
|
|
26
|
-
|
|
27
|
-
def test_basic_xpath(self):
|
|
28
|
-
assert self.d.xpath("""//*[@text="Hrgshsjs"]""").exists
|
|
29
|
-
assert self.d.xpath("""//android.widget.TextView[@text="hehzhe"]""").exists
|
|
30
|
-
assert self.d.xpath(
|
|
31
|
-
"""(//*[@resource-id="it.feio.android.omninotes.alpha:id/category_marker"])[3]"""
|
|
32
|
-
).exists
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if __name__ == "__main__":
|
|
36
|
-
unittest.main()
|
|
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
|
{kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so
RENAMED
|
File without changes
|
{kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so
RENAMED
|
File without changes
|
|
File without changes
|
{kea2_python-1.0.4 → kea2_python-1.0.5}/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
|
|
File without changes
|
{kea2_python-1.0.4 → kea2_python-1.0.5}/kea2/report/templates/merged_bug_report_template.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|