Testing guide
A structured walk-through for testers evaluating AppAutoPatch (AAP) 4.x. Each scenario lists a goal, preconditions, numbered steps, and what “pass” looks like. Work through them in order on a clean Mac and you’ll exercise every major surface in the app. If any test produces unexpected behavior, stop, generate a diagnostic bundle, and file an issue (see Reporting an issue).
Before you start
Section titled “Before you start”- macOS 14 (Sonoma) or later. macOS 15 (Sequoia) is the primary development platform.
- Apple Silicon or Intel.
- Admin rights (you’ll be approving a root LaunchDaemon).
- Recommended: a Mac without a previous AAP install. If you’ve run AAP before, see Resetting state between test runs first.
- The signed
.pkgfrom the Download page.
Reporting an issue
Section titled “Reporting an issue”The fastest path to a fix is a diagnostic bundle:
- Open AppAutoPatch.
- Settings → About → Diagnostics → Generate Diagnostic Bundle.
- Drag the resulting
.zipfrom your Desktop into a new issue in App-Auto-Patch/App-Auto-Patch using the Bug report template. The bundle contains logs, the redacted preferences plist, helper state, and the most recent patching receipts. Webhook URLs are redacted before the bundle is written; nothing leaves your Mac without your consent.
1. Installation & first launch
Section titled “1. Installation & first launch”1a. Fresh install via .pkg
Section titled “1a. Fresh install via .pkg”Goal: confirm the installer completes without errors and seeds the expected directory layout.
- Double-click
AppAutoPatch-<version>.pkg. - Click through Continue → Install → authenticate as admin.
- Wait for the “Installation was successful” screen.
Pass: the package finishes without warning, and
/Library/Application Support/AppAutoPatch/exists with ajobs/subfolder.
1b. First-run setup wizard
Section titled “1b. First-run setup wizard”Goal: the guided 3-step setup appears on first launch and walks you through helper approval, Installomator deploy, and initial discovery.
- Open
/Applications/App Auto-Patch.app. - The Status pane should show “Welcome to App Auto-Patch” with three numbered steps.
- Step 1: click Register Helper (or Open Login Items & Extensions if prompted), then approve AppAutoPatch in System Settings.
- Return to AAP — Step 1 should auto-advance to a green check.
- Step 2 (Installomator) auto-progresses while the helper deploys Installomator. This typically takes 10–30 seconds the first time.
- Step 3: click Discover Apps.
- After discovery completes, click Done. Pass: the dashboard appears with discovered apps; subsequent launches go directly to the dashboard, no wizard.
1c. Auto-skip on already-managed Macs
Section titled “1c. Auto-skip on already-managed Macs”Goal: confirm the wizard auto-completes when prerequisites are already in place.
- With the helper approved and Installomator deployed, run
defaults delete xyz.techitout.appAutoPatch FirstRunSetupCompletedin Terminal. - Quit and relaunch AAP. Pass: the dashboard opens directly. No wizard appears.
2. Discovery & manual updates
Section titled “2. Discovery & manual updates”2a. Discover Applications
Section titled “2a. Discover Applications”- From the dashboard, click Discover Applications.
- Wait for the scan to complete. Pass: the list of installed third-party apps populates with Current versions but no Latest versions yet.
2b. Check For Updates
Section titled “2b. Check For Updates”- Click Check For Updates.
- Wait — this typically takes 1–3 minutes depending on the size of your Installomator label set. Pass: each row shows both Current and Latest. Apps needing updates have an orange download icon; up-to-date apps show a green check; apps that failed the check show a red icon.
2c. Update a single app
Section titled “2c. Update a single app”Goal: per-row patching works.
- Pick an app with an available update (orange icon).
- Click the orange download icon.
- Watch the in-progress banner (“Updating labelname (1 of 1)…”). Pass: the app’s Current version updates to match Latest; the icon flips to green. A receipt is written (verify in 8a below).
2d. Update All
Section titled “2d. Update All”- Click Update All in the dashboard header.
- The deferral prompt appears. Click Install Now. Pass: every app with a pending update is patched in sequence.
2e. Updates-available notification
Section titled “2e. Updates-available notification”Goal: confirm the new “App Auto-Patch: N updates available” banner
fires when a Check For Updates run surfaces fresh pending updates,
and is suppressed in the foreground.
Preconditions: notification permission was granted on a previous
launch (any background-patch run is enough), and at least one new app
update is available since the last check.
- Send AAP to the background (
Cmd+H, or focus another app). - Click Check For Updates from the menu bar popover, or relaunch
AAP and let the dashboard’s
onAppearchain trigger one. - Wait for the check to finish.
- Click Check For Updates again with no new updates pending.
- Bring AAP forward (
Cmd+Tab) and run Check For Updates once more. Pass: step 3 produces exactly one banner notification titled “App Auto-Patch: N update(s) available” with body “Open App Auto-Patch to install or defer.” Step 4 produces no notification (count didn’t grow). Step 5 also produces no notification because the dashboard is foreground. Clicking the notification opens App Auto-Patch’s deferral prompt. Disable test: Settings → Advanced → Background Patching → Show a notification when new updates become available → off, Save Settings, then repeat steps 1–3. The toast should not appear.
3. Deferrals & deadlines
Section titled “3. Deferrals & deadlines”Clicking Update All opens the deferral prompt whenever there are pending updates. Two thresholds escalate the flow:
- A soft focus deadline tightens the prompt without forcing.
- A hard deadline force-installs with no prompt. Both can fire by count (deferrals so far) or by days (a date seeded on the first deferral of the cycle).
3a. DeferralWatcher resurrects the prompt automatically
Section titled “3a. DeferralWatcher resurrects the prompt automatically”Goal: confirm DeferralWatcher re-engages the prompt when
NextAutoLaunch elapses, without the user having to click Update All
again.
- From the dashboard, click Update All with at least one app pending.
- In the prompt, pick the shortest defer option available.
- Click Defer.
- Leave AAP open and wait out the deferral. Pass: within a few seconds of the deadline elapsing, the prompt re-appears on its own. Logs include “DeferralWatcher: Armed watchpoint” when the deferral is saved, then “Deadline trigger fired” at that time. If a check or patch is still running, the log says it is retrying in 15s rather than waiting out the one-minute timer.
3b. Hard count deadline forces install
Section titled “3b. Hard count deadline forces install”Goal: confirm DeadlineCountHard triggers a forced install with
no prompt.
defaults write xyz.techitout.appAutoPatch DeadlineCountHard -int 2.- Defer twice via Update All — or fast-forward the counter:
defaults write xyz.techitout.appAutoPatch HardDeferralCount -int 2. - Click Update All. Pass: AAP skips the prompt and patches immediately. Logs include “StatusViewModel: Hard deadline reached — installing immediately.”
3c. Hard day deadline forces install
Section titled “3c. Hard day deadline forces install”Goal: confirm DeadlineDaysHard works end-to-end (date seeded on
first deferral, checked against Date()).
defaults write xyz.techitout.appAutoPatch DeadlineDaysHard -int 1.- Click Update All → Defer once. The prompt’s footer should read “Auto-installs by <date 1 day out>.”
- Fast-forward the persisted date so it’s already in the past:
defaults write xyz.techitout.appAutoPatch HardDeferralDate "$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ)" - Click Update All. Pass: AAP force-installs without showing the prompt. Same hard deadline log line as 3b.
3d. A reached focus limit uses the normal dialog
Section titled “3d. A reached focus limit uses the normal dialog”Goal: once either focus limit is met, reminders continue with the normal dialog. There is no separate “strongly recommended” copy.
defaults write xyz.techitout.appAutoPatch DeadlineCountFocus -int 1.defaults write xyz.techitout.appAutoPatch FocusDeferralCount -int 1.- Click Update All.
Pass: the prompt headline is “Updates Available” and the button
is “Defer”. The timer picker still shows the full
DeferralTimerMenu.HardDeferralCountincreases by 1 if you defer.FocusDeferralCountstays at 1.
3e. Automatic focus deferral
Section titled “3e. Automatic focus deferral”Goal: Focus, Do Not Disturb, or a display-sleep assertion defers silently until a focus limit is reached. A manual Update All still shows the dialog.
defaults write xyz.techitout.appAutoPatch DeadlineCountFocus -int 2.defaults write xyz.techitout.appAutoPatch DeferralTimerFocus -int 1.defaults delete xyz.techitout.appAutoPatch FocusDeferralNotificationsEnabled.- Start
caffeinate -din Terminal, or turn on Focus. - With a pending update, set
NextAutoLauncha few seconds in the past:defaults write xyz.techitout.appAutoPatch NextAutoLaunch "$(date -u -v-1M +%Y-%m-%dT%H:%M:%SZ)"Pass: no dialog and no toast. The menu bar deadline row reads “Focus deferral — checking again in…” with a moon icon. The log says “Automatic focus deferral”.FocusDeferralCountis 1 andHardDeferralCountis unchanged.NextAutoLaunchis about 1 minute ahead. Click Update All whilecaffeinate -dis still running: the normal dialog appears, and deferring it increasesHardDeferralCountonly. Repeat the past-NextAutoLaunchstep once more. The second automatic deferral setsFocusDeferralCountto 2. Do it a third time: the normal dialog and toast appear even though Focus is still active. Toast override:defaults write xyz.techitout.appAutoPatch FocusDeferralNotificationsEnabled -bool trueposts the “Your deferral has ended” toast on an automatic deferral. The dialog stays hidden. Clicking the toast opens the normal dialog. Ignore list:defaults write xyz.techitout.appAutoPatch IgnoreDNDApps -string "caffeinate"makescaffeinate -dno longer count. A real Focus mode still defers.
3f. Aggressive patch-day deferral capping
Section titled “3f. Aggressive patch-day deferral capping”Goal: confirm AggressivePatchDayDeferral shrinks the picker
options as PatchingEndTime approaches, and bypasses the prompt
entirely once the window has passed.
- Enable the feature:
defaults write xyz.techitout.appAutoPatch AggressivePatchDayDeferral -bool true - Trigger a hard deadline (per 3b or 3c).
- Set
PatchingEndTimeto 45 minutes from now (e.g. if it’s 14:00, use14:45):defaults write xyz.techitout.appAutoPatch PatchingEndTime "HH:MM" - Click Update All. Pass (30–60 min remaining bracket): the picker shows only options ≤15 minutes. Click Defer — if all your configured options are larger, a synthesised 5-minute entry appears.
- Now set
PatchingEndTimeto 1 minute from now and click Update All again. Pass (past window): the deferral prompt is bypassed entirely and patching starts immediately — same as a hard-deadline force-install. Clean up:defaults delete xyz.techitout.appAutoPatch AggressivePatchDayDeferral PatchingEndTime
3g. Deferral audit trail
Section titled “3g. Deferral audit trail”Goal: confirm user-initiated deferrals, focus suppressions, and forced installs are recorded on disk and visible in the dashboard.
- With a pending update, click Update All → Defer.
- In Terminal:
ls /Library/Application\ Support/AppAutoPatch/deferral-events/Pass: a JSON file is present. Expand that app’s row in the dashboard — a Deferral Events section appears below the receipt history showing event type (userDeferred), escalation, minutes, and timestamp. After running test 3b or 3c (forced install), a.deadlineForcedevent appears for those labels. After 3e (focus suppression), a.focusSuppressedevent appears. - Generate a diagnostic bundle (Settings → About → Generate
Diagnostic Bundle) and open the zip — confirm a
deferral-events/folder is present alongsidereceipts/.
3h. A successful patch ends the cycle
Section titled “3h. A successful patch ends the cycle”Goal: confirm resetDeferralCounts() runs after a successful
patch and clears every cycle key.
- After any of 3a–3g, run a successful Update All → Install Now (or let the hard deadline force-install).
- After the run completes, in Terminal:
defaults read xyz.techitout.appAutoPatch | grep -E 'FocusDeferralCount|HardDeferralCount|FocusDeferralDate|HardDeferralDate|NextAutoLaunch|JitterOffset'Pass:FocusDeferralCountandHardDeferralCountare0. None ofFocusDeferralDate,HardDeferralDate,NextAutoLaunch, orBackgroundPatchStartupJitterOffsetMinutesappear (all cleared byresetDeferralCounts()). The next round of pending updates starts a fresh deferral cycle with a new random jitter offset.
4. Background patching
Section titled “4. Background patching”AAP runs in one of two scheduler modes, controlled by the
BackgroundPatchClosedAppsScheduler preference:
app(default) — in-process timer in the GUI app.helper— runs inside the persistent root LaunchDaemon.
4a. Update staging
Section titled “4a. Update staging”Goal: detected updates are downloaded before a closed-app patch, and the install reuses that file.
- Leave
WorkflowStageUpdatesat its default (true), or confirm Settings → Advanced → Background Patching → Download installers before installing is on. - Make sure at least one closed app has a pending update.
- Let a background pass run (in-app or helper daemon).
Pass: when a closed app is pending, the log shows
Patching closed apps before the open-apps notificationand the “open apps need updating” notification arrives only afterClosed-app pass finished; delivering the open-apps notification. If every pending app was closed, that notification is not posted. While the pass runs, the dashboard banner reads “Background patching in progress…” and does not revert to “Patching in progress…” or “Updating <app>…”. The log also showsStaging N update(s) before the open-apps notificationwhen an open app still needs a toast, and that toast arrives only afterStaging complete. The log also showsStaging N detected update(s) before the closed-app pass(orStaging N update(s) before installing) before Installomator runs, thenUsing staged installer. A successful install logsRemoved staged installerfor that label only. Updating one app from its row leaves other labels’ files in/private/tmp/AAPStage. SetWorkflowStageUpdatestofalseand confirm the next pass does not stage and the open-apps notification is not delayed.
4b. In-app scheduler smoke test
Section titled “4b. In-app scheduler smoke test”- Settings → Advanced → Background Patching.
- Confirm Enabled is on, Scheduler is In-app, and the interval is 1 hour (the test minimum).
- Make sure at least one closed app on the dashboard has a pending update.
- Quit any running instance of that app and leave AAP running.
- Wait one hour (or set the interval lower for testing — the floor the scheduler enforces is 1 hour, so plan around that). Pass: the app gets patched silently; a notification appears titled “AppAutoPatch updated apps”.
4c. Helper-daemon scheduler smoke test
Section titled “4c. Helper-daemon scheduler smoke test”Goal: background passes survive a full quit of the GUI app.
- Settings → Advanced → Background Patching → Scheduler → Helper daemon.
- Click Save Settings.
- Quit AAP entirely (menu bar popover → Quit AppAutoPatch).
- Wait out the configured interval.
Pass: when you next launch AAP, you should see logs in the in-app
Logs pane confirming a
HelperBackgroundPatchSchedulerpass ran. If you re-enabled the GUI’s helper observer (the default), a single completion toast appears within 60 seconds of relaunch.
4d. AC-power gate
Section titled “4d. AC-power gate”- Confirm
BackgroundPatchClosedAppsRequireOnACis true (the default). - Disconnect AC, close an app with a pending update, and refresh AAP.
- Refresh again without changing the pending count. Pass: the log shows “On battery and require-AC is enabled; skipping run.” No patches happen until you reconnect AC. Exactly one persistent App updates are waiting notification asks the user to connect power; step 3 does not duplicate it. Reconnect AC and confirm that the notification clears and patching resumes.
4e. Notification routing
Section titled “4e. Notification routing”- Install the example managed notification profile and grant notification access to both App Auto-Patch identities.
- Trigger a background-patch start/completion and an updates-available or AC-power-required notification.
- Set
TemporaryNotificationTypesto["updatesAvailable"]andPersistentNotificationTypesto["backgroundPatchCompleted"], then repeat. Pass: informational background notifications are temporary banners by default and action-required notifications are persistent alerts. Step 3 reverses those two selected routes. If the same type appears in both arrays, it uses the persistent identity.
4f. Allow / block list
Section titled “4f. Allow / block list”- Add a single label (e.g.
firefox) to the Block list in Background Patching settings. - Force a tick (set interval to 1 hour and wait, or restart AAP). Pass: Firefox is skipped even if it’s eligible. Other apps still patch.
4g. Captive portal guard
Section titled “4g. Captive portal guard”Goal: confirm the scheduler skips network phases when internet is unavailable or behind a captive portal.
- Block
captive.apple.comat the hosts level:sudo sh -c 'echo "0.0.0.0 captive.apple.com" >> /etc/hosts' - Force a background-scheduler tick.
Pass: log includes “No internet / captive portal detected —
skipping network phases this tick.” No update-check or label-refresh
activity is logged. Remove the hosts entry when done:
sudo sed -i '' '/captive.apple.com/d' /etc/hosts
5. Self-update
Section titled “5. Self-update”Prerequisite: an appautopatch Installomator label exists in your
deployed Installomator instance, OR you set SelfUpdateLabel to a
label you control.
- Settings → Advanced → Self-Update → set frequency to Daily and enable.
- Restart AAP.
- Wait roughly 5 minutes (the scheduler’s startup grace period).
Pass (interactive mode): a dashboard banner appears titled
“AppAutoPatch update available” with Update Now and Later
buttons. Clicking Update Now triggers the same XPC patch path used
by every other app; AAP quits and is replaced by the new version.
Pass (silent mode,
InteractiveMode == 0): AAP installs the new version automatically without surfacing a banner.
6. Menu bar & window lifecycle
Section titled “6. Menu bar & window lifecycle”6a. Menu bar persistence
Section titled “6a. Menu bar persistence”- Open AAP, then close the dashboard window (red close button or
Cmd+W).
Pass: the dashboard closes; the menu bar item stays visible. The
Dock icon disappears (app drops to
.accessory).
6b. Cmd+Q behavior
Section titled “6b. Cmd+Q behavior”- With AAP open, press Cmd+Q. Pass: the dashboard window closes; the menu bar item remains. The process keeps running. The only way to fully terminate is the menu bar popover’s Quit AppAutoPatch button.
6c. Reopen from menu bar
Section titled “6c. Reopen from menu bar”- Click the menu bar item.
- Click Open Dashboard. Pass: the dashboard window reappears with the Dock icon back.
7. Settings & MDM behavior
Section titled “7. Settings & MDM behavior”7a. Local Settings persist
Section titled “7a. Local Settings persist”- Change any preference (e.g. add a label to Ignored Labels).
- Click Save Settings.
- Quit AAP completely (menu bar → Quit) and relaunch. Pass: your change is still there.
7b. Managed preferences override
Section titled “7b. Managed preferences override”Goal: confirm that an MDM-delivered key is honored and the corresponding Settings field is disabled.
- Deploy a configuration profile setting
IgnoredLabelsto a known list, using the manifest inProfile Manifests/. - Open AAP → Settings → Advanced. Pass: the Ignored Labels field shows the managed value and is disabled. A “Provided by your organization through managed configuration” caption appears on each managed section.
7c. 3.5.0 profile carryover
Section titled “7c. 3.5.0 profile carryover”- Apply an existing AAP 3.5.0 configuration profile that uses the
IgnoredLabelskey (and any others —RequiredLabels,OptionalLabels,InteractiveMode, etc.). - Open AAP. Pass: every key is honored. No edits to the profile were needed.
8. Receipt history
Section titled “8. Receipt history”8a. Per-app history
Section titled “8a. Per-app history”Prerequisite: at least one patch run has completed (test 2c satisfies this).
- On the dashboard, click the chevron on the right of any patched app’s row. Pass: a Recent patches panel appears showing up to 5 receipts with timestamp, version, and status icon. Failed runs show their Installomator exit code in red.
8b. Empty history
Section titled “8b. Empty history”- Expand the row of an app you’ve never patched. Pass: the panel reads “No patches recorded for this label yet.”
9. Diagnostic bundle
Section titled “9. Diagnostic bundle”- Settings → About → Diagnostics → Generate Diagnostic Bundle.
- After ~5 seconds, click Reveal in Finder.
- Open the resulting zip. Pass: the zip contains
metadata.txt(app version, helper status, cdhash summary)prefs/user.plistand (if applicable)prefs/managed.plist— open them and confirmWebhookURLSlack/WebhookURLTeamsare<redacted>, andDiscoveredAppsDatais absent.logs/aap.logand (if rotated)logs-archive/helper-state.json(if helper-daemon mode has run at least once)receipts/(if any patching has happened)deferral-events/(if any deferrals, focus suppressions, or forced installs have occurred)
10. Helper recovery
Section titled “10. Helper recovery”10a. Manual Reset Helper
Section titled “10a. Manual Reset Helper”- Settings → About → Reset Helper → Reset. Pass: status flips to Re-registered. Approve in System Settings… or Helper re-registered and is running. depending on whether macOS needs re-approval.
10b. Auto-reset on upgrade (cdhash mismatch)
Section titled “10b. Auto-reset on upgrade (cdhash mismatch)”- Install AAP version A; let it register the helper.
- Install AAP version B (different build, hence different cdhash) on top.
- Launch AAP. Pass: the dashboard briefly shows “Refreshing root helper…” and auto-runs the reset. The status banner clears once the helper is re-bound. Logs include “Helper binary cdhash changed since last registration”.
10c. Operationally-broken auto-reset
Section titled “10c. Operationally-broken auto-reset”- Manually break the helper (e.g. revoke its approval in System Settings → Login Items & Extensions).
- Quit and relaunch AAP. Pass: AAP detects the helper isn’t operational and silently attempts a reset, capped at one attempt per session and once per hour.
Resetting state between test runs
Section titled “Resetting state between test runs”If you want to start fresh, run the following:
# Stop and unregister the helpersudo launchctl bootout system/xyz.techitout.appAutoPatch.roothelper 2>/dev/nullsudo rm -f /Library/LaunchDaemons/xyz.techitout.appAutoPatch.roothelper.plist# Wipe app statesudo rm -rf "/Library/Application Support/AppAutoPatch"# Wipe local prefsdefaults delete xyz.techitout.appAutoPatch 2>/dev/null# Optionally remove the .app (handles both new and pre-rename paths)sudo rm -rf "/Applications/App Auto-Patch.app" "/Applications/AppAutoPatch.app"Then re-install the .pkg from scratch and start at test 1a again.
Section titled “Then re-install the .pkg from scratch and start at test 1a again.”What we’re hoping you’ll catch
Section titled “What we’re hoping you’ll catch”- Behaviors that surprise you in any direction. “It worked, but I didn’t expect that” is just as useful as a hard failure.
- Wording on banners / notifications that’s confusing or misleading.
- Edge cases the matrix above doesn’t cover, especially:
- Multi-user Macs (Fast User Switching, multiple console users)
- Locked-screen / lid-closed behavior of the background scheduler
- Recovery after a forced power-off mid-patch
- Macs with restrictive endpoint security tools (CrowdStrike, SentinelOne, Sophos, etc.) that may interfere with the helper
- Anything an MDM-managed Mac does differently from an unmanaged one. Thank you for testing!
