Kea2-python 0.3.0__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.

Files changed (45) hide show
  1. {kea2_python-0.3.0 → kea2_python-0.3.1}/Kea2_python.egg-info/PKG-INFO +2 -1
  2. {kea2_python-0.3.0 → kea2_python-0.3.1}/Kea2_python.egg-info/requires.txt +1 -0
  3. {kea2_python-0.3.0 → kea2_python-0.3.1}/PKG-INFO +2 -1
  4. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/fastbotManager.py +3 -2
  5. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/keaUtils.py +11 -20
  6. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/u2Driver.py +128 -5
  7. kea2_python-0.3.1/kea2/utils.py +112 -0
  8. {kea2_python-0.3.0 → kea2_python-0.3.1}/pyproject.toml +4 -2
  9. kea2_python-0.3.0/kea2/utils.py +0 -71
  10. {kea2_python-0.3.0 → kea2_python-0.3.1}/Kea2_python.egg-info/SOURCES.txt +0 -0
  11. {kea2_python-0.3.0 → kea2_python-0.3.1}/Kea2_python.egg-info/dependency_links.txt +0 -0
  12. {kea2_python-0.3.0 → kea2_python-0.3.1}/Kea2_python.egg-info/entry_points.txt +0 -0
  13. {kea2_python-0.3.0 → kea2_python-0.3.1}/Kea2_python.egg-info/top_level.txt +0 -0
  14. {kea2_python-0.3.0 → kea2_python-0.3.1}/LICENSE +0 -0
  15. {kea2_python-0.3.0 → kea2_python-0.3.1}/README.md +0 -0
  16. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/__init__.py +0 -0
  17. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/absDriver.py +0 -0
  18. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/adbUtils.py +0 -0
  19. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot-thirdpart.jar +0 -0
  20. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/abl.strings +0 -0
  21. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/awl.strings +0 -0
  22. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.config +0 -0
  23. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
  24. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
  25. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.strings +0 -0
  26. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
  27. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_configs/widget.block.py +0 -0
  28. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
  29. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
  30. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
  31. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
  32. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/framework.jar +0 -0
  33. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/kea2-thirdpart.jar +0 -0
  34. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/monkeyq.jar +0 -0
  35. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/assets/quicktest.py +0 -0
  36. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/bug_report_generator.py +0 -0
  37. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/cli.py +0 -0
  38. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/kea_launcher.py +0 -0
  39. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/logWatcher.py +0 -0
  40. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/report_merger.py +0 -0
  41. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/resultSyncer.py +0 -0
  42. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/templates/bug_report_template.html +0 -0
  43. {kea2_python-0.3.0 → kea2_python-0.3.1}/kea2/templates/merged_bug_report_template.html +0 -0
  44. {kea2_python-0.3.0 → kea2_python-0.3.1}/setup.cfg +0 -0
  45. {kea2_python-0.3.0 → 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.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
 
@@ -2,3 +2,4 @@ rtree>=1.3.0
2
2
  jinja2>=3.0.0
3
3
  uiautomator2>=3.3.3
4
4
  adbutils>=2.9.3
5
+ setuptools>=75.3.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Kea2-python
3
- Version: 0.3.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 @@ 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, TypedDict, Union
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
- try:
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
- return u2.xpath.PageSource.parse(self._d.xml_raw)
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.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
+
@@ -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