Kea2-python 0.1.2__py3-none-any.whl → 0.1.3__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.

kea2/keaUtils.py CHANGED
@@ -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, selector_to_xpath
17
+ from .u2Driver import StaticU2UiObject
18
18
  from .fastbotManager import FastbotManager
19
19
  import uiautomator2 as u2
20
20
  import types
@@ -344,8 +344,6 @@ class KeaTestRunner(TextTestRunner):
344
344
  if self.options.profile_period and self.stepsCount % self.options.profile_period == 0:
345
345
  resultSyncer.sync_event.set()
346
346
 
347
- print(f"{len(propsSatisfiedPrecond)} precond satisfied.", flush=True)
348
-
349
347
  # Go to the next round if no precond satisfied
350
348
  if len(propsSatisfiedPrecond) == 0:
351
349
  continue
@@ -478,18 +476,25 @@ class KeaTestRunner(TextTestRunner):
478
476
  if not precond(test):
479
477
  valid = False
480
478
  break
479
+ except u2.UiObjectNotFoundError as e:
480
+ valid = False
481
+ break
481
482
  except Exception as e:
482
- print(f"[ERROR] Error when checking precond: {getFullPropName(test)}", flush=True)
483
+ logger.error(f"Error when checking precond: {getFullPropName(test)}")
483
484
  traceback.print_exc()
484
485
  valid = False
485
486
  break
486
487
  # if all the precond passed. make it the candidate prop.
487
488
  if valid:
488
- logger.debug(f"precond satisfied: {getFullPropName(test)}")
489
489
  if result.getExcuted(test) >= getattr(prop, MAX_TRIES_MARKER, float("inf")):
490
- logger.debug(f"{getFullPropName(test)} has reached its max_tries. Skip.")
490
+ print(f"{getFullPropName(test)} has reached its max_tries. Skip.", flush=True)
491
491
  continue
492
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)
493
498
  return validProps
494
499
 
495
500
  def _logScript(self, execution_info:Dict):
@@ -619,46 +624,56 @@ class KeaTestRunner(TextTestRunner):
619
624
  """
620
625
  def _get_xpath_widgets(func):
621
626
  blocked_set = set()
622
- try:
623
- script_driver = self.options.Driver.getScriptDriver()
624
- preconds = getattr(func, PRECONDITIONS_MARKER, [])
625
- if all(precond(script_driver) for precond in preconds):
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:
626
642
  _widgets = func(self.options.Driver.getStaticChecker())
627
643
  _widgets = _widgets if isinstance(_widgets, list) else [_widgets]
628
644
  for w in _widgets:
629
645
  if isinstance(w, StaticU2UiObject):
630
- xpath = selector_to_xpath(w.selector, True)
631
- blocked_set.add(xpath)
646
+ xpath = w.selector_to_xpath(w.selector)
647
+ if xpath != '//error':
648
+ blocked_set.add(xpath)
632
649
  elif isinstance(w, u2.xpath.XPathSelector):
633
650
  xpath = w._parent.xpath
634
651
  blocked_set.add(xpath)
635
652
  else:
636
- logger.warning(f"{w} Not supported")
637
- except Exception as e:
638
- logger.error(f"Error processing blocked widgets: {e}")
639
- traceback.print_exc()
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()
640
658
  return blocked_set
641
659
 
642
- res = {
660
+ result = {
643
661
  "widgets": set(),
644
662
  "trees": set()
645
663
  }
646
664
 
647
-
648
665
  for func in self._blockWidgetFuncs["widgets"]:
649
666
  widgets = _get_xpath_widgets(func)
650
- res["widgets"].update(widgets)
651
-
667
+ result["widgets"].update(widgets)
652
668
 
653
669
  for func in self._blockWidgetFuncs["trees"]:
654
670
  trees = _get_xpath_widgets(func)
655
- res["trees"].update(trees)
671
+ result["trees"].update(trees)
656
672
 
673
+ result["widgets"] = list(result["widgets"] - result["trees"])
674
+ result["trees"] = list(result["trees"])
657
675
 
658
- res["widgets"] = list(res["widgets"] - res["trees"])
659
- res["trees"] = list(res["trees"])
660
-
661
- return res
676
+ return result
662
677
 
663
678
 
664
679
  def __del__(self):
kea2/u2Driver.py CHANGED
@@ -100,39 +100,105 @@ class StaticU2UiObject(u2.UiObject):
100
100
  return filterDict[originKey]
101
101
  return originKey
102
102
 
103
- def _getXPath(self, kwargs: Dict[str, str]):
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
- def filter_selectors(kwargs: Dict[str, str]):
106
- """
107
- filter the selector
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
- kwargs = filter_selectors(kwargs)
111
+ Returns:
112
+ str: The corresponding XPath expression
113
+ """
114
+ try:
119
115
 
120
- attrLocs = [
121
- f"[@{k}='{v}']" for k, v in kwargs.items()
122
- ]
123
- xpath = f".//node{''.join(attrLocs)}"
124
- return xpath
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
- dict.update(self.selector, {"covered": "false"})
130
- xpath = self._getXPath(self.selector)
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._getXPath(self.selector)
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
- return StaticU2UiObject(session=self, selector=u2.Selector(**kwargs))
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
- self.d.xml = etree.fromstring(hierarchy.encode("utf-8"))
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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Kea2-python
3
- Version: 0.1.2
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
  [![PyPI Downloads](https://static.pepy.tech/badge/kea2-python)](https://pepy.tech/projects/kea2-python)
18
18
  ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
19
19
 
20
+
20
21
  <div>
21
- <img src="https://github.com/user-attachments/assets/1a64635b-a8f2-40f1-8f16-55e47b1d74e7" style="border-radius: 14px; width: 20%; height: 20%;"/>
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
@@ -4,11 +4,11 @@ kea2/adbUtils.py,sha256=j6goAAzV1Ikmv26DZEE0Oi8S1PrejcV1Io3u_Xw4ws4,9910
4
4
  kea2/bug_report_generator.py,sha256=G5h9nI1w1UaksjF3gkMC8UrXgiNmtU1D5VIuPiciOak,22792
5
5
  kea2/cli.py,sha256=YgWOe5JB0_WBTKyBShBEymhLIqj6K5gNR4Aaj6Pbtn0,2796
6
6
  kea2/fastbotManager.py,sha256=Ru7Aitcjlem52npJJLg-3KX5ctCoflaaLG4N0IgQ2QU,4828
7
- kea2/keaUtils.py,sha256=civETbAJaRJsVf7hb-ZKqcDRAaRfpxIdj0frBwyvsjk,25175
7
+ kea2/keaUtils.py,sha256=XylgzSS4_DcQn3SjbJJ2bA82v0LQV1qmbgZPRjBh-ZE,25932
8
8
  kea2/kea_launcher.py,sha256=xDMHAzD2Ig2Dug9K4MzkTPwieKvLASmjUahEst8aaVU,5291
9
9
  kea2/logWatcher.py,sha256=qpIsflbP37eX5b4EaWnrqyPbGP9lzQhRr3ainGFfZeI,2211
10
10
  kea2/resultSyncer.py,sha256=f3qfne8WQCo1AA3cwx9IZzLdenjjMWbEpAqPdvddAqk,1984
11
- kea2/u2Driver.py,sha256=1uViwhQAkBFu69XFR5i8dSgG8SL3v7wZ68-t0E0Xlpw,14876
11
+ kea2/u2Driver.py,sha256=__rP_d5uY029_eAxPNUiNw3lq8PVLf8QZRe3uwZEmb4,15779
12
12
  kea2/utils.py,sha256=8b4y-_B7knKRN_HqpWZM900IdRiZXwmzQnduVVN0GxQ,1384
13
13
  kea2/assets/fastbot-thirdpart.jar,sha256=0SZ_OoZFWDGMnazgXKceHgKvXdUDoIa3Gb2bcifaikk,85664
14
14
  kea2/assets/framework.jar,sha256=rTluOJJKj2DFwh7ascXso1udYdWv00BxBwSQ3Vmv-fw,1149240
@@ -28,9 +28,9 @@ kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so,sha256=UV8bhaiPoPKdd3q
28
28
  kea2/assets/fastbot_libs/x86/libfastbot_native.so,sha256=k-aw1gEXRWMKZRNHIggKNuZy0wC1y2BnveJGEIO6rbo,2036856
29
29
  kea2/assets/fastbot_libs/x86_64/libfastbot_native.so,sha256=tiofhlf4uMQcU5WAvrdLgTBME0lb83hVUoGtTwxmE8A,2121416
30
30
  kea2/templates/bug_report_template.html,sha256=sQ0Uu8SPeOIiyX5vPPTZPVKxh9-9raAZcCjSRS8YFe0,40624
31
- kea2_python-0.1.2.dist-info/licenses/LICENSE,sha256=nM9PPjcsXVo5SzNsjRqWgA-gdJlwqZZcRDSC6Qf6bVE,2034
32
- kea2_python-0.1.2.dist-info/METADATA,sha256=uVrc81g1R20PPcsSn2cJg7tuAZ84GnGcL8kMI1Hfrys,13233
33
- kea2_python-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- kea2_python-0.1.2.dist-info/entry_points.txt,sha256=mFX06TyxXiUAJQ6JZn8QHzfn8n5R8_KJ5W-pTm_fRCA,39
35
- kea2_python-0.1.2.dist-info/top_level.txt,sha256=TsgNH4PQoNOVhegpO7AcjutMVWp6Z4KDL1pBH9FnMmk,5
36
- kea2_python-0.1.2.dist-info/RECORD,,
31
+ kea2_python-0.1.3.dist-info/licenses/LICENSE,sha256=nM9PPjcsXVo5SzNsjRqWgA-gdJlwqZZcRDSC6Qf6bVE,2034
32
+ kea2_python-0.1.3.dist-info/METADATA,sha256=Y2ICVucbBO8JybwZMNKtTQSl_rJdndmKkD4PAVPwQWk,13234
33
+ kea2_python-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ kea2_python-0.1.3.dist-info/entry_points.txt,sha256=mFX06TyxXiUAJQ6JZn8QHzfn8n5R8_KJ5W-pTm_fRCA,39
35
+ kea2_python-0.1.3.dist-info/top_level.txt,sha256=TsgNH4PQoNOVhegpO7AcjutMVWp6Z4KDL1pBH9FnMmk,5
36
+ kea2_python-0.1.3.dist-info/RECORD,,