Conversation
# Conflicts: # kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so # kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so # kea2/assets/fastbot_libs/x86/libfastbot_native.so # kea2/assets/fastbot_libs/x86_64/libfastbot_native.so # kea2/assets/monkeyq.jar # kea2/keaUtils.py
# kea2/assets/monkeyq.jar # kea2/keaUtils.py
# Conflicts: # kea2/assets/monkeyq.jar # kea2/keaUtils.py # pyproject.toml # uv.lock
…used by high JDK version
# Conflicts: # kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so # kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so # kea2/assets/fastbot_libs/x86/libfastbot_native.so # kea2/assets/fastbot_libs/x86_64/libfastbot_native.so # kea2/assets/monkeyq.jar # kea2/keaUtils.py # pyproject.toml # uv.lock
| # if not hasattr(test, '_has_been_satisfied'): | ||
| # self.fb.sendFirstTimeSatisfiedSignal() | ||
| # test._has_been_satisfied = True |
| import uiautomator2 as u2 | ||
|
|
||
| from time import sleep | ||
| from kea2 import precondition, prob, KeaTestRunner, Options, max_tries |
There was a problem hiding this comment.
Pull request overview
Adds a property-guided signaling hook to Kea2’s runner/Fastbot integration and introduces a new UIAutomator2-based property test script intended for Douyin/TikTok page detection.
Changes:
- Add
FastbotManager.sendFirstTimeSatisfiedSignal()and invoke it when a property’s preconditions are satisfied. - Refactor
KeaTestRunner.run()to keep the Fastbot manager onself.fbinstead of a local variable. - Add
tictoktest.pywith multiple@precondition-guarded “page reached” properties and a helper to (attempt to) install the app.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| tictoktest.py | New property-based UI test script + installation helper for the app under test. |
| kea2/keaUtils.py | Runner changes to use self.fb and emit a new “first satisfied” signal when preconditions pass. |
| kea2/fastbotManager.py | Adds an HTTP call to the fastbot server for the new “property first satisfied” signal. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| PACKAGE_NAME = "com.ss.android.ugc.aweme" | ||
| FILE_NAME = "omninotes.apk" | ||
|
|
||
|
|
||
| def check_installation(serial=None): | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| d = u2.connect(serial) | ||
| # automatically install omni-notes | ||
| if PACKAGE_NAME not in d.app_list(): | ||
| if not os.path.exists(Path(".") / FILE_NAME): | ||
| print(f"[INFO] omninote.apk not exists.", flush=True) | ||
| print("[INFO] Installing omninotes.", flush=True) | ||
| d.app_install(FILE_NAME) |
There was a problem hiding this comment.
PACKAGE_NAME targets Douyin/TikTok (com.ss.android.ugc.aweme), but FILE_NAME/messages still reference omninotes.apk/"omninotes". As written, check_installation() will attempt to install the wrong APK and the package check will never become true. Align FILE_NAME + log messages with the package under test, or remove installation logic if the app is expected to be preinstalled.
| # automatically install omni-notes | ||
| if PACKAGE_NAME not in d.app_list(): | ||
| if not os.path.exists(Path(".") / FILE_NAME): | ||
| print(f"[INFO] omninote.apk not exists.", flush=True) | ||
| print("[INFO] Installing omninotes.", flush=True) | ||
| d.app_install(FILE_NAME) | ||
| d.stop_uiautomator() |
There was a problem hiding this comment.
When the APK file is missing, the code prints an info message but still proceeds to app_install(FILE_NAME), which will fail with a less clear error. Consider returning early or raising an exception after the missing-file check to make the failure explicit.
| # automatically install omni-notes | |
| if PACKAGE_NAME not in d.app_list(): | |
| if not os.path.exists(Path(".") / FILE_NAME): | |
| print(f"[INFO] omninote.apk not exists.", flush=True) | |
| print("[INFO] Installing omninotes.", flush=True) | |
| d.app_install(FILE_NAME) | |
| d.stop_uiautomator() | |
| apk_path = Path(".") / FILE_NAME | |
| try: | |
| # automatically install omni-notes | |
| if PACKAGE_NAME not in d.app_list(): | |
| if not os.path.exists(apk_path): | |
| print(f"[INFO] omninote.apk not exists.", flush=True) | |
| raise FileNotFoundError(f"Required APK not found: {apk_path}") | |
| print("[INFO] Installing omninotes.", flush=True) | |
| d.app_install(FILE_NAME) | |
| finally: | |
| d.stop_uiautomator() |
| allProperties: PropertyStore | ||
| allInvariants: PropertyStore | ||
| _block_funcs: Dict[Literal["widgets", "trees"], List[Callable]] = None | ||
| fb = FastbotManager |
There was a problem hiding this comment.
fb = FastbotManager sets the class attribute to the FastbotManager type, but elsewhere self.fb is used as an instance. This makes the default state ambiguous and can cause a TypeError if any code path accesses self.fb before run() assigns the instance. Prefer initializing as None (and typing it) to clearly indicate lifecycle (e.g., fb: FastbotManager | None = None).
| fb = FastbotManager | |
| fb: Union[FastbotManager, None] = None |
| self.fb.sendFirstTimeSatisfiedSignal() | ||
| # if not hasattr(test, '_has_been_satisfied'): | ||
| # self.fb.sendFirstTimeSatisfiedSignal() | ||
| # test._has_been_satisfied = True |
There was a problem hiding this comment.
sendFirstTimeSatisfiedSignal() is called every time a property's preconditions are satisfied, on every loop iteration. This can generate a large number of HTTP calls (one per satisfied property per step) and doesn’t match the method name’s “first time” semantics. Track per-property satisfaction (e.g., on test/result/runner state) and only emit the signal the first time a given property becomes satisfied.
| self.fb.sendFirstTimeSatisfiedSignal() | |
| # if not hasattr(test, '_has_been_satisfied'): | |
| # self.fb.sendFirstTimeSatisfiedSignal() | |
| # test._has_been_satisfied = True | |
| if not getattr(test, "_kea_first_time_satisfied_signal_sent", False): | |
| self.fb.sendFirstTimeSatisfiedSignal() | |
| test._kea_first_time_satisfied_signal_sent = True |
| from pathlib import Path | ||
|
|
||
| d = u2.connect(serial) | ||
| # automatically install omni-notes |
There was a problem hiding this comment.
The inline comment says “automatically install omni-notes”, but this script targets com.ss.android.ugc.aweme (Douyin/TikTok). Update the comment to match the actual app under test to avoid misleading future readers.
| # automatically install omni-notes | |
| # automatically install Douyin/TikTok if it is not already installed |
| break | ||
| # if all the precond passed. make it the candidate prop. | ||
| if valid: | ||
| self.fb.sendFirstTimeSatisfiedSignal() |
There was a problem hiding this comment.
sendFirstTimeSatisfiedSignal() is outside the precondition try/except blocks. If the fastbot server is temporarily unavailable (or the endpoint errors), this can raise after retries and abort the entire run even though the property itself is fine. Consider wrapping this call in a narrow try/except that logs a warning and continues.
| self.fb.sendFirstTimeSatisfiedSignal() | |
| try: | |
| self.fb.sendFirstTimeSatisfiedSignal() | |
| except Exception as e: | |
| logger.warning( | |
| f"Failed to send first-time satisfied signal for {propName}: {e}. Continuing without aborting the run." | |
| ) |
Property-guided algorithm