From 1844525e2a8b66a5fdd96630d8af58c0aef8a519 Mon Sep 17 00:00:00 2001 From: dadachi Date: Fri, 22 May 2026 20:14:39 +0900 Subject: [PATCH] fix(validation): pre-grant POST_NOTIFICATIONS before Android Stage 1 capture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Android 13+ (API 33+) pops the POST_NOTIFICATIONS runtime-permission dialog on first launch. It overlays the home screen exactly when Stage 1 grabs its screenshot, so Layer 3's "renders-cleanly" rubric fails on an otherwise-correct build — a false negative that fails the whole run. Insert a best-effort `adb shell pm grant POST_NOTIFICATIONS` between install and launch so the dialog never appears. Best-effort: on older API levels (where it isn't a runtime permission) or apps that don't declare it, `pm grant` errors and we ignore the result, so it never blocks the launch. Verified end-to-end: a real vet-clinic-queue run that was Layer 3 1/2 (Android renders-cleanly FAIL, modal over the welcome screen) now passes Layer 3 2/2 with a clean Android home capture — overall PASS. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/validation/launch.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/validation/launch.ts b/src/validation/launch.ts index 348c1d3..6d74e3e 100644 --- a/src/validation/launch.ts +++ b/src/validation/launch.ts @@ -36,6 +36,7 @@ const DEFAULT_TIMEOUT_MS = 60_000; // iOS: xcrun simctl install booted // xcrun simctl launch booted // Android: adb install +// adb shell pm grant POST_NOTIFICATIONS (best-effort) // adb shell monkey -p -c android.intent.category.LAUNCHER 1 // // `monkey` on Android is used over `am start -n /` because it @@ -102,6 +103,7 @@ async function installAndLaunchAndroid(apkPath: string, packageName: string, tim const targetArgs = targeting.serial !== undefined ? ["-s", targeting.serial] : []; const targetForCmd = targeting.serial !== undefined ? ` -s ${targeting.serial}` : ""; const installCmd = `${adb}${targetForCmd} install -r ${apkPath}`; + const grantCmd = `${adb}${targetForCmd} shell pm grant ${packageName} android.permission.POST_NOTIFICATIONS`; const launchCmd = `${adb}${targetForCmd} shell monkey -p ${packageName} -c android.intent.category.LAUNCHER 1`; const install = await runOnce(adb, [...targetArgs, "install", "-r", apkPath], timeoutMs); @@ -113,6 +115,19 @@ async function installAndLaunchAndroid(apkPath: string, packageName: string, tim ...(install.error !== undefined ? { error: install.error } : {}), }; } + + // Pre-grant the runtime notification permission so Android 13+ (API 33+) + // doesn't pop the POST_NOTIFICATIONS system dialog on first launch. That + // dialog overlays the home screen exactly when Stage 1 captures it, which + // fails Layer 3's "renders-cleanly" rubric. Best-effort: on older API + // levels (where it isn't a runtime permission) or apps that don't declare + // it, `pm grant` errors — we ignore the result so it never blocks launch. + await runOnce( + adb, + [...targetArgs, "shell", "pm", "grant", packageName, "android.permission.POST_NOTIFICATIONS"], + timeoutMs, + ); + const launch = await runOnce( adb, [...targetArgs, "shell", "monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1"], @@ -120,7 +135,7 @@ async function installAndLaunchAndroid(apkPath: string, packageName: string, tim ); return { ok: launch.ok, - command: `${installCmd} && ${launchCmd}`, + command: `${installCmd} && ${grantCmd} && ${launchCmd}`, durationMs: Date.now() - started, ...(launch.ok || launch.error === undefined ? {} : { error: launch.error }), };