From 921ecb910eb1b03053e7e567ef8a36368cd44b37 Mon Sep 17 00:00:00 2001 From: PJ Date: Wed, 10 Jun 2026 15:20:41 +0530 Subject: [PATCH] feat(cli): add --android-app-path for clear-state reinstall Wires the APK path from the test command through to the sidecar client so Android clear-state can reset apps on OEM builds that deny pm clear. --- cmd/sanderling/main.go | 14 ++++++++------ cmd/sanderling/test_run.go | 23 ++++++++++++----------- internal/testrun/driver.go | 1 + internal/testrun/testrun.go | 23 ++++++++++++----------- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/cmd/sanderling/main.go b/cmd/sanderling/main.go index 92b4a17..ac59eec 100644 --- a/cmd/sanderling/main.go +++ b/cmd/sanderling/main.go @@ -23,12 +23,13 @@ type testOptions struct { platform string avd string device string - iosDevice string - iosAppPath string - duration time.Duration - seed int64 - output string - clearData bool + iosDevice string + iosAppPath string + androidAppPath string + duration time.Duration + seed int64 + output string + clearData bool } const topUsage = `sanderling is a property-based UI fuzzer for mobile apps. @@ -56,6 +57,7 @@ func parseTestArgs(args []string, stderr io.Writer) (testOptions, error) { flagSet.StringVar(&options.device, "device", "", "Android device serial (from `adb devices`) to target when several are connected") flagSet.StringVar(&options.iosDevice, "ios-device", "", "iOS target: a simulator name/UDID to boot, or a connected device's name, UDID, or CoreDevice id") flagSet.StringVar(&options.iosAppPath, "ios-app-path", "", "path to the .app bundle for iOS clear-state reinstall (simulator: simctl; device: devicectl)") + flagSet.StringVar(&options.androidAppPath, "android-app-path", "", "path to the .apk for Android clear-state reinstall; required to reset apps on OEM builds that deny `pm clear`") flagSet.DurationVar(&options.duration, "duration", 5*time.Minute, "total test duration") flagSet.Int64Var(&options.seed, "seed", 0, "RNG seed (0 = random)") flagSet.StringVar(&options.output, "output", "./runs", "output directory for traces") diff --git a/cmd/sanderling/test_run.go b/cmd/sanderling/test_run.go index f8eb7f1..b1c4764 100644 --- a/cmd/sanderling/test_run.go +++ b/cmd/sanderling/test_run.go @@ -9,16 +9,17 @@ import ( func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer) error { return testrun.Execute(ctx, testrun.Options{ - Spec: options.spec, - BundleID: options.bundleID, - Platform: options.platform, - AVD: options.avd, - Device: options.device, - IosDevice: options.iosDevice, - IosAppPath: options.iosAppPath, - Duration: options.duration, - Seed: options.seed, - Output: options.output, - ClearData: options.clearData, + Spec: options.spec, + BundleID: options.bundleID, + Platform: options.platform, + AVD: options.avd, + Device: options.device, + IosDevice: options.iosDevice, + IosAppPath: options.iosAppPath, + AndroidAppPath: options.androidAppPath, + Duration: options.duration, + Seed: options.seed, + Output: options.output, + ClearData: options.clearData, }, stdout) } diff --git a/internal/testrun/driver.go b/internal/testrun/driver.go index cfb6f07..a46a857 100644 --- a/internal/testrun/driver.go +++ b/internal/testrun/driver.go @@ -170,6 +170,7 @@ func buildDriver(ctx context.Context, options Options, stdout io.Writer) (driver return nil, nil, fmt.Errorf("dial sidecar: %w", err) } driverClient.SetPlatform(options.Platform) + driverClient.SetClearStateReinstall(options.Device, options.AndroidAppPath, stdout) // WaitForHealth confirms the gRPC sidecar is up. For iOS, the WDA warmup // (absorbing the XCUITest startup race) runs inside IosDriverBackend.init // in the sidecar - no additional sleep needed here. diff --git a/internal/testrun/testrun.go b/internal/testrun/testrun.go index 9133ee8..f7db213 100644 --- a/internal/testrun/testrun.go +++ b/internal/testrun/testrun.go @@ -22,17 +22,18 @@ const sidecarStartupTimeout = 30 * time.Second // Options are the parameters for a single test pipeline run. type Options struct { - Spec string - BundleID string - Platform string - AVD string - Device string - IosDevice string - IosAppPath string - Duration time.Duration - Seed int64 - Output string - ClearData bool + Spec string + BundleID string + Platform string + AVD string + Device string + IosDevice string + IosAppPath string + AndroidAppPath string + Duration time.Duration + Seed int64 + Output string + ClearData bool // iosUDID, iosIsSimulator, and iosCoreDeviceID are filled by Execute after // resolving the iOS target, then read by buildDriver to choose the simulator