Kea2-python 0.0.1b1__tar.gz → 0.0.1b3__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 (40) hide show
  1. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/Kea2_python.egg-info/PKG-INFO +78 -46
  2. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/Kea2_python.egg-info/SOURCES.txt +0 -1
  3. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/PKG-INFO +78 -46
  4. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/README.md +77 -45
  5. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/adbUtils.py +29 -1
  6. kea2_python-0.0.1b3/kea2/assets/fastbot_configs/widget.block.py +38 -0
  7. kea2_python-0.0.1b3/kea2/assets/monkeyq.jar +0 -0
  8. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/cli.py +1 -0
  9. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/keaUtils.py +118 -40
  10. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/kea_launcher.py +15 -4
  11. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/logWatcher.py +11 -8
  12. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/u2Driver.py +1 -1
  13. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/pyproject.toml +2 -2
  14. kea2_python-0.0.1b1/kea2/assets/fastbot_configs/ADBKeyBoard.apk +0 -0
  15. kea2_python-0.0.1b1/kea2/assets/fastbot_configs/widget.block.py +0 -18
  16. kea2_python-0.0.1b1/kea2/assets/monkeyq.jar +0 -0
  17. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/Kea2_python.egg-info/dependency_links.txt +0 -0
  18. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/Kea2_python.egg-info/entry_points.txt +0 -0
  19. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/Kea2_python.egg-info/requires.txt +0 -0
  20. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/Kea2_python.egg-info/top_level.txt +0 -0
  21. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/LICENSE +0 -0
  22. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/__init__.py +0 -0
  23. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/absDriver.py +0 -0
  24. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot-thirdpart.jar +0 -0
  25. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_configs/abl.strings +0 -0
  26. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_configs/awl.strings +0 -0
  27. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_configs/max.config +0 -0
  28. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
  29. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
  30. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_configs/max.strings +0 -0
  31. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
  32. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
  33. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
  34. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
  35. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
  36. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/framework.jar +0 -0
  37. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/quicktest.py +0 -0
  38. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/assets/u2.jar +0 -0
  39. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/kea2/utils.py +0 -0
  40. {kea2_python-0.0.1b1 → kea2_python-0.0.1b3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Kea2-python
3
- Version: 0.0.1b1
3
+ Version: 0.0.1b3
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
@@ -20,7 +20,7 @@ Dynamic: license-file
20
20
  <img src="https://github.com/user-attachments/assets/58f68b00-cc9c-4620-9e2e-66c43cf7caae" style="border-radius: 14px; width: 20%; height: 20%;"/>
21
21
  </div>
22
22
 
23
- Kea2 is an easy-to-use Python library for supporting, customizing and improving automated UI testing for mobile apps. The library is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android) and [uiautomator2](https://github.com/openatx/uiautomator2), and targeting [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
23
+ Kea2 is an easy-to-use Python library for supporting, customizing and improving automated UI testing for mobile apps. Kea2's novelty is able to fuse the scripts (usually written by human) with automated UI testing tools, thus allowing many interesting and powerful features. Kea2 is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android) and [uiautomator2](https://github.com/openatx/uiautomator2) and targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
24
24
 
25
25
  ### Kea2 has three important features:
26
26
  - **Feature 1**(查找稳定性问题): coming with the full capability of [Fastbot](https://github.com/bytedance/Fastbot_Android) for stress testing and finding *stability problems* (i.e., *crashing bugs*);
@@ -49,7 +49,7 @@ Kea2, released as a Python library, currently works with:
49
49
  - [uiautomator2](https://github.com/openatx/uiautomator2) as the UI test driver;
50
50
  - [Fastbot](https://github.com/bytedance/Fastbot_Android) as the backend automated UI testing tool.
51
51
 
52
- In the future, Kea2 will be extended to support
52
+ **Roadmap**: In the future, Kea2 will be extended to support
53
53
  - [pytest](https://docs.pytest.org/en/stable/)
54
54
  - [Appium](https://github.com/appium/appium), [Hypium]() (for HarmonyOS)
55
55
  - other automated UI testing tools (not limited to Fastbot)
@@ -61,13 +61,14 @@ In the future, Kea2 will be extended to support
61
61
  Running requirements/environment:
62
62
  - support Windows, MacOS and Linux
63
63
  - python 3.8+
64
+ - Android 4.4+
64
65
  - Android SDK installed
65
66
  - **VPN closed** (Features 2 and 3 required)
66
67
 
67
68
 
68
69
  Install Kea2 by `pip`:
69
70
  ```bash
70
- python3 -m pip install Kea2-python
71
+ python3 -m pip install kea2-python
71
72
  ```
72
73
 
73
74
  Find Kea2's additional options by running
@@ -174,20 +175,31 @@ You can find the full example in script `quicktest.py`, and run this script with
174
175
  kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --running-minutes 10 --throttle 200 --driver-name d unittest discover -p quicktest.py
175
176
  ```
176
177
 
177
- ### Example 2: blacklisting specific UI widgets
178
+ ### Example 2: blacklisting specific UI elements
178
179
 
179
- We support blacklisting specific widgets so that Fastbot can avoid interacting with these widgets during fuzzing. We support (1) `Global Block List` (always taking effective), and (2) `Conditional Block List` (only taking effective when some conditions are met).
180
+ We support blacklisting specific elements so that Fastbot can avoid interacting with these
181
+ elements during fuzzing.
180
182
 
181
- The list of blocked widgets are specified in Kea2's config file `configs/widget.block.py` (generated when running `kea2 init`).
182
- The widgets needed to be blocked can be flexibly specified by u2 selector (e.g., `text`, `description`) or `xpath`, etc.
183
+ We support two granularity levels for UI blocking:
183
184
 
184
- #### Global Block List
185
+ - Widget Blocking: Block individual UI widgets.
186
+
187
+ - Tree Blocking : Block a UI widget trees by specifying its root node.
188
+ It can block the root node and all its descendants.
189
+
190
+ We support (1) `Global Block List` (always taking effective), and (2) `Conditional Block List` (only taking effective when some conditions are met).
191
+
192
+ The list of blocked elements are specified in Kea2's config file `configs/widget.block.py` (generated when running `kea2 init`).
193
+ The elements needed to be blocked can be flexibly specified by u2 selector (e.g., `text`, `description`) or `xpath`, etc.
194
+
195
+ #### Widget Blocking
196
+ ##### Global Block List
185
197
  We can define the function `global_block_widgets` to specify which UI widgets should be blocked globally. The blocking always takes effect.
186
198
 
187
199
  ```python
188
200
  # file: configs/widget.block.py
189
201
 
190
- def global_block_widgets(d: "Device"):
202
+ def global_block_widgets(d: "Device"):
191
203
  """
192
204
  global block list.
193
205
  return the widgets which should be blocked globally
@@ -196,8 +208,8 @@ We can define the function `global_block_widgets` to specify which UI widgets sh
196
208
  d.xpath(".//node[@text='widget to block']"),
197
209
  d(description="widgets to block")]
198
210
  ```
199
- #### Conditional Block List
200
- We can define any reserved function whose name starts with "block_" and decorate such function by `@precondition` to allow conditional block list.
211
+ ##### Conditional Block List
212
+ We can define any reserved function whose name starts with "block_" (but not requiring "block_tree_" prefix) and decorate such function by `@precondition` to allow conditional block list.
201
213
  In this case, the blocking only takes effect when the precondition is satisfied.
202
214
  ```python
203
215
  # file: configs/widget.block.py
@@ -211,6 +223,37 @@ def block_sth(d: "Device"):
211
223
  d(description="widgets to block")]
212
224
  ```
213
225
 
226
+ #### Tree Blocking
227
+ ##### Global Block List
228
+ We can define the function `global_block_tree` to specify which UI widget trees should be blocked globally. The blocking always takes effect.
229
+
230
+ ```python
231
+ # file: configs/widget.block.py
232
+
233
+ def global_block_tree(d: "Device"):
234
+ """
235
+ Specify UI widget trees to be blocked globally during testing.
236
+ Returns a list of root nodes whose entire subtrees will be blocked from exploration.
237
+ This function is only available in 'u2 agent' mode.
238
+ """
239
+ return [d(text="trees to block"), d.xpath(".//node[@text='tree to block']")]
240
+ ```
241
+ ##### Conditional Block List
242
+ We can define any reserved function whose name starts with "block_tree_" and decorate such function by `@precondition` to allow conditional block list.
243
+ In this case, the blocking only takes effect when the precondition is satisfied.
244
+ ```python
245
+ # file: configs/widget.block.py
246
+
247
+ # Example of conditional tree blocking with precondition
248
+
249
+ @precondition(lambda d: d(text="In the home page").exists)
250
+ def block_tree_sth(d: "Device"):
251
+ # Note: Function name must start with "block_tree_"
252
+ return [d(text="trees to block"),
253
+ d.xpath(".//node[@text='tree to block']"),
254
+ d(description="trees to block")]
255
+ ```
256
+
214
257
  ## Feature 3(支持断言机制): Supporting auto-assertions by scripts.
215
258
 
216
259
  Kea2 supports auto-assertions when running Fastbot for finding *logic bugs* (i.e., *non-crashing bugs*). To achieve this, you can add assertions in the scripts. When an assertion fails during automated UI testing, we find a likely functional bug. This idea is inspired by [property-based testing](https://en.wikipedia.org/wiki/Software_testing#Property_testing) inheritted from [Kea](https://github.com/ecnusse/Kea).
@@ -304,31 +347,6 @@ class MyFirstTest(unittest.TestCase):
304
347
 
305
348
  You can read [Kea - Write your fisrt property](https://kea-docs.readthedocs.io/en/latest/part-keaUserManuel/first_property.html) for more details.
306
349
 
307
- ### Notes
308
- Currently, in `@precondition` decorator and `widgets.block.py`. We only support basic selector (No parent-child relationship) in uiautomator2. The Script body in the function fully support uiautomator2.
309
-
310
- | | **Support** | **Not support** |
311
- | -- | -- | -- |
312
- | **Selctor** | `d(text="1").exist` | `d(text="1").child(text="2").exist` |
313
-
314
- If you need to specify `parent-child` relation ship in `@precondition`, specify it in xpath.
315
-
316
- for example:
317
-
318
- ```python
319
- # Do not use:
320
- # @precondition(lambda self:
321
- # self.d(className="android.widget.ListView").child(text="Bluetooth")
322
- # ):
323
- # ...
324
-
325
- # Use
326
- @precondition(lambda self:
327
- self.d.xpath('//android.widget.ListView/*[@text="Bluetooth"]')
328
- ):
329
- ...
330
- ```
331
-
332
350
 
333
351
  ## Launching Kea2
334
352
 
@@ -353,16 +371,17 @@ kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --runn
353
371
  kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --running-minutes 10 --throttle 200 --driver-name d unittest discover -s mytests/omni_notes
354
372
  ```
355
373
 
356
- | arg | meaning |
357
- | --- | --- |
358
- | -s | The serial of your device, which can be found by `adb devices` |
359
- | -p | The tested app's package name (e.g., com.example.app) |
360
- | -o | The ouput directory for logs and results |
361
- | --agent | {native, u2}. By default, `u2` is used and supports all the three important features of Kea2. If you hope to run the orignal Fastbot, please use `native`.|
362
- | --running-minutes | The time (m) to run Kea2 |
363
- | --max-step | The maxium number of monkey events to send (only available in `--agent u2`) |
364
- | --throttle | The delay time (ms) between two monkey events |
374
+ | arg | meaning | default |
375
+ | --- | --- | --- |
376
+ | -s | The serial of your device, which can be found by `adb devices` | |
377
+ | -p | *The tested app's package name (e.g., com.example.app) |
378
+ | -o | The ouput directory for logs and results | `output` |
379
+ | --agent | {native, u2}. By default, `u2` is used and supports all the three important features of Kea2. If you hope to run the orignal Fastbot, please use `native`.| `u2` |
380
+ | --running-minutes | The time (m) to run Kea2 | `10` |
381
+ | --max-step | The maxium number of monkey events to send (only available in `--agent u2`) | `inf` |
382
+ | --throttle | The delay time (ms) between two monkey events | `200` |
365
383
  | --driver-name | The name of driver used in the script. If `--driver-name d` is specified, you should use `d` to interact with a device, e..g, `self.d(..).click()`. |
384
+ | --log-stamp | the stamp for log file and result file. (e.g. `--log-stamp 123` then `fastbot_123.log` and `result_123.json` will be generated.) default: current time stamp | |
366
385
  | unittest | Specify to load which scripts. This sub-command `unittest` is fully compatible with unittest. See `python3 -m unittest -h` for more options of unittest. This option is only available in `--agent u2`.
367
386
 
368
387
 
@@ -425,6 +444,8 @@ running_mins: int = 10
425
444
  throttle: int = 200
426
445
  # the output_dir for saving logs and results
427
446
  output_dir: str = "output"
447
+ # the stamp for log file and result file, default: current time stamp
448
+ log_stamp: str = None
428
449
  ```
429
450
 
430
451
  ## Examining the running statistics of scripts .
@@ -469,6 +490,17 @@ Kea2 has been actively developed and maintained by the people in [ecnusse](https
469
490
  - [uiautomator2](https://github.com/openatx/uiautomator2)
470
491
  - [hypothesis](https://github.com/HypothesisWorks/hypothesis)
471
492
 
493
+ ### Relevant papers of Kea2
494
+
495
+ > General and Practical Property-based Testing for Android Apps. ASE 2024. [pdf](https://dl.acm.org/doi/10.1145/3691620.3694986)
496
+
497
+ > An Empirical Study of Functional Bugs in Android Apps. ISSTA 2023. [pdf](https://dl.acm.org/doi/10.1145/3597926.3598138)
498
+
499
+ > Fastbot2: Reusable Automated Model-based GUI Testing for Android Enhanced by Reinforcement Learning. ASE 2022. [pdf](https://dl.acm.org/doi/10.1145/3551349.3559505)
500
+
501
+ > Guided, Stochastic Model-Based GUI Testing of Android Apps. ESEC/FSE 2017. [pdf](https://dl.acm.org/doi/10.1145/3106237.3106298)
502
+
503
+
472
504
  [^1]: 不少UI自动化测试工具提供了“自定义事件序列”能力(如[Fastbot](https://github.com/bytedance/Fastbot_Android/blob/main/handbook-cn.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E5%BA%8F%E5%88%97) 和[AppCrawler](https://github.com/seveniruby/AppCrawler)),但在实际使用中存在不少问题,如自定义能力有限、使用不灵活等。此前不少Fastbot用户抱怨过其“自定义事件序列”在使用中的问题,如[#209](https://github.com/bytedance/Fastbot_Android/issues/209), [#225](https://github.com/bytedance/Fastbot_Android/issues/225), [#286](https://github.com/bytedance/Fastbot_Android/issues/286)等。
473
505
 
474
506
  [^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
@@ -21,7 +21,6 @@ kea2/assets/framework.jar
21
21
  kea2/assets/monkeyq.jar
22
22
  kea2/assets/quicktest.py
23
23
  kea2/assets/u2.jar
24
- kea2/assets/fastbot_configs/ADBKeyBoard.apk
25
24
  kea2/assets/fastbot_configs/abl.strings
26
25
  kea2/assets/fastbot_configs/awl.strings
27
26
  kea2/assets/fastbot_configs/max.config
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Kea2-python
3
- Version: 0.0.1b1
3
+ Version: 0.0.1b3
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
@@ -20,7 +20,7 @@ Dynamic: license-file
20
20
  <img src="https://github.com/user-attachments/assets/58f68b00-cc9c-4620-9e2e-66c43cf7caae" style="border-radius: 14px; width: 20%; height: 20%;"/>
21
21
  </div>
22
22
 
23
- Kea2 is an easy-to-use Python library for supporting, customizing and improving automated UI testing for mobile apps. The library is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android) and [uiautomator2](https://github.com/openatx/uiautomator2), and targeting [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
23
+ Kea2 is an easy-to-use Python library for supporting, customizing and improving automated UI testing for mobile apps. Kea2's novelty is able to fuse the scripts (usually written by human) with automated UI testing tools, thus allowing many interesting and powerful features. Kea2 is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android) and [uiautomator2](https://github.com/openatx/uiautomator2) and targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
24
24
 
25
25
  ### Kea2 has three important features:
26
26
  - **Feature 1**(查找稳定性问题): coming with the full capability of [Fastbot](https://github.com/bytedance/Fastbot_Android) for stress testing and finding *stability problems* (i.e., *crashing bugs*);
@@ -49,7 +49,7 @@ Kea2, released as a Python library, currently works with:
49
49
  - [uiautomator2](https://github.com/openatx/uiautomator2) as the UI test driver;
50
50
  - [Fastbot](https://github.com/bytedance/Fastbot_Android) as the backend automated UI testing tool.
51
51
 
52
- In the future, Kea2 will be extended to support
52
+ **Roadmap**: In the future, Kea2 will be extended to support
53
53
  - [pytest](https://docs.pytest.org/en/stable/)
54
54
  - [Appium](https://github.com/appium/appium), [Hypium]() (for HarmonyOS)
55
55
  - other automated UI testing tools (not limited to Fastbot)
@@ -61,13 +61,14 @@ In the future, Kea2 will be extended to support
61
61
  Running requirements/environment:
62
62
  - support Windows, MacOS and Linux
63
63
  - python 3.8+
64
+ - Android 4.4+
64
65
  - Android SDK installed
65
66
  - **VPN closed** (Features 2 and 3 required)
66
67
 
67
68
 
68
69
  Install Kea2 by `pip`:
69
70
  ```bash
70
- python3 -m pip install Kea2-python
71
+ python3 -m pip install kea2-python
71
72
  ```
72
73
 
73
74
  Find Kea2's additional options by running
@@ -174,20 +175,31 @@ You can find the full example in script `quicktest.py`, and run this script with
174
175
  kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --running-minutes 10 --throttle 200 --driver-name d unittest discover -p quicktest.py
175
176
  ```
176
177
 
177
- ### Example 2: blacklisting specific UI widgets
178
+ ### Example 2: blacklisting specific UI elements
178
179
 
179
- We support blacklisting specific widgets so that Fastbot can avoid interacting with these widgets during fuzzing. We support (1) `Global Block List` (always taking effective), and (2) `Conditional Block List` (only taking effective when some conditions are met).
180
+ We support blacklisting specific elements so that Fastbot can avoid interacting with these
181
+ elements during fuzzing.
180
182
 
181
- The list of blocked widgets are specified in Kea2's config file `configs/widget.block.py` (generated when running `kea2 init`).
182
- The widgets needed to be blocked can be flexibly specified by u2 selector (e.g., `text`, `description`) or `xpath`, etc.
183
+ We support two granularity levels for UI blocking:
183
184
 
184
- #### Global Block List
185
+ - Widget Blocking: Block individual UI widgets.
186
+
187
+ - Tree Blocking : Block a UI widget trees by specifying its root node.
188
+ It can block the root node and all its descendants.
189
+
190
+ We support (1) `Global Block List` (always taking effective), and (2) `Conditional Block List` (only taking effective when some conditions are met).
191
+
192
+ The list of blocked elements are specified in Kea2's config file `configs/widget.block.py` (generated when running `kea2 init`).
193
+ The elements needed to be blocked can be flexibly specified by u2 selector (e.g., `text`, `description`) or `xpath`, etc.
194
+
195
+ #### Widget Blocking
196
+ ##### Global Block List
185
197
  We can define the function `global_block_widgets` to specify which UI widgets should be blocked globally. The blocking always takes effect.
186
198
 
187
199
  ```python
188
200
  # file: configs/widget.block.py
189
201
 
190
- def global_block_widgets(d: "Device"):
202
+ def global_block_widgets(d: "Device"):
191
203
  """
192
204
  global block list.
193
205
  return the widgets which should be blocked globally
@@ -196,8 +208,8 @@ We can define the function `global_block_widgets` to specify which UI widgets sh
196
208
  d.xpath(".//node[@text='widget to block']"),
197
209
  d(description="widgets to block")]
198
210
  ```
199
- #### Conditional Block List
200
- We can define any reserved function whose name starts with "block_" and decorate such function by `@precondition` to allow conditional block list.
211
+ ##### Conditional Block List
212
+ We can define any reserved function whose name starts with "block_" (but not requiring "block_tree_" prefix) and decorate such function by `@precondition` to allow conditional block list.
201
213
  In this case, the blocking only takes effect when the precondition is satisfied.
202
214
  ```python
203
215
  # file: configs/widget.block.py
@@ -211,6 +223,37 @@ def block_sth(d: "Device"):
211
223
  d(description="widgets to block")]
212
224
  ```
213
225
 
226
+ #### Tree Blocking
227
+ ##### Global Block List
228
+ We can define the function `global_block_tree` to specify which UI widget trees should be blocked globally. The blocking always takes effect.
229
+
230
+ ```python
231
+ # file: configs/widget.block.py
232
+
233
+ def global_block_tree(d: "Device"):
234
+ """
235
+ Specify UI widget trees to be blocked globally during testing.
236
+ Returns a list of root nodes whose entire subtrees will be blocked from exploration.
237
+ This function is only available in 'u2 agent' mode.
238
+ """
239
+ return [d(text="trees to block"), d.xpath(".//node[@text='tree to block']")]
240
+ ```
241
+ ##### Conditional Block List
242
+ We can define any reserved function whose name starts with "block_tree_" and decorate such function by `@precondition` to allow conditional block list.
243
+ In this case, the blocking only takes effect when the precondition is satisfied.
244
+ ```python
245
+ # file: configs/widget.block.py
246
+
247
+ # Example of conditional tree blocking with precondition
248
+
249
+ @precondition(lambda d: d(text="In the home page").exists)
250
+ def block_tree_sth(d: "Device"):
251
+ # Note: Function name must start with "block_tree_"
252
+ return [d(text="trees to block"),
253
+ d.xpath(".//node[@text='tree to block']"),
254
+ d(description="trees to block")]
255
+ ```
256
+
214
257
  ## Feature 3(支持断言机制): Supporting auto-assertions by scripts.
215
258
 
216
259
  Kea2 supports auto-assertions when running Fastbot for finding *logic bugs* (i.e., *non-crashing bugs*). To achieve this, you can add assertions in the scripts. When an assertion fails during automated UI testing, we find a likely functional bug. This idea is inspired by [property-based testing](https://en.wikipedia.org/wiki/Software_testing#Property_testing) inheritted from [Kea](https://github.com/ecnusse/Kea).
@@ -304,31 +347,6 @@ class MyFirstTest(unittest.TestCase):
304
347
 
305
348
  You can read [Kea - Write your fisrt property](https://kea-docs.readthedocs.io/en/latest/part-keaUserManuel/first_property.html) for more details.
306
349
 
307
- ### Notes
308
- Currently, in `@precondition` decorator and `widgets.block.py`. We only support basic selector (No parent-child relationship) in uiautomator2. The Script body in the function fully support uiautomator2.
309
-
310
- | | **Support** | **Not support** |
311
- | -- | -- | -- |
312
- | **Selctor** | `d(text="1").exist` | `d(text="1").child(text="2").exist` |
313
-
314
- If you need to specify `parent-child` relation ship in `@precondition`, specify it in xpath.
315
-
316
- for example:
317
-
318
- ```python
319
- # Do not use:
320
- # @precondition(lambda self:
321
- # self.d(className="android.widget.ListView").child(text="Bluetooth")
322
- # ):
323
- # ...
324
-
325
- # Use
326
- @precondition(lambda self:
327
- self.d.xpath('//android.widget.ListView/*[@text="Bluetooth"]')
328
- ):
329
- ...
330
- ```
331
-
332
350
 
333
351
  ## Launching Kea2
334
352
 
@@ -353,16 +371,17 @@ kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --runn
353
371
  kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --running-minutes 10 --throttle 200 --driver-name d unittest discover -s mytests/omni_notes
354
372
  ```
355
373
 
356
- | arg | meaning |
357
- | --- | --- |
358
- | -s | The serial of your device, which can be found by `adb devices` |
359
- | -p | The tested app's package name (e.g., com.example.app) |
360
- | -o | The ouput directory for logs and results |
361
- | --agent | {native, u2}. By default, `u2` is used and supports all the three important features of Kea2. If you hope to run the orignal Fastbot, please use `native`.|
362
- | --running-minutes | The time (m) to run Kea2 |
363
- | --max-step | The maxium number of monkey events to send (only available in `--agent u2`) |
364
- | --throttle | The delay time (ms) between two monkey events |
374
+ | arg | meaning | default |
375
+ | --- | --- | --- |
376
+ | -s | The serial of your device, which can be found by `adb devices` | |
377
+ | -p | *The tested app's package name (e.g., com.example.app) |
378
+ | -o | The ouput directory for logs and results | `output` |
379
+ | --agent | {native, u2}. By default, `u2` is used and supports all the three important features of Kea2. If you hope to run the orignal Fastbot, please use `native`.| `u2` |
380
+ | --running-minutes | The time (m) to run Kea2 | `10` |
381
+ | --max-step | The maxium number of monkey events to send (only available in `--agent u2`) | `inf` |
382
+ | --throttle | The delay time (ms) between two monkey events | `200` |
365
383
  | --driver-name | The name of driver used in the script. If `--driver-name d` is specified, you should use `d` to interact with a device, e..g, `self.d(..).click()`. |
384
+ | --log-stamp | the stamp for log file and result file. (e.g. `--log-stamp 123` then `fastbot_123.log` and `result_123.json` will be generated.) default: current time stamp | |
366
385
  | unittest | Specify to load which scripts. This sub-command `unittest` is fully compatible with unittest. See `python3 -m unittest -h` for more options of unittest. This option is only available in `--agent u2`.
367
386
 
368
387
 
@@ -425,6 +444,8 @@ running_mins: int = 10
425
444
  throttle: int = 200
426
445
  # the output_dir for saving logs and results
427
446
  output_dir: str = "output"
447
+ # the stamp for log file and result file, default: current time stamp
448
+ log_stamp: str = None
428
449
  ```
429
450
 
430
451
  ## Examining the running statistics of scripts .
@@ -469,6 +490,17 @@ Kea2 has been actively developed and maintained by the people in [ecnusse](https
469
490
  - [uiautomator2](https://github.com/openatx/uiautomator2)
470
491
  - [hypothesis](https://github.com/HypothesisWorks/hypothesis)
471
492
 
493
+ ### Relevant papers of Kea2
494
+
495
+ > General and Practical Property-based Testing for Android Apps. ASE 2024. [pdf](https://dl.acm.org/doi/10.1145/3691620.3694986)
496
+
497
+ > An Empirical Study of Functional Bugs in Android Apps. ISSTA 2023. [pdf](https://dl.acm.org/doi/10.1145/3597926.3598138)
498
+
499
+ > Fastbot2: Reusable Automated Model-based GUI Testing for Android Enhanced by Reinforcement Learning. ASE 2022. [pdf](https://dl.acm.org/doi/10.1145/3551349.3559505)
500
+
501
+ > Guided, Stochastic Model-Based GUI Testing of Android Apps. ESEC/FSE 2017. [pdf](https://dl.acm.org/doi/10.1145/3106237.3106298)
502
+
503
+
472
504
  [^1]: 不少UI自动化测试工具提供了“自定义事件序列”能力(如[Fastbot](https://github.com/bytedance/Fastbot_Android/blob/main/handbook-cn.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E5%BA%8F%E5%88%97) 和[AppCrawler](https://github.com/seveniruby/AppCrawler)),但在实际使用中存在不少问题,如自定义能力有限、使用不灵活等。此前不少Fastbot用户抱怨过其“自定义事件序列”在使用中的问题,如[#209](https://github.com/bytedance/Fastbot_Android/issues/209), [#225](https://github.com/bytedance/Fastbot_Android/issues/225), [#286](https://github.com/bytedance/Fastbot_Android/issues/286)等。
473
505
 
474
506
  [^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
@@ -8,7 +8,7 @@
8
8
  <img src="https://github.com/user-attachments/assets/58f68b00-cc9c-4620-9e2e-66c43cf7caae" style="border-radius: 14px; width: 20%; height: 20%;"/>
9
9
  </div>
10
10
 
11
- Kea2 is an easy-to-use Python library for supporting, customizing and improving automated UI testing for mobile apps. The library is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android) and [uiautomator2](https://github.com/openatx/uiautomator2), and targeting [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
11
+ Kea2 is an easy-to-use Python library for supporting, customizing and improving automated UI testing for mobile apps. Kea2's novelty is able to fuse the scripts (usually written by human) with automated UI testing tools, thus allowing many interesting and powerful features. Kea2 is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android) and [uiautomator2](https://github.com/openatx/uiautomator2) and targets [Android](https://en.wikipedia.org/wiki/Android_(operating_system)) apps.
12
12
 
13
13
  ### Kea2 has three important features:
14
14
  - **Feature 1**(查找稳定性问题): coming with the full capability of [Fastbot](https://github.com/bytedance/Fastbot_Android) for stress testing and finding *stability problems* (i.e., *crashing bugs*);
@@ -37,7 +37,7 @@ Kea2, released as a Python library, currently works with:
37
37
  - [uiautomator2](https://github.com/openatx/uiautomator2) as the UI test driver;
38
38
  - [Fastbot](https://github.com/bytedance/Fastbot_Android) as the backend automated UI testing tool.
39
39
 
40
- In the future, Kea2 will be extended to support
40
+ **Roadmap**: In the future, Kea2 will be extended to support
41
41
  - [pytest](https://docs.pytest.org/en/stable/)
42
42
  - [Appium](https://github.com/appium/appium), [Hypium]() (for HarmonyOS)
43
43
  - other automated UI testing tools (not limited to Fastbot)
@@ -49,13 +49,14 @@ In the future, Kea2 will be extended to support
49
49
  Running requirements/environment:
50
50
  - support Windows, MacOS and Linux
51
51
  - python 3.8+
52
+ - Android 4.4+
52
53
  - Android SDK installed
53
54
  - **VPN closed** (Features 2 and 3 required)
54
55
 
55
56
 
56
57
  Install Kea2 by `pip`:
57
58
  ```bash
58
- python3 -m pip install Kea2-python
59
+ python3 -m pip install kea2-python
59
60
  ```
60
61
 
61
62
  Find Kea2's additional options by running
@@ -162,20 +163,31 @@ You can find the full example in script `quicktest.py`, and run this script with
162
163
  kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --running-minutes 10 --throttle 200 --driver-name d unittest discover -p quicktest.py
163
164
  ```
164
165
 
165
- ### Example 2: blacklisting specific UI widgets
166
+ ### Example 2: blacklisting specific UI elements
166
167
 
167
- We support blacklisting specific widgets so that Fastbot can avoid interacting with these widgets during fuzzing. We support (1) `Global Block List` (always taking effective), and (2) `Conditional Block List` (only taking effective when some conditions are met).
168
+ We support blacklisting specific elements so that Fastbot can avoid interacting with these
169
+ elements during fuzzing.
168
170
 
169
- The list of blocked widgets are specified in Kea2's config file `configs/widget.block.py` (generated when running `kea2 init`).
170
- The widgets needed to be blocked can be flexibly specified by u2 selector (e.g., `text`, `description`) or `xpath`, etc.
171
+ We support two granularity levels for UI blocking:
171
172
 
172
- #### Global Block List
173
+ - Widget Blocking: Block individual UI widgets.
174
+
175
+ - Tree Blocking : Block a UI widget trees by specifying its root node.
176
+ It can block the root node and all its descendants.
177
+
178
+ We support (1) `Global Block List` (always taking effective), and (2) `Conditional Block List` (only taking effective when some conditions are met).
179
+
180
+ The list of blocked elements are specified in Kea2's config file `configs/widget.block.py` (generated when running `kea2 init`).
181
+ The elements needed to be blocked can be flexibly specified by u2 selector (e.g., `text`, `description`) or `xpath`, etc.
182
+
183
+ #### Widget Blocking
184
+ ##### Global Block List
173
185
  We can define the function `global_block_widgets` to specify which UI widgets should be blocked globally. The blocking always takes effect.
174
186
 
175
187
  ```python
176
188
  # file: configs/widget.block.py
177
189
 
178
- def global_block_widgets(d: "Device"):
190
+ def global_block_widgets(d: "Device"):
179
191
  """
180
192
  global block list.
181
193
  return the widgets which should be blocked globally
@@ -184,8 +196,8 @@ We can define the function `global_block_widgets` to specify which UI widgets sh
184
196
  d.xpath(".//node[@text='widget to block']"),
185
197
  d(description="widgets to block")]
186
198
  ```
187
- #### Conditional Block List
188
- We can define any reserved function whose name starts with "block_" and decorate such function by `@precondition` to allow conditional block list.
199
+ ##### Conditional Block List
200
+ We can define any reserved function whose name starts with "block_" (but not requiring "block_tree_" prefix) and decorate such function by `@precondition` to allow conditional block list.
189
201
  In this case, the blocking only takes effect when the precondition is satisfied.
190
202
  ```python
191
203
  # file: configs/widget.block.py
@@ -199,6 +211,37 @@ def block_sth(d: "Device"):
199
211
  d(description="widgets to block")]
200
212
  ```
201
213
 
214
+ #### Tree Blocking
215
+ ##### Global Block List
216
+ We can define the function `global_block_tree` to specify which UI widget trees should be blocked globally. The blocking always takes effect.
217
+
218
+ ```python
219
+ # file: configs/widget.block.py
220
+
221
+ def global_block_tree(d: "Device"):
222
+ """
223
+ Specify UI widget trees to be blocked globally during testing.
224
+ Returns a list of root nodes whose entire subtrees will be blocked from exploration.
225
+ This function is only available in 'u2 agent' mode.
226
+ """
227
+ return [d(text="trees to block"), d.xpath(".//node[@text='tree to block']")]
228
+ ```
229
+ ##### Conditional Block List
230
+ We can define any reserved function whose name starts with "block_tree_" and decorate such function by `@precondition` to allow conditional block list.
231
+ In this case, the blocking only takes effect when the precondition is satisfied.
232
+ ```python
233
+ # file: configs/widget.block.py
234
+
235
+ # Example of conditional tree blocking with precondition
236
+
237
+ @precondition(lambda d: d(text="In the home page").exists)
238
+ def block_tree_sth(d: "Device"):
239
+ # Note: Function name must start with "block_tree_"
240
+ return [d(text="trees to block"),
241
+ d.xpath(".//node[@text='tree to block']"),
242
+ d(description="trees to block")]
243
+ ```
244
+
202
245
  ## Feature 3(支持断言机制): Supporting auto-assertions by scripts.
203
246
 
204
247
  Kea2 supports auto-assertions when running Fastbot for finding *logic bugs* (i.e., *non-crashing bugs*). To achieve this, you can add assertions in the scripts. When an assertion fails during automated UI testing, we find a likely functional bug. This idea is inspired by [property-based testing](https://en.wikipedia.org/wiki/Software_testing#Property_testing) inheritted from [Kea](https://github.com/ecnusse/Kea).
@@ -292,31 +335,6 @@ class MyFirstTest(unittest.TestCase):
292
335
 
293
336
  You can read [Kea - Write your fisrt property](https://kea-docs.readthedocs.io/en/latest/part-keaUserManuel/first_property.html) for more details.
294
337
 
295
- ### Notes
296
- Currently, in `@precondition` decorator and `widgets.block.py`. We only support basic selector (No parent-child relationship) in uiautomator2. The Script body in the function fully support uiautomator2.
297
-
298
- | | **Support** | **Not support** |
299
- | -- | -- | -- |
300
- | **Selctor** | `d(text="1").exist` | `d(text="1").child(text="2").exist` |
301
-
302
- If you need to specify `parent-child` relation ship in `@precondition`, specify it in xpath.
303
-
304
- for example:
305
-
306
- ```python
307
- # Do not use:
308
- # @precondition(lambda self:
309
- # self.d(className="android.widget.ListView").child(text="Bluetooth")
310
- # ):
311
- # ...
312
-
313
- # Use
314
- @precondition(lambda self:
315
- self.d.xpath('//android.widget.ListView/*[@text="Bluetooth"]')
316
- ):
317
- ...
318
- ```
319
-
320
338
 
321
339
  ## Launching Kea2
322
340
 
@@ -341,16 +359,17 @@ kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --runn
341
359
  kea2 run -s "emulator-5554" -p it.feio.android.omninotes.alpha --agent u2 --running-minutes 10 --throttle 200 --driver-name d unittest discover -s mytests/omni_notes
342
360
  ```
343
361
 
344
- | arg | meaning |
345
- | --- | --- |
346
- | -s | The serial of your device, which can be found by `adb devices` |
347
- | -p | The tested app's package name (e.g., com.example.app) |
348
- | -o | The ouput directory for logs and results |
349
- | --agent | {native, u2}. By default, `u2` is used and supports all the three important features of Kea2. If you hope to run the orignal Fastbot, please use `native`.|
350
- | --running-minutes | The time (m) to run Kea2 |
351
- | --max-step | The maxium number of monkey events to send (only available in `--agent u2`) |
352
- | --throttle | The delay time (ms) between two monkey events |
362
+ | arg | meaning | default |
363
+ | --- | --- | --- |
364
+ | -s | The serial of your device, which can be found by `adb devices` | |
365
+ | -p | *The tested app's package name (e.g., com.example.app) |
366
+ | -o | The ouput directory for logs and results | `output` |
367
+ | --agent | {native, u2}. By default, `u2` is used and supports all the three important features of Kea2. If you hope to run the orignal Fastbot, please use `native`.| `u2` |
368
+ | --running-minutes | The time (m) to run Kea2 | `10` |
369
+ | --max-step | The maxium number of monkey events to send (only available in `--agent u2`) | `inf` |
370
+ | --throttle | The delay time (ms) between two monkey events | `200` |
353
371
  | --driver-name | The name of driver used in the script. If `--driver-name d` is specified, you should use `d` to interact with a device, e..g, `self.d(..).click()`. |
372
+ | --log-stamp | the stamp for log file and result file. (e.g. `--log-stamp 123` then `fastbot_123.log` and `result_123.json` will be generated.) default: current time stamp | |
354
373
  | unittest | Specify to load which scripts. This sub-command `unittest` is fully compatible with unittest. See `python3 -m unittest -h` for more options of unittest. This option is only available in `--agent u2`.
355
374
 
356
375
 
@@ -413,6 +432,8 @@ running_mins: int = 10
413
432
  throttle: int = 200
414
433
  # the output_dir for saving logs and results
415
434
  output_dir: str = "output"
435
+ # the stamp for log file and result file, default: current time stamp
436
+ log_stamp: str = None
416
437
  ```
417
438
 
418
439
  ## Examining the running statistics of scripts .
@@ -457,6 +478,17 @@ Kea2 has been actively developed and maintained by the people in [ecnusse](https
457
478
  - [uiautomator2](https://github.com/openatx/uiautomator2)
458
479
  - [hypothesis](https://github.com/HypothesisWorks/hypothesis)
459
480
 
481
+ ### Relevant papers of Kea2
482
+
483
+ > General and Practical Property-based Testing for Android Apps. ASE 2024. [pdf](https://dl.acm.org/doi/10.1145/3691620.3694986)
484
+
485
+ > An Empirical Study of Functional Bugs in Android Apps. ISSTA 2023. [pdf](https://dl.acm.org/doi/10.1145/3597926.3598138)
486
+
487
+ > Fastbot2: Reusable Automated Model-based GUI Testing for Android Enhanced by Reinforcement Learning. ASE 2022. [pdf](https://dl.acm.org/doi/10.1145/3551349.3559505)
488
+
489
+ > Guided, Stochastic Model-Based GUI Testing of Android Apps. ESEC/FSE 2017. [pdf](https://dl.acm.org/doi/10.1145/3106237.3106298)
490
+
491
+
460
492
  [^1]: 不少UI自动化测试工具提供了“自定义事件序列”能力(如[Fastbot](https://github.com/bytedance/Fastbot_Android/blob/main/handbook-cn.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E5%BA%8F%E5%88%97) 和[AppCrawler](https://github.com/seveniruby/AppCrawler)),但在实际使用中存在不少问题,如自定义能力有限、使用不灵活等。此前不少Fastbot用户抱怨过其“自定义事件序列”在使用中的问题,如[#209](https://github.com/bytedance/Fastbot_Android/issues/209), [#225](https://github.com/bytedance/Fastbot_Android/issues/225), [#286](https://github.com/bytedance/Fastbot_Android/issues/286)等。
461
493
 
462
494
  [^2]: 在UI自动化测试过程中支持自动断言是一个很重要的能力,但几乎没有测试工具提供这样的能力。我们注意到[AppCrawler](https://ceshiren.com/t/topic/15801/5)的开发者曾经希望提供一种断言机制,得到了用户的热切响应,不少用户从21年就开始催更,但始终未能实现。
@@ -1,5 +1,5 @@
1
1
  import subprocess
2
- from typing import List, Optional
2
+ from typing import List, Optional, Set
3
3
  from .utils import getLogger
4
4
 
5
5
  logger = getLogger(__name__)
@@ -223,6 +223,34 @@ def remove_all_forwards(device: Optional[str] = None):
223
223
  return run_adb_command(["-s", device, "forward", "--remove-all"])
224
224
 
225
225
 
226
+ @ensure_device
227
+ def get_packages(device: Optional[str] = None) -> Set[str]:
228
+ """
229
+ Retrieves packages that match the specified regular expression pattern.
230
+
231
+ Parameters:
232
+ pattern (str): Regular expression pattern to match package names.
233
+ device (str, optional): The device serial number. If None, it is resolved automatically.
234
+
235
+ Returns:
236
+ set: A set of package names that match the pattern.
237
+ """
238
+ import re
239
+
240
+ cmd = ["-s", device, "shell", "pm", "list", "packages"]
241
+ output = run_adb_command(cmd)
242
+
243
+ packages = set()
244
+ if output:
245
+ compiled_pattern = re.compile(r"package:(.+)\n")
246
+ matches = compiled_pattern.findall(output)
247
+ for match in matches:
248
+ if match:
249
+ packages.add(match)
250
+
251
+ return packages
252
+
253
+
226
254
  if __name__ == '__main__':
227
255
  # For testing: print the list of currently connected devices.
228
256
  devices = get_devices()
@@ -0,0 +1,38 @@
1
+ from kea2.utils import Device
2
+ from kea2.keaUtils import precondition
3
+
4
+
5
+ def global_block_widgets(d: "Device"):
6
+ """
7
+ Specify UI widgets to be blocked globally during testing.
8
+ Returns a list of widgets that should be blocked from exploration.
9
+ This function is only available in 'u2 agent' mode.
10
+ """
11
+ # return [d(text="widgets to block"), d.xpath(".//node[@text='widget to block']")]
12
+ return []
13
+
14
+
15
+ # Example of conditional blocking with precondition
16
+ # @precondition(lambda d: d(text="In the home page").exists)
17
+ @precondition(lambda d: False)
18
+ def block_sth(d: "Device"):
19
+ # Note: Function name must start with "block_"
20
+ return []
21
+
22
+
23
+ def global_block_tree(d: "Device"):
24
+ """
25
+ Specify UI widget trees to be blocked globally during testing.
26
+ Returns a list of root nodes whose entire subtrees will be blocked from exploration.
27
+ This function is only available in 'u2 agent' mode.
28
+ """
29
+ # return [d(text="trees to block"), d.xpath(".//node[@text='tree to block']")]
30
+ return []
31
+
32
+
33
+ # Example of conditional tree blocking with precondition
34
+ # @precondition(lambda d: d(text="In the home page").exists)
35
+ @precondition(lambda d: False)
36
+ def block_tree_sth(d: "Device"):
37
+ # Note: Function name must start with "block_tree_"
38
+ return []
@@ -99,6 +99,7 @@ def main():
99
99
  args = parser.parse_args()
100
100
 
101
101
  import logging
102
+ logging.basicConfig(level=logging.INFO)
102
103
  if args.debug:
103
104
  logging.basicConfig(level=logging.DEBUG)
104
105
  logger.debug("args: %s", args)
@@ -119,6 +119,13 @@ class Options:
119
119
  output_dir: str = "output"
120
120
  # the stamp for log file and result file, default: current time stamp
121
121
  log_stamp: str = None
122
+ # the profiling period to get the coverage result.
123
+ profile_period: int = None
124
+
125
+ def __setattr__(self, name, value):
126
+ if value is None:
127
+ return
128
+ super().__setattr__(name, value)
122
129
 
123
130
  def __post_init__(self):
124
131
  if self.serial and self.Driver:
@@ -127,6 +134,17 @@ class Options:
127
134
  global LOGFILE, RESFILE
128
135
  LOGFILE = f"fastbot_{self.log_stamp}.log"
129
136
  RESFILE = f"result_{self.log_stamp}.json"
137
+ _check_package_installation(self.serial, self.packageNames)
138
+
139
+
140
+ def _check_package_installation(serial, packageNames):
141
+ from .adbUtils import get_packages
142
+ installed_packages = get_packages(device=serial)
143
+
144
+ for package in packageNames:
145
+ if package not in installed_packages:
146
+ logger.error(f"package {package} not installed. Abort.")
147
+ raise ValueError("package not installed")
130
148
 
131
149
 
132
150
  @dataclass
@@ -257,7 +275,7 @@ def startFastbotService(options: Options) -> threading.Thread:
257
275
  "reuseq",
258
276
  "--running-minutes", f"{options.running_mins}",
259
277
  "--throttle", f"{options.throttle}",
260
- "--bugreport", "--output-directory", "/sdcard/fastbot_report"
278
+ "--bugreport", "--output-directory", "/sdcard/fastbot_report",
261
279
  "-v", "-v", "-v"
262
280
  ]
263
281
 
@@ -287,7 +305,8 @@ class KeaTestRunner(TextTestRunner):
287
305
  resultclass: JsonResult
288
306
  allProperties: PropertyStore
289
307
  options: Options = None
290
- _block_widgets_funcs = None
308
+ _block_funcs: Dict[Literal["widgets", "trees"], List[Callable]] = None
309
+ # _block_trees_funcs = None
291
310
 
292
311
  @classmethod
293
312
  def setOptions(cls, options: Options):
@@ -354,18 +373,20 @@ class KeaTestRunner(TextTestRunner):
354
373
  check_alive(port=self.scriptDriver.lport)
355
374
 
356
375
  end_by_remote = False
357
- step = 0
358
- while step < self.options.maxStep:
376
+ self.stepsCount = 0
377
+ while self.stepsCount < self.options.maxStep:
359
378
 
360
- step += 1
379
+ self.stepsCount += 1
361
380
  print("[INFO] Sending monkeyEvent {}".format(
362
- f"({step} / {self.options.maxStep})" if self.options.maxStep != float("inf")
363
- else f"({step})"
381
+ f"({self.stepsCount} / {self.options.maxStep})" if self.options.maxStep != float("inf")
382
+ else f"({self.stepsCount})"
364
383
  )
365
384
  , flush=True)
366
385
 
367
386
  try:
368
- propsSatisfiedPrecond = self.getValidProperties(result)
387
+ xml_raw = self.stepMonkey()
388
+ stat = self._getStat()
389
+ propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
369
390
  except requests.ConnectionError:
370
391
  print(
371
392
  "[INFO] Exploration times up (--running-minutes)."
@@ -413,7 +434,6 @@ class KeaTestRunner(TextTestRunner):
413
434
 
414
435
  print(f"Finish sending monkey events.", flush=True)
415
436
  log_watcher.close()
416
- self.tearDown()
417
437
 
418
438
  # Source code from unittest Runner
419
439
  # process the result
@@ -455,14 +475,19 @@ class KeaTestRunner(TextTestRunner):
455
475
  """
456
476
  send a step monkey request to the server and get the xml string.
457
477
  """
458
- block_widgets: List[str] = self._getBlockedWidgets()
478
+ block_dict = self._getBlockedWidgets()
479
+ block_widgets: List[str] = block_dict['widgets']
480
+ block_trees: List[str] = block_dict['trees']
459
481
  URL = f"http://localhost:{self.scriptDriver.lport}/stepMonkey"
460
482
  logger.debug(f"Sending request: {URL}")
461
483
  logger.debug(f"Blocking widgets: {block_widgets}")
484
+ logger.debug(f"Blocking trees: {block_trees}")
462
485
  r = requests.post(
463
486
  url=URL,
464
487
  json={
465
- "block_widgets": block_widgets
488
+ "steps_count": self.stepsCount,
489
+ "block_widgets": block_widgets,
490
+ "block_trees": block_trees
466
491
  }
467
492
  )
468
493
 
@@ -481,9 +506,8 @@ class KeaTestRunner(TextTestRunner):
481
506
  res = r.content.decode(encoding="utf-8")
482
507
  print(f"[Server INFO] {res}", flush=True)
483
508
 
484
- def getValidProperties(self, result: JsonResult) -> PropertyStore:
509
+ def getValidProperties(self, xml_raw: str, result: JsonResult) -> PropertyStore:
485
510
 
486
- xml_raw = self.stepMonkey()
487
511
  staticCheckerDriver = self.options.Driver.getStaticChecker(hierarchy=xml_raw)
488
512
 
489
513
  validProps: PropertyStore = dict()
@@ -513,6 +537,15 @@ class KeaTestRunner(TextTestRunner):
513
537
  validProps[propName] = test
514
538
  return validProps
515
539
 
540
+ def _getStat(self):
541
+ # profile when reaching the profile period
542
+ if (self.options.profile_period and
543
+ self.stepsCount % self.options.profile_period == 0
544
+ ):
545
+ URL = f"http://localhost:{self.scriptDriver.lport}/getStat"
546
+ r = requests.get(URL)
547
+ res = json.loads(r.content)
548
+
516
549
  def collectAllProperties(self, test: TestSuite):
517
550
  """collect all the properties to prepare for PBT
518
551
  """
@@ -548,17 +581,24 @@ class KeaTestRunner(TextTestRunner):
548
581
  # save it into allProperties for PBT
549
582
  self.allProperties[testMethodName] = t
550
583
  print(f"[INFO] Load property: {getFullPropName(t)}", flush=True)
551
-
584
+
552
585
  @property
553
586
  def _blockWidgetFuncs(self):
554
- if self._block_widgets_funcs is None:
555
- self._block_widgets_funcs = list()
587
+ """
588
+ load and process blocking functions from widget.block.py configuration file.
589
+
590
+ Returns:
591
+ dict: A dictionary containing two lists:
592
+ - 'widgets': List of functions that block individual widgets
593
+ - 'trees': List of functions that block widget trees
594
+ """
595
+ if self._block_funcs is None:
596
+ self._block_funcs = {"widgets": list(), "trees": list()}
556
597
  root_dir = getProjectRoot()
557
598
  if root_dir is None or not os.path.exists(
558
- file_block_widgets := root_dir / "configs" / "widget.block.py"
599
+ file_block_widgets := root_dir / "configs" / "widget.block.py"
559
600
  ):
560
601
  print(f"[WARNING] widget.block.py not find", flush=True)
561
-
562
602
 
563
603
  def __get_block_widgets_module():
564
604
  import importlib.util
@@ -572,43 +612,81 @@ class KeaTestRunner(TextTestRunner):
572
612
 
573
613
  import inspect
574
614
  for func_name, func in inspect.getmembers(mod, inspect.isfunction):
575
- if func_name.startswith("block_") or func_name == "global_block_widgets":
615
+ if func_name == "global_block_widgets":
616
+ self._block_funcs["widgets"].append(func)
617
+ setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
618
+ continue
619
+ if func_name == "global_block_tree":
620
+ self._block_funcs["trees"].append(func)
621
+ setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
622
+ continue
623
+ if func_name.startswith("block_") and not func_name.startswith("block_tree_"):
576
624
  if getattr(func, PRECONDITIONS_MARKER, None) is None:
577
- if func_name.startswith("block_"):
578
- logger.warning(f"No precondition in block widget function: {func_name}. Default globally active.")
579
- setattr(func, PRECONDITIONS_MARKER, (lambda d: True, ))
580
- self._block_widgets_funcs.append(func)
625
+ logger.warning(f"No precondition in block widget function: {func_name}. Default globally active.")
626
+ setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
627
+ self._block_funcs["widgets"].append(func)
628
+ continue
629
+ if func_name.startswith("block_tree_"):
630
+ if getattr(func, PRECONDITIONS_MARKER, None) is None:
631
+ logger.warning(f"No precondition in block tree function: {func_name}. Default globally active.")
632
+ setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
633
+ self._block_funcs["trees"].append(func)
634
+
635
+ return self._block_funcs
581
636
 
582
- return self._block_widgets_funcs
583
637
 
584
638
  def _getBlockedWidgets(self):
585
- blocked_widgets = list()
586
- for func in self._blockWidgetFuncs:
639
+ """
640
+ Executes all blocking functions to get lists of widgets and trees to be blocked during testing.
641
+
642
+ Returns:
643
+ dict: A dictionary containing:
644
+ - 'widgets': List of XPath strings for individual widgets to block
645
+ - 'trees': List of XPath strings for widget trees to block
646
+ """
647
+ def _get_xpath_widgets(func):
648
+ blocked_set = set()
587
649
  try:
588
650
  script_driver = self.options.Driver.getScriptDriver()
589
- preconds = getattr(func, PRECONDITIONS_MARKER)
590
- if all([precond(script_driver) for precond in preconds]):
651
+ preconds = getattr(func, PRECONDITIONS_MARKER, [])
652
+ if all(precond(script_driver) for precond in preconds):
591
653
  _widgets = func(self.options.Driver.getStaticChecker())
592
- if not isinstance(_widgets, list):
593
- _widgets = [_widgets]
654
+ _widgets = _widgets if isinstance(_widgets, list) else [_widgets]
594
655
  for w in _widgets:
595
656
  if isinstance(w, StaticU2UiObject):
596
- blocked_widgets.append(w._getXPath(w.selector))
657
+ xpath = w._getXPath(w.selector)
658
+ blocked_set.add(xpath) # 集合去重
597
659
  elif isinstance(w, u2.xpath.XPathSelector):
598
- def getXPathRepr(w):
599
- return w._parent.xpath
600
- blocked_widgets.append(getXPathRepr(w))
660
+ xpath = w._parent.xpath
661
+ blocked_set.add(xpath)
601
662
  else:
602
663
  logger.warning(f"{w} Not supported")
603
- # blocked_widgets.extend([
604
- # w._getXPath(w.selector) for w in _widgets
605
- # ])
606
664
  except Exception as e:
607
- logger.error(f"error when getting blocked widgets: {e}")
608
- import traceback
665
+ logger.error(f"Error processing blocked widgets: {e}")
609
666
  traceback.print_exc()
667
+ return blocked_set
668
+
669
+ res = {
670
+ "widgets": set(),
671
+ "trees": set()
672
+ }
673
+
674
+
675
+ for func in self._blockWidgetFuncs["widgets"]:
676
+ widgets = _get_xpath_widgets(func)
677
+ res["widgets"].update(widgets)
678
+
679
+
680
+ for func in self._blockWidgetFuncs["trees"]:
681
+ trees = _get_xpath_widgets(func)
682
+ res["trees"].update(trees)
683
+
684
+
685
+ res["widgets"] = list(res["widgets"] - res["trees"])
686
+ res["trees"] = list(res["trees"])
687
+
688
+ return res
610
689
 
611
- return blocked_widgets
612
690
 
613
691
  def __del__(self):
614
692
  """tearDown method. Cleanup the env.
@@ -83,6 +83,14 @@ def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.Argument
83
83
  required=False,
84
84
  help="the stamp for log file and result file, default: current time stamp",
85
85
  )
86
+
87
+ parser.add_argument(
88
+ "--profile-period",
89
+ dest="profile_period",
90
+ type=int,
91
+ required=False,
92
+ help="Steps to profile the testing statistics.",
93
+ )
86
94
 
87
95
  parser.add_argument(
88
96
  "extra",
@@ -120,6 +128,7 @@ def parse_args(argv: List):
120
128
  args = parser.parse_args(argv)
121
129
  return args
122
130
 
131
+
123
132
  def _sanitize_args(args):
124
133
  if args.agent == "u2" and not args.driver_name:
125
134
  if args.extra == []:
@@ -127,6 +136,7 @@ def _sanitize_args(args):
127
136
  else:
128
137
  raise ValueError("--driver-name should be specified when customizing script in --agent u2")
129
138
 
139
+
130
140
  def run(args=None):
131
141
  if args is None:
132
142
  args = parse_args(sys.argv[1:])
@@ -144,10 +154,11 @@ def run(args=None):
144
154
  Driver=U2Driver,
145
155
  packageNames=args.package_names,
146
156
  serial=args.serial,
147
- running_mins=args.running_minutes if args.running_minutes else 10,
148
- maxStep=args.max_step if args.max_step else 500,
149
- throttle=args.throttle_ms if args.throttle_ms else 200,
150
- log_stamp=args.log_stamp
157
+ running_mins=args.running_minutes,
158
+ maxStep=args.max_step,
159
+ throttle=args.throttle_ms,
160
+ log_stamp=args.log_stamp,
161
+ profile_period=args.profile_period,
151
162
  )
152
163
 
153
164
  KeaTestRunner.setOptions(options)
@@ -46,17 +46,19 @@ class LogWatcher:
46
46
  exception_body +
47
47
  "\nSee fastbot.log for details."
48
48
  )
49
- statistic_match = PATTERN_STATISTIC.search(buffer)
50
- if statistic_match:
51
- statistic_body = statistic_match.group(1).strip()
52
- if statistic_body:
53
- print(
54
- "[INFO] Fastbot exit:\n" +
55
- statistic_body
56
- , flush=True)
49
+ if self.end_flag:
50
+ statistic_match = PATTERN_STATISTIC.search(buffer)
51
+ if statistic_match:
52
+ statistic_body = statistic_match.group(1).strip()
53
+ if statistic_body:
54
+ print(
55
+ "[INFO] Fastbot exit:\n" +
56
+ statistic_body
57
+ , flush=True)
57
58
 
58
59
  def __init__(self, log_file):
59
60
  self.log_file = log_file
61
+ self.end_flag = False
60
62
 
61
63
  threading.excepthook = thread_excepthook
62
64
  t = threading.Thread(target=self.watcher, daemon=True)
@@ -64,6 +66,7 @@ class LogWatcher:
64
66
 
65
67
  def close(self):
66
68
  time.sleep(0.2) # wait for the written logfile close
69
+ self.end_flag = True
67
70
  self.read_log()
68
71
 
69
72
 
@@ -235,7 +235,7 @@ class U2StaticDevice(u2.Device):
235
235
 
236
236
  def __getattr__(self, attr):
237
237
  """Proxy other methods to script_driver"""
238
- # logger.debug(f"{attr} not exists in static checker, proxy to script_driver.")
238
+ logger.debug(f"{attr} not exists in static checker, proxy to script_driver.")
239
239
  return getattr(self._script_driver, attr)
240
240
 
241
241
  class _XPathEntry(u2.xpath.XPathEntry):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Kea2-python"
3
- version = "0.0.1b1"
3
+ version = "0.0.1b3"
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"
@@ -19,4 +19,4 @@ kea2 = "kea2.cli:main"
19
19
  include = ["kea2"]
20
20
 
21
21
  [tool.setuptools.package-data]
22
- kea2 = ["assets/**/*"]
22
+ kea2 = ["assets/**/*"]
@@ -1,18 +0,0 @@
1
- from kea2.utils import Device
2
- from kea2.keaUtils import precondition
3
-
4
-
5
- def global_block_widgets(d: "Device"):
6
- """
7
- global block widgets.
8
- return the widgets you want to block globally
9
- only available in mode `u2 agent`
10
- """
11
- return []
12
-
13
-
14
- # conditional block list
15
- @precondition(lambda d: d(text="In the home page").exists)
16
- def block_sth(d: "Device"):
17
- # Important: function shold starts with "block_"
18
- return [d(text="widgets to block"), d.xpath(".//node[@text='widget to block']")]
Binary file
File without changes
File without changes