Repository navigation
feat(apps): add standalone uninstall command - #3720
Conversation
Expose `uninstall <app>` so a test can start without the app installed, for example before a fresh install from a build or a store. Android reuses the adb uninstall helper reinstall already owns; iOS simulators use `simctl uninstall` and CoreDevice devices use `devicectl device uninstall app`, through the helper reinstall already uses. The app resolves against installed apps: a package name or bundle id that is not installed succeeds with `wasInstalled: false`, and another name that matches nothing fails with APP_NOT_INSTALLED. Other owners report the operation unavailable.
A stopped AVD is inventoried by its AVD name; resolve and remove the package through the live device ensureAndroidReady returns, and keep the handler harness's uninstall fact to the kinds the platform owners admit.
There was a problem hiding this comment.
1 issue found across 48 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/platform-apple/src/deployment/runtime.ts">
<violation number="1" location="packages/platform-apple/src/deployment/runtime.ts:146">
P2: This condition treats every dotted target as a bundle ID. An unmatched app name containing a period therefore succeeds with `wasInstalled: false`, although only unmatched bundle IDs should be idempotent; distinguish bundle-ID targets from display-name targets before converting `APP_NOT_INSTALLED`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| }); | ||
| } catch (error) { | ||
| const notInstalled = error instanceof AppError && error.code === 'APP_NOT_INSTALLED'; | ||
| if (notInstalled && input.app.trim().includes('.')) return { wasInstalled: false }; |
There was a problem hiding this comment.
P2: This condition treats every dotted target as a bundle ID. An unmatched app name containing a period therefore succeeds with wasInstalled: false, although only unmatched bundle IDs should be idempotent; distinguish bundle-ID targets from display-name targets before converting APP_NOT_INSTALLED.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/platform-apple/src/deployment/runtime.ts, line 146:
<comment>This condition treats every dotted target as a bundle ID. An unmatched app name containing a period therefore succeeds with `wasInstalled: false`, although only unmatched bundle IDs should be idempotent; distinguish bundle-ID targets from display-name targets before converting `APP_NOT_INSTALLED`.</comment>
<file context>
@@ -122,6 +128,29 @@ async function deployAppleApp(
+ });
+ } catch (error) {
+ const notInstalled = error instanceof AppError && error.code === 'APP_NOT_INSTALLED';
+ if (notInstalled && input.app.trim().includes('.')) return { wasInstalled: false };
+ throw error;
+ }
</file context>
There was a problem hiding this comment.
Not changing this one here. When nothing installed matches, the inventory can't tell whether Booking.co was meant as a bundle id or a display name. A bundle-id regex doesn't help either, since Booking.com has the same shape as a bundle id. I'd rather keep the simple dotted rule and document it. It's listed under known gaps in the PR description.
A cold AVD boots under a different adb serial, so the cache invalidation now wraps the device that readiness returns. The uninstall handler test covers iOS simulator and CoreDevice removals, checks the runtime input, and asserts the typed refusal reason. Docs list uninstall among the commands macOS does not support, and the changelog names client.apps.uninstall.
… feat/uninstall-command # Conflicts: # CHANGELOG.md
|
The uninstall code looks right at a918aa6, but the new device routes have no live proof yet. The PR adds adb Not blocking, and you can take or leave these: the CHANGELOG says uninstall works "on Android", but Appium-backed Android and Limrun Android refuse it, so "adb-backed Android" (as the mobile-data entry says) is more accurate, and the Apple id-versus-name check at runtime.ts:146 re-derives the dotted rule that app-resolution.ts already owns, so it could key on a typed detail such as details.targetKind on APP_NOT_INSTALLED, shared with Android. On the open inline threads, the cubic-dev-ai P2 on the dotted-name limitation (#3720 (comment)) still applies, but I read it as a documented limitation, not a merge stopper, since the resolver cannot tell the two apart either and commands.md and the PR body state the gap. I did not run tests or gates. I judged this from reading the diff only. CI is still pending: Smoke Tests is in progress. This diff touches what the help and command-surface smoke checks enumerate, so please check that result when it finishes. The live uninstall runs above must land before merge. |
|
Live run on an Android emulator (ADB), from 1. Installed package id removes it $ agent-device uninstall com.callstack.uninstallproof --platform android --serial emulator-5554 --json
{"success":true,"data":{"platform":"android","app":"com.callstack.uninstallproof","wasInstalled":true,"appId":"com.callstack.uninstallproof","message":"Uninstalled: com.callstack.uninstallproof"}}Before the uninstall, 2. Same command again $ agent-device uninstall com.callstack.uninstallproof --platform android --serial emulator-5554 --json
{"success":true,"data":{"platform":"android","app":"com.callstack.uninstallproof","wasInstalled":false,"message":"Not installed: com.callstack.uninstallproof"}}3. Display name resolves and removes it $ agent-device uninstall Uninstallproof --platform android --serial emulator-5554 --json
{"success":true,"data":{"platform":"android","app":"Uninstallproof","wasInstalled":true,"appId":"com.callstack.uninstallproof","message":"Uninstalled: com.callstack.uninstallproof"}}The app is gone from 4. Unmatched name without a dot $ agent-device uninstall NoSuchAppXyz --platform android --serial emulator-5554 --json
{"success":false,"error":{"code":"APP_NOT_INSTALLED","message":"No package found matching \"NoSuchAppXyz\"","hint":"Run agent-device apps --platform android to discover the installed package name, then retry open with that exact package.",...}}Exit code 1. The hint still says "retry open" because uninstall shares the Android resolver's hint. The emulator was already booted, so this run doesn't cover readiness on a cold emulator. iOS simulator: not run yet. I'm stuck on an Xcode update on this machine. I'll post the same four checks for an iOS simulator as soon as that's fixed. I don't have a CoreDevice iPhone to test on right now. |
|
Live run on an iOS simulator, from 0. Cold simulator Before the command, $ agent-device uninstall com.callstack.uninstallproof --platform ios --udid B927EFE0-F0E9-486E-91A4-465238341FCC --json
{"success":true,"data":{"platform":"ios","app":"com.callstack.uninstallproof","wasInstalled":false,"message":"Not installed: com.callstack.uninstallproof"}}Afterwards it showed 1. Installed bundle id removes it $ agent-device uninstall com.callstack.uninstallproof --platform ios --udid B927EFE0-F0E9-486E-91A4-465238341FCC --json
{"success":true,"data":{"platform":"ios","app":"com.callstack.uninstallproof","wasInstalled":true,"appId":"com.callstack.uninstallproof","message":"Uninstalled: com.callstack.uninstallproof"}}Before the uninstall, 2. Same command again $ agent-device uninstall com.callstack.uninstallproof --platform ios --udid B927EFE0-F0E9-486E-91A4-465238341FCC --json
{"success":true,"data":{"platform":"ios","app":"com.callstack.uninstallproof","wasInstalled":false,"message":"Not installed: com.callstack.uninstallproof"}}3. Display name resolves and removes it $ agent-device uninstall "Uninstall Proof" --platform ios --udid B927EFE0-F0E9-486E-91A4-465238341FCC --json
{"success":true,"data":{"platform":"ios","app":"Uninstall Proof","wasInstalled":true,"appId":"com.callstack.uninstallproof","message":"Uninstalled: com.callstack.uninstallproof"}}The app is gone from 4. Unmatched name without a dot $ agent-device uninstall NoSuchAppXyz --platform ios --udid B927EFE0-F0E9-486E-91A4-465238341FCC --json
{"success":false,"error":{"code":"APP_NOT_INSTALLED","message":"No app found matching \"NoSuchAppXyz\"","hint":"Run apps to discover the exact installed package or bundle id, or install the app before open.",...}}Exit code 1. simctl on a bundle that's already gone: I still don't have a CoreDevice run. |
|
Thanks for the live runs, @mikolajadamowicz. They cover what I asked for at a918aa6 on both an ADB Android emulator and a cold iOS simulator: an installed id is removed and gone from Not blocking: the Android hint for an unmatched name still says "retry open", because uninstall shares the resolver hint. |
Appium and Limrun Android sessions refuse uninstall, so the entry now names the backends it covers, the way the mobile-data entry does.
#3720 added the uninstall row with `linux.gap(...)` after #3717 had removed the Linux known-gap builder, so main fails to load the coverage manifest (`TypeError: linux.gap is not a function`). Uninstall belongs to the app-deployment family the Linux runtime leaves unavailable, so it is an accepted limitation like install, reinstall and install-from-source. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Adds
uninstall <app>, so a test can start with the app removed, for example before installing a fresh build or the store version.Android uses the existing
uninstallAndroidPackage. iOS simulators and CoreDevice devices useuninstallAppleApp, the helperreinstallalready calls (simctl uninstallanddevicectl device uninstall app). Other platforms report the operation as unavailable.reinstalldoesn't change.The app name resolves against installed apps. A package name or bundle id that isn't installed returns
wasInstalled: false. Any other name that matches nothing fails withAPP_NOT_INSTALLED.48 files, about 590 lines. Roughly 120 are behavior. The rest is the registry declarations a new command needs, coverage pins and tests.
Known gaps I left alone:
Validation
Gates ran on
36c92021fwithpnpm gate <id>.dd532cb3fadds the Android readiness fix and its regression test, and lint, typecheck, platform and daemon tests pass on it.smoke-daemon-cleanfailures. All of them fail on base too, in a root-user container.