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

Files changed (42) hide show
  1. {kea2_python-0.1.2 → kea2_python-0.1.3}/Kea2_python.egg-info/PKG-INFO +3 -2
  2. {kea2_python-0.1.2 → kea2_python-0.1.3}/Kea2_python.egg-info/SOURCES.txt +2 -1
  3. {kea2_python-0.1.2 → kea2_python-0.1.3}/PKG-INFO +3 -2
  4. {kea2_python-0.1.2 → kea2_python-0.1.3}/README.md +2 -1
  5. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/keaUtils.py +40 -25
  6. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/u2Driver.py +122 -108
  7. {kea2_python-0.1.2 → kea2_python-0.1.3}/pyproject.toml +1 -1
  8. kea2_python-0.1.3/tests/test_u2Selector.py +200 -0
  9. {kea2_python-0.1.2 → kea2_python-0.1.3}/Kea2_python.egg-info/dependency_links.txt +0 -0
  10. {kea2_python-0.1.2 → kea2_python-0.1.3}/Kea2_python.egg-info/entry_points.txt +0 -0
  11. {kea2_python-0.1.2 → kea2_python-0.1.3}/Kea2_python.egg-info/requires.txt +0 -0
  12. {kea2_python-0.1.2 → kea2_python-0.1.3}/Kea2_python.egg-info/top_level.txt +0 -0
  13. {kea2_python-0.1.2 → kea2_python-0.1.3}/LICENSE +0 -0
  14. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/__init__.py +0 -0
  15. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/absDriver.py +0 -0
  16. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/adbUtils.py +0 -0
  17. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot-thirdpart.jar +0 -0
  18. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/abl.strings +0 -0
  19. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/awl.strings +0 -0
  20. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.config +0 -0
  21. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
  22. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
  23. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.strings +0 -0
  24. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
  25. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_configs/widget.block.py +0 -0
  26. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
  27. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
  28. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
  29. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
  30. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/framework.jar +0 -0
  31. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/kea2-thirdpart.jar +0 -0
  32. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/monkeyq.jar +0 -0
  33. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/assets/quicktest.py +0 -0
  34. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/bug_report_generator.py +0 -0
  35. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/cli.py +0 -0
  36. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/fastbotManager.py +0 -0
  37. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/kea_launcher.py +0 -0
  38. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/logWatcher.py +0 -0
  39. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/resultSyncer.py +0 -0
  40. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/templates/bug_report_template.html +0 -0
  41. {kea2_python-0.1.2 → kea2_python-0.1.3}/kea2/utils.py +0 -0
  42. {kea2_python-0.1.2 → 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.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
@@ -36,4 +36,5 @@ kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so
36
36
  kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so
37
37
  kea2/assets/fastbot_libs/x86/libfastbot_native.so
38
38
  kea2/assets/fastbot_libs/x86_64/libfastbot_native.so
39
- 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.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,8 +4,9 @@
4
4
  [![PyPI Downloads](https://static.pepy.tech/badge/kea2-python)](https://pepy.tech/projects/kea2-python)
5
5
  ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
6
6
 
7
+
7
8
  <div>
8
- <img src="https://github.com/user-attachments/assets/1a64635b-a8f2-40f1-8f16-55e47b1d74e7" style="border-radius: 14px; width: 20%; height: 20%;"/>
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
@@ -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):
@@ -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
  [project]
2
2
  name = "Kea2-python"
3
- version = "0.1.2"
3
+ version = "0.1.3"
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"
@@ -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()
File without changes
File without changes
File without changes
File without changes