From aae9536119a6c79b723ed0f976968bf83a81bb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Xu=C3=A2n=20D=C5=A9ng?= Date: Thu, 8 Oct 2026 01:03:58 +0700 Subject: [PATCH 1/2] fix(e2e): start_app resolves the real MainActivity namespace PACKAGE moved to the new applicationId com.handlive.android, but start_app's .MainActivity shorthand resolves relative to -n's package segment, not the manifest namespace (still app.handlive.android) -- so am start -n com.handlive.android/.MainActivity pointed at a class that does not exist. Add NAMESPACE and use the full component name. self_test.py: 0 failed. --- tools/e2e/adb_device.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/e2e/adb_device.py b/tools/e2e/adb_device.py index 4e25ac8..5a3a2a6 100644 --- a/tools/e2e/adb_device.py +++ b/tools/e2e/adb_device.py @@ -16,6 +16,8 @@ SDK_DEFAULT = "/opt/homebrew/share/android-commandlinetools" PACKAGE = "com.handlive.android" +# applicationId differs from the Kotlin/manifest namespace, so component names (unlike pm/am package args) need this +NAMESPACE = "app.handlive.android" def lines_since(text: str, since: float) -> list[str]: @@ -113,7 +115,7 @@ def granted(self, permission: str, package: str = PACKAGE) -> bool: return re.search(rf"android\.permission\.{permission}: granted=true", dump) is not None def start_app(self, package: str = PACKAGE) -> None: - self.shell(f"am start -W -n {package}/.MainActivity", timeout=60) + self.shell(f"am start -W -n {package}/{NAMESPACE}.MainActivity", timeout=60) def force_stop(self, package: str = PACKAGE) -> None: self.shell(f"am force-stop {package}", check=False) From 3896f7da29636dc88f22a9b42c8db58444271f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Xu=C3=A2n=20D=C5=A9ng?= Date: Thu, 8 Oct 2026 02:08:52 +0700 Subject: [PATCH 2/2] feat(e2e): capability() takes an optional app_version override Play Console screenshots paired against the fake Mac showed its literal "0.0.0 (e2e)" self-reported version in the phone's device-details screen, which reads as test data on a public listing. Default is unchanged; callers staging real-looking screenshots can now pass app_version explicitly. --- tools/e2e/fake_mac.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/e2e/fake_mac.py b/tools/e2e/fake_mac.py index 92a26fd..09f18e3 100644 --- a/tools/e2e/fake_mac.py +++ b/tools/e2e/fake_mac.py @@ -21,10 +21,13 @@ def capability(platform: str = "macos", *, clipboard: bool = True, sms: bool = True, call: bool = True, - relay: bool = False, notify: bool = True, app_calls: bool = False) -> dict: + relay: bool = False, notify: bool = True, app_calls: bool = False, + app_version: str = "0.0.0 (e2e)") -> dict: """0.7.2 as the Apple app builds it (apple/…/LocalDevice.swift): clipboard, SMS, calls and the relay switch; iPhone and iPad add sms.notify and call.notify. A Mac reports call.app_calls (CALL-05) only when a scenario asks - for it, so the other scenarios never get calls of other apps.""" + for it, so the other scenarios never get calls of other apps. `app_version` defaults to a value that marks the + traffic as the fake client's own in logs and benchmarks; callers staging real-looking screenshots may override + it.""" mobile = platform in ("ios", "ipados") features = { "clipboard": {"enabled": clipboard, "auto_send": True, "max_text_bytes": 1_048_576, @@ -34,7 +37,7 @@ def capability(platform: str = "macos", *, clipboard: bool = True, sms: bool = T **({"app_calls": app_calls and call} if not mobile else {})}, "relay": {"enabled": relay}, } - return {"protocol": 1, "app_version": "0.0.0 (e2e)", "platform": platform, + return {"protocol": 1, "app_version": app_version, "platform": platform, "os_version": "18.0" if mobile else "15.0", "model": CLIENT_MODELS.get(platform, "Mac15,3"), "features": features}