Kea2-python 0.2.5__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Kea2-python might be problematic. Click here for more details.
- {kea2_python-0.2.5 → kea2_python-0.3.1}/Kea2_python.egg-info/PKG-INFO +2 -1
- {kea2_python-0.2.5 → kea2_python-0.3.1}/Kea2_python.egg-info/requires.txt +1 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/PKG-INFO +2 -1
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/bug_report_generator.py +4 -4
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/fastbotManager.py +3 -2
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/keaUtils.py +11 -20
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/u2Driver.py +128 -5
- kea2_python-0.3.1/kea2/utils.py +112 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/pyproject.toml +4 -2
- kea2_python-0.2.5/kea2/utils.py +0 -71
- {kea2_python-0.2.5 → kea2_python-0.3.1}/Kea2_python.egg-info/SOURCES.txt +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/Kea2_python.egg-info/dependency_links.txt +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/Kea2_python.egg-info/entry_points.txt +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/Kea2_python.egg-info/top_level.txt +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/LICENSE +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/README.md +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/__init__.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/absDriver.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/adbUtils.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot-thirdpart.jar +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/abl.strings +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/awl.strings +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.config +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.strings +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/widget.block.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/framework.jar +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/kea2-thirdpart.jar +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/quicktest.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/cli.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/kea_launcher.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/logWatcher.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/report_merger.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/resultSyncer.py +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/templates/bug_report_template.html +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/templates/merged_bug_report_template.html +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/setup.cfg +0 -0
- {kea2_python-0.2.5 → kea2_python-0.3.1}/tests/test_u2Selector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -10,6 +10,7 @@ Requires-Dist: rtree>=1.3.0
|
|
|
10
10
|
Requires-Dist: jinja2>=3.0.0
|
|
11
11
|
Requires-Dist: uiautomator2>=3.3.3
|
|
12
12
|
Requires-Dist: adbutils>=2.9.3
|
|
13
|
+
Requires-Dist: setuptools>=75.3.2
|
|
13
14
|
Dynamic: license-file
|
|
14
15
|
|
|
15
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -10,6 +10,7 @@ Requires-Dist: rtree>=1.3.0
|
|
|
10
10
|
Requires-Dist: jinja2>=3.0.0
|
|
11
11
|
Requires-Dist: uiautomator2>=3.3.3
|
|
12
12
|
Requires-Dist: adbutils>=2.9.3
|
|
13
|
+
Requires-Dist: setuptools>=75.3.2
|
|
13
14
|
Dynamic: license-file
|
|
14
15
|
|
|
15
16
|
|
|
@@ -3,7 +3,7 @@ import re
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Dict, TypedDict, List, Deque, NewType, Union, Optional
|
|
6
|
+
from typing import Dict, Tuple, TypedDict, List, Deque, NewType, Union, Optional
|
|
7
7
|
from collections import deque
|
|
8
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
9
9
|
|
|
@@ -444,7 +444,7 @@ class BugReportGenerator:
|
|
|
444
444
|
logger.error(f"Error when marking screenshots: {e}")
|
|
445
445
|
|
|
446
446
|
|
|
447
|
-
def _mark_screenshot_interaction(self, step_type: str, screenshot_name: str, action_type: str, position: Union[List,
|
|
447
|
+
def _mark_screenshot_interaction(self, step_type: str, screenshot_name: str, action_type: str, position: Union[List, Tuple]) -> bool:
|
|
448
448
|
"""
|
|
449
449
|
Mark interaction on screenshot with colored rectangle
|
|
450
450
|
|
|
@@ -630,7 +630,7 @@ class BugReportGenerator:
|
|
|
630
630
|
})
|
|
631
631
|
|
|
632
632
|
def _process_script_info(self, property_name: str, state: str, step_index: int, screenshot: str,
|
|
633
|
-
current_property: str, current_test: Dict, property_violations: Dict) ->
|
|
633
|
+
current_property: str, current_test: Dict, property_violations: Dict) -> Tuple:
|
|
634
634
|
"""
|
|
635
635
|
Process ScriptInfo step for property violations tracking
|
|
636
636
|
|
|
@@ -831,7 +831,7 @@ class BugReportGenerator:
|
|
|
831
831
|
|
|
832
832
|
return property_execution_trend
|
|
833
833
|
|
|
834
|
-
def _load_crash_dump_data(self) ->
|
|
834
|
+
def _load_crash_dump_data(self) -> Tuple[List[Dict], List[Dict]]:
|
|
835
835
|
"""
|
|
836
836
|
Load crash and ANR events from crash-dump.log file
|
|
837
837
|
|
|
@@ -3,7 +3,7 @@ from retry.api import retry_call
|
|
|
3
3
|
from dataclasses import asdict
|
|
4
4
|
import requests
|
|
5
5
|
from time import sleep
|
|
6
|
-
|
|
6
|
+
from pkg_resources import parse_version
|
|
7
7
|
|
|
8
8
|
from uiautomator2.core import HTTPResponse, _http_request
|
|
9
9
|
from kea2.adbUtils import ADBDevice, ADBStreamShell_V2
|
|
@@ -28,6 +28,7 @@ class FastbotManager:
|
|
|
28
28
|
self._device_output_dir = None
|
|
29
29
|
ADBDevice.setDevice(options.serial, options.transport_id)
|
|
30
30
|
self.dev = ADBDevice()
|
|
31
|
+
self.android_release = parse_version(self.dev.getprop("ro.build.version.release"))
|
|
31
32
|
|
|
32
33
|
def _activateFastbot(self) -> ADBStreamShell_V2:
|
|
33
34
|
"""
|
|
@@ -219,7 +220,7 @@ class FastbotManager:
|
|
|
219
220
|
if self.thread.is_running():
|
|
220
221
|
logger.info("Waiting for Fastbot to exit.")
|
|
221
222
|
return self.thread.wait()
|
|
222
|
-
return self.thread.poll()
|
|
223
|
+
return self.thread.poll() if self.android_release >= parse_version("7.0") else 0
|
|
223
224
|
|
|
224
225
|
def start(self):
|
|
225
226
|
# kill the fastbot process if runing.
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import traceback
|
|
6
6
|
import time
|
|
7
|
-
from typing import Callable, Any, Deque, Dict, List, Literal, NewType,
|
|
7
|
+
from typing import Callable, Any, Deque, Dict, List, Literal, NewType, Union
|
|
8
8
|
from unittest import TextTestRunner, registerResult, TestSuite, TestCase, TextTestResult
|
|
9
9
|
import random
|
|
10
10
|
import warnings
|
|
@@ -14,8 +14,8 @@ from functools import wraps
|
|
|
14
14
|
from kea2.bug_report_generator import BugReportGenerator
|
|
15
15
|
from kea2.resultSyncer import ResultSyncer
|
|
16
16
|
from kea2.logWatcher import LogWatcher
|
|
17
|
-
from kea2.utils import TimeStamp, getProjectRoot, getLogger
|
|
18
|
-
from kea2.u2Driver import StaticU2UiObject
|
|
17
|
+
from kea2.utils import TimeStamp, catchException, getProjectRoot, getLogger, timer
|
|
18
|
+
from kea2.u2Driver import StaticU2UiObject, StaticXpathUiObject
|
|
19
19
|
from kea2.fastbotManager import FastbotManager
|
|
20
20
|
from kea2.adbUtils import ADBDevice
|
|
21
21
|
import uiautomator2 as u2
|
|
@@ -640,13 +640,10 @@ class KeaTestRunner(TextTestRunner):
|
|
|
640
640
|
_widgets = func(self.options.Driver.getStaticChecker())
|
|
641
641
|
_widgets = _widgets if isinstance(_widgets, list) else [_widgets]
|
|
642
642
|
for w in _widgets:
|
|
643
|
-
if isinstance(w, StaticU2UiObject):
|
|
643
|
+
if isinstance(w, (StaticU2UiObject, StaticXpathUiObject)):
|
|
644
644
|
xpath = w.selector_to_xpath(w.selector)
|
|
645
645
|
if xpath != '//error':
|
|
646
646
|
blocked_set.add(xpath)
|
|
647
|
-
elif isinstance(w, u2.xpath.XPathSelector):
|
|
648
|
-
xpath = w._parent.xpath
|
|
649
|
-
blocked_set.add(xpath)
|
|
650
647
|
else:
|
|
651
648
|
logger.error(f"block widget defined in {func.__name__} Not supported.")
|
|
652
649
|
except Exception as e:
|
|
@@ -673,6 +670,12 @@ class KeaTestRunner(TextTestRunner):
|
|
|
673
670
|
|
|
674
671
|
return result
|
|
675
672
|
|
|
673
|
+
@timer(r"Generating bug report cost %cost_time seconds.")
|
|
674
|
+
@catchException("Error when generating bug report")
|
|
675
|
+
def _generate_bug_report(self):
|
|
676
|
+
logger.info("Generating bug report")
|
|
677
|
+
report_generator = BugReportGenerator(self.options.output_dir)
|
|
678
|
+
report_generator.generate_report()
|
|
676
679
|
|
|
677
680
|
def __del__(self):
|
|
678
681
|
"""tearDown method. Cleanup the env.
|
|
@@ -680,16 +683,4 @@ class KeaTestRunner(TextTestRunner):
|
|
|
680
683
|
if self.options.Driver:
|
|
681
684
|
self.options.Driver.tearDown()
|
|
682
685
|
|
|
683
|
-
|
|
684
|
-
start_time = time.time()
|
|
685
|
-
logger.info("Generating bug report")
|
|
686
|
-
report_generator = BugReportGenerator(self.options.output_dir)
|
|
687
|
-
report_generator.generate_report()
|
|
688
|
-
|
|
689
|
-
end_time = time.time()
|
|
690
|
-
generation_time = end_time - start_time
|
|
691
|
-
|
|
692
|
-
logger.info(f"Bug report generation completed in {generation_time:.2f} seconds")
|
|
693
|
-
|
|
694
|
-
except Exception as e:
|
|
695
|
-
logger.error(f"Error generating bug report: {e}", flush=True)
|
|
686
|
+
self._generate_bug_report()
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import functools
|
|
1
2
|
import random
|
|
2
3
|
import socket
|
|
4
|
+
import time
|
|
3
5
|
from time import sleep
|
|
4
6
|
import uiautomator2 as u2
|
|
5
7
|
import adbutils
|
|
6
8
|
import types
|
|
7
9
|
import rtree
|
|
8
10
|
import re
|
|
9
|
-
from typing import Any, Dict, List, Union
|
|
11
|
+
from typing import Any, Dict, List, Union, Optional
|
|
10
12
|
from http.client import HTTPResponse
|
|
11
13
|
from lxml import etree
|
|
12
14
|
from .absDriver import AbstractScriptDriver, AbstractStaticChecker, AbstractDriver
|
|
@@ -205,6 +207,122 @@ class StaticU2UiObject(u2.UiObject):
|
|
|
205
207
|
def __getattr__(self, attr):
|
|
206
208
|
return getattr(super(), attr)
|
|
207
209
|
|
|
210
|
+
"""
|
|
211
|
+
The definition of XpathStaticChecker
|
|
212
|
+
"""
|
|
213
|
+
class StaticXpathUiObject(u2.xpath.XPathSelector):
|
|
214
|
+
def __init__(self, session, selector):
|
|
215
|
+
self.session: U2StaticDevice = session
|
|
216
|
+
self.selector = selector
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def exists(self):
|
|
220
|
+
source = self.session.get_page_source()
|
|
221
|
+
return len(self.selector.all(source)) > 0
|
|
222
|
+
|
|
223
|
+
def __and__(self, value) -> 'StaticXpathUiObjectr':
|
|
224
|
+
s = u2.xpath.XPathSelector(self.selector)
|
|
225
|
+
s._next_xpath = u2.xpath.XPathSelector.create(value.selector)
|
|
226
|
+
s._operator = u2.xpath.Operator.AND
|
|
227
|
+
s._parent = self.selector._parent
|
|
228
|
+
self.selector = s
|
|
229
|
+
return self
|
|
230
|
+
|
|
231
|
+
def __or__(self, value) -> 'StaticXpathUiObject':
|
|
232
|
+
s = u2.xpath.XPathSelector(self.selector)
|
|
233
|
+
s._next_xpath = u2.xpath.XPathSelector.create(value.selector)
|
|
234
|
+
s._operator = u2.xpath.Operator.OR
|
|
235
|
+
s._parent = self.selector._parent
|
|
236
|
+
self.selector = s
|
|
237
|
+
return self
|
|
238
|
+
|
|
239
|
+
def selector_to_xpath(self, selector: u2.xpath.XPathSelector) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Convert an XPathSelector to a standard XPath expression.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
selector: The XPathSelector object to convert.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
A standard XPath expression as a string.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def _handle_path(path):
|
|
251
|
+
if isinstance(path, u2.xpath.XPathSelector):
|
|
252
|
+
return self.selector_to_xpath(path)
|
|
253
|
+
elif isinstance(path, u2.xpath.XPath):
|
|
254
|
+
return str(path)
|
|
255
|
+
else:
|
|
256
|
+
return path
|
|
257
|
+
|
|
258
|
+
base_xpath = _handle_path(selector._base_xpath)
|
|
259
|
+
base_xpath = base_xpath.replace('//*', './/node')
|
|
260
|
+
|
|
261
|
+
if selector._operator is None:
|
|
262
|
+
return base_xpath
|
|
263
|
+
else:
|
|
264
|
+
print("Unsupported operator: {}".format(selector._operator))
|
|
265
|
+
return "//error"
|
|
266
|
+
|
|
267
|
+
def xpath(self, _xpath: Union[list, tuple, str]) -> 'StaticXpathUiObject':
|
|
268
|
+
"""
|
|
269
|
+
add xpath to condition list
|
|
270
|
+
the element should match all conditions
|
|
271
|
+
|
|
272
|
+
Deprecated, using a & b instead
|
|
273
|
+
"""
|
|
274
|
+
if isinstance(_xpath, (list, tuple)):
|
|
275
|
+
self.selector = functools.reduce(lambda a, b: a & b, _xpath, self)
|
|
276
|
+
else:
|
|
277
|
+
self.selector = self.selector & _xpath
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
def child(self, _xpath: str) -> "StaticXpathUiObject":
|
|
281
|
+
"""
|
|
282
|
+
add child xpath
|
|
283
|
+
"""
|
|
284
|
+
if self.selector._operator or not isinstance(self.selector._base_xpath, u2.xpath.XPath):
|
|
285
|
+
raise u2.xpath.XPathError("can't use child when base is not XPath or operator is set")
|
|
286
|
+
new = self.selector.copy()
|
|
287
|
+
new._base_xpath = self.selector._base_xpath.joinpath(_xpath)
|
|
288
|
+
self.selector = new
|
|
289
|
+
return self
|
|
290
|
+
|
|
291
|
+
def get(self, timeout=None) -> "XMLElement":
|
|
292
|
+
"""
|
|
293
|
+
Get first matched element
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
timeout (float): max seconds to wait
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
XMLElement
|
|
300
|
+
|
|
301
|
+
"""
|
|
302
|
+
if not self.exists:
|
|
303
|
+
return None
|
|
304
|
+
return self.get_last_match()
|
|
305
|
+
|
|
306
|
+
def get_last_match(self) -> "u2.xpath.XMLElement":
|
|
307
|
+
return self.selector.all(self.selector._last_source)[0]
|
|
308
|
+
|
|
309
|
+
def parent_exists(self, xpath: Optional[str] = None):
|
|
310
|
+
el = self.get()
|
|
311
|
+
if el is None:
|
|
312
|
+
return False
|
|
313
|
+
element = el.parent(xpath) if hasattr(el, 'parent') else None
|
|
314
|
+
return True if element is not None else False
|
|
315
|
+
|
|
316
|
+
def __getattr__(self, key: str):
|
|
317
|
+
"""
|
|
318
|
+
In IPython console, attr:_ipython_canary_method_should_not_exist_ will be called
|
|
319
|
+
So here ignore all attr startswith _
|
|
320
|
+
"""
|
|
321
|
+
if key.startswith("_"):
|
|
322
|
+
raise AttributeError("Invalid attr", key)
|
|
323
|
+
if not hasattr(u2.xpath.XMLElement, key):
|
|
324
|
+
raise AttributeError("Invalid attr", key)
|
|
325
|
+
return getattr(super(), key)
|
|
208
326
|
|
|
209
327
|
def _get_bounds(raw_bounds):
|
|
210
328
|
pattern = re.compile(r"\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]")
|
|
@@ -299,7 +417,8 @@ class U2StaticDevice(u2.Device):
|
|
|
299
417
|
def xpath(self) -> u2.xpath.XPathEntry:
|
|
300
418
|
def get_page_source(self):
|
|
301
419
|
# print("[Debug] Using static get_page_source method")
|
|
302
|
-
|
|
420
|
+
xml_raw = etree.tostring(self._d.xml, encoding='unicode')
|
|
421
|
+
return u2.xpath.PageSource.parse(xml_raw)
|
|
303
422
|
xpathEntry = _XPathEntry(self)
|
|
304
423
|
xpathEntry.get_page_source = types.MethodType(
|
|
305
424
|
get_page_source, xpathEntry
|
|
@@ -316,10 +435,14 @@ class _XPathEntry(u2.xpath.XPathEntry):
|
|
|
316
435
|
self.xpath = None
|
|
317
436
|
super().__init__(d)
|
|
318
437
|
|
|
319
|
-
def __call__(self, xpath, source = None):
|
|
438
|
+
# def __call__(self, xpath, source = None):
|
|
320
439
|
# TODO fully support xpath in widget.block.py
|
|
321
|
-
self.xpath = xpath
|
|
322
|
-
return super().__call__(xpath, source)
|
|
440
|
+
# self.xpath = xpath
|
|
441
|
+
# return super().__call__(xpath, source)
|
|
442
|
+
def __call__(self, xpath, source=None):
|
|
443
|
+
ui = StaticXpathUiObject(session=self, selector=u2.xpath.XPathSelector(xpath, source=source))
|
|
444
|
+
return ui
|
|
445
|
+
|
|
323
446
|
|
|
324
447
|
|
|
325
448
|
class U2StaticChecker(AbstractStaticChecker):
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import traceback
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from functools import wraps
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .keaUtils import Options
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def getLogger(name: str) -> logging.Logger:
|
|
14
|
+
logger = logging.getLogger(name)
|
|
15
|
+
|
|
16
|
+
def enable_pretty_logging():
|
|
17
|
+
if not logger.handlers:
|
|
18
|
+
# Configure handler
|
|
19
|
+
handler = logging.StreamHandler()
|
|
20
|
+
handler.flush = lambda: handler.stream.flush() # 确保每次都flush
|
|
21
|
+
formatter = logging.Formatter('[%(levelname)1s][%(asctime)s %(module)s:%(lineno)d pid:%(process)d] %(message)s')
|
|
22
|
+
handler.setFormatter(formatter)
|
|
23
|
+
logger.addHandler(handler)
|
|
24
|
+
logger.propagate = False
|
|
25
|
+
|
|
26
|
+
enable_pretty_logging()
|
|
27
|
+
return logger
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def singleton(cls):
|
|
34
|
+
_instance = {}
|
|
35
|
+
|
|
36
|
+
def inner():
|
|
37
|
+
if cls not in _instance:
|
|
38
|
+
_instance[cls] = cls()
|
|
39
|
+
return _instance[cls]
|
|
40
|
+
return inner
|
|
41
|
+
|
|
42
|
+
@singleton
|
|
43
|
+
class TimeStamp:
|
|
44
|
+
time_stamp = None
|
|
45
|
+
|
|
46
|
+
def getTimeStamp(cls):
|
|
47
|
+
if cls.time_stamp is None:
|
|
48
|
+
import datetime
|
|
49
|
+
cls.time_stamp = datetime.datetime.now().strftime('%Y%m%d%H_%M%S%f')
|
|
50
|
+
return cls.time_stamp
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
from uiautomator2 import Device
|
|
54
|
+
d = Device
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def getProjectRoot():
|
|
58
|
+
root = Path(Path.cwd().anchor)
|
|
59
|
+
cur_dir = Path.absolute(Path(os.curdir))
|
|
60
|
+
while not os.path.isdir(cur_dir / "configs"):
|
|
61
|
+
if cur_dir == root:
|
|
62
|
+
return None
|
|
63
|
+
cur_dir = cur_dir.parent
|
|
64
|
+
return cur_dir
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def timer(log_info: str=None):
|
|
68
|
+
""" ### Decorator to measure the execution time of a function.
|
|
69
|
+
|
|
70
|
+
This decorator can be used to wrap functions where you want to log the time taken for execution
|
|
71
|
+
|
|
72
|
+
### Usage:
|
|
73
|
+
- @timer("Function execution took %cost_time seconds.")
|
|
74
|
+
- @timer() # If no log_info is provided, it will print the function name and execution time.
|
|
75
|
+
|
|
76
|
+
`%cost_time` will be replaced with the actual time taken for execution.
|
|
77
|
+
"""
|
|
78
|
+
def accept(func):
|
|
79
|
+
@wraps(func)
|
|
80
|
+
def wrapper(*args, **kwargs):
|
|
81
|
+
start_time = time.time()
|
|
82
|
+
result = func(*args, **kwargs)
|
|
83
|
+
end_time = time.time()
|
|
84
|
+
if log_info:
|
|
85
|
+
logger.info(log_info.replace(r"%cost_time", f"{end_time - start_time:.4f}"))
|
|
86
|
+
else:
|
|
87
|
+
logger.info(f"Function '{func.__name__}' executed in {(end_time - start_time):.4f} seconds.")
|
|
88
|
+
return result
|
|
89
|
+
return wrapper
|
|
90
|
+
return accept
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def catchException(log_info: str):
|
|
94
|
+
""" ### Decorator to catch exceptions and print log info.
|
|
95
|
+
|
|
96
|
+
This decorator can be used to wrap functions that may raise exceptions,
|
|
97
|
+
allowing you to log a message when the exception is raised.
|
|
98
|
+
|
|
99
|
+
### Usage:
|
|
100
|
+
- @catchException("An error occurred in the function ****.")
|
|
101
|
+
"""
|
|
102
|
+
def accept(func):
|
|
103
|
+
@wraps(func)
|
|
104
|
+
def wrapper(*args, **kwargs):
|
|
105
|
+
try:
|
|
106
|
+
return func(*args, **kwargs)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.info(log_info)
|
|
109
|
+
tb = traceback.format_exception(type(e), e, e.__traceback__.tb_next)
|
|
110
|
+
print(''.join(tb), end='', flush=True)
|
|
111
|
+
return wrapper
|
|
112
|
+
return accept
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Kea2-python"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.1"
|
|
4
4
|
description = "A python library for supporting and customizing automated UI testing for mobile apps"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.8"
|
|
@@ -8,7 +8,8 @@ dependencies = [
|
|
|
8
8
|
"rtree>=1.3.0",
|
|
9
9
|
"jinja2>=3.0.0",
|
|
10
10
|
"uiautomator2>=3.3.3",
|
|
11
|
-
"adbutils>=2.9.3"
|
|
11
|
+
"adbutils>=2.9.3",
|
|
12
|
+
"setuptools>=75.3.2",
|
|
12
13
|
]
|
|
13
14
|
authors = [
|
|
14
15
|
{ name = "Xixian Liang", email = "xixian@stu.ecnu.edu.cn" }
|
|
@@ -22,3 +23,4 @@ include = ["kea2"]
|
|
|
22
23
|
|
|
23
24
|
[tool.setuptools.package-data]
|
|
24
25
|
kea2 = ["assets/**/*", "templates/**/*"]
|
|
26
|
+
|
kea2_python-0.2.5/kea2/utils.py
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
|
|
6
|
-
import time
|
|
7
|
-
from functools import wraps
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from .keaUtils import Options
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def getLogger(name: str) -> logging.Logger:
|
|
13
|
-
logger = logging.getLogger(name)
|
|
14
|
-
|
|
15
|
-
def enable_pretty_logging():
|
|
16
|
-
if not logger.handlers:
|
|
17
|
-
# Configure handler
|
|
18
|
-
handler = logging.StreamHandler()
|
|
19
|
-
handler.flush = lambda: handler.stream.flush() # 确保每次都flush
|
|
20
|
-
formatter = logging.Formatter('[%(levelname)1s][%(asctime)s %(module)s:%(lineno)d pid:%(process)d] %(message)s')
|
|
21
|
-
handler.setFormatter(formatter)
|
|
22
|
-
logger.addHandler(handler)
|
|
23
|
-
logger.propagate = False
|
|
24
|
-
|
|
25
|
-
enable_pretty_logging()
|
|
26
|
-
return logger
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def singleton(cls):
|
|
30
|
-
_instance = {}
|
|
31
|
-
|
|
32
|
-
def inner():
|
|
33
|
-
if cls not in _instance:
|
|
34
|
-
_instance[cls] = cls()
|
|
35
|
-
return _instance[cls]
|
|
36
|
-
return inner
|
|
37
|
-
|
|
38
|
-
@singleton
|
|
39
|
-
class TimeStamp:
|
|
40
|
-
time_stamp = None
|
|
41
|
-
|
|
42
|
-
def getTimeStamp(cls):
|
|
43
|
-
if cls.time_stamp is None:
|
|
44
|
-
import datetime
|
|
45
|
-
cls.time_stamp = datetime.datetime.now().strftime('%Y%m%d%H_%M%S%f')
|
|
46
|
-
return cls.time_stamp
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
from uiautomator2 import Device
|
|
50
|
-
d = Device
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def getProjectRoot():
|
|
54
|
-
root = Path(Path.cwd().anchor)
|
|
55
|
-
cur_dir = Path.absolute(Path(os.curdir))
|
|
56
|
-
while not os.path.isdir(cur_dir / "configs"):
|
|
57
|
-
if cur_dir == root:
|
|
58
|
-
return None
|
|
59
|
-
cur_dir = cur_dir.parent
|
|
60
|
-
return cur_dir
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def timer(func):
|
|
64
|
-
@wraps(func)
|
|
65
|
-
def wrapper(*args, **kwargs):
|
|
66
|
-
start_time = time.time()
|
|
67
|
-
result = func(*args, **kwargs)
|
|
68
|
-
end_time = time.time()
|
|
69
|
-
print(f"Function '{func.__name__}' executed in {(end_time - start_time):.4f} seconds.")
|
|
70
|
-
return result
|
|
71
|
-
return wrapper
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so
RENAMED
|
File without changes
|
{kea2_python-0.2.5 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so
RENAMED
|
File without changes
|
|
File without changes
|
{kea2_python-0.2.5 → kea2_python-0.3.1}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|