Build automations that handle interruptions
A useful automation handles more than the one screen sequence seen during development. Loading indicators, permission prompts, onboarding, network errors, login changes and different languages can change the path.
Design each step as check current state → handle a known interruption → act once → verify the next state. No script can guarantee that every future alert, app update or network failure will be handled. Unexpected states should produce a clear failure, not endless taps.
Decide what can be handled automatically
| Situation | Response |
|---|---|
| Known optional welcome banner | Match its specific container and dismiss control, then verify disappearance |
| Loading indicator | Wait for disappearance with a deadline |
| Known transient network error | Retry only a read or otherwise safe operation, with a small attempt limit |
| Screen already shows the requested outcome | Report success without repeating the action |
| Login, MFA, CAPTCHA, payment or a permission choice not supplied by the user | Pause/stop with useful instructions; do not guess a choice |
| Unknown modal, stale selector or app mismatch | Capture evidence and stop or reinspect |
Never search for a generic “OK”, “Continue” or “Allow” across the entire screen and tap it blindly. Scope the button to the specific observed dialog. System-owned prompts may not belong to the app’s UIHelper tree; inspect their actual owner and screenshot before choosing a handling method.
A bounded helper for known dialogs
Save this as helpers/waitReady.js. It is a script helper, not a built-in API. Its callbacks run outside UIHelper.waitFor(), whose callback must remain read-only.
exports.waitReady = function (ui, target, handlers, timeoutMs = 8000) {
const deadline = Date.now() + timeoutMs;
let recoveries = 0;
while (Date.now() < deadline) {
let handled = false;
for (const handler of handlers) {
const dialog = ui.findElement(handler.when, {timeout: 0});
if (!dialog) continue;
if (++recoveries > 3) throw new Error("Interruption keeps returning");
console.log("Handling:", handler.name);
handler.resolve(dialog);
ui.waitForGone(handler.when, Math.max(0, Math.min(1500, deadline - Date.now())));
handled = true;
break;
}
if (handled) continue;
const element = ui.waitForElement(target, {
timeout: Math.max(0, Math.min(250, deadline - Date.now())),
interval: 100,
throwOnTimeout: false
});
if (element) return element;
}
throw new Error("Expected screen did not become ready: " + JSON.stringify(target));
};The deadline bounds polling; an individual native call may take longer. All errors propagate, including cancellation. The three-recovery cap prevents a recurring dialog from creating an endless loop.
Use selectors you actually inspected
This template needs real identifiers from your target app. It does not claim to work against a particular app without inspection.
const {waitReady} = require("./helpers/waitReady");
const ui = UIHelper.forApp("com.example.app");
const handlers = [{
name: "Optional welcome dialog",
when: {id: "welcomeDialog", visible: true},
resolve(dialog) {
dialog.waitForElement({id: "dismissWelcome", visible: true, enabled: true}, 1000).tap();
}
}];
const next = waitReady(ui,
{id: "continueButton", visible: true, enabled: true}, handlers);
next.tap();
waitReady(ui, {id: "resultScreen", visible: true}, handlers);Check interruption handlers before each meaningful action and while waiting for transitions. A popup can appear between a readiness check and a tap. If the next state does not appear, inspect what happened: do not wrap the whole action in an unconditional retry. The tap may already have succeeded, or it may have been intercepted.
Make retries intentional
Separate observation retries from action retries. Retrying a query is usually safe. Repeating “Follow”, “Send”, “Buy” or a toggle may duplicate or reverse an action. Inspect the final state first. If the result is uncertain, fail with the step name, run ID and relevant evidence.
For a request such as “open this profile and follow it”, verify the exact profile identity, check whether it is already followed, perform the authorized action once, and wait for the resulting state. Add only interruption handlers whose selectors and meaning you verified. Do not claim this template provides an app-specific integration.
Test the alternate paths
Test with and without each known dialog, an already-complete state, delayed loading, a missing target and cancellation. Repeat from the documented starting screen. Where practical, try the device orientations and languages you intend to support. Record which scenarios were actually tested; untested handlers remain unverified.
Prefer targeted queries over continuous full screenshots. Use captures when diagnosing ambiguity, fresh references after navigation, and finite waits throughout.