Kea2-python 0.1.1__tar.gz → 0.1.3__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.1.1 → kea2_python-0.1.3}/Kea2_python.egg-info/PKG-INFO +5 -4
- {kea2_python-0.1.1 → kea2_python-0.1.3}/Kea2_python.egg-info/SOURCES.txt +3 -1
- {kea2_python-0.1.1 → kea2_python-0.1.3}/PKG-INFO +5 -4
- {kea2_python-0.1.1 → kea2_python-0.1.3}/README.md +4 -3
- kea2_python-0.1.3/kea2/assets/kea2-thirdpart.jar +0 -0
- kea2_python-0.1.3/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/quicktest.py +3 -1
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/bug_report_generator.py +3 -1
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/fastbotManager.py +12 -2
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/keaUtils.py +63 -34
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/kea_launcher.py +10 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/u2Driver.py +122 -108
- {kea2_python-0.1.1 → kea2_python-0.1.3}/pyproject.toml +1 -1
- kea2_python-0.1.3/tests/test_u2Selector.py +200 -0
- kea2_python-0.1.1/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/Kea2_python.egg-info/dependency_links.txt +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/Kea2_python.egg-info/entry_points.txt +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/Kea2_python.egg-info/requires.txt +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/Kea2_python.egg-info/top_level.txt +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/LICENSE +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/__init__.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/absDriver.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/adbUtils.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot-thirdpart.jar +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/abl.strings +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/awl.strings +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.config +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.strings +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/widget.block.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/framework.jar +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/cli.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/logWatcher.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/resultSyncer.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/templates/bug_report_template.html +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/utils.py +0 -0
- {kea2_python-0.1.1 → kea2_python-0.1.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -17,8 +17,9 @@ Dynamic: license-file
|
|
|
17
17
|
[](https://pepy.tech/projects/kea2-python)
|
|
18
18
|

|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
<div>
|
|
21
|
-
<img src="https://github.com/user-attachments/assets/
|
|
22
|
+
<img src="https://github.com/user-attachments/assets/aa5839fc-4542-46f6-918b-c9f891356c84" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
22
23
|
</div>
|
|
23
24
|
|
|
24
25
|
## About
|
|
@@ -67,7 +68,7 @@ In the future, Kea2 will be extended to support
|
|
|
67
68
|
|
|
68
69
|
Running environment:
|
|
69
70
|
- support Windows, MacOS and Linux
|
|
70
|
-
- python 3.8+, Android
|
|
71
|
+
- python 3.8+, Android 5.0+ (Android SDK installed)
|
|
71
72
|
- **VPN closed** (Features 2 and 3 required)
|
|
72
73
|
|
|
73
74
|
Install Kea2 by `pip`:
|
|
@@ -251,4 +252,4 @@ Kea2 has also received many valuable insights, advices, feedbacks and lessons sh
|
|
|
251
252
|
|
|
252
253
|
[^1]: 不少UI自动化测试工具提供了“自定义事件序列”能力(如[Fastbot](https://github.com/bytedance/Fastbot_Android/blob/main/handbook-cn.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E5%BA%8F%E5%88%97) 和[AppCrawler](https://github.com/seveniruby/AppCrawler)),但在实际使用中存在不少问题,如自定义能力有限、使用不灵活等。此前不少Fastbot用户抱怨过其“自定义事件序列”在使用中的问题,如[#209](https://github.com/bytedance/Fastbot_Android/issues/209), [#225](https://github.com/bytedance/Fastbot_Android/issues/225), [#286](https://github.com/bytedance/Fastbot_Android/issues/286)等。
|
|
253
254
|
|
|
254
|
-
[^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
|
|
255
|
+
[^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
|
|
@@ -21,6 +21,7 @@ kea2/u2Driver.py
|
|
|
21
21
|
kea2/utils.py
|
|
22
22
|
kea2/assets/fastbot-thirdpart.jar
|
|
23
23
|
kea2/assets/framework.jar
|
|
24
|
+
kea2/assets/kea2-thirdpart.jar
|
|
24
25
|
kea2/assets/monkeyq.jar
|
|
25
26
|
kea2/assets/quicktest.py
|
|
26
27
|
kea2/assets/fastbot_configs/abl.strings
|
|
@@ -35,4 +36,5 @@ kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so
|
|
|
35
36
|
kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so
|
|
36
37
|
kea2/assets/fastbot_libs/x86/libfastbot_native.so
|
|
37
38
|
kea2/assets/fastbot_libs/x86_64/libfastbot_native.so
|
|
38
|
-
kea2/templates/bug_report_template.html
|
|
39
|
+
kea2/templates/bug_report_template.html
|
|
40
|
+
tests/test_u2Selector.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -17,8 +17,9 @@ Dynamic: license-file
|
|
|
17
17
|
[](https://pepy.tech/projects/kea2-python)
|
|
18
18
|

|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
<div>
|
|
21
|
-
<img src="https://github.com/user-attachments/assets/
|
|
22
|
+
<img src="https://github.com/user-attachments/assets/aa5839fc-4542-46f6-918b-c9f891356c84" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
22
23
|
</div>
|
|
23
24
|
|
|
24
25
|
## About
|
|
@@ -67,7 +68,7 @@ In the future, Kea2 will be extended to support
|
|
|
67
68
|
|
|
68
69
|
Running environment:
|
|
69
70
|
- support Windows, MacOS and Linux
|
|
70
|
-
- python 3.8+, Android
|
|
71
|
+
- python 3.8+, Android 5.0+ (Android SDK installed)
|
|
71
72
|
- **VPN closed** (Features 2 and 3 required)
|
|
72
73
|
|
|
73
74
|
Install Kea2 by `pip`:
|
|
@@ -251,4 +252,4 @@ Kea2 has also received many valuable insights, advices, feedbacks and lessons sh
|
|
|
251
252
|
|
|
252
253
|
[^1]: 不少UI自动化测试工具提供了“自定义事件序列”能力(如[Fastbot](https://github.com/bytedance/Fastbot_Android/blob/main/handbook-cn.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E5%BA%8F%E5%88%97) 和[AppCrawler](https://github.com/seveniruby/AppCrawler)),但在实际使用中存在不少问题,如自定义能力有限、使用不灵活等。此前不少Fastbot用户抱怨过其“自定义事件序列”在使用中的问题,如[#209](https://github.com/bytedance/Fastbot_Android/issues/209), [#225](https://github.com/bytedance/Fastbot_Android/issues/225), [#286](https://github.com/bytedance/Fastbot_Android/issues/286)等。
|
|
253
254
|
|
|
254
|
-
[^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
|
|
255
|
+
[^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
[](https://pepy.tech/projects/kea2-python)
|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
<div>
|
|
8
|
-
<img src="https://github.com/user-attachments/assets/
|
|
9
|
+
<img src="https://github.com/user-attachments/assets/aa5839fc-4542-46f6-918b-c9f891356c84" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
9
10
|
</div>
|
|
10
11
|
|
|
11
12
|
## About
|
|
@@ -54,7 +55,7 @@ In the future, Kea2 will be extended to support
|
|
|
54
55
|
|
|
55
56
|
Running environment:
|
|
56
57
|
- support Windows, MacOS and Linux
|
|
57
|
-
- python 3.8+, Android
|
|
58
|
+
- python 3.8+, Android 5.0+ (Android SDK installed)
|
|
58
59
|
- **VPN closed** (Features 2 and 3 required)
|
|
59
60
|
|
|
60
61
|
Install Kea2 by `pip`:
|
|
@@ -238,4 +239,4 @@ Kea2 has also received many valuable insights, advices, feedbacks and lessons sh
|
|
|
238
239
|
|
|
239
240
|
[^1]: 不少UI自动化测试工具提供了“自定义事件序列”能力(如[Fastbot](https://github.com/bytedance/Fastbot_Android/blob/main/handbook-cn.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E5%BA%8F%E5%88%97) 和[AppCrawler](https://github.com/seveniruby/AppCrawler)),但在实际使用中存在不少问题,如自定义能力有限、使用不灵活等。此前不少Fastbot用户抱怨过其“自定义事件序列”在使用中的问题,如[#209](https://github.com/bytedance/Fastbot_Android/issues/209), [#225](https://github.com/bytedance/Fastbot_Android/issues/225), [#286](https://github.com/bytedance/Fastbot_Android/issues/286)等。
|
|
240
241
|
|
|
241
|
-
[^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
|
|
242
|
+
[^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
|
|
Binary file
|
|
Binary file
|
|
@@ -81,7 +81,9 @@ if __name__ == "__main__":
|
|
|
81
81
|
Driver=U2Driver,
|
|
82
82
|
packageNames=[PACKAGE_NAME],
|
|
83
83
|
# serial="emulator-5554", # specify the serial
|
|
84
|
-
maxStep=
|
|
84
|
+
maxStep=50,
|
|
85
|
+
profile_period=10,
|
|
86
|
+
take_screenshots=True, # whether to take screenshots, default is False
|
|
85
87
|
# running_mins=10, # specify the maximal running time in minutes, default value is 10m
|
|
86
88
|
# throttle=200, # specify the throttle in milliseconds, default value is 200ms
|
|
87
89
|
agent="u2" # 'native' for running the vanilla Fastbot, 'u2' for running Kea2
|
|
@@ -68,7 +68,7 @@ class BugReportGenerator:
|
|
|
68
68
|
with open(report_path, "w", encoding="utf-8") as f:
|
|
69
69
|
f.write(html_content)
|
|
70
70
|
|
|
71
|
-
logger.
|
|
71
|
+
logger.info(f"Bug report saved to: {report_path}")
|
|
72
72
|
|
|
73
73
|
except Exception as e:
|
|
74
74
|
logger.error(f"Error generating bug report: {e}")
|
|
@@ -323,6 +323,8 @@ class BugReportGenerator:
|
|
|
323
323
|
if lines:
|
|
324
324
|
# Collect coverage trend data
|
|
325
325
|
for line in lines:
|
|
326
|
+
if not line.strip():
|
|
327
|
+
continue
|
|
326
328
|
try:
|
|
327
329
|
coverage_data = json.loads(line)
|
|
328
330
|
data["coverage_trend"].append({
|
|
@@ -42,6 +42,11 @@ class FastbotManager:
|
|
|
42
42
|
"/sdcard/fastbot-thirdpart.jar",
|
|
43
43
|
device=options.serial,
|
|
44
44
|
)
|
|
45
|
+
push_file(
|
|
46
|
+
Path.joinpath(cur_dir, "assets/kea2-thirdpart.jar"),
|
|
47
|
+
"/sdcard/kea2-thirdpart.jar",
|
|
48
|
+
device=options.serial,
|
|
49
|
+
)
|
|
45
50
|
push_file(
|
|
46
51
|
Path.joinpath(cur_dir, "assets/framework.jar"),
|
|
47
52
|
"/sdcard/framework.jar",
|
|
@@ -91,7 +96,12 @@ class FastbotManager:
|
|
|
91
96
|
|
|
92
97
|
def _startFastbotService(self) -> threading.Thread:
|
|
93
98
|
shell_command = [
|
|
94
|
-
"CLASSPATH
|
|
99
|
+
"CLASSPATH="
|
|
100
|
+
"/sdcard/monkeyq.jar:"
|
|
101
|
+
"/sdcard/framework.jar:"
|
|
102
|
+
"/sdcard/fastbot-thirdpart.jar:"
|
|
103
|
+
"/sdcard/kea2-thirdpart.jar",
|
|
104
|
+
|
|
95
105
|
"exec", "app_process",
|
|
96
106
|
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
97
107
|
"-p", *self.options.packageNames,
|
|
@@ -126,7 +136,7 @@ class FastbotManager:
|
|
|
126
136
|
self.return_code = proc.wait()
|
|
127
137
|
f.close()
|
|
128
138
|
if self.return_code != 0:
|
|
129
|
-
raise RuntimeError(f"Fastbot Error: Terminated with [code {self.return_code}]")
|
|
139
|
+
raise RuntimeError(f"Fastbot Error: Terminated with [code {self.return_code}] See {self.log_file} for details.")
|
|
130
140
|
|
|
131
141
|
def get_return_code(self):
|
|
132
142
|
if self.thread:
|
|
@@ -14,7 +14,7 @@ from .bug_report_generator import BugReportGenerator
|
|
|
14
14
|
from .resultSyncer import ResultSyncer
|
|
15
15
|
from .logWatcher import LogWatcher
|
|
16
16
|
from .utils import TimeStamp, getProjectRoot, getLogger
|
|
17
|
-
from .u2Driver import StaticU2UiObject
|
|
17
|
+
from .u2Driver import StaticU2UiObject
|
|
18
18
|
from .fastbotManager import FastbotManager
|
|
19
19
|
import uiautomator2 as u2
|
|
20
20
|
import types
|
|
@@ -126,6 +126,8 @@ class Options:
|
|
|
126
126
|
profile_period: int = 25
|
|
127
127
|
# take screenshots for every step
|
|
128
128
|
take_screenshots: bool = False
|
|
129
|
+
# The root of output dir on device
|
|
130
|
+
device_output_root: str = "/sdcard"
|
|
129
131
|
# the debug mode
|
|
130
132
|
debug: bool = False
|
|
131
133
|
|
|
@@ -135,10 +137,18 @@ class Options:
|
|
|
135
137
|
super().__setattr__(name, value)
|
|
136
138
|
|
|
137
139
|
def __post_init__(self):
|
|
140
|
+
import logging
|
|
141
|
+
logging.basicConfig(level=logging.DEBUG if self.debug else logging.INFO)
|
|
138
142
|
if self.serial and self.Driver:
|
|
139
143
|
self.Driver.setDeviceSerial(self.serial)
|
|
140
144
|
global LOGFILE, RESFILE, STAMP
|
|
141
145
|
if self.log_stamp:
|
|
146
|
+
illegal_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|', '\n', '\r', '\t', '\0']
|
|
147
|
+
for char in illegal_chars:
|
|
148
|
+
if char in self.log_stamp:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"char: `{char}` is illegal in --log-stamp. current stamp: {self.log_stamp}"
|
|
151
|
+
)
|
|
142
152
|
STAMP = self.log_stamp
|
|
143
153
|
self.output_dir = Path(self.output_dir).absolute() / f"res_{STAMP}"
|
|
144
154
|
LOGFILE = f"fastbot_{STAMP}.log"
|
|
@@ -324,8 +334,9 @@ class KeaTestRunner(TextTestRunner):
|
|
|
324
334
|
xml_raw = self.stepMonkey()
|
|
325
335
|
propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
|
|
326
336
|
except requests.ConnectionError:
|
|
337
|
+
logger.info("Connection refused by remote.")
|
|
327
338
|
if fb.get_return_code() == 0:
|
|
328
|
-
logger.info("
|
|
339
|
+
logger.info("Exploration times up (--running-minutes).")
|
|
329
340
|
end_by_remote = True
|
|
330
341
|
break
|
|
331
342
|
raise RuntimeError("Fastbot Aborted.")
|
|
@@ -333,8 +344,6 @@ class KeaTestRunner(TextTestRunner):
|
|
|
333
344
|
if self.options.profile_period and self.stepsCount % self.options.profile_period == 0:
|
|
334
345
|
resultSyncer.sync_event.set()
|
|
335
346
|
|
|
336
|
-
print(f"{len(propsSatisfiedPrecond)} precond satisfied.", flush=True)
|
|
337
|
-
|
|
338
347
|
# Go to the next round if no precond satisfied
|
|
339
348
|
if len(propsSatisfiedPrecond) == 0:
|
|
340
349
|
continue
|
|
@@ -467,18 +476,25 @@ class KeaTestRunner(TextTestRunner):
|
|
|
467
476
|
if not precond(test):
|
|
468
477
|
valid = False
|
|
469
478
|
break
|
|
479
|
+
except u2.UiObjectNotFoundError as e:
|
|
480
|
+
valid = False
|
|
481
|
+
break
|
|
470
482
|
except Exception as e:
|
|
471
|
-
|
|
483
|
+
logger.error(f"Error when checking precond: {getFullPropName(test)}")
|
|
472
484
|
traceback.print_exc()
|
|
473
485
|
valid = False
|
|
474
486
|
break
|
|
475
487
|
# if all the precond passed. make it the candidate prop.
|
|
476
488
|
if valid:
|
|
477
|
-
logger.debug(f"precond satisfied: {getFullPropName(test)}")
|
|
478
489
|
if result.getExcuted(test) >= getattr(prop, MAX_TRIES_MARKER, float("inf")):
|
|
479
|
-
|
|
490
|
+
print(f"{getFullPropName(test)} has reached its max_tries. Skip.", flush=True)
|
|
480
491
|
continue
|
|
481
492
|
validProps[propName] = test
|
|
493
|
+
|
|
494
|
+
print(f"{len(validProps)} precond satisfied.", flush=True)
|
|
495
|
+
if len(validProps) > 0:
|
|
496
|
+
print("[INFO] Valid properties:",flush=True)
|
|
497
|
+
print("\n".join([f' - {getFullPropName(p)}' for p in validProps.values()]), flush=True)
|
|
482
498
|
return validProps
|
|
483
499
|
|
|
484
500
|
def _logScript(self, execution_info:Dict):
|
|
@@ -495,7 +511,8 @@ class KeaTestRunner(TextTestRunner):
|
|
|
495
511
|
URL = f"http://localhost:{self.scriptDriver.lport}/init"
|
|
496
512
|
data = {
|
|
497
513
|
"takeScreenshots": self.options.take_screenshots,
|
|
498
|
-
"Stamp": STAMP
|
|
514
|
+
"Stamp": STAMP,
|
|
515
|
+
"deviceOutputRoot": self.options.device_output_root,
|
|
499
516
|
}
|
|
500
517
|
print(f"[INFO] Init fastbot: {data}", flush=True)
|
|
501
518
|
r = requests.post(
|
|
@@ -505,7 +522,7 @@ class KeaTestRunner(TextTestRunner):
|
|
|
505
522
|
res = r.content.decode(encoding="utf-8")
|
|
506
523
|
import re
|
|
507
524
|
self.device_output_dir = re.match(r"outputDir:(.+)", res).group(1)
|
|
508
|
-
print(f"[INFO] Fastbot initiated.
|
|
525
|
+
print(f"[INFO] Fastbot initiated. outputDir: {res}", flush=True)
|
|
509
526
|
|
|
510
527
|
def collectAllProperties(self, test: TestSuite):
|
|
511
528
|
"""collect all the properties to prepare for PBT
|
|
@@ -607,60 +624,72 @@ class KeaTestRunner(TextTestRunner):
|
|
|
607
624
|
"""
|
|
608
625
|
def _get_xpath_widgets(func):
|
|
609
626
|
blocked_set = set()
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
627
|
+
script_driver = self.options.Driver.getScriptDriver()
|
|
628
|
+
preconds = getattr(func, PRECONDITIONS_MARKER, [])
|
|
629
|
+
|
|
630
|
+
def preconds_pass(preconds):
|
|
631
|
+
try:
|
|
632
|
+
return all(precond(script_driver) for precond in preconds)
|
|
633
|
+
except u2.UiObjectNotFoundError as e:
|
|
634
|
+
return False
|
|
635
|
+
except Exception as e:
|
|
636
|
+
logger.error(f"Error processing precond. Check if precond: {e}")
|
|
637
|
+
traceback.print_exc()
|
|
638
|
+
return False
|
|
639
|
+
|
|
640
|
+
if preconds_pass(preconds):
|
|
641
|
+
try:
|
|
614
642
|
_widgets = func(self.options.Driver.getStaticChecker())
|
|
615
643
|
_widgets = _widgets if isinstance(_widgets, list) else [_widgets]
|
|
616
644
|
for w in _widgets:
|
|
617
645
|
if isinstance(w, StaticU2UiObject):
|
|
618
|
-
xpath = selector_to_xpath(w.selector
|
|
619
|
-
|
|
646
|
+
xpath = w.selector_to_xpath(w.selector)
|
|
647
|
+
if xpath != '//error':
|
|
648
|
+
blocked_set.add(xpath)
|
|
620
649
|
elif isinstance(w, u2.xpath.XPathSelector):
|
|
621
650
|
xpath = w._parent.xpath
|
|
622
651
|
blocked_set.add(xpath)
|
|
623
652
|
else:
|
|
624
|
-
logger.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
653
|
+
logger.error(f"block widget defined in {func.__name__} Not supported.")
|
|
654
|
+
except Exception as e:
|
|
655
|
+
logger.error(f"Error processing blocked widgets in: {func}")
|
|
656
|
+
logger.error(e)
|
|
657
|
+
traceback.print_exc()
|
|
628
658
|
return blocked_set
|
|
629
659
|
|
|
630
|
-
|
|
660
|
+
result = {
|
|
631
661
|
"widgets": set(),
|
|
632
662
|
"trees": set()
|
|
633
663
|
}
|
|
634
664
|
|
|
635
|
-
|
|
636
665
|
for func in self._blockWidgetFuncs["widgets"]:
|
|
637
666
|
widgets = _get_xpath_widgets(func)
|
|
638
|
-
|
|
639
|
-
|
|
667
|
+
result["widgets"].update(widgets)
|
|
640
668
|
|
|
641
669
|
for func in self._blockWidgetFuncs["trees"]:
|
|
642
670
|
trees = _get_xpath_widgets(func)
|
|
643
|
-
|
|
671
|
+
result["trees"].update(trees)
|
|
644
672
|
|
|
673
|
+
result["widgets"] = list(result["widgets"] - result["trees"])
|
|
674
|
+
result["trees"] = list(result["trees"])
|
|
645
675
|
|
|
646
|
-
|
|
647
|
-
res["trees"] = list(res["trees"])
|
|
648
|
-
|
|
649
|
-
return res
|
|
676
|
+
return result
|
|
650
677
|
|
|
651
678
|
|
|
652
679
|
def __del__(self):
|
|
653
680
|
"""tearDown method. Cleanup the env.
|
|
654
681
|
"""
|
|
655
|
-
try:
|
|
656
|
-
logger.debug("Generating test bug report")
|
|
657
|
-
report_generator = BugReportGenerator(self.options.output_dir)
|
|
658
|
-
report_generator.generate_report()
|
|
659
|
-
except Exception as e:
|
|
660
|
-
logger.error(f"Error generating bug report: {e}", flush=True)
|
|
661
682
|
try:
|
|
662
683
|
self.stopMonkey()
|
|
663
684
|
except Exception as e:
|
|
664
685
|
pass
|
|
686
|
+
|
|
665
687
|
if self.options.Driver:
|
|
666
688
|
self.options.Driver.tearDown()
|
|
689
|
+
|
|
690
|
+
try:
|
|
691
|
+
logger.info("Generating bug report")
|
|
692
|
+
report_generator = BugReportGenerator(self.options.output_dir)
|
|
693
|
+
report_generator.generate_report()
|
|
694
|
+
except Exception as e:
|
|
695
|
+
logger.error(f"Error generating bug report: {e}", flush=True)
|
|
@@ -91,6 +91,15 @@ def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.Argument
|
|
|
91
91
|
default=25,
|
|
92
92
|
help="Steps to profile the testing statistics.",
|
|
93
93
|
)
|
|
94
|
+
|
|
95
|
+
parser.add_argument(
|
|
96
|
+
"--device-output-root",
|
|
97
|
+
dest="device_output_root",
|
|
98
|
+
type=str,
|
|
99
|
+
required=False,
|
|
100
|
+
default="/sdcard",
|
|
101
|
+
help="The root of device output dir. (Saving tmp log files and screenshots)",
|
|
102
|
+
)
|
|
94
103
|
|
|
95
104
|
parser.add_argument(
|
|
96
105
|
"--take-screenshots",
|
|
@@ -171,6 +180,7 @@ def run(args=None):
|
|
|
171
180
|
log_stamp=args.log_stamp,
|
|
172
181
|
profile_period=args.profile_period,
|
|
173
182
|
take_screenshots=args.take_screenshots,
|
|
183
|
+
device_output_root=args.device_output_root
|
|
174
184
|
)
|
|
175
185
|
|
|
176
186
|
KeaTestRunner.setOptions(options)
|
|
@@ -100,39 +100,105 @@ class StaticU2UiObject(u2.UiObject):
|
|
|
100
100
|
return filterDict[originKey]
|
|
101
101
|
return originKey
|
|
102
102
|
|
|
103
|
-
def
|
|
103
|
+
def selector_to_xpath(self, selector: u2.Selector, is_initial: bool = True) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Convert a u2 Selector into an XPath expression compatible with Java Android UI controls.
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"""
|
|
109
|
-
new_kwargs = dict()
|
|
110
|
-
SPECIAL_KEY = {"mask", "childOrSibling", "childOrSiblingSelector"}
|
|
111
|
-
for key, val in kwargs.items():
|
|
112
|
-
if key in SPECIAL_KEY:
|
|
113
|
-
continue
|
|
114
|
-
key = self._transferU2Keys(key)
|
|
115
|
-
new_kwargs[key] = val
|
|
116
|
-
return new_kwargs
|
|
107
|
+
Args:
|
|
108
|
+
selector (u2.Selector): A u2 Selector object
|
|
109
|
+
is_initial (bool): Whether it is the initial node, defaults to True
|
|
117
110
|
|
|
118
|
-
|
|
111
|
+
Returns:
|
|
112
|
+
str: The corresponding XPath expression
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
116
|
+
xpath = ".//node" if is_initial else "node"
|
|
117
|
+
|
|
118
|
+
conditions = []
|
|
119
|
+
|
|
120
|
+
if "className" in selector:
|
|
121
|
+
conditions.insert(0, f"[@class='{selector['className']}']")
|
|
122
|
+
|
|
123
|
+
if "text" in selector:
|
|
124
|
+
conditions.append(f"[@text='{selector['text']}']")
|
|
125
|
+
elif "textContains" in selector:
|
|
126
|
+
conditions.append(f"[contains(@text, '{selector['textContains']}')]")
|
|
127
|
+
elif "textStartsWith" in selector:
|
|
128
|
+
conditions.append(f"[starts-with(@text, '{selector['textStartsWith']}')]")
|
|
129
|
+
elif "textMatches" in selector:
|
|
130
|
+
raise NotImplementedError("'textMatches' syntax is not supported")
|
|
131
|
+
|
|
132
|
+
if "description" in selector:
|
|
133
|
+
conditions.append(f"[@content-desc='{selector['description']}']")
|
|
134
|
+
elif "descriptionContains" in selector:
|
|
135
|
+
conditions.append(f"[contains(@content-desc, '{selector['descriptionContains']}')]")
|
|
136
|
+
elif "descriptionStartsWith" in selector:
|
|
137
|
+
conditions.append(f"[starts-with(@content-desc, '{selector['descriptionStartsWith']}')]")
|
|
138
|
+
elif "descriptionMatches" in selector:
|
|
139
|
+
raise NotImplementedError("'descriptionMatches' syntax is not supported")
|
|
140
|
+
|
|
141
|
+
if "packageName" in selector:
|
|
142
|
+
conditions.append(f"[@package='{selector['packageName']}']")
|
|
143
|
+
elif "packageNameMatches" in selector:
|
|
144
|
+
raise NotImplementedError("'packageNameMatches' syntax is not supported")
|
|
145
|
+
|
|
146
|
+
if "resourceId" in selector:
|
|
147
|
+
conditions.append(f"[@resource-id='{selector['resourceId']}']")
|
|
148
|
+
elif "resourceIdMatches" in selector:
|
|
149
|
+
raise NotImplementedError("'resourceIdMatches' syntax is not supported")
|
|
150
|
+
|
|
151
|
+
bool_props = ["checkable", "checked", "clickable", "longClickable", "scrollable", "enabled", "focusable",
|
|
152
|
+
"focused", "selected", "covered"]
|
|
153
|
+
|
|
154
|
+
def str_to_bool(value):
|
|
155
|
+
"""Convert string 'true'/'false' to boolean, or return original value if already boolean"""
|
|
156
|
+
if isinstance(value, str):
|
|
157
|
+
return value.lower() == "true"
|
|
158
|
+
return bool(value)
|
|
159
|
+
|
|
160
|
+
for prop in bool_props:
|
|
161
|
+
if prop in selector:
|
|
162
|
+
bool_value = str_to_bool(selector[prop])
|
|
163
|
+
value = "true" if bool_value else "false"
|
|
164
|
+
conditions.append(f"[@{prop}='{value}']")
|
|
165
|
+
|
|
166
|
+
if "index" in selector:
|
|
167
|
+
conditions.append(f"[@index='{selector['index']}']")
|
|
168
|
+
|
|
169
|
+
xpath += "".join(conditions)
|
|
170
|
+
|
|
171
|
+
if "childOrSibling" in selector and selector["childOrSibling"]:
|
|
172
|
+
for i, relation in enumerate(selector["childOrSibling"]):
|
|
173
|
+
sub_selector = selector["childOrSiblingSelector"][i]
|
|
174
|
+
sub_xpath = self.selector_to_xpath(sub_selector, False)
|
|
175
|
+
|
|
176
|
+
if relation == "child":
|
|
177
|
+
xpath += f"//{sub_xpath}"
|
|
178
|
+
elif relation == "sibling":
|
|
179
|
+
cur_root = xpath
|
|
180
|
+
following_sibling = cur_root + f"/following-sibling::{sub_xpath}"
|
|
181
|
+
preceding_sibling = cur_root + f"/preceding-sibling::{sub_xpath}"
|
|
182
|
+
xpath = f"({following_sibling} | {preceding_sibling})"
|
|
183
|
+
if "instance" in selector:
|
|
184
|
+
xpath = f"({xpath})[{selector['instance'] + 1}]"
|
|
185
|
+
|
|
186
|
+
return xpath
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
print(f"Error occurred during selector conversion: {e}")
|
|
190
|
+
return "//error"
|
|
125
191
|
|
|
126
192
|
|
|
127
193
|
@property
|
|
128
194
|
def exists(self):
|
|
129
|
-
|
|
130
|
-
xpath = self.
|
|
195
|
+
set_covered_to_deepest_node(self.selector)
|
|
196
|
+
xpath = self.selector_to_xpath(self.selector)
|
|
131
197
|
matched_widgets = self.session.xml.xpath(xpath)
|
|
132
198
|
return bool(matched_widgets)
|
|
133
199
|
|
|
134
200
|
def __len__(self):
|
|
135
|
-
xpath = self.
|
|
201
|
+
xpath = self.selector_to_xpath(self.selector)
|
|
136
202
|
matched_widgets = self.session.xml.xpath(xpath)
|
|
137
203
|
return len(matched_widgets)
|
|
138
204
|
|
|
@@ -141,6 +207,9 @@ class StaticU2UiObject(u2.UiObject):
|
|
|
141
207
|
|
|
142
208
|
def sibling(self, **kwargs):
|
|
143
209
|
return StaticU2UiObject(self.session, self.selector.clone().sibling(**kwargs))
|
|
210
|
+
|
|
211
|
+
def __getattr__(self, attr):
|
|
212
|
+
return getattr(super(), attr)
|
|
144
213
|
|
|
145
214
|
|
|
146
215
|
def _get_bounds(raw_bounds):
|
|
@@ -227,7 +296,9 @@ class U2StaticDevice(u2.Device):
|
|
|
227
296
|
self._script_driver = script_driver
|
|
228
297
|
|
|
229
298
|
def __call__(self, **kwargs):
|
|
230
|
-
|
|
299
|
+
ui = StaticU2UiObject(session=self, selector=u2.Selector(**kwargs))
|
|
300
|
+
ui.jsonrpc = self._script_driver.jsonrpc
|
|
301
|
+
return ui
|
|
231
302
|
|
|
232
303
|
@property
|
|
233
304
|
def xpath(self) -> u2.xpath.XPathEntry:
|
|
@@ -274,7 +345,12 @@ class U2StaticChecker(AbstractStaticChecker):
|
|
|
274
345
|
def setHierarchy(self, hierarchy: str):
|
|
275
346
|
if hierarchy is None:
|
|
276
347
|
return
|
|
277
|
-
|
|
348
|
+
if isinstance(hierarchy, str):
|
|
349
|
+
self.d.xml = etree.fromstring(hierarchy.encode("utf-8"))
|
|
350
|
+
elif isinstance(hierarchy, etree._Element):
|
|
351
|
+
self.d.xml = hierarchy
|
|
352
|
+
elif isinstance(hierarchy, etree._ElementTree):
|
|
353
|
+
self.d.xml = hierarchy.getroot()
|
|
278
354
|
_HindenWidgetFilter(self.d.xml)
|
|
279
355
|
|
|
280
356
|
def getInstance(self, hierarchy: str=None):
|
|
@@ -330,90 +406,6 @@ def forward_port(self, remote: Union[int, str]) -> int:
|
|
|
330
406
|
logger.debug(f"forwading port: tcp:{local_port} -> {remote}")
|
|
331
407
|
return local_port
|
|
332
408
|
|
|
333
|
-
|
|
334
|
-
def selector_to_xpath(selector: u2.Selector, is_initial: bool = True) -> str:
|
|
335
|
-
"""
|
|
336
|
-
Convert a u2 Selector into an XPath expression compatible with Java Android UI controls.
|
|
337
|
-
|
|
338
|
-
Args:
|
|
339
|
-
selector (u2.Selector): A u2 Selector object
|
|
340
|
-
is_initial (bool): Whether it is the initial node, defaults to True
|
|
341
|
-
|
|
342
|
-
Returns:
|
|
343
|
-
str: The corresponding XPath expression
|
|
344
|
-
"""
|
|
345
|
-
try:
|
|
346
|
-
if is_initial:
|
|
347
|
-
xpath = ".//node"
|
|
348
|
-
else:
|
|
349
|
-
xpath = "node"
|
|
350
|
-
|
|
351
|
-
conditions = []
|
|
352
|
-
|
|
353
|
-
if "className" in selector:
|
|
354
|
-
conditions.insert(0, f"[@class='{selector['className']}']") # 将 className 条件放在前面
|
|
355
|
-
|
|
356
|
-
if "text" in selector:
|
|
357
|
-
conditions.append(f"[@text='{selector['text']}']")
|
|
358
|
-
elif "textContains" in selector:
|
|
359
|
-
conditions.append(f"[contains(@text, '{selector['textContains']}')]")
|
|
360
|
-
elif "textMatches" in selector:
|
|
361
|
-
conditions.append(f"[re:match(@text, '{selector['textMatches']}')]")
|
|
362
|
-
elif "textStartsWith" in selector:
|
|
363
|
-
conditions.append(f"[starts-with(@text, '{selector['textStartsWith']}')]")
|
|
364
|
-
|
|
365
|
-
if "description" in selector:
|
|
366
|
-
conditions.append(f"[@content-desc='{selector['description']}']")
|
|
367
|
-
elif "descriptionContains" in selector:
|
|
368
|
-
conditions.append(f"[contains(@content-desc, '{selector['descriptionContains']}')]")
|
|
369
|
-
elif "descriptionMatches" in selector:
|
|
370
|
-
conditions.append(f"[re:match(@content-desc, '{selector['descriptionMatches']}')]")
|
|
371
|
-
elif "descriptionStartsWith" in selector:
|
|
372
|
-
conditions.append(f"[starts-with(@content-desc, '{selector['descriptionStartsWith']}')]")
|
|
373
|
-
|
|
374
|
-
if "packageName" in selector:
|
|
375
|
-
conditions.append(f"[@package='{selector['packageName']}']")
|
|
376
|
-
elif "packageNameMatches" in selector:
|
|
377
|
-
conditions.append(f"[re:match(@package, '{selector['packageNameMatches']}')]")
|
|
378
|
-
|
|
379
|
-
if "resourceId" in selector:
|
|
380
|
-
conditions.append(f"[@resource-id='{selector['resourceId']}']")
|
|
381
|
-
elif "resourceIdMatches" in selector:
|
|
382
|
-
conditions.append(f"[re:match(@resource-id, '{selector['resourceIdMatches']}')]")
|
|
383
|
-
|
|
384
|
-
bool_props = [
|
|
385
|
-
"checkable", "checked", "clickable", "longClickable", "scrollable",
|
|
386
|
-
"enabled", "focusable", "focused", "selected", "covered"
|
|
387
|
-
]
|
|
388
|
-
for prop in bool_props:
|
|
389
|
-
if prop in selector:
|
|
390
|
-
value = "true" if selector[prop] else "false"
|
|
391
|
-
conditions.append(f"[@{prop}='{value}']")
|
|
392
|
-
|
|
393
|
-
if "index" in selector:
|
|
394
|
-
conditions.append(f"[@index='{selector['index']}']")
|
|
395
|
-
elif "instance" in selector:
|
|
396
|
-
conditions.append(f"[@instance='{selector['instance']}']")
|
|
397
|
-
|
|
398
|
-
xpath += "".join(conditions)
|
|
399
|
-
|
|
400
|
-
if "childOrSibling" in selector and selector["childOrSibling"]:
|
|
401
|
-
for i, relation in enumerate(selector["childOrSibling"]):
|
|
402
|
-
sub_selector = selector["childOrSiblingSelector"][i]
|
|
403
|
-
sub_xpath = selector_to_xpath(sub_selector, False) # 递归处理子选择器
|
|
404
|
-
|
|
405
|
-
if relation == "child":
|
|
406
|
-
xpath += f"/{sub_xpath}"
|
|
407
|
-
elif relation == "sibling":
|
|
408
|
-
xpath_initial = xpath
|
|
409
|
-
xpath = '(' + xpath_initial + f"/following-sibling::{sub_xpath} | " + xpath_initial + f"/preceding-sibling::{sub_xpath})"
|
|
410
|
-
|
|
411
|
-
return xpath
|
|
412
|
-
|
|
413
|
-
except Exception as e:
|
|
414
|
-
print(f"Error occurred during selector conversion: {e}")
|
|
415
|
-
return "//error"
|
|
416
|
-
|
|
417
409
|
def is_port_in_use(port: int) -> bool:
|
|
418
410
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
419
411
|
return s.connect_ex(('127.0.0.1', port)) == 0
|
|
@@ -435,3 +427,25 @@ def get_free_port():
|
|
|
435
427
|
if not is_port_in_use(port):
|
|
436
428
|
return port
|
|
437
429
|
raise RuntimeError("No free port found")
|
|
430
|
+
|
|
431
|
+
def set_covered_to_deepest_node(selector: u2.Selector):
|
|
432
|
+
|
|
433
|
+
def find_deepest_nodes(node):
|
|
434
|
+
deepest_node = None
|
|
435
|
+
is_leaf = True
|
|
436
|
+
if "childOrSibling" in node and node["childOrSibling"]:
|
|
437
|
+
for i, relation in enumerate(node["childOrSibling"]):
|
|
438
|
+
sub_selector = node["childOrSiblingSelector"][i]
|
|
439
|
+
deepest_node = find_deepest_nodes(sub_selector)
|
|
440
|
+
is_leaf = False
|
|
441
|
+
|
|
442
|
+
if is_leaf:
|
|
443
|
+
deepest_node = node
|
|
444
|
+
return deepest_node
|
|
445
|
+
|
|
446
|
+
deepest_node = find_deepest_nodes(selector)
|
|
447
|
+
|
|
448
|
+
if deepest_node is not None:
|
|
449
|
+
dict.update(deepest_node, {"covered": False})
|
|
450
|
+
|
|
451
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from kea2.u2Driver import _HindenWidgetFilter, U2Driver
|
|
3
|
+
from lxml import etree
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
XML_PATH = Path(__file__).parent / "hidden_widget_test.xml"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_static_checker():
|
|
11
|
+
xml: etree._ElementTree = etree.parse(XML_PATH)
|
|
12
|
+
d = U2Driver.getStaticChecker(xml)
|
|
13
|
+
return d
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestHiddenWidget(unittest.TestCase):
|
|
17
|
+
|
|
18
|
+
def test_hidden_widget(self):
|
|
19
|
+
d = get_static_checker()
|
|
20
|
+
assert not d(text="微信(690)").exists
|
|
21
|
+
|
|
22
|
+
import unittest
|
|
23
|
+
|
|
24
|
+
class TestSupportedAttributes(unittest.TestCase):
|
|
25
|
+
|
|
26
|
+
def setUp(self):
|
|
27
|
+
self.d = get_static_checker()
|
|
28
|
+
|
|
29
|
+
def test_text(self):
|
|
30
|
+
assert self.d(text="添加朋友").exists
|
|
31
|
+
assert self.d(textContains="朋友").exists
|
|
32
|
+
assert self.d(textStartsWith="添加").exists
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_class_name(self):
|
|
36
|
+
assert self.d(className="android.widget.Button").exists
|
|
37
|
+
|
|
38
|
+
def test_description(self):
|
|
39
|
+
assert self.d(description="企业微信联系人,,通过手机号搜索企业微信用户").exists
|
|
40
|
+
assert self.d(descriptionContains="微信联系人").exists
|
|
41
|
+
assert self.d(descriptionStartsWith="企业微信").exists
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_clickable_true(self):
|
|
45
|
+
assert self.d(clickable=True).exists
|
|
46
|
+
assert self.d(clickable=False).exists
|
|
47
|
+
assert self.d(enabled=True).exists
|
|
48
|
+
assert self.d(focusable=True).exists
|
|
49
|
+
assert self.d(focusable=False).exists
|
|
50
|
+
assert self.d(scrollable=False).exists
|
|
51
|
+
assert self.d(checkable=False).exists
|
|
52
|
+
assert self.d(checked=False).exists
|
|
53
|
+
assert self.d(focused=False).exists
|
|
54
|
+
assert self.d(selected=False).exists
|
|
55
|
+
assert self.d(packageName="com.tencent.mm").exists
|
|
56
|
+
assert self.d(resourceId="com.tencent.mm:id/search_ll").exists
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_combined_text_and_className(self):
|
|
60
|
+
assert self.d(text="添加朋友", className="android.widget.TextView").exists
|
|
61
|
+
|
|
62
|
+
def test_combined_resourceId_and_className(self):
|
|
63
|
+
assert self.d(resourceId="com.tencent.mm:id/search_ll", className="android.widget.LinearLayout").exists
|
|
64
|
+
|
|
65
|
+
def test_combined_attributes_search_button(self):
|
|
66
|
+
assert self.d(text="添加朋友", clickable=False).exists
|
|
67
|
+
|
|
68
|
+
def test_child_element(self):
|
|
69
|
+
assert self.d(resourceId="android:id/list").child(description="手机联系人,,添加通讯录中的朋友").exists
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_sibling_element(self):
|
|
73
|
+
assert self.d(description="雷达,,添加身边的朋友").sibling(description="手机联系人,,添加通讯录中的朋友").exists
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestUnsupportedMethods(unittest.TestCase):
|
|
78
|
+
|
|
79
|
+
def setUp(self):
|
|
80
|
+
self.d = get_static_checker()
|
|
81
|
+
|
|
82
|
+
def test_positional_left_not_supported(self):
|
|
83
|
+
try:
|
|
84
|
+
result = self.d(text="微信(690)").left(text="搜索")
|
|
85
|
+
assert False, "left() method should not be supported"
|
|
86
|
+
except:
|
|
87
|
+
assert True
|
|
88
|
+
|
|
89
|
+
def test_positional_right_not_supported(self):
|
|
90
|
+
try:
|
|
91
|
+
result = self.d(text="微信(690)").right(text="搜索")
|
|
92
|
+
assert False, "right() method should not be supported"
|
|
93
|
+
except:
|
|
94
|
+
assert True
|
|
95
|
+
|
|
96
|
+
def test_positional_up_not_supported(self):
|
|
97
|
+
try:
|
|
98
|
+
result = self.d(text="通讯录").up(text="10:21")
|
|
99
|
+
assert False, "up() method should not be supported"
|
|
100
|
+
except:
|
|
101
|
+
assert True
|
|
102
|
+
|
|
103
|
+
def test_positional_down_not_supported(self):
|
|
104
|
+
try:
|
|
105
|
+
result = self.d(text="10:21").down(text="通讯录")
|
|
106
|
+
assert False, "down() method should not be supported"
|
|
107
|
+
except:
|
|
108
|
+
assert True
|
|
109
|
+
|
|
110
|
+
def test_child_by_text_not_supported(self):
|
|
111
|
+
try:
|
|
112
|
+
result = self.d(resourceId="android:id/list").child_by_text("通讯录")
|
|
113
|
+
assert False, "child_by_text() method should not be supported"
|
|
114
|
+
except:
|
|
115
|
+
assert True
|
|
116
|
+
|
|
117
|
+
def test_child_by_description_not_supported(self):
|
|
118
|
+
try:
|
|
119
|
+
result = self.d(className="android.widget.ListView").child_by_description("扫描")
|
|
120
|
+
assert False, "child_by_description() method should not be supported"
|
|
121
|
+
except:
|
|
122
|
+
assert True
|
|
123
|
+
|
|
124
|
+
def test_child_by_instance_not_supported(self):
|
|
125
|
+
try:
|
|
126
|
+
result = self.d(className="android.widget.LinearLayout").child_by_instance(1)
|
|
127
|
+
assert False, "child_by_instance() method should not be supported"
|
|
128
|
+
except:
|
|
129
|
+
assert True
|
|
130
|
+
|
|
131
|
+
def test_instance_parameter_not_supported(self):
|
|
132
|
+
try:
|
|
133
|
+
result = self.d(className="android.widget.TextView", instance=0)
|
|
134
|
+
assert False, "instance parameter should not be supported"
|
|
135
|
+
except:
|
|
136
|
+
assert True
|
|
137
|
+
|
|
138
|
+
def test_text_matches_not_supported(self):
|
|
139
|
+
try:
|
|
140
|
+
result = self.d(textMatches="微信.*")
|
|
141
|
+
assert False, "textMatches should not be supported"
|
|
142
|
+
except:
|
|
143
|
+
assert True
|
|
144
|
+
|
|
145
|
+
def test_class_name_matches_not_supported(self):
|
|
146
|
+
try:
|
|
147
|
+
result = self.d(classNameMatches=".*TextView")
|
|
148
|
+
assert False, "classNameMatches should not be supported"
|
|
149
|
+
except:
|
|
150
|
+
assert True
|
|
151
|
+
|
|
152
|
+
def test_description_matches_not_supported(self):
|
|
153
|
+
try:
|
|
154
|
+
result = self.d(descriptionMatches=".*搜索.*")
|
|
155
|
+
assert False, "descriptionMatches should not be supported"
|
|
156
|
+
except:
|
|
157
|
+
assert True
|
|
158
|
+
|
|
159
|
+
def test_package_name_matches_not_supported(self):
|
|
160
|
+
try:
|
|
161
|
+
result = self.d(packageNameMatches="com.tencent.*")
|
|
162
|
+
assert False, "packageNameMatches should not be supported"
|
|
163
|
+
except:
|
|
164
|
+
assert True
|
|
165
|
+
|
|
166
|
+
def test_resource_id_matches_not_supported(self):
|
|
167
|
+
try:
|
|
168
|
+
result = self.d(resourceIdMatches=".*:id/.*")
|
|
169
|
+
assert False, "resourceIdMatches should not be supported"
|
|
170
|
+
except:
|
|
171
|
+
assert True
|
|
172
|
+
|
|
173
|
+
class TestEdgeCases(unittest.TestCase):
|
|
174
|
+
|
|
175
|
+
def setUp(self):
|
|
176
|
+
self.d = get_static_checker()
|
|
177
|
+
|
|
178
|
+
def test_empty_text(self):
|
|
179
|
+
assert self.d(text="").exists
|
|
180
|
+
|
|
181
|
+
def test_empty_resource_id(self):
|
|
182
|
+
assert self.d(resourceId="").exists
|
|
183
|
+
|
|
184
|
+
def test_special_characters_in_text(self):
|
|
185
|
+
assert not self.d(text="微信(690)").exists
|
|
186
|
+
assert not self.d(text="[有人@我] 测试: [聊天记录]").exists
|
|
187
|
+
|
|
188
|
+
def test_non_existent_element(self):
|
|
189
|
+
assert not self.d(text="不存在的文本").exists
|
|
190
|
+
assert not self.d(resourceId="com.example.nonexistent").exists
|
|
191
|
+
|
|
192
|
+
class TestWidget(unittest.TestCase):
|
|
193
|
+
|
|
194
|
+
def test_widget(self):
|
|
195
|
+
d = get_static_checker()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
unittest.main()
|
|
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
|
{kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so
RENAMED
|
File without changes
|
{kea2_python-0.1.1 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so
RENAMED
|
File without changes
|
|
File without changes
|
{kea2_python-0.1.1 → kea2_python-0.1.3}/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
|