Kea2-python 1.0.6b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Kea2-python might be problematic. Click here for more details.

Files changed (52) hide show
  1. kea2/__init__.py +3 -0
  2. kea2/absDriver.py +56 -0
  3. kea2/adbUtils.py +554 -0
  4. kea2/assets/config_version.json +16 -0
  5. kea2/assets/fastbot-thirdpart.jar +0 -0
  6. kea2/assets/fastbot_configs/abl.strings +2 -0
  7. kea2/assets/fastbot_configs/awl.strings +3 -0
  8. kea2/assets/fastbot_configs/max.config +7 -0
  9. kea2/assets/fastbot_configs/max.fuzzing.strings +699 -0
  10. kea2/assets/fastbot_configs/max.schema.strings +1 -0
  11. kea2/assets/fastbot_configs/max.strings +3 -0
  12. kea2/assets/fastbot_configs/max.tree.pruning +27 -0
  13. kea2/assets/fastbot_configs/teardown.py +18 -0
  14. kea2/assets/fastbot_configs/widget.block.py +38 -0
  15. kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
  16. kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
  17. kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
  18. kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
  19. kea2/assets/framework.jar +0 -0
  20. kea2/assets/kea2-thirdpart.jar +0 -0
  21. kea2/assets/monkeyq.jar +0 -0
  22. kea2/assets/quicktest.py +126 -0
  23. kea2/cli.py +320 -0
  24. kea2/fastbotManager.py +267 -0
  25. kea2/fastbotx/ActivityTimes.py +52 -0
  26. kea2/fastbotx/ReuseEntry.py +74 -0
  27. kea2/fastbotx/ReuseModel.py +63 -0
  28. kea2/fastbotx/__init__.py +7 -0
  29. kea2/fbm_parser.py +871 -0
  30. kea2/fs_lock.py +131 -0
  31. kea2/kea2_api.py +166 -0
  32. kea2/keaUtils.py +1112 -0
  33. kea2/kea_launcher.py +319 -0
  34. kea2/logWatcher.py +92 -0
  35. kea2/mixin.py +22 -0
  36. kea2/report/__init__.py +0 -0
  37. kea2/report/bug_report_generator.py +793 -0
  38. kea2/report/mixin.py +482 -0
  39. kea2/report/report_merger.py +797 -0
  40. kea2/report/templates/bug_report_template.html +3876 -0
  41. kea2/report/templates/merged_bug_report_template.html +3333 -0
  42. kea2/report/utils.py +10 -0
  43. kea2/resultSyncer.py +65 -0
  44. kea2/u2Driver.py +610 -0
  45. kea2/utils.py +184 -0
  46. kea2/version_manager.py +102 -0
  47. kea2_python-1.0.6b0.dist-info/METADATA +447 -0
  48. kea2_python-1.0.6b0.dist-info/RECORD +52 -0
  49. kea2_python-1.0.6b0.dist-info/WHEEL +5 -0
  50. kea2_python-1.0.6b0.dist-info/entry_points.txt +2 -0
  51. kea2_python-1.0.6b0.dist-info/licenses/LICENSE +16 -0
  52. kea2_python-1.0.6b0.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ snssdk1128://webcast_feed?gd_label=88888
@@ -0,0 +1,3 @@
1
+ 1 搜索
2
+ 2 打开
3
+ 3 检查
@@ -0,0 +1,27 @@
1
+ [
2
+ {
3
+ "activity":"com.ss.android.xxx.NewActivity",
4
+ "xpath": "//*[@resource-id='com.xxx.go:id/aaa']",
5
+ "resourceid": "",
6
+ "contentdesc":"",
7
+ "text":"",
8
+ "classname":""
9
+ },
10
+ {
11
+ "activity":"com.ss.android.xxx.MainActivity",
12
+ "xpath": "//*[@resource-id='com.xxx.go:id/bbb' and @text='other']",
13
+ "resourceid": "",
14
+ "contentdesc":"",
15
+ "text":"",
16
+ "classname":""
17
+ },
18
+ {
19
+ "activity":"com.ss.android.xxx.SplashActivity",
20
+ "xpath": "//*[@resource-id='com.com.xxx.go:id/ccc' and @text='other']",
21
+ "resourceid": "",
22
+ "contentdesc":"",
23
+ "text":"",
24
+ "classname":"",
25
+ "clickable":"false"
26
+ }
27
+ ]
@@ -0,0 +1,18 @@
1
+ from uiautomator2 import Device
2
+ import time
3
+
4
+
5
+ class HybridTestCase:
6
+ d: Device
7
+
8
+ PACKAGE_NAME = "it.feio.android.omninotes.alpha"
9
+ MAIN_ACTIVITY = "it.feio.android.omninotes.MainActivity"
10
+
11
+
12
+ def setUp(self: HybridTestCase):
13
+ self.d.app_start(PACKAGE_NAME, MAIN_ACTIVITY)
14
+ time.sleep(2)
15
+
16
+
17
+ def tearDown(self: HybridTestCase):
18
+ self.d.app_stop(PACKAGE_NAME)
@@ -0,0 +1,38 @@
1
+ from kea2.utils import Device
2
+ from kea2.keaUtils import precondition
3
+
4
+
5
+ def global_block_widgets(d: "Device"):
6
+ """
7
+ Specify UI widgets to be blocked globally during testing.
8
+ Returns a list of widgets that should be blocked from exploration.
9
+ This function is only available in 'u2 agent' mode.
10
+ """
11
+ # return [d(text="widgets to block"), d.xpath(".//node[@text='widget to block']")]
12
+ return []
13
+
14
+
15
+ # Example of conditional blocking with precondition
16
+ # @precondition(lambda d: d(text="In the home page").exists)
17
+ @precondition(lambda d: False)
18
+ def block_sth(d: "Device"):
19
+ # Note: Function name must start with "block_"
20
+ return []
21
+
22
+
23
+ def global_block_tree(d: "Device"):
24
+ """
25
+ Specify UI widget trees to be blocked globally during testing.
26
+ Returns a list of root nodes whose entire subtrees will be blocked from exploration.
27
+ This function is only available in 'u2 agent' mode.
28
+ """
29
+ # return [d(text="trees to block"), d.xpath(".//node[@text='tree to block']")]
30
+ return []
31
+
32
+
33
+ # Example of conditional tree blocking with precondition
34
+ # @precondition(lambda d: d(text="In the home page").exists)
35
+ @precondition(lambda d: False)
36
+ def block_tree_sth(d: "Device"):
37
+ # Note: Function name must start with "block_tree_"
38
+ return []
Binary file
Binary file
Binary file
@@ -0,0 +1,126 @@
1
+ import unittest
2
+ import uiautomator2 as u2
3
+
4
+ from time import sleep
5
+ from kea2 import precondition, prob, KeaTestRunner, Options
6
+ from kea2.u2Driver import U2Driver
7
+
8
+
9
+ class Omni_Notes_Sample(unittest.TestCase):
10
+
11
+ def setUp(self):
12
+ self.d = u2.connect()
13
+
14
+ @prob(0.2)
15
+ @precondition(
16
+ lambda self: self.d(description="Navigate up").exists
17
+ )
18
+ def test_goBack(self):
19
+ print("Navigate back")
20
+ self.d(description="Navigate up").click()
21
+ sleep(0.5)
22
+
23
+ @prob(0.2)
24
+ @precondition(
25
+ lambda self: self.d(description="drawer closed").exists
26
+ )
27
+ def test_openDrawer(self):
28
+ print("Open drawer")
29
+ self.d(description="drawer closed").click()
30
+ sleep(0.5)
31
+
32
+ @prob(0.5) # The probability of executing the function when precondition is satisfied.
33
+ @precondition(
34
+ lambda self: self.d(text="Omni Notes Alpha").exists
35
+ and self.d(text="Settings").exists
36
+ )
37
+ def test_goToPrivacy(self):
38
+ """
39
+ The ability to jump out of the UI tarpits
40
+
41
+ precond:
42
+ The drawer was opened
43
+ action:
44
+ go to settings -> privacy
45
+ """
46
+ print("trying to click Settings")
47
+ self.d(text="Settings").click()
48
+ sleep(0.5)
49
+ print("trying to click Privacy")
50
+ self.d(text="Privacy").click()
51
+
52
+ @precondition(
53
+ lambda self: self.d(resourceId="it.feio.android.omninotes.alpha:id/search_src_text").exists
54
+ )
55
+ def test_rotation(self):
56
+ """
57
+ The ability to make assertion to find functional bug
58
+
59
+ precond:
60
+ The search input box is opened
61
+ action:
62
+ rotate the device (set it to landscape, then back to natural)
63
+ assertion:
64
+ The search input box is still being opened
65
+ """
66
+ print("rotate the device")
67
+ self.d.set_orientation("l")
68
+ sleep(2)
69
+ self.d.set_orientation("n")
70
+ sleep(2)
71
+ assert self.d(resourceId="it.feio.android.omninotes.alpha:id/search_src_text").exists()
72
+
73
+
74
+ URL = "https://github.com/federicoiosue/Omni-Notes/releases/download/6.2.0_alpha/OmniNotes-alphaRelease-6.2.0.apk"
75
+ FALL_BACK_URL = "https://gitee.com/XixianLiang/Kea2/raw/main/omninotes.apk"
76
+ PACKAGE_NAME = "it.feio.android.omninotes.alpha"
77
+ FILE_NAME = "omninotes.apk"
78
+
79
+
80
+ def download_omninotes():
81
+ import socket
82
+ socket.setdefaulttimeout(30)
83
+ try:
84
+ import urllib.request
85
+ urllib.request.urlretrieve(URL, FILE_NAME)
86
+ except Exception as e:
87
+ print(f"[WARN] Download from {URL} failed: {e}. Try to download from fallback URL {FALL_BACK_URL}", flush=True)
88
+ try:
89
+ urllib.request.urlretrieve(FALL_BACK_URL, FILE_NAME)
90
+ except Exception as e2:
91
+ print(f"[ERROR] Download from fallback URL {FALL_BACK_URL} also failed: {e2}", flush=True)
92
+ raise e2
93
+
94
+
95
+ def check_installation(serial=None):
96
+ import os
97
+ from pathlib import Path
98
+
99
+ d = u2.connect(serial)
100
+ # automatically install omni-notes
101
+ if PACKAGE_NAME not in d.app_list():
102
+ if not os.path.exists(Path(".") / FILE_NAME):
103
+ print(f"[INFO] omninote.apk not exists. Downloading from {URL}", flush=True)
104
+ download_omninotes()
105
+ print("[INFO] Installing omninotes.", flush=True)
106
+ d.app_install(FILE_NAME)
107
+ d.stop_uiautomator()
108
+
109
+
110
+ if __name__ == "__main__":
111
+ check_installation(serial=None)
112
+ KeaTestRunner.setOptions(
113
+ Options(
114
+ driverName="d",
115
+ Driver=U2Driver,
116
+ packageNames=[PACKAGE_NAME],
117
+ # serial="emulator-5554", # specify the serial
118
+ maxStep=50,
119
+ profile_period=10,
120
+ take_screenshots=True, # whether to take screenshots, default is False
121
+ # running_mins=10, # specify the maximal running time in minutes, default value is 10m
122
+ # throttle=200, # specify the throttle in milliseconds, default value is 200ms
123
+ agent="u2" # 'native' for running the vanilla Fastbot, 'u2' for running Kea2
124
+ )
125
+ )
126
+ unittest.main(testRunner=KeaTestRunner)
kea2/cli.py ADDED
@@ -0,0 +1,320 @@
1
+ # coding: utf-8
2
+ # cli.py
3
+
4
+ from __future__ import absolute_import, print_function
5
+ from datetime import datetime
6
+ import sys
7
+ from .utils import getProjectRoot, getLogger
8
+ from .kea_launcher import run
9
+ from .version_manager import check_config_compatibility, get_cur_version
10
+ import argparse
11
+
12
+ import os
13
+ from pathlib import Path
14
+
15
+
16
+ logger = getLogger(__name__)
17
+
18
+
19
+ def cmd_version(args):
20
+ print(get_cur_version(), flush=True)
21
+
22
+
23
+ def cmd_init(args):
24
+ cwd = Path(os.getcwd())
25
+ configs_dir = cwd / "configs"
26
+ if os.path.isdir(configs_dir):
27
+ logger.warning("Kea2 project already initialized")
28
+ return
29
+
30
+ import shutil
31
+ def copy_configs():
32
+ src = Path(__file__).parent / "assets" / "fastbot_configs"
33
+ dst = configs_dir
34
+ shutil.copytree(src, dst)
35
+
36
+ def copy_samples():
37
+ src = Path(__file__).parent / "assets" / "quicktest.py"
38
+ dst = cwd / "quicktest.py"
39
+ shutil.copyfile(src, dst)
40
+
41
+ def save_version():
42
+ import json
43
+ version_file = configs_dir / "version.json"
44
+ with open(version_file, "w") as fp:
45
+ json.dump({"version": get_cur_version(), "init date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, fp, indent=4)
46
+
47
+ copy_configs()
48
+ copy_samples()
49
+ save_version()
50
+ logger.info("Kea2 project initialized.")
51
+
52
+
53
+ def cmd_load_configs(args):
54
+ pass
55
+
56
+
57
+ def cmd_report(args):
58
+ from .report.bug_report_generator import BugReportGenerator
59
+ report_dirs = args.path
60
+
61
+ for report_dir in report_dirs:
62
+ report_dir = Path(report_dir).resolve()
63
+
64
+ if not report_dir.exists():
65
+ logger.error(f"Report directory does not exist: {str(report_dir)}, Skipped.")
66
+ continue
67
+
68
+ logger.debug(f"Generating test report from directory: {report_dir}")
69
+ BugReportGenerator(report_dir).generate_report()
70
+
71
+
72
+ def cmd_merge(args):
73
+ """Merge multiple test report directories and generate a combined report"""
74
+ from .report.report_merger import TestReportMerger
75
+
76
+ try:
77
+ # Validate input paths
78
+ if not args.paths or len(args.paths) < 2:
79
+ logger.error("At least 2 test report paths are required for merging. Use -p to specify paths.")
80
+ return
81
+
82
+ # Validate that all paths exist
83
+ for path in args.paths:
84
+ path_obj = Path(path)
85
+ if not path_obj.exists():
86
+ raise FileNotFoundError(f"{path_obj}")
87
+ if not path_obj.is_dir():
88
+ raise NotADirectoryError(f"{path_obj}")
89
+
90
+ logger.debug(f"Merging {len(args.paths)} test report directories...")
91
+
92
+ # Initialize merger
93
+ merger = TestReportMerger()
94
+
95
+ # Merge test reports
96
+ merged_report = merger.merge_reports(args.paths, args.output)
97
+
98
+ if merged_report is not None:
99
+ print(f"✅ Test reports merged successfully!", flush=True)
100
+ print(f"📊 Merged report: {merged_report}", flush=True)
101
+ # Get merge summary
102
+ merge_summary = merger.get_merge_summary()
103
+ print(f"📈 Merged {merge_summary.get('merged_directories', 0)} directories", flush=True)
104
+
105
+ except Exception as e:
106
+ logger.error(f"Error during merge operation: {e}")
107
+
108
+
109
+ def cmd_mergefbm(args):
110
+ """Merge all FBM files in the specified folder and its subfolders using sum mode"""
111
+ from .fbm_parser import FBMMerger
112
+ import glob
113
+ import shutil
114
+
115
+ try:
116
+ # Validate input path
117
+ input_path = Path(args.path).resolve()
118
+ if not input_path.exists():
119
+ logger.error(f"Input directory does not exist: {input_path}")
120
+ return
121
+ if not input_path.is_dir():
122
+ logger.error(f"Input path is not a directory: {input_path}")
123
+ return
124
+
125
+ # Find all FBM files in the directory and its subdirectories
126
+ fbm_files = glob.glob(str(input_path / "**" / "*.fbm"), recursive=True)
127
+
128
+ if not fbm_files:
129
+ logger.error(f"No FBM files found in {input_path} or its subdirectories")
130
+ return
131
+
132
+ logger.debug(f"Found {len(fbm_files)} FBM files to merge:")
133
+ for fbm_file in fbm_files:
134
+ logger.debug(f" - {fbm_file}")
135
+
136
+ # Set default output file if not provided
137
+ if not args.output:
138
+ output_file = input_path / "merged.fbm"
139
+ else:
140
+ output_file = Path(args.output).resolve()
141
+
142
+ # Initialize merger
143
+ merger = FBMMerger()
144
+
145
+ # Handle different cases
146
+ if len(fbm_files) == 1:
147
+ # Only one file, just copy it to output
148
+ shutil.copyfile(fbm_files[0], output_file)
149
+ logger.info(f"Only one FBM file found, copied to {output_file}")
150
+ else:
151
+ # Merge files iteratively: start with the first file and merge with each subsequent file
152
+ # Create a temporary file for the intermediate merged result
153
+ temp_output = input_path / f".tmp_merged.fbm"
154
+
155
+ # Start with the first file as the initial merged result
156
+ shutil.copyfile(fbm_files[0], temp_output)
157
+
158
+ # Iterate through the remaining files and merge them one by one
159
+ for i in range(1, len(fbm_files)):
160
+ current_file = fbm_files[i]
161
+ next_temp = input_path / f".tmp_merged_{i}.fbm"
162
+
163
+ logger.debug(f"Merging {temp_output} and {current_file} into {next_temp}")
164
+ success = merger.merge(str(temp_output), str(current_file), str(next_temp), merge_mode='sum')
165
+
166
+ if not success:
167
+ logger.error(f"Failed to merge {temp_output} and {current_file}")
168
+ # Clean up temporary files
169
+ for f in [temp_output, next_temp]:
170
+ if f.exists() and f.name.startswith(".tmp_"):
171
+ f.unlink()
172
+ return
173
+
174
+ # Remove the previous temporary file and update to the new one
175
+ temp_output.unlink()
176
+ temp_output = next_temp
177
+
178
+ # Move the final merged file to the output location
179
+ if temp_output != output_file:
180
+ temp_output.replace(output_file)
181
+
182
+ print(f"✅ All FBM files merged successfully!", flush=True)
183
+ print(f"📊 Merged FBM file: {output_file}", flush=True)
184
+ print(f"📈 Merged {len(fbm_files)} FBM files", flush=True)
185
+
186
+ except Exception as e:
187
+ logger.error(f"Error during FBM merge operation: {e}")
188
+ import traceback
189
+ logger.debug(traceback.format_exc())
190
+
191
+
192
+ def cmd_run(args):
193
+ base_dir = getProjectRoot()
194
+ if base_dir is None:
195
+ logger.error("kea2 project not initialized. Use `kea2 init`.")
196
+ return
197
+
198
+ check_config_compatibility()
199
+
200
+ run(args)
201
+
202
+
203
+ _commands = [
204
+ dict(action=cmd_version, command="version", help="show version"),
205
+ dict(
206
+ action=cmd_init,
207
+ command="init",
208
+ help="init the Kea2 project in current directory",
209
+ ),
210
+ dict(
211
+ action=cmd_report,
212
+ command="report",
213
+ help="generate test report from existing test results",
214
+ flags=[
215
+ dict(
216
+ name=["report_dir"],
217
+ args=["-p", "--path"],
218
+ type=str,
219
+ nargs="+",
220
+ required=True,
221
+ help="Root directory path of the test results to generate report from"
222
+ )
223
+ ]
224
+ ),
225
+ dict(
226
+ action=cmd_merge,
227
+ command="merge",
228
+ help="merge multiple test report directories and generate a combined report",
229
+ flags=[
230
+ dict(
231
+ name=["paths"],
232
+ args=["-p", "--paths"],
233
+ type=str,
234
+ nargs='+',
235
+ required=True,
236
+ help="Paths to test report directories (res_* directories) to merge"
237
+ ),
238
+ dict(
239
+ name=["output"],
240
+ args=["-o", "--output"],
241
+ type=str,
242
+ required=False,
243
+ help="Output directory for merged report (optional)"
244
+ )
245
+ ]
246
+ ),
247
+ dict(
248
+ action=cmd_mergefbm,
249
+ command="mergefbm",
250
+ help="merge all FBM files in the specified folder and its subfolders using sum mode",
251
+ flags=[
252
+ dict(
253
+ name=["path"],
254
+ args=["-p", "--path"],
255
+ type=str,
256
+ required=True,
257
+ help="Path to the folder containing FBM files to merge"
258
+ ),
259
+ dict(
260
+ name=["output"],
261
+ args=["-o", "--output"],
262
+ type=str,
263
+ required=False,
264
+ help="Output file path for merged FBM file (optional, default: merged.fbm in the input folder)"
265
+ )
266
+ ]
267
+ )
268
+ ]
269
+
270
+
271
+ def main():
272
+ parser = argparse.ArgumentParser(
273
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter
274
+ )
275
+ parser.add_argument("-d", "--debug", action="store_true",
276
+ help="show detail log")
277
+
278
+ subparser = parser.add_subparsers(dest='subparser')
279
+
280
+ actions = {}
281
+ for c in _commands:
282
+ cmd_name = c['command']
283
+ actions[cmd_name] = c['action']
284
+ sp = subparser.add_parser(
285
+ cmd_name,
286
+ help=c.get('help'),
287
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter
288
+ )
289
+ for f in c.get('flags', []):
290
+ args = f.get('args')
291
+ if not args:
292
+ args = ['-'*min(2, len(n)) + n for n in f['name']]
293
+ kwargs = f.copy()
294
+ kwargs.pop('name', None)
295
+ kwargs.pop('args', None)
296
+ sp.add_argument(*args, **kwargs)
297
+
298
+ from .kea_launcher import _set_runner_parser
299
+ _set_runner_parser(subparser)
300
+ actions["run"] = cmd_run
301
+ if sys.argv[1:] == ["run"]:
302
+ sys.argv.append("-h")
303
+ args = parser.parse_args()
304
+
305
+ import logging
306
+ from .utils import LoggingLevel
307
+ LoggingLevel.set_level(logging.INFO)
308
+ if args.debug:
309
+ LoggingLevel.set_level(logging.DEBUG)
310
+ logger.debug("args: %s", args)
311
+
312
+ if args.subparser:
313
+ actions[args.subparser](args)
314
+ return
315
+
316
+ parser.print_help()
317
+
318
+
319
+ if __name__ == "__main__":
320
+ main()