ArgusJS Examples and Recipes

ArgusJS Examples and Recipes - Part 1

This part covers basic script structure, messages, reports, clicks, gestures, swipes, scrolling, and simple reusable helpers.

These examples are written for copying and adapting. Replace package names, image names, text labels, and coordinates with the values that match your own project.


Basic script structure

Use this as a simple starting point for most scripts.

setReferenceResolution(1080, 1920);

clearReport();
reportStep("Script started");

toast("Running script...");
log("Script started");

// Put your automation steps here.

reportStep("Script finished");
setSuccessMessage("Script completed successfully.");


Show a short message

Use toast(...) for quick user feedback.

toast("Hello from ArgusJS");


Show a longer message

toastLong("This message stays visible longer.");


Write to the log

Use log(...) while testing and debugging.

log("Script started");
log("Checking screen...");
log("Step 1 complete");


Log numbers, booleans, and objects

log(123);
log(true);
log({ step: "login", ok: true });


Add a report step

Use reportStep(...) for important progress events.

reportStep("Opened the app");


Add report details

reportInfo("Button found", {
    file: "start_button.png",
    similarity: 0.91
});


Report an error

reportError("Start button was not found");


Stop a script intentionally

Use scriptExit(...) when the script should stop cleanly.

var allowed = false;

if (!allowed) {
    scriptExit("Required condition was not met.");
}

log("This line will not run.");


Wait safely

sleep(...) uses milliseconds.

log("Waiting...");
sleep(1000);
log("One second passed.");


Wait after a screen transition

A short wait helps after clicks, app changes, popups, or overlays.

click(500, 1200);
sleep(500);

var next = waitText("Continue", 5);

if (next) {
    click(next);
}


Click a screen position

Coordinates are absolute screen pixels.

click(500, 1200);
sleep(300);


Check if a click was dispatched

var ok = click(500, 1200);

if (!ok) {
    reportError("Click failed. Check Accessibility permission.");
}


Click with a location object

var point = createLocation(500, 1200);
click(point);


Click above a point

var point = createLocation(500, 1200);
click(point.above(80));


Click below a point

var point = createLocation(500, 1200);
click(point.below(80));


Click left of a point

var point = createLocation(500, 1200);
click(point.left(50));


Click right of a point

var point = createLocation(500, 1200);
click(point.right(50));


Create a region

A region is a rectangle: x, y, w, h.

var area = createRegion(100, 500, 800, 300);


Click a region

A region click targets the center of the rectangle.

var buttonArea = createRegion(300, 1000, 500, 160);
click(buttonArea);


Click a region using the region method

var buttonArea = createRegion(300, 1000, 500, 160);

if (!buttonArea.click()) {
    reportError("Region click failed");
}


Click the center of a region manually

var area = createRegion(300, 1000, 500, 160);
var center = area.getCenter();

click(center);


Click with random offset

This is useful when you do not want to tap the exact same pixel every time.

var button = createLocation(540, 1400);
click(button, 6);


Click an image match with random offset

var start = waitFor("start_button.png", 5);

if (start) {
    click(start, 4);
}


Double click a point

doubleClick(createLocation(540, 1200), 120);


Double click a found image

var item = waitFor("item.png", 5);

if (item) {
    doubleClick(item, 150);
}


Long click a point

longClick(createLocation(540, 1200), 800);


Long click a region

var card = createRegion(100, 500, 880, 300);
card.longClick(1000);


Press and hold

longPress(createLocation(540, 1200));
sleep(1500);
stopLongPress();


Swipe up

swipe(540, 1600, 540, 500, 600);


Swipe down

swipe(540, 500, 540, 1600, 600);


Swipe left

swipe(900, 1000, 150, 1000, 600);


Swipe right

swipe(150, 1000, 900, 1000, 600);


Swipe using locations

var from = createLocation(540, 1600);
var to = createLocation(540, 500);

swipe(from, to, 600);


Human-like swipe

Use humanSwipe(...) when you want a more natural-looking movement.

var from = createLocation(540, 1600);
var to = createLocation(540, 500);

humanSwipe(from, to, 800, 12);


Scroll inside a region

var list = createRegion(0, 400, getScreenWidth(), 1200);

scroll(list, "up", 700, 500);


Scroll down inside a region

var list = createRegion(0, 400, getScreenWidth(), 1200);

scroll(list, "down", 700, 500);


Drag and drop between two points

var from = createLocation(300, 1200);
var to = createLocation(800, 1200);

dragDrop(from, to);


Drag one region to another

var source = createRegion(100, 700, 250, 250);
var target = createRegion(700, 700, 250, 250);

dragDrop(source, target);


Adjust drag timing

setDragTiming(400, 800, 200);
setDragStepCount(20);

dragDrop(
    createLocation(300, 1200),
    createLocation(800, 1200)
);


Continue clicking a target

Click a target multiple times.

continueClick(createLocation(540, 1200), {
    times: 10,
    interval: 100,
    randomize: 4
});


Continue clicking an image

continueClick("attack.png", {
    times: 20,
    interval: 80,
    randomize: 3
});


Continue clicking for a duration

continueClick(createLocation(540, 1200), {
    duration: 5000,
    interval: 120,
    randomize: 5
});


Press Back

pressBack();


Press Home

pressHome();


Press Recents

pressRecents();


Open notifications

openNotifications();
sleep(500);


Hide keyboard

hideKeyboard();


Wake the device

wakeup();


Lock the screen

lockScreen();


Use a global action

globalAction(1);


Get screen size

var width = getScreenWidth();
var height = getScreenHeight();

log("Screen: " + width + "x" + height);


Get app usable screen area

var usable = getAppUsableScreenSize();

if (usable) {
    highlight(usable, "Usable area");
}


Create common screen regions

var width = getScreenWidth();
var height = getScreenHeight();

var top = createRegion(0, 0, width, Math.floor(height * 0.25));

var middle = createRegion(
    0,
    Math.floor(height * 0.25),
    width,
    Math.floor(height * 0.5)
);

var bottom = createRegion(
    0,
    Math.floor(height * 0.75),
    width,
    Math.floor(height * 0.25)
);


Create a bottom area helper

function bottomRegion(percent) {
    var h = getScreenHeight();
    var w = getScreenWidth();
    var regionHeight = Math.floor(h * percent);

    return createRegion(0, h - regionHeight, w, regionHeight);
}

var bottom = bottomRegion(0.30);
highlight(bottom, "Bottom area");


Click a bottom button by text

var bottom = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var button = text("Continue")
    .contains()
    .in(bottom)
    .wait(5);

if (button) {
    click(button);
}


Highlight a region

var area = createRegion(100, 500, 800, 300);

highlight(area, "Target area");


Timed highlight

var area = createRegion(100, 500, 800, 300);

highlight(area, 2);


Timed highlight with text

var area = createRegion(100, 500, 800, 300);

highlight(area, {
    text: "Checking here",
    duration: 2
});


Non-blocking highlight

var area = createRegion(100, 500, 800, 300);

var handle = highlight(area, {
    text: "Loading",
    duration: 5,
    wait: false,
    key: "loading-area"
});

sleep(1000);
highlightUpdate(handle, "Still loading...");

sleep(1000);
highlightOff(handle);


Persistent highlight with key

var area = createRegion(100, 500, 800, 300);

var handle = highlight(area, {
    text: "Watching this area",
    key: "watch-area"
});

sleep(2000);

highlightUpdate("watch-area", "Updated label");
sleep(2000);

highlightOff("watch-area");


Remove one highlight

var area = createRegion(100, 500, 800, 300);
var handle = highlight(area, "Temporary highlight");

sleep(1000);

highlightOff(handle);


Remove all highlights

highlightAllOff();


Basic app launch script

clearReport();

startApp("com.example.app");
sleep(1000);

reportStep("App launch attempted");


Open an app and wait

clearReport();

startApp("com.example.app");

if (waitApp("com.example.app", 5)) {
    reportStep("App is foreground");
} else {
    reportError("App did not open");
}


Check the foreground app

var foreground = getForegroundApp();

log("Foreground app: " + foreground);

if (foreground !== "com.example.app") {
    reportError("Unexpected foreground app");
}


Check if an app is installed

if (isAppInstalled("com.example.app")) {
    log("App is installed");
} else {
    reportError("App is not installed");
}


Check if an app can be launched

if (!isAppLaunchable("com.example.app")) {
    reportError("App has no launch activity");
}


Close an app

var ok = closeApp("com.example.app");

if (!ok) {
    reportError("Close app failed");
}


Wait for an app to close

if (waitAppVanish("com.example.app", 5)) {
    reportStep("App vanished");
} else {
    reportError("App is still foreground");
}


Device information

log("Android SDK: " + getAndroidVersion());
log("Battery: " + getBatteryLevel() + "%");
log("Charging: " + isCharging());
log("Network: " + getNetworkType());


Stop on low battery

var battery = getBatteryLevel();

if (battery <= 15 && !isCharging()) {
    scriptExit("Battery is too low: " + battery + "%");
}


Check Wi-Fi

if (!isWifiConnected()) {
    reportError("Wi-Fi is not connected");
}


Set volume

var max = getMaxVolume();

setVolume(Math.floor(max * 0.5));


Vibrate

vibrate(300);


Vibration pattern

vibrate([100, 200, 100, 200]);


Simple click helper

function safeClick(target, label) {
    var ok = click(target);

    if (!ok) {
        reportError("Click failed: " + label);
        return false;
    }

    reportStep("Clicked: " + label);
    return true;
}

safeClick(createLocation(540, 1200), "Main button");


Safe function for clicking text

function clickText(label, seconds) {
    var match = text(label)
        .contains()
        .wait(seconds || 5);

    if (match) {
        return click(match);
    }

    reportError("Text not found: " + label);
    return false;
}

clickText("Continue", 5);


Safe function for clicking an image

function clickImage(fileName, seconds, similarity) {
    var pattern = createPattern(fileName).similar(similarity || 0.85);
    var match = waitFor(pattern, seconds || 5);

    if (match) {
        return click(match);
    }

    reportError("Image not found: " + fileName);
    return false;
}

clickImage("start_button.png", 5, 0.9);


Safe function for opening an app

function openTargetApp(packageName) {
    startApp(packageName);

    if (!waitApp(packageName, 8)) {
        reportError("App did not open: " + packageName);
        return false;
    }

    return true;
}

if (!openTargetApp("com.example.app")) {
    scriptExit("Cannot continue without target app");
}


Retry click helper

function retryClickText(label, times) {
    for (var i = 0; i < times; i++) {
        var match = text(label).contains().find();

        if (match && click(match)) {
            return true;
        }

        sleep(500);
    }

    return false;
}

if (!retryClickText("Confirm", 3)) {
    reportError("Could not click Confirm");
}


Scroll until text appears

var list = createRegion(0, 400, getScreenWidth(), 1300);
var target = null;

for (var i = 0; i < 8; i++) {
    target = text("Target Item")
        .contains()
        .in(list)
        .find();

    if (target) {
        break;
    }

    scroll(list, "up", 700, 500);
    sleep(500);
}

if (target) {
    click(target);
} else {
    reportError("Target Item was not found after scrolling");
}


Scroll and click all matching items

var list = createRegion(0, 400, getScreenWidth(), 1300);

for (var page = 0; page < 5; page++) {
    var items = text("Claim")
        .contains()
        .in(list)
        .findAll();

    for (var i = 0; i < items.length; i++) {
        click(items[i]);
        sleep(300);
    }

    scroll(list, "up", 700, 500);
    sleep(500);
}


Basic debug snapshot helper

function saveDebug(name) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

    saveColor(full, "debug/" + name + "_screen.png");
    saveReport("debug/" + name + "_report.json");
}

var ok = waitText("Done", 5, false);

if (!ok) {
    reportError("Done was not found");
    saveDebug("done_not_found");
}


Simple full starter script

clearReport();
setReferenceResolution(1080, 1920);

reportStep("Starting script");

if (!openTargetApp("com.example.app")) {
    scriptExit("Target app did not open");
}

sleep(1000);

var start = text("Start")
    .contains()
    .wait(5);

if (start) {
    click(start);
    reportStep("Start clicked");
} else {
    reportError("Start text was not found");
    saveDebug("start_not_found");
}

reportStep("Script finished");


Practical notes for Part 1

  • Use sleep(...) in milliseconds.

  • Use click(x, y) only when the coordinate is stable.

  • Use createLocation(...) for reusable points.

  • Use createRegion(...) for areas that you want to click, search, highlight, or scroll.

  • Use reportStep(...), reportInfo(...), and reportError(...) to make runs easier to review.

  • Use scriptExit(...) when the script should stop cleanly.

  • Use short waits after clicks, app launches, popups, and screen transitions.

  • Use helpers when you repeat the same action many times.

  • Save debug screenshots and reports when something important is not found.

ArgusJS Examples and Recipes - Part 2

This part covers screen capture, screen cache, static screenshots, image matching, image search regions, image wait helpers, color checks, multi-color checks, and basic debug screenshots.

These examples are written for copying and adapting. Replace image names, regions, colors, and coordinates with values that match your own project.


Recommended image project layout

Put image targets in the project images folder.

MyProject/
  MyProject.js
  images/
    start_button.png
    close.png
    home.png
  debug/
  reports/

ArgusJS image lookup checks the project images folder and then the project root.


Set reference resolution

Use setReferenceResolution(...) before image matching when your image assets were captured at a known resolution.

setReferenceResolution(1080, 1920);

var start = waitFor("start_button.png", 5);

if (start) {
    click(start);
}

setReferenceResolution(...) helps image matching scale correctly. It does not rescale direct click(x, y) coordinates.


Basic screen capture check

Use this to confirm screen capture is working.

var screen = captureScreen();

if (screen) {
    log("Screen capture OK");
    screen.recycle();
} else {
    reportError("Screen capture failed");
}

Always recycle a manually captured screen when you are finished with it.


Use screen capture safely

Use withCapturedScreen(...) when you want one screenshot and automatic cleanup.

withCapturedScreen(function(screen) {
    var staticScreen = onScreen(screen);

    if (!staticScreen) {
        reportError("Could not create static screen region");
        return;
    }

    var match = staticScreen.find(createPattern("home.png"));

    if (match) {
        log("Home image found");
    } else {
        log("Home image not found");
    }
});


Manual screen capture with cleanup

Use try/finally so the screen is recycled even if something fails.

var screen = captureScreen();

if (screen) {
    try {
        var staticScreen = onScreen(screen);
        var match = staticScreen.find(createPattern("logo.png"));

        if (match) {
            log("Logo found in static screenshot");
        } else {
            log("Logo not found");
        }
    } finally {
        screen.recycle();
    }
} else {
    reportError("Screen capture failed");
}


Use a static screen for repeated checks

This avoids repeated screenshot capture when the screen is not expected to change.

var screen = captureScreen();

if (screen) {
    try {
        var area = createRegion(
            0,
            0,
            getScreenWidth(),
            getScreenHeight()
        ).inScreen(screen);

        var logo = area.find(createPattern("logo.png"));
        var ready = area.findText("Ready", false);
        var green = area.findColor("#00FF00", 20);

        if (logo || ready || green) {
            reportStep("Expected screen found");
        } else {
            reportError("Expected screen was not found");
        }
    } finally {
        screen.recycle();
    }
}

Do not use wait APIs on a static screen because the screenshot cannot change.


Search a static screenshot region

Use inScreen(screen) to bind a region to one captured screenshot.

var screen = captureScreen();

if (screen) {
    try {
        var topBar = createRegion(0, 0, getScreenWidth(), 300).inScreen(screen);

        var menu = topBar.find(createPattern("menu.png"));

        if (menu) {
            log("Menu found in top bar");
        }
    } finally {
        screen.recycle();
    }
}


Avoid wait APIs on static screenshots

This is not valid because the static screen cannot change.

var screen = captureScreen();

if (screen) {
    try {
        var area = createRegion(0, 0, getScreenWidth(), getScreenHeight()).inScreen(screen);

        // Do not do this on a static screen:
        // area.waitFor("button.png", 5);
    } finally {
        screen.recycle();
    }
}

Use find(...), findText(...), or findColor(...) on static screenshots.


Refresh screen cache

Use the screen cache when several screenshot-based APIs need to read the same screen.

refreshScreenCache();

withScreenCache(function() {
    var start = find(createPattern("start_button.png"));
    var title = findText("Welcome");

    if (start) {
        click(start);
    } else if (title) {
        log("Welcome text found");
    }
});


Check whether screen cache exists

if (!hasScreenCache()) {
    cacheScreen();
}

log("Screen cache age: " + getScreenCacheAge() + " ms");

clearScreenCache();


Use screen cache for several checks

if (refreshScreenCache()) {
    withScreenCache(function() {
        var home = find(createPattern("home.png"));
        var continueText = findText("Continue");
        var green = findColor("#00FF00", 20);

        if (home) {
            reportStep("Home image found");
        }

        if (continueText) {
            reportStep("Continue text found");
        }

        if (green) {
            reportStep("Green color found");
        }
    });
} else {
    reportError("Could not refresh screen cache");
}


Clear screen cache after use

refreshScreenCache();

withScreenCache(function() {
    var ok = findText("OK");

    if (ok) {
        click(ok);
    }
});

clearScreenCache();


Find and click an image

var match = find(createPattern("start_button.png"));

if (match) {
    click(match);
} else {
    toast("Image not found");
}


Wait for an image and click it

var match = waitFor("start_button.png", 8);

if (match) {
    click(match);
    reportStep("Clicked start_button.png");
} else {
    reportError("start_button.png was not found");
}


Use a Pattern with similarity

var pattern = createPattern("start_button.png").similar(0.9);

var match = waitFor(pattern, 5);

if (match) {
    click(match);
}


Lower similarity for flexible matching

Use this when the image changes slightly because of scaling, shadows, animation, or compression.

var pattern = createPattern("button.png").similar(0.78);

var match = waitFor(pattern, 5);

if (match) {
    click(match);
} else {
    reportError("Button was not found");
}


Higher similarity for strict matching

Use this when false positives are a problem.

var pattern = createPattern("exact_icon.png").similar(0.95);

var match = waitFor(pattern, 5);

if (match) {
    click(match);
} else {
    reportError("Exact icon was not found");
}


Log image match score

var pattern = createPattern("start_button.png").similar(0.85);
var match = find(pattern);

if (match) {
    log("Match score: " + match.score);
    click(match);
} else {
    log("No match found");
}


Use color image matching

By default, patterns use grayscale. Use .color() when color is important.

var pattern = createPattern("red_button.png")
    .similar(0.92)
    .color();

var match = waitFor(pattern, 5);

if (match) {
    click(match);
}


Use grayscale image matching

Use grayscale when the shape matters more than the exact color.

var pattern = createPattern("button.png")
    .similar(0.85)
    .grayscale();

var match = waitFor(pattern, 5);

if (match) {
    click(match);
}


Use target offset on an image

Use this when the template is not the exact place you want to tap.

var pattern = createPattern("label.png")
    .similar(0.9)
    .targetOffset(220, 0);

var label = waitFor(pattern, 5);

if (label) {
    click(label.getTarget());
}


Click below a found image

var label = waitFor("username_label.png", 5);

if (label) {
    click(label.below(80));
}


Click beside a found image

var label = waitFor("price_label.png", 5);

if (label) {
    click(label.right(200));
}


Highlight a found image

var match = waitFor("target.png", 5);

if (match) {
    highlight(match, "Found target");
    sleep(1000);
    highlightOff(match);
}


Save a found image region

var match = waitFor("target.png", 5);

if (match) {
    saveColor(match, "debug/found_target.png");
}


Search inside a region

var bottomHalf = createRegion(0, 900, getScreenWidth(), 900);

var ok = bottomHalf.waitFor("ok.png", 3);

if (ok) {
    ok.click();
} else {
    reportError("ok.png was not found in the bottom half");
}


Search top bar only

var topBar = createRegion(0, 0, getScreenWidth(), 300);

var back = topBar.waitFor("back.png", 3);

if (back) {
    click(back);
}


Search bottom controls only

var bottom = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var continueButton = bottom.waitFor("continue.png", 5);

if (continueButton) {
    click(continueButton);
}


Find image in a region using global function

var region = createRegion(0, 900, getScreenWidth(), 900);
var pattern = createPattern("ok.png").similar(0.9);

var match = findIn(region, pattern);

if (match) {
    click(match);
}


Find all image matches

var pattern = createPattern("coin.png").similar(0.88);
var matches = findAll(pattern);

log("Coins found: " + matches.length);

for (var i = 0; i < matches.length; i++) {
    click(matches[i]);
    sleep(100);
}


Find all image matches in a region

var area = createRegion(0, 300, getScreenWidth(), 1300);
var pattern = createPattern("item.png").similar(0.86);

var matches = findAllIn(area, pattern);

log("Items found: " + matches.length);

for (var i = 0; i < matches.length; i++) {
    highlight(matches[i], 1);
}


Click the first image from a list

var found = waitAny([
    "home.png",
    "login.png",
    "error.png"
], 10);

if (found) {
    click(found);
} else {
    reportError("No expected image appeared");
}


Wait for any image in a region

var topBar = createRegion(0, 0, getScreenWidth(), 300);

var found = waitAny([
    "back.png",
    "close.png",
    "menu.png"
], topBar, 5);

if (found) {
    click(found);
}


Wait for an image to disappear

var gone = waitVanish("loading.png", 10);

if (gone) {
    reportStep("Loading disappeared");
} else {
    reportError("Loading did not disappear in time");
}


Wait for an image to disappear in a region

var center = createRegion(0, 500, getScreenWidth(), 900);

var gone = waitVanish("spinner.png", center, 10);

if (gone) {
    reportStep("Spinner disappeared");
} else {
    reportError("Spinner is still visible");
}


Click only if image exists

var close = exists("close.png", 0);

if (close) {
    click(close);
}


Check image once

Use exists(..., 0) for a one-time check.

var popup = exists("popup.png", 0);

if (popup) {
    reportStep("Popup is visible");
} else {
    log("Popup is not visible");
}


Wait briefly for optional image

var popup = exists("popup.png", 2);

if (popup) {
    click(popup);
}


Use waitClick

waitClick(...) waits, clicks, and returns the match only if the click succeeds.

var clicked = waitClick("continue.png", 5);

if (clicked) {
    reportStep("Continue clicked");
} else {
    reportError("Continue was not found or click failed");
}


Use existsClick

existsClick(...) checks or waits, then clicks if found.

var clicked = existsClick("close.png", 2);

if (clicked) {
    reportStep("Closed popup");
} else {
    log("No close button found");
}


Image plus OCR fallback

Use this when image matching may fail but visible text may still exist.

var button = waitFor("continue.png", 3);

if (button) {
    click(button);
} else {
    var textButton = waitText("Continue", 3, false);

    if (textButton) {
        click(textButton);
    } else {
        reportError("Continue button was not found by image or OCR");
    }
}


Image plus node fallback

Use this for Android UI where Accessibility nodes may be available.

var button = waitFor("continue.png", 2);

if (button) {
    click(button);
} else {
    var nodeButton = waitNode({ byText: "Continue" }, 3);

    if (nodeButton) {
        nodeButton.click();
    } else {
        reportError("Continue was not found by image or node");
    }
}


Robust image click helper

function clickImage(fileName, seconds, similarity) {
    var pattern = createPattern(fileName).similar(similarity || 0.85);
    var match = waitFor(pattern, seconds || 5);

    if (match) {
        reportStep("Image found: " + fileName);
        return click(match);
    }

    reportError("Image not found: " + fileName);
    return false;
}

clickImage("start_button.png", 5, 0.9);


Image search with debug screenshot

function clickImageOrDebug(fileName, seconds, similarity) {
    var pattern = createPattern(fileName).similar(similarity || 0.85);
    var match = waitFor(pattern, seconds || 5);

    if (match) {
        click(match);
        return true;
    }

    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());
    saveColor(full, "debug/" + fileName + "_not_found.png");

    reportError("Image not found: " + fileName);
    return false;
}

clickImageOrDebug("start_button.png", 5, 0.9);


Find image, then verify text

var card = waitFor("card_icon.png", 5);

if (card) {
    var cardRegion = createRegion(card.x - 50, card.y, card.w + 500, card.h + 200);
    var title = findTextIn(cardRegion, "Available", false);

    if (title) {
        click(card);
    } else {
        reportError("Card icon found, but Available text was missing");
    }
}


Find image, then check nearby color

var button = waitFor("submit.png", 5);

if (button) {
    var center = button.getCenter();

    if (compareColor(center, "#00AA00", 30)) {
        click(button);
    } else {
        reportError("Submit button was found but does not look enabled");
    }
}


Save a grayscale region image

save(...) writes a grayscale image.

var area = createRegion(100, 300, 500, 500);

save(area, "debug/area_gray.png");


Save a color region image

saveColor(...) writes a color image.

var area = createRegion(100, 300, 500, 500);

saveColor(area, "debug/area_color.png");


Save full screen screenshot

createDir("debug");

var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

saveColor(full, "debug/full_screen.png");


Save screenshot after failure

var start = waitFor("start_button.png", 5);

if (!start) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());
    saveColor(full, "debug/start_not_found.png");

    reportError("start_button.png was not found");
}


Hide overlays before screenshot

Use this when overlays may cover the target screen.

hideAllOverlays();
sleep(500);

var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());
saveColor(full, "debug/clean_screen.png");

showAllOverlays();


Capture after touch recording or overlay change

After hiding overlays or recording touch events, add a short wait before one-shot capture.

hideAllOverlays();
sleep(500);

var screen = captureScreen();

showAllOverlays();

if (screen) {
    try {
        log("Clean capture available");
    } finally {
        screen.recycle();
    }
}


Check color at a point

var color = getColor(createLocation(100, 100));

if (color) {
    log("R=" + color.r + " G=" + color.g + " B=" + color.b);
}


Check color at a region center

var area = createRegion(300, 1000, 500, 160);
var color = getColor(area);

if (color) {
    log("Center color: " + colorToHexString(color));
}


Convert color to hex string

var color = getColor(createLocation(100, 100));

if (color) {
    var hex = colorToHexString(color);
    log("Color: " + hex);
}


Compare color at coordinates

if (compareColor(100, 100, "#FFFFFF", 10)) {
    log("Pixel is close to white");
}


Compare color at a target

var target = createLocation(540, 1200);

if (compareColor(target, "#00FF00", 20)) {
    log("Target is green");
}


Compare color on an image match

var button = waitFor("button.png", 5);

if (button) {
    if (compareColor(button, "#00AA00", 25)) {
        click(button);
    } else {
        reportError("Button was found but color check failed");
    }
}


Wait for color

var target = createLocation(540, 1200);

var color = waitColor(target, "#00FF00", 5, 20);

if (color) {
    reportStep("Green color appeared");
} else {
    reportError("Green color did not appear");
}


Wait for color comparison

var target = createLocation(540, 1200);

if (waitCompareColor(target, "#00FF00", 5, 20)) {
    reportStep("Target became green");
} else {
    reportError("Target did not become green");
}


Wait for color to vanish

var target = createLocation(540, 1200);

if (waitVanishColor(target, "#FF0000", 5, 20)) {
    reportStep("Red warning disappeared");
} else {
    reportError("Red warning stayed visible");
}


Find color on screen

var location = findColor("#00FF00", 20);

if (location) {
    click(location);
}


Find color in a region

var area = createRegion(0, 900, getScreenWidth(), 900);
var location = findColorIn("#00FF00", area, 20);

if (location) {
    click(location);
}


Find all color matches

var points = findAllColor("#FFCC00", 15);

log("Points found: " + points.length);

for (var i = 0; i < points.length && i < 5; i++) {
    var marker = createRegion(points[i].x - 10, points[i].y - 10, 20, 20);
    highlight(marker, 1);
}


Find all color matches in a region

var area = createRegion(0, 300, getScreenWidth(), 1200);
var points = findAllColorIn("#FFCC00", area, 15);

log("Points found in area: " + points.length);


Count color in region

var area = createRegion(0, 900, getScreenWidth(), 900);
var count = getColorCount(area, "#00FF00", 20);

log("Green pixels: " + count);


Use color count as a state check

var buttonArea = createRegion(300, 1000, 500, 160);
var greenCount = getColorCount(buttonArea, "#00AA00", 25);

if (greenCount > 100) {
    reportStep("Button appears enabled");
    click(buttonArea);
} else {
    reportError("Button appears disabled");
}


Multi-color search

Use multi-color when one color alone is not unique enough.

var point = findMultiColor(
    "#FF0000",
    [
        "10|0|#00FF00",
        "0|10|#0000FF"
    ],
    20
);

if (point) {
    click(point);
}


Multi-color search in region

var area = createRegion(0, 500, getScreenWidth(), 1000);

var point = findMultiColorIn(
    "#FF0000",
    [
        "10|0|#00FF00",
        "0|10|#0000FF"
    ],
    area,
    20
);

if (point) {
    click(point);
}


Find all multi-color matches

var points = findAllMultiColor(
    "#FF0000",
    [
        "10|0|#00FF00",
        "0|10|#0000FF"
    ],
    20
);

log("Multi-color matches: " + points.length);


Find all multi-color matches in a region

var area = createRegion(0, 500, getScreenWidth(), 1000);

var points = findAllMultiColorIn(
    "#FF0000",
    [
        "10|0|#00FF00",
        "0|10|#0000FF"
    ],
    area,
    20
);

log("Region multi-color matches: " + points.length);


Color range rule example

A secondary color rule can use a range.

var point = findMultiColor(
    "#FF0000",
    [
        "10|0|#00AA00-#00FF00",
        "0|10|#0000AA-#0000FF"
    ],
    20
);

if (point) {
    click(point);
}


Color OR rule example

A secondary color rule can allow more than one color using |.

var point = findMultiColor(
    "#FF0000",
    [
        "10|0|#00FF00|#00AA00",
        "0|10|#0000FF|#0000AA"
    ],
    20
);

if (point) {
    click(point);
}


Detect enabled button using image and color

var button = waitFor("submit_button.png", 5);

if (!button) {
    reportError("Submit button was not found");
} else {
    var greenCount = getColorCount(button, "#00AA00", 30);

    if (greenCount > 50) {
        click(button);
        reportStep("Submit clicked");
    } else {
        reportError("Submit button appears disabled");
    }
}


Detect loading spinner with image

var spinner = exists("loading_spinner.png", 0);

if (spinner) {
    reportStep("Spinner found. Waiting for it to disappear.");

    if (!waitVanish("loading_spinner.png", 15)) {
        reportError("Spinner did not disappear");
    }
}


Detect loading spinner with color

var center = createRegion(
    Math.floor(getScreenWidth() * 0.25),
    Math.floor(getScreenHeight() * 0.25),
    Math.floor(getScreenWidth() * 0.50),
    Math.floor(getScreenHeight() * 0.50)
);

var spinnerPoint = findColorIn("#999999", center, 25);

if (spinnerPoint) {
    reportStep("Possible loading indicator found");
}


Wait for page ready by image or color

var ready = false;

var home = waitFor("home_icon.png", 3);

if (home) {
    ready = true;
}

if (!ready) {
    var green = waitColor(createLocation(540, 1200), "#00AA00", 3, 25);

    if (green) {
        ready = true;
    }
}

if (ready) {
    reportStep("Page appears ready");
} else {
    reportError("Page did not become ready");
}


Wait for page ready with race

var result = race([
    waitAsync("home_icon.png", 10),
    waitTextAsync("Home", 10),
    findColorAsync("#00AA00", 25)
]);

if (result) {
    reportStep("Page ready signal found. Winner index: " + result.index);
} else {
    reportError("No ready signal found");
}


Save debug package on image failure

function saveImageDebug(name) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

    saveColor(full, "debug/" + name + "_screen.png");
    saveReport("debug/" + name + "_report.json");

    var textValue = String(readText());
    writeFile("debug/" + name + "_ocr.txt", textValue);
}

var start = waitFor("start_button.png", 5);

if (!start) {
    reportError("Start button was not found");
    saveImageDebug("start_not_found");
}


Debug image matching score

Use this when the image is visible but matching is failing.

var pattern = createPattern("start_button.png").similar(0.7);
var match = find(pattern);

if (match) {
    log("Loose match score: " + match.score);
    highlight(match, "Loose match");
} else {
    log("No loose match found");
}


Compare multiple similarity values

var fileName = "start_button.png";
var similarities = [0.95, 0.90, 0.85, 0.80, 0.75];

for (var i = 0; i < similarities.length; i++) {
    var pattern = createPattern(fileName).similar(similarities[i]);
    var match = find(pattern);

    if (match) {
        log("Found at similarity " + similarities[i] + " score=" + match.score);
        highlight(match, 1);
        break;
    } else {
        log("Not found at similarity " + similarities[i]);
    }
}


Test image inside smaller regions

var fileName = "button.png";

var regions = [
    createRegion(0, 0, getScreenWidth(), 500),
    createRegion(0, 500, getScreenWidth(), 700),
    createRegion(0, 1200, getScreenWidth(), 720)
];

for (var i = 0; i < regions.length; i++) {
    var match = regions[i].find(createPattern(fileName).similar(0.85));

    if (match) {
        log("Found in region index: " + i);
        highlight(match, "Found here");
        break;
    }
}


Click nearest image to a known point

var pattern = createPattern("item.png").similar(0.85);
var matches = findAll(pattern);
var targetPoint = createLocation(540, 1200);

var nearest = null;
var nearestDistance = 999999;

for (var i = 0; i < matches.length; i++) {
    var center = matches[i].getCenter();

    var dx = center.x - targetPoint.x;
    var dy = center.y - targetPoint.y;
    var distance = Math.sqrt(dx * dx + dy * dy);

    if (distance < nearestDistance) {
        nearestDistance = distance;
        nearest = matches[i];
    }
}

if (nearest) {
    click(nearest);
}


Click top-most image match

var matches = findAll(createPattern("item.png").similar(0.85));

if (matches.length > 0) {
    var topMost = matches[0];

    for (var i = 1; i < matches.length; i++) {
        if (matches[i].y < topMost.y) {
            topMost = matches[i];
        }
    }

    click(topMost);
}


Click bottom-most image match

var matches = findAll(createPattern("item.png").similar(0.85));

if (matches.length > 0) {
    var bottomMost = matches[0];

    for (var i = 1; i < matches.length; i++) {
        if (matches[i].y > bottomMost.y) {
            bottomMost = matches[i];
        }
    }

    click(bottomMost);
}


Avoid clicking duplicate image matches

var matches = findAll(createPattern("coin.png").similar(0.85));
var clicked = [];

for (var i = 0; i < matches.length; i++) {
    var current = matches[i];
    var duplicate = false;

    for (var j = 0; j < clicked.length; j++) {
        var dx = Math.abs(current.x - clicked[j].x);
        var dy = Math.abs(current.y - clicked[j].y);

        if (dx < 20 && dy < 20) {
            duplicate = true;
            break;
        }
    }

    if (!duplicate) {
        click(current);
        clicked.push(current);
        sleep(100);
    }
}


Detect screen state by image list

function detectScreen() {
    if (exists("home.png", 0)) {
        return "home";
    }

    if (exists("login.png", 0)) {
        return "login";
    }

    if (exists("error.png", 0)) {
        return "error";
    }

    return "unknown";
}

var state = detectScreen();

log("Screen state: " + state);


Act based on image screen state

var state = "unknown";

if (exists("home.png", 0)) {
    state = "home";
} else if (exists("login.png", 0)) {
    state = "login";
} else if (exists("error.png", 0)) {
    state = "error";
}

if (state === "home") {
    reportStep("Already on home");
} else if (state === "login") {
    reportStep("Need login");
} else if (state === "error") {
    reportError("Error screen detected");
} else {
    reportError("Unknown screen");
}


Open app and wait for image

clearReport();
setReferenceResolution(1080, 1920);

startApp("com.example.app");

if (!waitApp("com.example.app", 5)) {
    scriptExit("Target app did not open");
}

var home = waitFor("home.png", 10);

if (home) {
    reportStep("Home screen found");
} else {
    reportError("Home screen was not found");
}


Open app, close popup, then click start

clearReport();
setReferenceResolution(1080, 1920);

startApp("com.example.app");
waitApp("com.example.app", 5);

var close = exists("close.png", 2);

if (close) {
    click(close);
    sleep(500);
}

var start = waitFor("start_button.png", 8);

if (start) {
    click(start);
    reportStep("Start clicked");
} else {
    reportError("Start button was not found");
}


Wait for loading, then click continue

var loading = exists("loading.png", 0);

if (loading) {
    reportStep("Loading detected");

    if (!waitVanish("loading.png", 15)) {
        reportError("Loading did not finish");
        scriptExit("Stopped at loading screen");
    }
}

var next = waitFor("continue.png", 5);

if (next) {
    click(next);
}


Image matching checklist

Use this checklist when image matching does not work:

  • Confirm screen capture permission is active.

  • Confirm the image file is inside the project images folder.

  • Confirm the filename matches exactly.

  • Try lowering .similar(...).

  • Try .color() if color is important.

  • Try .grayscale() if color changes between states.

  • Search inside a smaller region.

  • Save a debug screenshot and compare it with the image asset.

  • Add a short sleep(...) after transitions before one-shot checks.

  • Use waitFor(...) instead of find(...) when the screen may still be changing.


Color matching checklist

Use this checklist when color matching does not work:

  • Confirm screen capture permission is active.

  • Check the color with getColor(...).

  • Convert it with colorToHexString(...).

  • Increase tolerance.

  • Search inside a smaller region.

  • Use getColorCount(...) instead of checking only one pixel.

  • Use multi-color if one color is not unique.

  • Save a debug screenshot to verify the actual color.


Practical notes for Part 2

  • Use waitFor(...) when the screen may change.

  • Use find(...) when you only need one immediate check.

  • Use exists(..., 0) for a single image check.

  • Use waitVanish(...) for loading images, spinners, and temporary popups.

  • Use regions to improve speed and reduce false positives.

  • Use saveColor(...) for debug screenshots.

  • Use withCapturedScreen(...) or try/finally when working with static screenshots.

  • Do not call wait APIs on static screenshots.

  • Add short waits after overlay changes, app transitions, and clicks before one-shot screenshots.

  • Use color checks as support signals, not always as the only detection method.

  • Use image plus OCR or image plus node fallback for more resilient scripts.

ArgusJS Examples and Recipes - Part 3

This part covers OCR, TextQuery, text matching, text regions, async text checks, Accessibility nodes, node selectors, node clicking, node scrolling, and node fallback patterns.

These examples are written for copying and adapting. Replace visible text, view IDs, class names, regions, and package names with values that match your own target app.


When to use OCR

Use OCR when the target app shows visible text but Accessibility nodes are not available or are unreliable.

Good OCR targets:

  • Buttons with visible text

  • Labels

  • Status messages

  • Prices

  • Ride times

  • Error messages

  • Confirmation screens

OCR can fail when text is too small, animated, low contrast, stylized, partly hidden, or changing too quickly.


Find text once

Use findText(...) for one immediate OCR check.

var match = findText("Continue");

if (match) {
    click(match);
} else {
    log("Continue was not found");
}


Wait for text

Use waitText(...) when the text may appear after a delay.

var match = waitText("Continue", 5);

if (match) {
    click(match);
} else {
    reportError("Continue text was not found");
}


Case-insensitive text search

The third parameter controls case sensitivity.

var match = waitText("continue", 5, false);

if (match) {
    click(match);
}


Case-sensitive text search

var match = waitText("OK", 5, true);

if (match) {
    click(match);
}


Find text in a region

Use regions to improve speed and reduce false positives.

var bottomArea = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var confirm = findTextIn(bottomArea, "Confirm", false);

if (confirm) {
    click(confirm);
}


Wait for text in a region

var bottomArea = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var confirm = waitTextIn(bottomArea, "Confirm", 5, false);

if (confirm) {
    click(confirm);
} else {
    reportError("Confirm was not found in the bottom area");
}


Search top area only

var topArea = createRegion(0, 0, getScreenWidth(), 400);

var title = waitTextIn(topArea, "Home", 5, false);

if (title) {
    reportStep("Home title found");
}


Search middle area only

var middleArea = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.25),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.50)
);

var message = findTextIn(middleArea, "Success", false);

if (message) {
    reportStep("Success message found");
}


Search bottom area only

var bottomArea = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var button = waitTextIn(bottomArea, "Continue", 5, false);

if (button) {
    click(button);
}


Wait for any text

Use waitAnyText(...) when several possible labels can appear.

var result = waitAnyText([
    "Continue",
    "Retry",
    "Cancel"
], 8, false);

if (result) {
    log("Found text: " + result.text);
    click(result.match);
} else {
    reportError("None of the expected texts appeared");
}


Wait for any text in a region

var dialogArea = createRegion(100, 500, 880, 700);

var result = waitAnyTextIn(dialogArea, [
    "OK",
    "Cancel",
    "Try again"
], 5, false);

if (result) {
    log("Dialog option found: " + result.text);
    click(result.match);
}


Wait for text to disappear

if (waitVanishText("Loading", 10, false)) {
    reportStep("Loading text disappeared");
} else {
    reportError("Loading text stayed visible");
}


Wait for text to disappear in a region

var centerArea = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.25),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.50)
);

if (waitVanishTextIn(centerArea, "Please wait", 10, false)) {
    reportStep("Please wait disappeared");
} else {
    reportError("Please wait did not disappear");
}


Check if text exists once

Use existsText(..., 0) for a one-time OCR check.

var popup = existsText("Not now", 0, false);

if (popup) {
    click(popup);
}


Wait briefly for optional text

var popup = existsText("Not now", 2, false);

if (popup) {
    click(popup);
}


Read all screen text

Convert the result with String(...) before using JavaScript string methods.

var allText = String(readText());

log("OCR length: " + allText.length);
log(allText);


Read text from a region

var area = createRegion(0, 400, getScreenWidth(), 1000);
var textValue = String(readText(area));

log(textValue);


Save OCR result to a file

createDir("debug");

var textValue = String(readText());

writeFile("debug/ocr_full_screen.txt", textValue);


Get all OCR words

var words = getWords();

for (var i = 0; i < words.length; i++) {
    log(words[i].text + " at " + words[i].x + ", " + words[i].y);
}


Get OCR words in a region

var area = createRegion(0, 400, getScreenWidth(), 1000);
var words = getWordsIn(area);

for (var i = 0; i < words.length; i++) {
    log(words[i].text);
}


Highlight all words found

var words = getWords();

for (var i = 0; i < words.length && i < 20; i++) {
    highlight(words[i], words[i].text);
    sleep(100);
}

sleep(2000);
highlightAllOff();


Click a word by exact OCR text

var words = getWords();

for (var i = 0; i < words.length; i++) {
    if (String(words[i].text) === "OK") {
        click(words[i]);
        break;
    }
}


TextQuery: simple contains search

TextQuery is better when you need chaining, regions, sorting, regex, or advanced matching.

var match = text("Continue")
    .contains()
    .find();

if (match) {
    click(match);
}


TextQuery: wait for text

var match = text("Continue")
    .contains()
    .wait(5);

if (match) {
    click(match);
} else {
    reportError("Continue was not found");
}


TextQuery: exact search

Use .exact() when the OCR result must match exactly.

var match = text("Confirm")
    .exact()
    .wait(5);

if (match) {
    click(match);
}


TextQuery: case-insensitive search

var match = text("continue")
    .contains()
    .caseInsensitive()
    .wait(5);

if (match) {
    click(match);
}


TextQuery: case-sensitive search

var match = text("OK")
    .exact()
    .caseSensitive()
    .wait(5);

if (match) {
    click(match);
}


TextQuery: starts with

var match = text("Order")
    .startsWith()
    .wait(5);

if (match) {
    log("Found text starting with Order");
}


TextQuery: ends with

var match = text("completed")
    .endsWith()
    .wait(5);

if (match) {
    log("Found text ending with completed");
}


TextQuery: regex search

Use .regex() explicitly. ArgusJS does not auto-detect regex.

var rides = text("\\d+\\s+minutes?\\s+away")
    .regex()
    .caseInsensitive()
    .normalizeSpaces()
    .findAll();

for (var i = 0; i < rides.length; i++) {
    log("Ride: " + rides[i].getText());
}

This can match:

  • 1 minute away

  • 5 minutes away

  • 12 minutes away


TextQuery: regex price search

var prices = text("(QAR\\s*)?\\d+(\\.\\d+)?")
    .regex()
    .caseInsensitive()
    .normalizeSpaces()
    .findAll();

for (var i = 0; i < prices.length; i++) {
    log("Price: " + prices[i].getText());
}


TextQuery: regex phone number search

var phone = text("\\+?\\d[\\d\\s-]{7,}")
    .regex()
    .normalizeSpaces()
    .find();

if (phone) {
    log("Possible phone number: " + phone.getText());
}


TextQuery: search in a region

var rideList = createRegion(0, 500, getScreenWidth(), 1200);

var ride = text("\\d+\\s+minutes?\\s+away")
    .regex()
    .in(rideList)
    .sortByTop()
    .first();

if (ride) {
    click(ride);
}


TextQuery: find all in a region

var listArea = createRegion(0, 400, getScreenWidth(), 1300);

var items = text("Claim")
    .contains()
    .in(listArea)
    .findAll();

for (var i = 0; i < items.length; i++) {
    click(items[i]);
    sleep(300);
}


TextQuery: wait and click

var clicked = text("Request")
    .contains()
    .waitClick(5);

if (!clicked) {
    reportError("Request button was not found or click failed");
}


TextQuery: click without waiting

var clicked = text("Close")
    .contains()
    .click();

if (!clicked) {
    log("Close was not visible");
}


TextQuery: wait vanish

var gone = text("Loading")
    .contains()
    .waitVanish(10);

if (gone) {
    reportStep("Loading disappeared");
} else {
    reportError("Loading did not disappear");
}


TextQuery: match any text

var result = text()
    .any(["Error", "Retry", "Continue"])
    .wait(5);

if (result) {
    log("Matched text: " + result.text);
    log("Matched index: " + result.index);
    click(result.match);
}


TextQuery: match any text in a region

var bottomArea = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var result = text()
    .any(["Confirm", "Continue", "Pay now"])
    .in(bottomArea)
    .wait(5);

if (result) {
    log("Matched: " + result.text);
    click(result.match);
}


TextQuery: normalize spaces

Use .normalizeSpaces() when OCR may read extra spaces or line breaks.

var match = text("Total QAR")
    .contains()
    .normalizeSpaces()
    .wait(5);

if (match) {
    log("Total line found");
}


TextQuery: trim text

var match = text("Done")
    .exact()
    .trim()
    .wait(5);

if (match) {
    click(match);
}


TextQuery: minimum length

var longTexts = text()
    .regex()
    .any([".*"])
    .minLength(10)
    .findAll();

log("Long OCR candidates: " + longTexts.length);


TextQuery: maximum length

var shortTexts = text()
    .regex()
    .any([".*"])
    .maxLength(5)
    .findAll();

log("Short OCR candidates: " + shortTexts.length);


TextQuery: sort by top

var items = text("Item")
    .contains()
    .sortByTop()
    .findAll();

for (var i = 0; i < items.length; i++) {
    log("Item " + i + " y=" + items[i].y);
}


TextQuery: sort by left

var tabs = text()
    .any(["Home", "Orders", "Profile"])
    .sortByLeft()
    .findAll();

for (var i = 0; i < tabs.length; i++) {
    log("Tab: " + tabs[i].getText());
}


TextQuery: limit results

var buttons = text("Claim")
    .contains()
    .sortByTop()
    .limit(3)
    .findAll();

for (var i = 0; i < buttons.length; i++) {
    click(buttons[i]);
    sleep(300);
}


TextQuery: first result

var first = text("Claim")
    .contains()
    .sortByTop()
    .first();

if (first) {
    click(first);
}


TextQuery: nth result

nth(0) is the first match. nth(1) is the second match.

var second = text("Claim")
    .contains()
    .sortByTop()
    .nth(1);

if (second) {
    click(second);
}


TextQuery: closest to a point

var center = createLocation(
    Math.floor(getScreenWidth() / 2),
    Math.floor(getScreenHeight() / 2)
);

var nearest = text("Select")
    .contains()
    .closestTo(center)
    .find();

if (nearest) {
    click(nearest);
}


TextQuery: closest to another text

var label = text("Pick-up")
    .contains()
    .find();

if (label) {
    var value = text("Legtaifiya")
        .contains()
        .below(label)
        .closestTo(label)
        .find();

    if (value) {
        click(value);
    }
}


TextQuery: text below another text

var label = text("Email")
    .contains()
    .find();

if (label) {
    var value = text()
        .regex()
        .any([".+@.+"])
        .below(label)
        .closestTo(label)
        .find();

    if (value) {
        log("Email value: " + value.getText());
    }
}


TextQuery: text above another text

var total = text("Total")
    .contains()
    .find();

if (total) {
    var amount = text("(QAR\\s*)?\\d+(\\.\\d+)?")
        .regex()
        .above(total)
        .closestTo(total)
        .find();

    if (amount) {
        log("Amount above Total: " + amount.getText());
    }
}


TextQuery: text left of another text

var value = text("Enabled")
    .contains()
    .find();

if (value) {
    var label = text("Notifications")
        .contains()
        .leftOf(value)
        .closestTo(value)
        .find();

    if (label) {
        log("Found label beside Enabled");
    }
}


TextQuery: text right of another text

var label = text("Status")
    .contains()
    .find();

if (label) {
    var value = text()
        .any(["Active", "Inactive", "Pending"])
        .rightOf(label)
        .closestTo(label)
        .find();

    if (value) {
        log("Status value found");
    }
}


TextQuery: use words source

Use .words() when you want individual word-level OCR candidates.

var match = text("OK")
    .exact()
    .words()
    .find();

if (match) {
    click(match);
}


TextQuery: use lines source

Lines are the default and usually better for phrases.

var match = text("Payment completed")
    .contains()
    .lines()
    .wait(5);

if (match) {
    reportStep("Payment completed");
}


TextQuery: use blocks source

Blocks can help with larger grouped OCR text.

var block = text("Order summary")
    .contains()
    .blocks()
    .find();

if (block) {
    highlight(block, "Order summary block");
}


TextQuery: use full text source

Use .fullText() when checking if the full OCR output contains something.

var found = text("Terms and Conditions")
    .contains()
    .fullText()
    .find();

if (found) {
    log("Terms text appears somewhere on the screen");
}


TextQuery: debug mode

Use .debug(true) when you need OCR matching logs.

var matches = text("\\d+\\s+minutes?\\s+away")
    .regex()
    .caseInsensitive()
    .normalizeSpaces()
    .debug(true)
    .findAll();

log("Matches: " + matches.length);


TextQuery: invalid regex safety

Invalid regex should return safe values and log an error.

var result = text("[broken")
    .regex()
    .find();

if (!result) {
    log("Invalid regex returned no result");
}


Async TextQuery with race

var winner = race([
    text("Error").contains().waitAsync(10),
    text("Confirm").contains().waitAsync(10),
    text("Success").contains().waitAsync(10)
]);

if (winner) {
    log("Winner index: " + winner.index);
    click(winner.result);
} else {
    reportError("No expected text appeared");
}


Async TextQuery with awaitAll

var results = awaitAll([
    text("Confirm").contains().existsAsync(2),
    text("Cancel").contains().existsAsync(2),
    text("\\d+\\s+minutes?\\s+away").regex().lines().findAllAsync()
]);

log("Confirm: " + results[0]);
log("Cancel: " + results[1]);
log("Ride matches: " + results[2].length);


Race between image and text

var winner = race([
    waitAsync("home.png", 10),
    waitTextAsync("Login", 10),
    text("Error").contains().waitAsync(10)
]);

if (winner) {
    log("Winner index: " + winner.index);
} else {
    reportError("No image or text matched");
}


Wait for page ready using text signals

var ready = race([
    text("Home").contains().waitAsync(10),
    text("Dashboard").contains().waitAsync(10),
    text("Welcome").contains().waitAsync(10)
]);

if (ready) {
    reportStep("Page is ready");
} else {
    reportError("Page did not become ready");
}


Detect error message

var error = text()
    .any(["Error", "Failed", "Try again", "Network error"])
    .caseInsensitive()
    .wait(5);

if (error) {
    reportError("Error text found: " + error.text);
}


Detect success message

var success = text()
    .any(["Success", "Done", "Completed", "Saved"])
    .caseInsensitive()
    .wait(10);

if (success) {
    reportStep("Success text found: " + success.text);
} else {
    reportError("No success message found");
}


OCR fallback click helper

function clickText(label, seconds) {
    var match = text(label)
        .contains()
        .wait(seconds || 5);

    if (match) {
        return click(match);
    }

    reportError("Text not found: " + label);
    return false;
}

clickText("Continue", 5);


Click any text helper

function clickAnyText(labels, seconds) {
    var result = text()
        .any(labels)
        .wait(seconds || 5);

    if (result) {
        reportStep("Clicking: " + result.text);
        return click(result.match);
    }

    reportError("None found: " + labels.join(", "));
    return false;
}

clickAnyText(["OK", "Continue", "Confirm"], 5);


Scroll until text appears

var list = createRegion(0, 400, getScreenWidth(), 1300);
var target = null;

for (var i = 0; i < 8; i++) {
    target = text("Target Item")
        .contains()
        .in(list)
        .find();

    if (target) {
        break;
    }

    scroll(list, "up", 700, 500);
    sleep(500);
}

if (target) {
    click(target);
} else {
    reportError("Target Item was not found after scrolling");
}


Click all visible text matches

var list = createRegion(0, 400, getScreenWidth(), 1300);

var buttons = text("Claim")
    .contains()
    .in(list)
    .sortByTop()
    .findAll();

for (var i = 0; i < buttons.length; i++) {
    click(buttons[i]);
    sleep(300);
}


Find cheapest visible price

var priceRegion = createRegion(0, 400, getScreenWidth(), 1300);

var prices = text("(QAR\\s*)?\\d+(\\.\\d+)?")
    .regex()
    .in(priceRegion)
    .sortByTop()
    .findAll();

var cheapest = null;
var cheapestValue = 999999;

for (var i = 0; i < prices.length; i++) {
    var raw = String(prices[i].getText());
    var numeric = Number(raw.replace("QAR", "").replace(/[^0-9.]/g, ""));

    if (!isNaN(numeric) && numeric < cheapestValue) {
        cheapestValue = numeric;
        cheapest = prices[i];
    }
}

if (cheapest) {
    reportStep("Cheapest price: " + cheapestValue);
    click(cheapest);
} else {
    reportError("No valid price found");
}


Accessibility nodes

Use Accessibility nodes when the target app exposes native Android UI. Nodes are often faster and more reliable than OCR for normal Android apps.

Good node targets:

  • Buttons

  • EditText fields

  • Switches

  • Checkboxes

  • Native menus

  • Native dialogs

  • Views with text, content description, view ID, or class name

Nodes may not exist in games, canvas views, custom-rendered apps, or some WebViews.


Find a node by text

var button = findNode({ byText: "OK" });

if (button) {
    button.click();
}


Wait for a node by text

var button = waitNode({ byText: "Allow" }, 5);

if (button) {
    button.click();
} else {
    reportError("Allow node was not found");
}


Find a node by content description

var menu = waitNode({ byContentDescription: "Menu" }, 5);

if (menu) {
    menu.click();
}


Find a node by view ID

var input = waitNode({
    byViewId: "com.example.app:id/email"
}, 5);

if (input) {
    input.click();
}


Find a node by class name

var input = waitNode({
    byClassName: "android.widget.EditText"
}, 5);

if (input) {
    input.click();
}


Find a node using multiple selectors

var loginButton = waitNode({
    byText: "Login",
    byClassName: "android.widget.Button"
}, 5);

if (loginButton) {
    loginButton.click();
}


Inspect node fields

var node = waitNode({ byText: "OK" }, 5);

if (node) {
    log("Text: " + node.text);
    log("Content description: " + node.contentDescription);
    log("View ID: " + node.viewIdResourceName);
    log("Class: " + node.widgetClassName);
}


Find all nodes

var nodes = findAllNodes({
    byClassName: "android.widget.Button"
});

log("Buttons found: " + nodes.length);

for (var i = 0; i < nodes.length; i++) {
    log("Button text: " + nodes[i].text);
}


Click a node

node.click() performs an Accessibility click action.

var button = waitNode({ byText: "Submit" }, 5);

if (button) {
    button.click();
}


Touch-click a node region

click(node) resolves the node bounds and sends a touch click.

var button = waitNode({ byText: "Submit" }, 5);

if (button) {
    click(button);
}


Node click vs touch click

Use node.click() first for native UI. Use click(node) when the node action does not work but the bounds are correct.

var button = waitNode({ byText: "Submit" }, 5);

if (button) {
    if (!button.click()) {
        click(button);
    }
}


Convert node to region

var node = waitNode({ byText: "Submit" }, 5);

if (node) {
    var region = node.toRegion();

    highlight(region, "Submit button");
    click(region);
}


Check if a node is enabled

var button = waitNode({ byText: "Submit" }, 5);

if (button && button.isEnabled()) {
    button.click();
} else {
    reportError("Submit button is missing or disabled");
}


Check if a checkbox is checked

var checkbox = waitNode({ byText: "Remember me" }, 5);

if (checkbox) {
    log("Checked: " + checkbox.isChecked());
}


Click a checkbox if not checked

var checkbox = waitNode({ byText: "Remember me" }, 5);

if (checkbox && !checkbox.isChecked()) {
    checkbox.click();
}


Check if a node is scrollable

var list = waitNode({
    byClassName: "android.widget.ScrollView"
}, 5);

if (list && list.isScrollable()) {
    list.scrollForward();
}


Scroll a node forward

var list = waitNode({
    byClassName: "android.widget.ScrollView"
}, 5);

if (list) {
    list.scrollForward();
}


Scroll a node backward

var list = waitNode({
    byClassName: "android.widget.ScrollView"
}, 5);

if (list) {
    list.scrollBackward();
}


Find parent node

var child = waitNode({ byText: "Details" }, 5);

if (child) {
    var parent = child.getParent();

    if (parent) {
        highlight(parent.toRegion(), "Parent");
    }
}


Find child node

var parent = waitNode({
    byClassName: "android.widget.LinearLayout"
}, 5);

if (parent && parent.getChildCount() > 0) {
    var firstChild = parent.getChild(0);

    if (firstChild) {
        log("First child text: " + firstChild.text);
    }
}


Get node at location

var node = getNodeAt(createLocation(540, 1200));

if (node) {
    log("Node text: " + node.text);
    log("Node class: " + node.widgetClassName);
}


Wait for a node to disappear

var gone = waitVanishNode({ byText: "Loading" }, 10);

if (gone) {
    reportStep("Loading node disappeared");
} else {
    reportError("Loading node stayed visible");
}


Async node wait with race

var winner = race([
    waitNodeAsync({ byText: "Home" }, 10),
    waitNodeAsync({ byText: "Login" }, 10),
    waitNodeAsync({ byText: "Error" }, 10)
]);

if (winner) {
    log("Node winner index: " + winner.index);
    click(winner.result);
} else {
    reportError("No expected node appeared");
}


Node plus OCR fallback

var button = waitNode({ byText: "Continue" }, 3);

if (button) {
    button.click();
} else {
    var textButton = waitText("Continue", 3, false);

    if (textButton) {
        click(textButton);
    } else {
        reportError("Continue was not found by node or OCR");
    }
}


Node plus image fallback

var button = waitNode({ byText: "Continue" }, 2);

if (button) {
    button.click();
} else {
    var imageButton = waitFor("continue.png", 3);

    if (imageButton) {
        click(imageButton);
    } else {
        reportError("Continue was not found by node or image");
    }
}


Node, OCR, and image fallback

var clicked = false;

var nodeButton = waitNode({ byText: "Start" }, 2);

if (nodeButton) {
    clicked = nodeButton.click();
}

if (!clicked) {
    var textButton = waitText("Start", 2, false);

    if (textButton) {
        clicked = click(textButton);
    }
}

if (!clicked) {
    var imageButton = waitFor("start_button.png", 2);

    if (imageButton) {
        clicked = click(imageButton);
    }
}

if (!clicked) {
    reportError("Start was not found by node, OCR, or image");
}


Type into field found by node

var input = waitNode({
    byClassName: "android.widget.EditText"
}, 5);

if (input) {
    input.click();
    typeOnFocus("hello@example.com");
}


Clear and replace field text

var input = waitNode({
    byClassName: "android.widget.EditText"
}, 5);

if (input) {
    input.click();
    input.clearText();
    typeOnFocus("new value");
}


Fill login form with nodes

var email = waitNode({
    byViewId: "com.example.app:id/email"
}, 5);

var password = waitNode({
    byViewId: "com.example.app:id/password"
}, 5);

var login = waitNode({
    byText: "Login"
}, 5);

if (!email || !password || !login) {
    scriptExit("Login form was not found");
}

email.click();
typeOnFocus("user@example.com");

password.click();
typeOnFocus("password123");

login.click();


Handle permission dialog with nodes

var permission = race([
    waitNodeAsync({ byText: "Allow" }, 3),
    waitNodeAsync({ byText: "While using the app" }, 3),
    waitNodeAsync({ byText: "Only this time" }, 3)
]);

if (permission) {
    click(permission.result);
    reportStep("Permission handled");
}


Close common popups with nodes or OCR

var popup = race([
    waitNodeAsync({ byText: "Not now" }, 2),
    waitNodeAsync({ byText: "Maybe later" }, 2),
    waitTextAsync("Skip", 2)
]);

if (popup) {
    click(popup.result);
    sleep(500);
}


Dump layout for debugging

Use dumpLayout() to inspect available Accessibility nodes.

createDir("debug");

var layout = String(dumpLayout());

writeFile("debug/layout.txt", layout);
log("Layout saved");


Use dump layout to check if text exists

var layout = String(dumpLayout());

if (layout.indexOf("Continue") >= 0) {
    log("Continue appears in Accessibility layout");
} else {
    log("Continue does not appear in Accessibility layout");
}


Save OCR and layout together

createDir("debug");

writeFile("debug/ocr.txt", String(readText()));
writeFile("debug/layout.txt", String(dumpLayout()));

var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());
saveColor(full, "debug/screen.png");


Node click helper

function clickNodeText(label, seconds) {
    var node = waitNode({ byText: label }, seconds || 5);

    if (node) {
        if (node.click()) {
            reportStep("Clicked node: " + label);
            return true;
        }

        return click(node);
    }

    reportError("Node not found: " + label);
    return false;
}

clickNodeText("Continue", 5);


Node input helper

function setNodeTextByViewId(viewId, value, seconds) {
    var input = waitNode({ byViewId: viewId }, seconds || 5);

    if (!input) {
        reportError("Input not found: " + viewId);
        return false;
    }

    input.click();
    return typeOnFocus(value);
}

setNodeTextByViewId("com.example.app:id/email", "user@example.com", 5);


Smart click helper

This tries node first, then OCR, then image.

function smartClick(label, imageFile, seconds) {
    var node = waitNode({ byText: label }, 2);

    if (node && node.click()) {
        reportStep("Clicked node: " + label);
        return true;
    }

    var textMatch = waitText(label, 2, false);

    if (textMatch && click(textMatch)) {
        reportStep("Clicked OCR text: " + label);
        return true;
    }

    var imageMatch = waitFor(imageFile, seconds || 5);

    if (imageMatch && click(imageMatch)) {
        reportStep("Clicked image: " + imageFile);
        return true;
    }

    reportError("Smart click failed: " + label);
    return false;
}

smartClick("Start", "start_button.png", 5);


Detect screen with nodes and text

function detectScreen() {
    if (findNode({ byText: "Home" })) {
        return "home";
    }

    if (findNode({ byText: "Login" })) {
        return "login";
    }

    if (findText("Error")) {
        return "error";
    }

    return "unknown";
}

var state = detectScreen();

log("Screen state: " + state);


Wait for one of several screens

var winner = race([
    waitNodeAsync({ byText: "Home" }, 10),
    waitNodeAsync({ byText: "Login" }, 10),
    text("Error").contains().waitAsync(10),
    text("Try again").contains().waitAsync(10)
]);

if (!winner) {
    reportError("Unknown screen");
} else if (winner.index === 0) {
    reportStep("Home screen");
} else if (winner.index === 1) {
    reportStep("Login screen");
} else {
    reportError("Problem screen detected");
}


OCR and Accessibility troubleshooting checklist

Use this checklist when text or nodes are not found:

  • Confirm Accessibility service is enabled for node APIs.

  • Confirm screen capture is active for OCR APIs.

  • Use dumpLayout() to see if the text exists as a node.

  • Use readText() to see if OCR can read the text.

  • Use a smaller region for OCR.

  • Try .caseInsensitive().

  • Try .normalizeSpaces().

  • Try .words(), .lines(), or .blocks().

  • Use .debug(true) with TextQuery.

  • Add a short sleep(...) after screen transitions.

  • Use node plus OCR plus image fallback for important actions.


Practical notes for Part 3

  • Use nodes first for native Android UI.

  • Use OCR when nodes are unavailable or incomplete.

  • Use TextQuery for advanced OCR workflows.

  • Use regions to improve OCR speed and accuracy.

  • Use .regex() only when you really need pattern matching.

  • Use .normalizeSpaces() when OCR adds extra spaces or line breaks.

  • Use .wait(...) when the screen may still be changing.

  • Use .find(...) for immediate checks.

  • Use race(...) when several possible texts or nodes can appear.

  • Use dumpLayout() and readText() for debugging.

  • Convert Java/Kotlin string-like values using String(...) before using JavaScript string methods.

ArgusJS Examples and Recipes - Part 4

This part covers keyboard input, Argus Keyboard, app helpers, project files, persistent variables, and dialogs.

Replace package names, view IDs, labels, file paths, and values with the ones used in your project.


Keyboard input overview

Use these APIs for text:

  • type(...) types into the focused field.

  • typeOnFocus(...) replaces focused field text.

  • imeType(...) types through Argus Keyboard.

  • setText(...) sets clipboard text only.

  • paste(...) pastes into the focused field.

For reliable typing, enable Argus Keyboard and use IME APIs.


Clipboard text

setText("hello@example.com");

setClipboardText("hello@example.com");

var value = getClipboardText();
log("Clipboard: " + value);


Paste text

setClipboardText("hello@example.com");
paste();

Paste temporary text:

paste("Temporary pasted text");


Type into focused field

type("hello@example.com");

Replace focused field text:

typeOnFocus("hello@example.com");


Click field and type

var field = waitNode({
    byClassName: "android.widget.EditText"
}, 5);

if (field) {
    field.click();
    type("hello@example.com");
}


Click field and replace text

var field = waitNode({
    byClassName: "android.widget.EditText"
}, 5);

if (field) {
    field.click();
    typeOnFocus("hello@example.com");
}


Basic text editing

clearText();
selectAll();
copy();
cut();
paste();


Keyboard actions

pressEnter();
pressBackspace();
pressBackspace(5);
pressDelete();

pressLeft();
pressLeft(3);
pressRight();
pressRight(3);

Aliases:

backspace(2);
deleteForward(1);
cursorLeft(3);
cursorRight(3);

Input actions:

done();
search();
send();
next();


Argus Keyboard basics

Show, hide, toggle, and reset Argus Keyboard:

argusKeyboardShow();
argusKeyboardHide();
argusKeyboardToggle();
argusKeyboardReset();

Check state:

if (isArgusKeyboardActive()) {
    log("Argus Keyboard is active");
}

if (isArgusKeyboardVisible()) {
    log("Argus Keyboard is visible");
}

Get mode:

var mode = argusKeyboardMode();
log("Keyboard mode: " + mode);


Set keyboard mode

setArgusKeyboardMode("full");
setArgusKeyboardMode("stealth");
setArgusKeyboardMode("hidden");

Accepted aliases:

setArgusKeyboardMode("show");
setArgusKeyboardMode("visible");

setArgusKeyboardMode("handle");
setArgusKeyboardMode("tiny");

setArgusKeyboardMode("hide");


Use Argus Keyboard safely

Use try/finally so keyboard state resets even if the script fails.

try {
    argusKeyboardShow();
    sleep(500);

    var field = waitNode({
        byClassName: "android.widget.EditText"
    }, 5);

    if (!field) {
        scriptExit("Input field was not found");
    }

    field.click();

    if (!imeType("Hello from ArgusJS")) {
        reportError("IME typing failed");
    }

    imeEnter();
} finally {
    argusKeyboardReset();
}


IME typing

imeType("hello@example.com");

imeBackspace(1);
imeDeleteForward(1);

imeEnter();
imeDone();
imeSearch();
imeSend();
imeNext();


IME cursor and selection

imeType("hello");
imeCursorLeft(2);
imeType("X");
imeCursorRight(2);

Select all and replace:

imeSelectAll();
imeType("new value");

Clear text:

imeClearText();

Paste:

setClipboardText("Text from clipboard");
imePaste();

Temporary IME paste:

imePaste("Temporary IME paste text");


Fill one input field with Argus Keyboard

try {
    argusKeyboardShow();
    sleep(500);

    var input = waitNode({
        byClassName: "android.widget.EditText"
    }, 5);

    if (!input) {
        scriptExit("Input field was not found");
    }

    input.click();
    imeType("hello@example.com");
    imeDone();
} finally {
    argusKeyboardReset();
}


Fill form with nodes and IME

try {
    argusKeyboardShow();
    sleep(500);

    var nameField = waitNode({
        byViewId: "com.example.app:id/name"
    }, 5);

    var emailField = waitNode({
        byViewId: "com.example.app:id/email"
    }, 5);

    var submit = waitNode({
        byText: "Submit"
    }, 5);

    if (!nameField || !emailField || !submit) {
        scriptExit("Form fields were not found");
    }

    nameField.click();
    typeOnFocus("Ahmed");

    emailField.click();
    typeOnFocus("ahmed@example.com");

    submit.click();
} finally {
    argusKeyboardReset();
}


Fill form with OCR labels

try {
    argusKeyboardShow();
    sleep(500);

    var emailLabel = text("Email")
        .contains()
        .wait(5);

    if (!emailLabel) {
        scriptExit("Email label not found");
    }

    click(emailLabel.below(80));
    typeOnFocus("user@example.com");

    var passwordLabel = text("Password")
        .contains()
        .wait(5);

    if (!passwordLabel) {
        scriptExit("Password label not found");
    }

    click(passwordLabel.below(80));
    typeOnFocus("password123");

    var login = text("Login")
        .exact()
        .wait(5);

    if (login) {
        click(login);
    }
} finally {
    argusKeyboardReset();
}


Reusable input helper by view ID

function setNodeTextByViewId(viewId, value, seconds) {
    var input = waitNode({ byViewId: viewId }, seconds || 5);

    if (!input) {
        reportError("Input not found: " + viewId);
        return false;
    }

    input.click();
    return typeOnFocus(value);
}

setNodeTextByViewId("com.example.app:id/email", "user@example.com", 5);


Reusable input helper by visible label

function setTextBelowLabel(label, value, seconds) {
    var labelMatch = text(label)
        .contains()
        .wait(seconds || 5);

    if (!labelMatch) {
        reportError("Label not found: " + label);
        return false;
    }

    click(labelMatch.below(80));
    return typeOnFocus(value);
}

setTextBelowLabel("Email", "user@example.com", 5);


App helpers overview

Common app APIs:

  • startApp(...)

  • launchApp(...)

  • ensureApp(...)

  • waitApp(...)

  • getForegroundApp()

  • isAppInstalled(...)

  • isAppLaunchable(...)

  • closeApp(...)

  • closeAppEx(...)

  • waitAppVanish(...)


Start app and wait

startApp("com.example.app");

if (waitApp("com.example.app", 5)) {
    reportStep("App is foreground");
} else {
    reportError("App did not open");
}


Launch app with rich result

var result = launchApp("com.example.app", {
    wait: true,
    timeout: 8,
    stableMs: 500,
    retries: 1,
    debug: true
});

if (result.success) {
    reportStep("App launched");
} else {
    reportError("Launch failed", result.error || result.warning);
}


Ensure app is open

var result = ensureApp("com.example.app", {
    timeout: 8,
    retries: 2,
    retryDelayMs: 800
});

if (!result.success) {
    scriptExit("Could not open target app.");
}


Check app state

var foreground = getForegroundApp();
log("Foreground app: " + foreground);

if (!isAppInstalled("com.example.app")) {
    reportError("App is not installed");
}

if (!isAppLaunchable("com.example.app")) {
    reportError("App has no launch activity");
}

var state = getAppState("com.example.app");

log("Installed: " + state.installed);
log("Launchable: " + state.launchable);
log("Foreground: " + state.foreground);


Wait for foreground

var result = waitForeground({
    packageName: "com.example.app",
    timeout: 8,
    stableMs: 500,
    debug: true
});

if (result.success) {
    reportStep("App reached foreground");
} else {
    reportError("Foreground wait failed", result.error || result.warning);
}


Close app

var ok = closeApp("com.example.app");

if (!ok) {
    reportError("Close app failed");
}

Rich close result:

var result = closeAppEx({
    packageName: "com.example.app",
    pressHome: true,
    killBackground: true,
    verifyVanish: true,
    timeout: 5
});

if (result.success) {
    reportStep("App closed");
} else {
    reportError("App close was not verified", result.warning || result.error);
}


Wait for app to vanish

if (waitAppVanish("com.example.app", 5)) {
    reportStep("App vanished");
} else {
    reportError("App is still foreground");
}


Safe app open helper

function openTargetApp(packageName) {
    var result = ensureApp(packageName, {
        timeout: 8,
        retries: 2,
        retryDelayMs: 800
    });

    if (!result.success) {
        reportError("App did not open: " + packageName);
        return false;
    }

    reportStep("App opened: " + packageName);
    return true;
}

if (!openTargetApp("com.example.app")) {
    scriptExit("Cannot continue without target app");
}


Open app and handle permission popup

var packageName = "com.example.app";

if (!ensureApp(packageName, { timeout: 8 }).success) {
    scriptExit("App did not open");
}

var permission = race([
    waitNodeAsync({ byText: "Allow" }, 3),
    waitNodeAsync({ byText: "While using the app" }, 3),
    waitTextAsync("Allow", 3)
]);

if (permission) {
    click(permission.result);
    sleep(500);
    reportStep("Permission handled");
}


Open app, close popup, and continue

var packageName = "com.example.app";

if (!ensureApp(packageName, { timeout: 8 }).success) {
    scriptExit("App did not open");
}

var popup = race([
    waitNodeAsync({ byText: "Not now" }, 2),
    waitNodeAsync({ byText: "Maybe later" }, 2),
    waitTextAsync("Skip", 2),
    waitAsync("close.png", 2)
]);

if (popup) {
    click(popup.result);
    sleep(500);
}

var start = waitFor("start_button.png", 5);

if (start) {
    click(start);
}


File and project API overview

File paths are project-relative. Do not use ../.

Common file APIs:

  • scriptPath()

  • scanDir(...)

  • createDir(...)

  • readFile(...)

  • writeFile(...)

  • appendFile(...)

  • deleteFile(...)

  • deleteDirectory(...)

  • unzip(...)

  • save(...)

  • saveColor(...)


Project path and folders

var path = scriptPath();
log("Script path: " + path);

createDir("logs");
createDir("reports");
createDir("debug");
createDir("data");

Scan folders:

var files = scanDir("");

for (var i = 0; i < files.length; i++) {
    log(files[i]);
}

var images = scanDir("images");

for (var j = 0; j < images.length; j++) {
    log("Image: " + images[j]);
}


Read and write text files

Read:

var config = readFile("data/config.txt");

if (config !== null) {
    log(config);
} else {
    reportError("Config file could not be read");
}

Write:

var ok = writeFile("data/output.txt", "Hello from ArgusJS");

if (!ok) {
    reportError("Could not write file");
}

Append:

createDir("logs");

appendFile("logs/run.log", "Script started\n");
appendFile("logs/run.log", "Script finished\n");


JSON config file

Create config:

createDir("data");

var config = {
    packageName: "com.example.app",
    timeout: 8,
    mode: "safe"
};

writeFile("data/config.json", JSON.stringify(config, null, 2));

Read config:

var textValue = readFile("data/config.json");

if (!textValue) {
    scriptExit("Missing data/config.json");
}

var config = JSON.parse(String(textValue));

log("Package: " + config.packageName);
log("Timeout: " + config.timeout);


Use config to open an app

var textValue = readFile("data/config.json");

if (!textValue) {
    scriptExit("Missing data/config.json");
}

var config = JSON.parse(String(textValue));

if (!ensureApp(config.packageName, { timeout: config.timeout }).success) {
    scriptExit("Configured app did not open");
}


Delete file or folder

var deletedFile = deleteFile("logs/old.log");

if (deletedFile) {
    log("Old log deleted");
}

var deletedFolder = deleteDirectory("debug/old");

if (deletedFolder) {
    log("Old debug folder deleted");
}


Unzip project file

var ok = unzip("data/assets.zip", "assets");

if (ok) {
    reportStep("Zip extracted");
} else {
    reportError("Zip extraction failed");
}


Save screenshot files

Grayscale:

createDir("debug");

var area = createRegion(100, 300, 500, 500);
save(area, "debug/area_gray.png");

Color:

createDir("debug");

var area = createRegion(100, 300, 500, 500);
saveColor(area, "debug/area_color.png");

Full screen:

createDir("debug");

var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());
saveColor(full, "debug/full_screen.png");


Save failure debug package

function saveDebug(name) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

    saveColor(full, "debug/" + name + "_screen.png");
    saveReport("debug/" + name + "_report.json");
    writeFile("debug/" + name + "_ocr.txt", String(readText()));
    writeFile("debug/" + name + "_layout.txt", String(dumpLayout()));
}

var done = waitText("Done", 5, false);

if (!done) {
    reportError("Done was not found");
    saveDebug("done_not_found");
}


Persistent variables overview

Persistent variables are saved per project.

Use them for:

  • counters

  • preferences

  • last-used values

  • simple JSON-like settings

Do not store passwords, tokens, or private secrets.


Save and load variable

saveVar("runCount", 1);

var count = loadVar("runCount", 0);
log("Run count: " + count);

Increment counter:

var count = loadVar("runCount", 0);
count = Number(count) + 1;

saveVar("runCount", count);

log("Run count: " + count);


Save and load settings

var settings = {
    retries: 3,
    mode: "safe",
    enabled: true,
    labels: ["A", "B"]
};

saveVar("settings", settings);

Load with defaults:

var settings = loadVar("settings", {
    retries: 3,
    mode: "safe",
    enabled: true,
    labels: ["A", "B"]
});

log("Mode: " + settings.mode);
log("Retries: " + settings.retries);

Update settings:

settings.retries = Number(settings.retries) + 1;
settings.lastRun = new Date().toString();

saveVar("settings", settings);


Remove or list variables

removeVar("runCount");

var keys = listVars();

for (var i = 0; i < keys.length; i++) {
    log("Saved key: " + keys[i]);
}

Clear all variables:

clearVars();


Save last selected option

var lastMode = loadVar("lastMode", "safe");

var mode = choose("Select mode", ["fast", "safe", "debug"], lastMode);

if (mode) {
    saveVar("lastMode", mode);
    log("Saved mode: " + mode);
}


Save simple history list

var history = loadVar("history", []);

history.push({
    time: new Date().toString(),
    result: "success"
});

while (history.length > 10) {
    history.shift();
}

saveVar("history", history);


Dialog overview

Common dialog APIs:

  • confirm(...)

  • prompt(...)

  • choose(...)

  • multiChoose(...)

  • dialogAlert(...)

  • dialogInit(...)

  • dialogShow(...)

  • dialogShowFullScreen(...)

  • dialog("Title")


Simple dialogs

Confirm:

if (confirm("Start automation?", "ArgusJS")) {
    reportStep("User confirmed");
} else {
    scriptExit("User cancelled");
}

Alert:

dialogAlert("Script finished.", "ArgusJS");

Prompt:

var name = prompt("Enter target name", "", "ArgusJS");

if (name) {
    log("Target: " + name);
} else {
    scriptExit("No target entered");
}

Choose one:

var mode = choose("Select mode", ["fast", "safe", "debug"], "safe");

if (mode) {
    log("Mode: " + mode);
}

Choose multiple:

var selected = multiChoose(
    "Select checks",
    ["image", "ocr", "node", "color"],
    ["image", "ocr"]
);

if (selected) {
    log("Selected: " + selected.join(", "));
}


Structured dialog with input

dialogInit("Run Options");

addInput("target", "Target name", "", "text", true);
addDropdown("mode", ["fast", "safe"], "safe");
addCheckbox("notify", "Notify when done", true);

var options = dialogShow({
    positiveButton: "Run",
    negativeButton: "Cancel"
});

if (options && options.target) {
    reportStep("Target: " + options.target);
    reportStep("Mode: " + options.mode);
} else {
    scriptExit("Target name is required");
}


Structured dialog with password

dialogInit("Login");

addInput("username", "Username", "", "text", true);
addInput("password", "Password", "", "password", true);

var result = dialogShow({
    positiveButton: "Login",
    negativeButton: "Cancel"
});

if (!result) {
    scriptExit("Login cancelled");
}

log("Username: " + result.username);


Dialog with checkbox, switch, and slider

dialogInit("Options");

addCheckbox("saveReport", "Save report", true);
addSwitch("debugMode", "Debug mode", false);
addSlider("speed", 1, 10, 5);

var result = dialogShow({
    positiveButton: "Start",
    negativeButton: "Cancel"
});

if (!result) {
    scriptExit("Cancelled");
}

log("Save report: " + result.saveReport);
log("Debug mode: " + result.debugMode);
log("Speed: " + result.speed);


Dialog with multi-select

dialogInit("Checks");

addMultiSelect(
    "checks",
    ["Image", "OCR", "Node", "Color"],
    ["Image", "OCR"]
);

var result = dialogShow({
    positiveButton: "Run",
    negativeButton: "Cancel"
});

if (result) {
    log("Checks: " + result.checks.join(", "));
}


Dialog with radio group

dialogInit("Mode");

addRadioGroup(
    "mode",
    "Select mode",
    ["fast", "safe", "debug"],
    "safe",
    true
);

var result = dialogShow({
    positiveButton: "Start",
    negativeButton: "Cancel"
});

if (result) {
    log("Mode: " + result.mode);
}


Full-screen dialog

dialogInit("Long Form");

addInput("username", "Username", "", "text", true);
addInput("password", "Password", "", "password", true);
addSwitch("remember", "Remember", false);
addSlider("speed", 1, 10, 5);
addMultiSelect("features", ["OCR", "Image", "Node", "Color"], ["OCR", "Image"]);

var result = dialogShowFullScreen({
    positiveButton: "Start",
    negativeButton: "Cancel",
    cancelBehavior: "returnNull"
});

if (!result) {
    scriptExit("Cancelled");
}

log("Username: " + result.username);
log("Speed: " + result.speed);


Dialog with timeout

dialogInit("Continue?");

addLabel("Automation will start soon.");

var result = dialogShow({
    positiveButton: "Start",
    negativeButton: "Cancel",
    timeoutSeconds: 10,
    timeoutBehavior: "returnNull"
});

if (!result) {
    scriptExit("No response before timeout");
}


Fluent dialog builder

var result = dialog("Route")
    .text("name", "Route Name", "", { required: true })
    .dropdown("pickup", "Pickup", ["Office", "Home"], "Office", { required: true })
    .comboBox("drop", "Drop", ["Work", "LMS"], "Work", { allowCustomValue: true })
    .timePicker("time", "Target Time", "16:03", { required: true })
    .show({
        positiveButton: "Start",
        negativeButton: "Cancel"
    });

if (result) {
    log("Route: " + result.name);
    log("Pickup: " + result.pickup);
    log("Drop: " + result.drop);
    log("Time: " + result.time);
}


Dialog using saved defaults

var previous = loadVar("runOptions", {
    target: "",
    mode: "safe",
    notify: true
});

dialogInit("Run Options");

addInput("target", "Target name", previous.target, "text", true);
addDropdown("mode", ["fast", "safe", "debug"], previous.mode);
addCheckbox("notify", "Notify when done", previous.notify);

var options = dialogShow({
    positiveButton: "Run",
    negativeButton: "Cancel"
});

if (!options) {
    scriptExit("Cancelled");
}

saveVar("runOptions", options);


Use dialog result to choose a flow

var mode = choose("Select mode", ["login", "check", "logout"], "check");

if (!mode) {
    scriptExit("No mode selected");
}

if (mode === "login") {
    reportStep("Running login flow");
} else if (mode === "check") {
    reportStep("Running check flow");
} else if (mode === "logout") {
    reportStep("Running logout flow");
}


Complete example: configurable app opener

clearReport();

var defaults = loadVar("appOpenOptions", {
    packageName: "com.example.app",
    timeout: 8,
    debug: false
});

dialogInit("Open App");

addInput("packageName", "Package Name", defaults.packageName, "text", true);
addSlider("timeout", 1, 30, defaults.timeout);
addCheckbox("debug", "Debug mode", defaults.debug);

var options = dialogShow({
    positiveButton: "Open",
    negativeButton: "Cancel"
});

if (!options) {
    scriptExit("Cancelled");
}

saveVar("appOpenOptions", options);

var result = ensureApp(options.packageName, {
    timeout: Number(options.timeout),
    retries: 2,
    debug: options.debug
});

if (result.success) {
    reportStep("App opened: " + options.packageName);
} else {
    reportError("App failed to open", result.error || result.warning);
}


Complete example: file-based configuration

Create this file in your project:

data/config.json

Example content:

{
  "packageName": "com.example.app",
  "startText": "Start",
  "timeout": 8
}

Script:

clearReport();

var configText = readFile("data/config.json");

if (!configText) {
    scriptExit("Missing data/config.json");
}

var config = JSON.parse(String(configText));

if (!ensureApp(config.packageName, { timeout: config.timeout }).success) {
    scriptExit("App did not open");
}

var start = waitText(config.startText, config.timeout, false);

if (start) {
    click(start);
    reportStep("Clicked: " + config.startText);
} else {
    reportError("Start text was not found");
}


Troubleshooting checklist

Keyboard:

  • Confirm Argus Keyboard is enabled and active.

  • Confirm the field is focused before typing.

  • Try typeOnFocus(...) instead of type(...).

  • Try imeType(...) after argusKeyboardShow().

  • Use paste(text) when typing is blocked.

  • Always call argusKeyboardReset() in finally.

App helpers:

  • Confirm the package name is correct.

  • Confirm the app is installed and launchable.

  • Use getForegroundApp() to check what is open.

  • Use ensureApp(...) for stronger launch logic.

  • Handle permission dialogs and popups after app launch.

Files:

  • Use project-relative paths only.

  • Do not use ../.

  • Create folders before writing files.

  • Convert string-like values with String(...).

  • Check return values from write/save APIs.

  • Do not read protected .argusx files with readFile(...).

Dialogs:

  • Use dialogInit(...) before structured fields.

  • Use dialogShow(...) after adding fields.

  • Check for null when the user cancels.

  • Use saved defaults with saveVar(...).

  • Keep dialog field IDs simple and unique.


Practical notes for Part 4

  • Prefer Argus Keyboard and IME APIs for reliable typing.

  • Use typeOnFocus(...) when replacing field text.

  • Use setText(...) only for clipboard behavior.

  • Use ensureApp(...) for app launch flows.

  • Use project-relative file paths.

  • Save debug screenshots, OCR, layout, and reports when a flow fails.

  • Use persistent variables for preferences and counters.

  • Do not store secrets in persistent variables.

  • Use dialogs for configurable scripts.

  • Always check dialog results because cancel usually returns null.

ArgusJS Examples and Recipes - Part 5

This part covers async jobs, race(...), awaitAll(...), timers, watchers, PixelSet, PixelDNA, ML Kit helpers, notifications, HTTP, downloads, and email.

Replace image names, text labels, regions, URLs, email addresses, and API endpoints with values that match your project.


Async overview

Async APIs return an AsyncResult ticket.

Use:

  • race([...]) for the first successful result.

  • awaitAll([...]) when you need all results in input order.

Common async APIs:

  • waitAsync(...)

  • waitTextAsync(...)

  • waitNodeAsync(...)

  • waitColorAsync(...)

  • findPixelSetAsync(...)

  • waitPixelDNAAsync(...)

  • waitVanishTextAsync(...)

  • waitVanishNodeAsync(...)


Race between image and text

var winner = race([
    waitAsync("home.png", 10),
    waitTextAsync("Login", 10)
]);

if (winner) {
    log("Winner index: " + winner.index);
    click(winner.result);
} else {
    reportError("No image or text appeared");
}


Race between success and error

var result = race([
    waitAsync("success.png", 20),
    waitTextAsync("Network error", 20),
    waitTextAsync("Try again", 20)
]);

if (result && result.index === 0) {
    reportStep("Success screen detected");
} else if (result) {
    reportError("Error state detected. Winner index: " + result.index);
} else {
    reportError("No success or error state detected");
}


Race between several text states

var winner = race([
    text("Home").contains().waitAsync(10),
    text("Login").contains().waitAsync(10),
    text("Error").contains().waitAsync(10),
    text("Try again").contains().waitAsync(10)
]);

if (!winner) {
    reportError("Unknown screen");
} else if (winner.index === 0) {
    reportStep("Home screen detected");
} else if (winner.index === 1) {
    reportStep("Login screen detected");
} else {
    reportError("Problem screen detected");
}


Race between image, text, and node

var winner = race([
    waitAsync("home.png", 10),
    waitTextAsync("Home", 10),
    waitNodeAsync({ byText: "Home" }, 10)
]);

if (winner) {
    reportStep("Home detected. Winner index: " + winner.index);
} else {
    reportError("Home was not detected");
}


Race for optional popup

var popup = race([
    waitNodeAsync({ byText: "Not now" }, 2),
    waitTextAsync("Skip", 2),
    waitAsync("close.png", 2)
]);

if (popup) {
    click(popup.result);
    sleep(500);
}


Race for permission dialog

var permission = race([
    waitNodeAsync({ byText: "Allow" }, 3),
    waitNodeAsync({ byText: "While using the app" }, 3),
    waitTextAsync("Allow", 3)
]);

if (permission) {
    click(permission.result);
    reportStep("Permission handled");
}


Await all checks

Use awaitAll(...) when you want all results in the original order.

var results = awaitAll([
    text("Confirm").contains().existsAsync(2),
    text("Cancel").contains().existsAsync(2),
    text("\\d+\\s+minutes?\\s+away").regex().lines().findAllAsync()
]);

log("Confirm: " + results[0]);
log("Cancel: " + results[1]);
log("Ride matches: " + results[2].length);


Await all image checks

var results = awaitAll([
    waitAsync("home.png", 2),
    waitAsync("login.png", 2),
    waitAsync("error.png", 2)
]);

if (results[0]) {
    reportStep("Home found");
}

if (results[1]) {
    reportStep("Login found");
}

if (results[2]) {
    reportError("Error image found");
}


Wait until custom condition

var result = waitUntil(function() {
    var ok = existsText("Ready", 0, false);

    if (ok) {
        return ok;
    }

    var button = exists("ready_button.png", 0);

    if (button) {
        return button;
    }

    return null;
}, 10, 300);

if (result) {
    click(result);
} else {
    reportError("Ready state was not found");
}


Retry an action

var result = retry(3, function() {
    var button = waitFor("retry_button.png", 2);

    if (button) {
        click(button);
        sleep(500);
    }

    return waitText("Done", 2, false);
}, 500);

if (result) {
    reportStep("Action succeeded after retry");
} else {
    reportError("Action failed after retries");
}


Repeat until condition

var result = repeatUntil(
    function() {
        scroll(540, 1600, "up", 700, 500);
        sleep(500);
    },
    function() {
        return findText("Target item");
    },
    8,
    500
);

if (result) {
    click(result);
} else {
    reportError("Target item was not found");
}


Timer overview

Timer APIs:

  • setInterval(...)

  • setTimeout(...)

  • runAsync(...)

  • clearInterval(...)

  • getInterval(...)

  • getIntervals(...)

  • clearAllIntervals(...)

Timers are cleaned up when the script ends.


Run code later

setTimeout(function() {
    toast("Timeout fired");
}, 2000);

sleep(3000);


Repeat code

var id = setInterval(function() {
    log("Tick");
}, 1000);

sleep(5000);
clearInterval(id);


Inspect interval

var id = setInterval(function() {
    return "last result";
}, 1000);

sleep(2500);

var info = getInterval(id);

if (info) {
    log("Run count: " + info.runCount);
    log("Last result: " + info.lastResult);
}

clearInterval(id);


List intervals

var idA = setInterval(function() {
    log("A");
}, 1000);

var idB = setInterval(function() {
    log("B");
}, 1000);

sleep(2500);

var intervals = getIntervals();

for (var i = 0; i < intervals.length; i++) {
    log("Timer ID: " + intervals[i].id + " type=" + intervals[i].type);
}

clearInterval(idA);
clearInterval(idB);


Clear all intervals

setInterval(function() {
    log("A");
}, 1000);

setInterval(function() {
    log("B");
}, 1000);

sleep(3000);
clearAllIntervals();


Run async callback

runAsync(function() {
    sleep(1000);
    log("Async work finished");
});

log("Main script continues");
sleep(2000);


Watcher overview

Watchers repeat named checks.

Common APIs:

  • watch(...)

  • getWatcher(...)

  • getWatchers(...)

  • stopWatcher(...)

  • clearWatchers(...)


Watch for error text

watch("errorWatcher", function() {
    var error = existsText("Error", 0, false);

    if (error) {
        reportError("Error text appeared");
        return true;
    }

    return false;
}, 1000);

sleep(10000);
stopWatcher("errorWatcher");


Watch for popup and close it

watch("popupCloser", function() {
    var popup = existsText("Not now", 0, false);

    if (popup) {
        click(popup);
        reportStep("Popup closed");
        return true;
    }

    return false;
}, 1000);

sleep(10000);
stopWatcher("popupCloser");


Inspect watcher

watch("statusWatcher", function() {
    return existsText("Ready", 0, false);
}, 1000);

sleep(3000);

var info = getWatcher("statusWatcher");

if (info) {
    log("Watcher runs: " + info.runCount);
    log("Last result: " + info.lastResult);
}

stopWatcher("statusWatcher");


Clear watchers

watch("a", function() {
    return false;
}, 1000);

watch("b", function() {
    return false;
}, 1000);

sleep(2000);
clearWatchers();


PixelSet overview

PixelSet captures a small visual fingerprint from a target area.

Use it when:

  • you do not want to manage image files

  • a small visual target is stable

  • OCR and nodes are unavailable

A saved raw PixelSet string searches in grayscale mode.


Capture PixelSet

var target = createLocation(540, 1200);
var pixelSet = getPixelSet(target, 40, false);

if (pixelSet) {
    log("PixelSet size: " + pixelSet.width + "x" + pixelSet.height);
    saveVar("startButtonPixelSet", String(pixelSet));
}


Find saved PixelSet

var saved = loadVar("startButtonPixelSet", "");

if (saved) {
    var found = findPixelSet(saved, 0.9);

    if (found) {
        click(found);
    } else {
        reportError("Saved PixelSet was not found");
    }
}


Search PixelSet in a region

var saved = loadVar("startButtonPixelSet", "");
var area = createRegion(0, 900, getScreenWidth(), 900);

if (saved) {
    var found = findPixelSetIn(area, saved, 0.9);

    if (found) {
        click(found);
    }
}


Find all PixelSet matches

var saved = loadVar("coinPixelSet", "");

if (saved) {
    var matches = findAllPixelSet(saved, 0.9);

    for (var i = 0; i < matches.length; i++) {
        click(matches[i]);
        sleep(100);
    }
}


PixelSet with awaitAll

var savedA = loadVar("targetA", "");
var savedB = loadVar("targetB", "");

var results = awaitAll([
    findPixelSetAsync(savedA, 0.9),
    findPixelSetAsync(savedB, 0.9)
]);

if (results[0]) {
    reportStep("Target A found");
}

if (results[1]) {
    reportStep("Target B found");
}


PixelDNA overview

PixelDNA uses selected relative pixel color samples. It is useful for small stable visual targets.

For hardcoded DNA, use:

{
    size: 40,
    dna: [
        { rx: 0, ry: 0, r: 255, g: 0, b: 0, tolerance: 10 }
    ]
}


Capture and wait for PixelDNA

var target = createLocation(540, 1200);
var dna = getPixelDNA(target, 1, 40, 10);

if (dna) {
    var found = waitPixelDNA(dna, 5);

    if (found) {
        click(found);
    }
}


Find PixelDNA in region

var dna = getPixelDNA(createLocation(540, 1200), 1, 40, 10);
var area = createRegion(0, 900, getScreenWidth(), 900);

if (dna) {
    var found = waitPixelDNAIn(area, dna, 5);

    if (found) {
        click(found);
    }
}


Wait for PixelDNA to vanish

var dna = getPixelDNA(createLocation(540, 1200), 1, 40, 10);

if (dna) {
    click(540, 1200);

    if (waitVanishPixelDNA(dna, 5)) {
        reportStep("Target disappeared");
    }
}


Hardcoded PixelDNA

var dna = {
    size: 40,
    dna: [
        { rx: 0, ry: 0, r: 255, g: 0, b: 0, tolerance: 10 },
        { rx: 10, ry: 0, r: 0, g: 255, b: 0, tolerance: 10 },
        { rx: 0, ry: 10, r: 0, g: 0, b: 255, tolerance: 10 }
    ]
};

var found = findPixelDNA(dna);

if (found) {
    click(found);
}


PixelDNA with race

var dnaA = getPixelDNA(createLocation(300, 1000), 1, 40, 10);
var dnaB = getPixelDNA(createLocation(700, 1000), 1, 40, 10);

if (dnaA && dnaB) {
    var winner = race([
        waitPixelDNAAsync(dnaA, 10),
        waitPixelDNAAsync(dnaB, 10)
    ]);

    if (winner) {
        click(winner.result);
    }
}


ML Kit overview

ML Kit helpers are best-effort detectors.

Use them as helper signals, not replacements for precise automation.

Available families:

  • barcode and QR scanning

  • image labeling

  • object detection

  • object tracking


Wait for QR code

var qr = waitBarcode({
    formats: "QR_CODE"
}, 5);

if (qr) {
    log("QR value: " + qr.rawValue);
    click(qr);
} else {
    reportError("QR code was not found");
}


Find all barcodes

var codes = findBarcodes({
    formats: ["QR_CODE", "EAN_13"]
});

for (var i = 0; i < codes.length; i++) {
    log(codes[i].format + ": " + codes[i].rawValue);
}


Scan barcode in region

var area = createRegion(0, 300, getScreenWidth(), 900);

var qr = findBarcode({
    formats: "QR_CODE",
    region: area
});

if (qr) {
    highlight(qr, "QR");
}


Label screen

var labels = labelScreen({
    minConfidence: 0.6,
    maxResults: 5
});

for (var i = 0; i < labels.length; i++) {
    log(labels[i].text + ": " + labels[i].confidence);
}


Label a region

var card = createRegion(0, 500, getScreenWidth(), 800);

var labels = labelRegion(card, {
    minConfidence: 0.3,
    maxResults: 10
});

for (var i = 0; i < labels.length; i++) {
    log(labels[i].text + ": " + labels[i].confidence);
}


Check for label

if (hasLabel("Food", 0.6)) {
    log("Screen may contain food");
}


Detect objects

var objects = detectObjects({
    classify: true,
    multipleObjects: true,
    minConfidence: 0.5
});

log("Objects found: " + objects.length);

for (var i = 0; i < objects.length; i++) {
    highlight(objects[i], 1);
    log("Object label: " + objects[i].label);
}


Detect objects in region

var area = createRegion(0, 500, getScreenWidth(), 1200);

var objects = detectObjectsIn(area, {
    classify: true,
    multipleObjects: true,
    minConfidence: 0.3
});

if (objects.length > 0) {
    click(objects[0]);
}


Object detection as OCR helper

var area = createRegion(0, 500, getScreenWidth(), 1200);

var objects = detectObjectsIn(area, {
    classify: true,
    multipleObjects: true,
    minConfidence: 0.1
});

if (objects.length > 0) {
    var obj = objects[0];
    var objRegion = createRegion(obj.x, obj.y, obj.w, obj.h);

    var confirm = text("Confirm")
        .contains()
        .in(objRegion)
        .find();

    if (confirm) {
        click(confirm);
    }
}


Wait for object

var obj = waitObject({
    classify: true,
    multipleObjects: true,
    minConfidence: 0.3
}, 5);

if (obj) {
    highlight(obj, "Object");
} else {
    log("No object found");
}


Object tracking

var trackingId = startObjectTracking({
    classify: true,
    multipleObjects: true,
    minConfidence: 0.3,
    intervalMs: 1000
});

log("Tracking ID: " + trackingId);

sleep(5000);

stopObjectTracking(trackingId);


Single object tracking pass

var objects = trackObjects({
    classify: true,
    multipleObjects: true,
    minConfidence: 0.3
});

log("Objects tracked: " + objects.length);


Progress notification

notifyProgress("ArgusJS", "Starting...", 0, 100, true);

for (var i = 1; i <= 100; i++) {
    notifyProgress("ArgusJS", "Progress " + i + "%", i, 100, true);
    sleep(100);
}

clearNotification();


Progress notification for steps

notifyProgress("Long task", "Preparing", 0, 5, true);

for (var step = 1; step <= 5; step++) {
    reportStep("Running step " + step);
    notifyProgress("Long task", "Step " + step + " of 5", step, 5, true);
    sleep(1000);
}

notifyProgress("Long task", "Done", 5, 5, false);


Reporting

clearReport();

reportStep("Start");
reportInfo("Checking home screen");

var home = waitFor("home.png", 5);

if (home) {
    reportStep("Home found");
} else {
    reportError("Home not found");
}

saveReport("reports/latest.json");


HTTP overview

Network APIs require network access to be enabled in settings.

Common APIs:

  • httpGet(...)

  • httpPost(...)

  • httpPut(...)

  • httpDelete(...)

  • httpRequest(...)

  • httpDownload(...)

  • openUrl(...)

HTTP responses use this shape:

{
  status: 200,
  body: "...",
  headers: {},
  error: null
}

Blocked or failed requests usually return:

{
  status: 0,
  body: null,
  headers: {},
  error: "message"
}


Safe HTTP GET

var res = httpGet("https://example.com/status", {
    headers: {
        "Accept": "application/json"
    }
});

if (res && res.status === 200) {
    log(res.body);
} else {
    reportError("HTTP failed: " + (res ? res.error : "no response"));
}


Safe HTTP POST

var res = httpPost(
    "https://example.com/api/run",
    {
        status: "done",
        count: 3
    },
    {
        contentType: "application/json",
        headers: {
            "Accept": "application/json"
        }
    }
);

if (res && res.status >= 200 && res.status < 300) {
    reportStep("POST succeeded");
} else {
    reportError("POST failed", res ? res.error : "no response");
}


General HTTP request

var res = httpRequest({
    url: "https://example.com/api/items",
    method: "PUT",
    data: {
        name: "Updated"
    },
    contentType: "application/json",
    headers: {
        "Accept": "application/json"
    }
});

if (res && res.status === 200) {
    log(res.body);
}


HTTP DELETE

var res = httpDelete(
    "https://example.com/api/item/123",
    null,
    {
        headers: {
            "Accept": "application/json"
        }
    }
);

if (res && res.status >= 200 && res.status < 300) {
    reportStep("Delete succeeded");
} else {
    reportError("Delete failed", res ? res.error : "no response");
}


Download file

createDir("downloads");

var ok = httpDownload(
    "https://example.com/file.png",
    "downloads/file.png"
);

if (!ok) {
    reportError("Download failed");
}


Open URL

openUrl("https://example.com");


Upload run status example

var status = {
    battery: getBatteryLevel(),
    charging: isCharging(),
    network: getNetworkType(),
    foreground: getForegroundApp(),
    screen: getScreenWidth() + "x" + getScreenHeight()
};

reportInfo("Device status", status);

var res = httpPost(
    "https://example.com/argus/status",
    status,
    {
        contentType: "application/json",
        headers: {
            "Accept": "application/json"
        }
    }
);

if (res && res.status >= 200 && res.status < 300) {
    reportStep("Status uploaded");
} else {
    reportError("Status upload failed", res ? res.error : "no response");
}


Email overview

Email requires network access and email sending to be enabled in settings.

Common options:

  • to

  • subject

  • body

  • attachReport

  • attachScreenshot


Send simple email

var ok = sendEmail({
    to: "me@example.com",
    subject: "ArgusJS message",
    body: "Script finished."
});

if (!ok) {
    reportError("Email failed");
}


Send email with report

reportStep("Finished run");

var ok = sendEmail({
    to: "me@example.com",
    subject: "ArgusJS report",
    body: "Run report is attached.",
    attachReport: true
});

if (!ok) {
    reportError("Email failed");
}


Send email with report and screenshot

reportStep("Finished run");

var ok = sendEmail({
    to: "me@example.com",
    subject: "ArgusJS result",
    body: "Report and screenshot are attached.",
    attachReport: true,
    attachScreenshot: true
});

if (!ok) {
    reportError("Email failed");
}


Send test email

var ok = sendTestEmail();

if (ok) {
    reportStep("Test email sent");
} else {
    reportError("Test email failed");
}


Complete example: async state machine

clearReport();

var winner = race([
    waitAsync("home.png", 15),
    waitTextAsync("Login", 15),
    waitTextAsync("Network error", 15)
]);

if (!winner) {
    reportError("No known state appeared");
} else if (winner.index === 0) {
    reportStep("Home screen");
} else if (winner.index === 1) {
    reportStep("Login required");
    click(winner.result);
} else {
    reportError("Network error detected");
}


Complete example: monitored long run

clearReport();

var errorFound = false;

watch("errorWatcher", function() {
    var error = text()
        .any(["Error", "Failed", "Try again"])
        .find();

    if (error) {
        errorFound = true;
        reportError("Error detected by watcher");
        highlight(error.match || error, "Error");
        return true;
    }

    return false;
}, 1000);

notifyProgress("Long run", "Starting", 0, 5, true);

for (var step = 1; step <= 5; step++) {
    if (errorFound) {
        break;
    }

    reportStep("Running step " + step);
    notifyProgress("Long run", "Step " + step + " of 5", step, 5, true);
    sleep(1000);
}

stopWatcher("errorWatcher");
clearNotification();

if (errorFound) {
    scriptExit("Stopped because an error appeared");
}

reportStep("Long run completed");


Complete example: PixelSet calibration

clearReport();

var saved = loadVar("targetPixelSet", "");

if (!saved) {
    var target = waitText("Target", 10, false);

    if (!target) {
        scriptExit("Target text was not found for calibration");
    }

    var pixelSet = getPixelSet(target, 40, false);

    if (!pixelSet) {
        scriptExit("PixelSet capture failed");
    }

    saveVar("targetPixelSet", String(pixelSet));
    reportStep("PixelSet calibrated");
} else {
    var found = waitUntil(function() {
        return findPixelSet(saved, 0.9);
    }, 10, 300);

    if (found) {
        click(found);
        reportStep("Saved PixelSet clicked");
    } else {
        reportError("Saved PixelSet was not found");
    }
}


Complete example: HTTP status report after run

clearReport();

reportStep("Starting task");

var success = !!waitText("Done", 10, false);

if (success) {
    reportStep("Done found");
} else {
    reportError("Done not found");
}

var payload = {
    success: success,
    battery: getBatteryLevel(),
    network: getNetworkType(),
    report: String(getReport())
};

var res = httpPost(
    "https://example.com/argus/result",
    payload,
    {
        contentType: "application/json",
        headers: {
            "Accept": "application/json"
        }
    }
);

if (res && res.status >= 200 && res.status < 300) {
    reportStep("Result uploaded");
} else {
    reportError("Result upload failed", res ? res.error : "no response");
}


Troubleshooting checklist

Async:

  • Use race(...) for first success.

  • Use awaitAll(...) for ordered results.

  • Remember that null and false do not win a race.

  • Do not reuse the same AsyncResult ticket in multiple waits.

Timers and watchers:

  • Store timer IDs when you need to stop them.

  • Use clearInterval(id) for intervals, timeouts, async callbacks, and compatible tracking IDs.

  • Use stopWatcher(name) for named watchers.

  • Use clearAllIntervals() during manual cleanup when needed.

PixelSet and PixelDNA:

  • Confirm screen capture is active.

  • Use stable visual targets.

  • Search inside regions when possible.

  • Recalibrate if UI theme, scale, or resolution changes.

ML Kit:

  • Treat results as helper signals.

  • Barcode detection is the most deterministic.

  • Object detection may return 0 even when objects are visible.

  • Use OCR, nodes, image matching, PixelSet, or PixelDNA for precise automation.

Network and email:

  • Enable network access in settings.

  • Check HTTPS and domain rules.

  • Enable downloads before using httpDownload(...).

  • Enable email sending and configure SMTP before sendEmail(...).

  • Always check HTTP status and error.


Practical notes for Part 5

  • Use async races for uncertain screen states.

  • Use watchers for background monitoring during long flows.

  • Use progress notifications for long-running scripts.

  • Use PixelSet or PixelDNA for stable visual targets without image files.

  • Use ML Kit helpers only as support signals.

  • Use HTTP and email only when network settings allow it.

  • Save reports before uploading or emailing results.

  • Keep network payloads small and avoid sending sensitive screen data unless intended.

ArgusJS Examples and Recipes - Part 6

This part contains complete recipes and debugging templates that combine the APIs from the previous parts.

These examples are meant to be copied, adapted, and tested on your own target app. Replace package names, text labels, image names, regions, and values with your own project details.


Recommended project layout

MyProject/
  MyProject.js
  images/
    start_button.png
    close.png
    home.png
    error.png
  data/
    config.json
  debug/
  reports/
  logs/

Use:

  • images/ for image targets.

  • data/ for config files.

  • debug/ for screenshots, OCR, layout dumps, and failure files.

  • reports/ for saved reports.

  • logs/ for custom log text files.


Basic reusable debug helper

Use this when something important is not found.

function saveDebug(name) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

    saveColor(full, "debug/" + name + "_screen.png");
    saveReport("debug/" + name + "_report.json");
    writeFile("debug/" + name + "_ocr.txt", String(readText()));
    writeFile("debug/" + name + "_layout.txt", String(dumpLayout()));
}

Example:

var done = waitText("Done", 5, false);

if (!done) {
    reportError("Done was not found");
    saveDebug("done_not_found");
}


Debug-first script template

This template is good for new scripts because it saves useful debug files when something fails.

clearReport();
setReferenceResolution(1080, 1920);

function saveDebug(name) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

    saveColor(full, "debug/" + name + "_screen.png");
    saveReport("debug/" + name + "_report.json");
    writeFile("debug/" + name + "_ocr.txt", String(readText()));
    writeFile("debug/" + name + "_layout.txt", String(dumpLayout()));
}

try {
    reportStep("Starting automation");

    var app = ensureApp("com.example.app", {
        timeout: 8,
        retries: 2
    });

    if (!app.success) {
        saveDebug("app_launch_failed");
        scriptExit("App launch failed");
    }

    var start = race([
        waitAsync("start_button.png", 8),
        waitTextAsync("Start", 8),
        waitNodeAsync({ byText: "Start" }, 8)
    ]);

    if (!start) {
        saveDebug("start_not_found");
        scriptExit("Start not found");
    }

    click(start.result);
    reportStep("Start clicked");

    var done = waitText("Done", 20, false);

    if (!done) {
        saveDebug("done_not_found");
        scriptExit("Done not found");
    }

    reportStep("Automation completed");
    setSuccessMessage("Done");
} catch (e) {
    saveDebug("script_error");
    throw e;
}


Resilient start button recipe

This tries Accessibility node, image matching, and OCR.

clearReport();
setReferenceResolution(1080, 1920);

function clickStart() {
    var nodeButton = waitNode({ byText: "Start" }, 2);

    if (nodeButton) {
        reportStep("Start found by node");
        return nodeButton.click();
    }

    var imageButton = waitFor(
        createPattern("start_button.png").similar(0.88),
        2
    );

    if (imageButton) {
        reportStep("Start found by image");
        return click(imageButton);
    }

    var textButton = text("Start")
        .contains()
        .wait(2);

    if (textButton) {
        reportStep("Start found by OCR");
        return click(textButton);
    }

    return false;
}

if (!clickStart()) {
    saveDebug("start_failed");
    reportError("Could not find Start");
} else {
    reportStep("Start clicked");
}


Open app, handle permission, then start

clearReport();
setReferenceResolution(1080, 1920);

var packageName = "com.example.app";

reportStep("Launching app");

if (!ensureApp(packageName, { timeout: 8, retries: 2 }).success) {
    saveDebug("launch_failed");
    scriptExit("Failed to launch app");
}

var permission = race([
    waitNodeAsync({ byText: "Allow" }, 3),
    waitNodeAsync({ byText: "While using the app" }, 3),
    waitNodeAsync({ byText: "Only this time" }, 3),
    waitTextAsync("Allow", 3)
]);

if (permission) {
    click(permission.result);
    sleep(500);
    reportStep("Permission handled");
}

var start = race([
    waitAsync("start_button.png", 8),
    waitTextAsync("Start", 8),
    waitNodeAsync({ byText: "Start" }, 8)
]);

if (start) {
    click(start.result);
    reportStep("Start clicked");
} else {
    saveDebug("start_not_found");
    reportError("Start was not found");
}


Open app and close common popups

clearReport();

var packageName = "com.example.app";

if (!ensureApp(packageName, { timeout: 8, retries: 2 }).success) {
    saveDebug("app_not_opened");
    scriptExit("App did not open");
}

var popup = race([
    waitNodeAsync({ byText: "Not now" }, 2),
    waitNodeAsync({ byText: "Maybe later" }, 2),
    waitTextAsync("Skip", 2),
    waitTextAsync("Close", 2),
    waitAsync("close.png", 2)
]);

if (popup) {
    click(popup.result);
    sleep(500);
    reportStep("Popup closed");
} else {
    log("No popup found");
}


Login with Accessibility nodes

Use this when the target app exposes native Android fields.

clearReport();

var packageName = "com.example.app";

if (!ensureApp(packageName, { timeout: 8 }).success) {
    scriptExit("App did not open");
}

try {
    argusKeyboardShow();
    sleep(500);

    var email = waitNode({
        byViewId: "com.example.app:id/email"
    }, 5);

    var password = waitNode({
        byViewId: "com.example.app:id/password"
    }, 5);

    var login = waitNode({
        byText: "Login"
    }, 5);

    if (!email || !password || !login) {
        saveDebug("login_fields_missing");
        scriptExit("Login screen was not ready");
    }

    email.click();
    typeOnFocus("user@example.com");

    password.click();
    typeOnFocus("password123");

    login.click();

    if (waitText("Welcome", 10, false)) {
        reportStep("Login succeeded");
    } else {
        saveDebug("login_not_confirmed");
        reportError("Login result was not confirmed");
    }
} finally {
    argusKeyboardReset();
}


Login with OCR labels

Use this when fields are visible but native nodes are not reliable.

clearReport();

try {
    argusKeyboardShow();
    sleep(500);

    var emailLabel = text("Email")
        .contains()
        .wait(5);

    if (!emailLabel) {
        saveDebug("email_label_missing");
        scriptExit("Email label not found");
    }

    click(emailLabel.below(80));
    typeOnFocus("user@example.com");

    var passwordLabel = text("Password")
        .contains()
        .wait(5);

    if (!passwordLabel) {
        saveDebug("password_label_missing");
        scriptExit("Password label not found");
    }

    click(passwordLabel.below(80));
    typeOnFocus("password123");

    var login = text("Login")
        .exact()
        .wait(5);

    if (!login) {
        saveDebug("login_button_missing");
        scriptExit("Login button not found");
    }

    click(login);

    if (waitText("Welcome", 10, false)) {
        reportStep("Login succeeded");
    } else {
        saveDebug("welcome_missing");
        reportError("Welcome text was not found");
    }
} finally {
    argusKeyboardReset();
}


Form fill with saved defaults

This lets the user enter values once and reuse them later.

clearReport();

var defaults = loadVar("formDefaults", {
    name: "Ahmed",
    email: "ahmed@example.com",
    packageName: "com.example.app"
});

dialogInit("Form Fill");

addInput("packageName", "Package Name", defaults.packageName, "text", true);
addInput("name", "Name", defaults.name, "text", true);
addInput("email", "Email", defaults.email, "text", true);

var form = dialogShow({
    positiveButton: "Run",
    negativeButton: "Cancel"
});

if (!form) {
    scriptExit("Cancelled");
}

saveVar("formDefaults", form);

if (!ensureApp(form.packageName, { timeout: 8 }).success) {
    saveDebug("app_open_failed");
    scriptExit("App did not open");
}

try {
    argusKeyboardShow();
    sleep(500);

    var nameField = waitNode({
        byViewId: form.packageName + ":id/name"
    }, 5);

    var emailField = waitNode({
        byViewId: form.packageName + ":id/email"
    }, 5);

    var submit = waitNode({ byText: "Submit" }, 5);

    if (!nameField || !emailField || !submit) {
        saveDebug("form_fields_missing");
        scriptExit("Form fields were not found");
    }

    nameField.click();
    typeOnFocus(form.name);

    emailField.click();
    typeOnFocus(form.email);

    submit.click();

    reportStep("Form submitted");
} finally {
    argusKeyboardReset();
}


File-based configuration recipe

Create this file:

data/config.json

Example content:

{
  "packageName": "com.example.app",
  "startText": "Start",
  "doneText": "Done",
  "timeout": 8
}

Script:

clearReport();

var configText = readFile("data/config.json");

if (!configText) {
    scriptExit("Missing data/config.json");
}

var config = JSON.parse(String(configText));

if (!ensureApp(config.packageName, { timeout: config.timeout }).success) {
    saveDebug("configured_app_failed");
    scriptExit("App did not open");
}

var start = waitText(config.startText, config.timeout, false);

if (!start) {
    saveDebug("configured_start_missing");
    scriptExit("Start text was not found");
}

click(start);
reportStep("Clicked: " + config.startText);

var done = waitText(config.doneText, config.timeout, false);

if (done) {
    reportStep("Done text found");
} else {
    saveDebug("configured_done_missing");
    reportError("Done text was not found");
}


Click first visible option from a list

var option = text()
    .any(["Basic", "Standard", "Premium"])
    .wait(5);

if (option) {
    log("Selected option: " + option.text);
    click(option.match);
} else {
    saveDebug("option_missing");
    reportError("No option was found");
}


Choose option by dialog

var selected = choose(
    "Select plan",
    ["Basic", "Standard", "Premium"],
    "Standard"
);

if (!selected) {
    scriptExit("No plan selected");
}

var option = text(selected)
    .contains()
    .wait(5);

if (option) {
    click(option);
    reportStep("Clicked plan: " + selected);
} else {
    saveDebug("selected_plan_missing");
    reportError("Selected plan was not visible: " + selected);
}


Scroll until text appears

clearReport();

var list = createRegion(0, 400, getScreenWidth(), 1300);
var target = null;

for (var i = 0; i < 8; i++) {
    target = text("Target Item")
        .contains()
        .in(list)
        .find();

    if (target) {
        break;
    }

    scroll(list, "up", 700, 500);
    sleep(500);
}

if (target) {
    click(target);
    reportStep("Target item clicked");
} else {
    saveDebug("target_item_missing");
    reportError("Target Item was not found after scrolling");
}


Scroll and click all matching buttons

clearReport();

var list = createRegion(0, 400, getScreenWidth(), 1300);

for (var page = 0; page < 5; page++) {
    var buttons = text("Claim")
        .contains()
        .in(list)
        .sortByTop()
        .findAll();

    for (var i = 0; i < buttons.length; i++) {
        click(buttons[i]);
        sleep(300);
    }

    scroll(list, "up", 700, 500);
    sleep(500);
}

reportStep("Claim pass finished");


Collect rewards until none remain

clearReport();

var rewardRegion = createRegion(0, 300, getScreenWidth(), 1400);
var totalClicked = 0;

for (var pass = 0; pass < 6; pass++) {
    var rewards = text("Claim")
        .contains()
        .in(rewardRegion)
        .sortByTop()
        .findAll();

    if (rewards.length === 0) {
        scroll(rewardRegion, "up", 700, 500);
        sleep(700);
        continue;
    }

    for (var i = 0; i < rewards.length; i++) {
        click(rewards[i]);
        totalClicked++;
        sleep(800);

        var ok = race([
            waitTextAsync("OK", 2),
            waitTextAsync("Collected", 2),
            waitAsync("close.png", 2)
        ]);

        if (ok) {
            click(ok.result);
            sleep(300);
        }
    }

    scroll(rewardRegion, "up", 700, 500);
    sleep(700);
}

reportStep("Reward collection finished. Clicked: " + totalClicked);


Wait for loading screen to finish

clearReport();

var loading = existsText("Loading", 0, false);

if (loading) {
    reportStep("Waiting for loading to finish");

    if (!waitVanishText("Loading", 15, false)) {
        saveDebug("loading_timeout");
        scriptExit("Loading did not finish");
    }
}

reportStep("Loading finished or was not visible");


Wait for page ready using multiple signals

clearReport();

var ready = race([
    waitAsync("home_icon.png", 10),
    waitTextAsync("Home", 10),
    waitNodeAsync({ byText: "Home" }, 10)
]);

if (ready) {
    reportStep("Home page is ready");
} else {
    saveDebug("home_not_ready");
    reportError("Home page was not detected");
}


Detect screen state

function detectScreen() {
    if (findNode({ byText: "Home" })) {
        return "home";
    }

    if (findNode({ byText: "Login" })) {
        return "login";
    }

    if (findText("Error")) {
        return "error";
    }

    if (exists("loading.png", 0)) {
        return "loading";
    }

    return "unknown";
}

var state = detectScreen();

log("Screen state: " + state);


Act based on screen state

var state = detectScreen();

if (state === "home") {
    reportStep("Already on home");
} else if (state === "login") {
    reportStep("Login required");
} else if (state === "error") {
    saveDebug("error_screen");
    reportError("Error screen detected");
} else if (state === "loading") {
    waitVanish("loading.png", 10);
} else {
    saveDebug("unknown_screen");
    reportError("Unknown screen");
}


Wait for one of several screens

var winner = race([
    waitNodeAsync({ byText: "Home" }, 10),
    waitNodeAsync({ byText: "Login" }, 10),
    text("Error").contains().waitAsync(10),
    text("Try again").contains().waitAsync(10)
]);

if (!winner) {
    saveDebug("unknown_screen");
    reportError("Unknown screen");
} else if (winner.index === 0) {
    reportStep("Home screen");
} else if (winner.index === 1) {
    reportStep("Login screen");
} else {
    saveDebug("problem_screen");
    reportError("Problem screen detected");
}


Detect and click enabled button

This uses OCR plus a color count check.

var button = text("Submit")
    .contains()
    .wait(5);

if (!button) {
    saveDebug("submit_missing");
    scriptExit("Submit button was not found");
}

var greenCount = getColorCount(button, "#00AA00", 30);

if (greenCount > 50) {
    click(button);
    reportStep("Submit clicked");
} else {
    saveDebug("submit_disabled");
    reportError("Submit button appears disabled");
}


Checkout flow recipe

clearReport();

function clickAnyText(labels, seconds) {
    var result = text()
        .any(labels)
        .wait(seconds || 5);

    if (result) {
        reportStep("Clicking: " + result.text);
        return click(result.match);
    }

    reportError("None found: " + labels.join(", "));
    return false;
}

if (!clickAnyText(["Checkout", "Continue"], 8)) {
    saveDebug("checkout_missing");
    scriptExit("Checkout button not found");
}

sleep(1000);

var address = text("Address")
    .contains()
    .wait(5);

if (address) {
    reportStep("Address screen detected");
    clickAnyText(["Confirm address", "Use this address"], 5);
}

sleep(1000);

if (!clickAnyText(["Place order", "Pay now", "Confirm"], 8)) {
    saveDebug("final_confirm_missing");
    scriptExit("Final confirmation button not found");
}

var done = race([
    waitTextAsync("Order placed", 15),
    waitTextAsync("Payment failed", 15),
    waitTextAsync("Try again", 15)
]);

if (done && done.index === 0) {
    reportStep("Order placed");
} else {
    saveDebug("checkout_failed");
    reportError("Checkout did not complete successfully");
}


Ride availability recipe

clearReport();

var rideRegion = createRegion(0, 500, getScreenWidth(), 1200);

var winner = race([
    text("\\d+\\s+minutes?\\s+away")
        .regex()
        .caseInsensitive()
        .normalizeSpaces()
        .in(rideRegion)
        .waitAsync(30),

    text("No drivers")
        .contains()
        .caseInsensitive()
        .waitAsync(30),

    text("Try again")
        .contains()
        .caseInsensitive()
        .waitAsync(30)
]);

if (!winner) {
    saveDebug("ride_state_unknown");
    reportError("No ride state detected");
} else if (winner.index === 0) {
    reportStep("Ride found");
    click(winner.result);
} else {
    saveDebug("ride_unavailable");
    reportError("Ride unavailable or retry needed");
}


Choose cheapest visible price

This example assumes visible prices such as QAR 18, QAR 25, or 18.50.

clearReport();

var priceRegion = createRegion(0, 400, getScreenWidth(), 1300);

var prices = text("(QAR\\s*)?\\d+(\\.\\d+)?")
    .regex()
    .caseInsensitive()
    .normalizeSpaces()
    .in(priceRegion)
    .sortByTop()
    .findAll();

var cheapest = null;
var cheapestValue = 999999;

for (var i = 0; i < prices.length; i++) {
    var raw = String(prices[i].getText());
    var numeric = Number(raw.replace("QAR", "").replace(/[^0-9.]/g, ""));

    if (!isNaN(numeric) && numeric < cheapestValue) {
        cheapestValue = numeric;
        cheapest = prices[i];
    }
}

if (cheapest) {
    reportStep("Cheapest price: " + cheapestValue);
    click(cheapest);
} else {
    saveDebug("price_missing");
    reportError("No valid price found");
}


Monitor for errors during a long flow

clearReport();

var errorFound = false;

watch("errorWatcher", function() {
    var error = text()
        .any(["Error", "Failed", "Try again"])
        .find();

    if (error) {
        errorFound = true;
        reportError("Error detected by watcher");

        if (error.match) {
            highlight(error.match, "Error");
        } else {
            highlight(error, "Error");
        }

        return true;
    }

    return false;
}, 1000);

for (var step = 1; step <= 10; step++) {
    if (errorFound) {
        break;
    }

    reportStep("Running step " + step);
    sleep(1000);
}

stopWatcher("errorWatcher");

if (errorFound) {
    saveDebug("watched_error");
    scriptExit("Stopped because an error appeared");
}

reportStep("Flow completed without watched errors");


Long run with progress notification

clearReport();

notifyProgress("Long run", "Starting", 0, 5, true);

for (var step = 1; step <= 5; step++) {
    reportStep("Running step " + step);
    notifyProgress("Long run", "Step " + step + " of 5", step, 5, true);

    sleep(1000);
}

notifyProgress("Long run", "Done", 5, 5, false);

reportStep("Long run completed");


PixelSet calibration recipe

This captures a target once, saves it, and reuses it later.

clearReport();

var saved = loadVar("targetPixelSet", "");

if (!saved) {
    var target = waitText("Target", 10, false);

    if (!target) {
        saveDebug("pixelset_target_missing");
        scriptExit("Target text was not found for calibration");
    }

    var pixelSet = getPixelSet(target, 40, false);

    if (!pixelSet) {
        saveDebug("pixelset_capture_failed");
        scriptExit("PixelSet capture failed");
    }

    saveVar("targetPixelSet", String(pixelSet));
    reportStep("PixelSet calibrated");
} else {
    var found = waitUntil(function() {
        return findPixelSet(saved, 0.9);
    }, 10, 300);

    if (found) {
        click(found);
        reportStep("Saved PixelSet clicked");
    } else {
        saveDebug("pixelset_not_found");
        reportError("Saved PixelSet was not found");
    }
}


Email report after failure

clearReport();

var done = waitText("Done", 10, false);

if (done) {
    reportStep("Done found");
} else {
    reportError("Done not found");
    saveDebug("done_failure");

    sendEmail({
        to: "me@example.com",
        subject: "ArgusJS failure report",
        body: "The script failed. Report and screenshot are attached.",
        attachReport: true,
        attachScreenshot: true
    });
}


HTTP status report after run

clearReport();

var success = !!waitText("Done", 10, false);

if (success) {
    reportStep("Done found");
} else {
    reportError("Done not found");
}

var payload = {
    success: success,
    battery: getBatteryLevel(),
    network: getNetworkType(),
    foreground: getForegroundApp(),
    report: String(getReport())
};

var res = httpPost(
    "https://example.com/argus/result",
    payload,
    {
        contentType: "application/json",
        headers: {
            "Accept": "application/json"
        }
    }
);

if (res && res.status >= 200 && res.status < 300) {
    reportStep("Result uploaded");
} else {
    reportError("Result upload failed", res ? res.error : "no response");
}


Final cleanup pattern

Use try/finally when your script changes keyboard, overlays, timers, watchers, or notifications.

clearReport();

var watcherStarted = false;

try {
    argusKeyboardShow();

    watch("errorWatcher", function() {
        return existsText("Error", 0, false);
    }, 1000);

    watcherStarted = true;

    notifyProgress("Script", "Running", 0, 1, true);

    reportStep("Doing work");
    sleep(3000);

    reportStep("Work finished");
} finally {
    if (watcherStarted) {
        stopWatcher("errorWatcher");
    }

    clearNotification();
    argusKeyboardReset();
    highlightAllOff();
    clearScreenCache();
}


Common reusable helpers

Safe click by text

function clickText(label, seconds) {
    var match = text(label)
        .contains()
        .wait(seconds || 5);

    if (match) {
        return click(match);
    }

    reportError("Text not found: " + label);
    return false;
}

Safe click by image

function clickImage(fileName, seconds, similarity) {
    var pattern = createPattern(fileName).similar(similarity || 0.85);
    var match = waitFor(pattern, seconds || 5);

    if (match) {
        return click(match);
    }

    reportError("Image not found: " + fileName);
    return false;
}

Smart click

function smartClick(label, imageFile, seconds) {
    var node = waitNode({ byText: label }, 2);

    if (node && node.click()) {
        reportStep("Clicked node: " + label);
        return true;
    }

    var textMatch = waitText(label, 2, false);

    if (textMatch && click(textMatch)) {
        reportStep("Clicked OCR text: " + label);
        return true;
    }

    var imageMatch = waitFor(imageFile, seconds || 5);

    if (imageMatch && click(imageMatch)) {
        reportStep("Clicked image: " + imageFile);
        return true;
    }

    reportError("Smart click failed: " + label);
    return false;
}


Debug checklist

When a script fails, collect these files:

  • screenshot

  • OCR text

  • Accessibility layout

  • report JSON

  • relevant logs

  • target image asset

  • current app package name

  • device resolution

  • Android version

Useful debug calls:

log("Foreground: " + getForegroundApp());
log("Screen: " + getScreenWidth() + "x" + getScreenHeight());
log("Android SDK: " + getAndroidVersion());

saveDebug("manual_debug");


Automation reliability checklist

Before sharing a script with other users:

  • Test on the target resolution.

  • Test after restarting the app.

  • Test with slow network.

  • Test when popups appear.

  • Test when popups do not appear.

  • Test with dark mode and light mode if relevant.

  • Test when text takes longer to appear.

  • Test with notifications and overlays visible.

  • Save debug output on failure.

  • Use regions to reduce false positives.

  • Use fallback detection for important buttons.

  • Use try/finally for cleanup.


Practical notes for Part 6

  • Start new scripts with a debug-first template.

  • Use race(...) for uncertain screens.

  • Use fallback detection for important actions.

  • Save screenshots, OCR, layout, and reports when something fails.

  • Use dialogs or config files instead of hardcoding every value.

  • Use try/finally for keyboard, watchers, notifications, overlays, and cache cleanup.

  • Keep recipes small enough to test one flow at a time.

  • Build reliability by handling success, failure, loading, and unknown states.

ArgusJS Examples and Recipes - Part 7

This optional part covers best practices, migration notes, common mistakes, debugging habits, and reliability rules.

Use this as a closing guide after Parts 1-6.


What this part is for

Parts 1-6 give examples.

This part explains how to write better scripts, avoid fragile patterns, and debug problems faster.

It is useful when:

  • a script works once but fails later

  • image matching gives false positives

  • OCR reads the wrong text

  • nodes are missing

  • typing is unreliable

  • the screen changes too quickly

  • a popup breaks the flow

  • the script works on one device but not another


Recommended detection order

Use the most stable signal first.

Recommended order:

  1. Accessibility nodes

  2. OCR text

  3. Image matching

  4. Color checks

  5. PixelSet

  6. PixelDNA

  7. ML Kit helpers

Why:

  • Nodes are usually best for native Android UI.

  • OCR is useful when visible text exists but nodes do not.

  • Images are useful for icons, games, custom UI, and visual buttons.

  • Color is useful for state checks such as enabled, disabled, selected, or warning.

  • PixelSet and PixelDNA are useful when you need visual matching without maintaining image files.

  • ML Kit is helpful, but it should not be the only signal for precise automation.


Do not rely on one signal for important actions

Fragile:

var start = waitFor("start_button.png", 5);

if (start) {
    click(start);
}

Better:

var start = race([
    waitNodeAsync({ byText: "Start" }, 5),
    waitTextAsync("Start", 5),
    waitAsync("start_button.png", 5)
]);

if (start) {
    click(start.result);
} else {
    reportError("Start was not found");
}

For important buttons, use fallback detection.


Prefer regions over full-screen searches

Fragile:

var button = waitText("Continue", 5, false);

Better:

var bottom = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var button = waitTextIn(bottom, "Continue", 5, false);

Why:

  • faster search

  • fewer false positives

  • easier debugging

  • more reliable screen intent


Avoid hardcoded coordinates when possible

Fragile:

click(540, 1600);

Better:

var button = waitText("Continue", 5, false);

if (button) {
    click(button);
}

Coordinates are acceptable when:

  • the UI is fixed

  • the target is always in the same place

  • no better signal exists

  • the script is device-specific

When using coordinates, set the reference resolution:

setReferenceResolution(1080, 1920);


Use waits after screen-changing actions

Fragile:

click(540, 1200);
var done = findText("Done");

Better:

click(540, 1200);
sleep(500);

var done = waitText("Done", 5, false);

Clicks, swipes, app launches, popups, and dialogs often need a short delay.


Use find only for immediate checks

Use find(...) when checking the current screen only.

var popup = findText("Not now");

Use wait...(...) when the screen may change.

var popup = waitText("Not now", 3, false);

Common mistake:

startApp("com.example.app");
var home = findText("Home");

Better:

startApp("com.example.app");
waitApp("com.example.app", 5);

var home = waitText("Home", 8, false);


Use exists(..., 0) for one-time optional checks

For optional popups:

var popup = exists("close.png", 0);

if (popup) {
    click(popup);
}

For optional OCR text:

var popup = existsText("Not now", 0, false);

if (popup) {
    click(popup);
}

Use a small timeout when the popup may animate in:

var popup = existsText("Not now", 2, false);

if (popup) {
    click(popup);
}


Always handle unknown screens

Fragile:

if (waitText("Home", 5, false)) {
    runHomeFlow();
}

Better:

var screen = race([
    waitTextAsync("Home", 8),
    waitTextAsync("Login", 8),
    waitTextAsync("Error", 8),
    waitAsync("loading.png", 8)
]);

if (!screen) {
    saveDebug("unknown_screen");
    scriptExit("Unknown screen");
}

if (screen.index === 0) {
    reportStep("Home screen");
} else if (screen.index === 1) {
    reportStep("Login screen");
} else if (screen.index === 2) {
    reportError("Error screen");
} else if (screen.index === 3) {
    reportStep("Loading screen");
}

Unknown screens should be treated as real states, not ignored.


Always save debug files on important failures

Create a helper once:

function saveDebug(name) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

    saveColor(full, "debug/" + name + "_screen.png");
    saveReport("debug/" + name + "_report.json");
    writeFile("debug/" + name + "_ocr.txt", String(readText()));
    writeFile("debug/" + name + "_layout.txt", String(dumpLayout()));
}

Use it on failure:

var done = waitText("Done", 5, false);

if (!done) {
    reportError("Done was not found");
    saveDebug("done_not_found");
    scriptExit("Done was not found");
}

A good failure report should include:

  • screenshot

  • OCR text

  • Accessibility layout

  • report JSON

  • current app package

  • screen size

  • relevant logs


Convert string-like values with String(...)

Some values come from Android or Kotlin. Convert them before using JavaScript string methods.

Fragile:

var textValue = readText();

if (textValue.indexOf("Done") >= 0) {
    log("Done found");
}

Better:

var textValue = String(readText());

if (textValue.indexOf("Done") >= 0) {
    log("Done found");
}

Use this habit with:

  • readText()

  • dumpLayout()

  • file contents

  • OCR values

  • report values

  • Java/Kotlin-returned strings


Use createRegion, createLocation, and createPattern

Do not write direct constructors like this:

var region = Region(0, 0, 100, 100);
var location = Location(500, 500);
var pattern = Pattern("button.png");

Use factory helpers:

var region = createRegion(0, 0, 100, 100);
var location = createLocation(500, 500);
var pattern = createPattern("button.png");

This is the safer style for ArgusJS scripts.


Do not call wait APIs on static screenshots

Static screenshots do not change.

Wrong idea:

var screen = captureScreen();

if (screen) {
    var area = createRegion(0, 0, getScreenWidth(), getScreenHeight()).inScreen(screen);

    // Do not wait on a static screen.
    // area.waitFor("button.png", 5);

    screen.recycle();
}

Use immediate checks:

var screen = captureScreen();

if (screen) {
    try {
        var area = createRegion(0, 0, getScreenWidth(), getScreenHeight()).inScreen(screen);

        var button = area.find(createPattern("button.png"));
        var label = area.findText("Continue", false);
        var color = area.findColor("#00FF00", 20);
    } finally {
        screen.recycle();
    }
}

Use live wait APIs when the real screen may change.


Recycle manually captured screens

Fragile:

var screen = captureScreen();

var area = createRegion(0, 0, getScreenWidth(), getScreenHeight()).inScreen(screen);
var match = area.find(createPattern("button.png"));

Better:

var screen = captureScreen();

if (screen) {
    try {
        var area = createRegion(0, 0, getScreenWidth(), getScreenHeight()).inScreen(screen);
        var match = area.find(createPattern("button.png"));

        if (match) {
            click(match);
        }
    } finally {
        screen.recycle();
    }
}

Best for simple cases:

withCapturedScreen(function(screen) {
    var area = createRegion(0, 0, getScreenWidth(), getScreenHeight()).inScreen(screen);
    var match = area.find(createPattern("button.png"));

    if (match) {
        click(match);
    }
});


Use screen cache for repeated same-screen checks

Without cache:

var logo = find(createPattern("logo.png"));
var ready = findText("Ready");
var green = findColor("#00FF00", 20);

With cache:

if (refreshScreenCache()) {
    withScreenCache(function() {
        var logo = find(createPattern("logo.png"));
        var ready = findText("Ready");
        var green = findColor("#00FF00", 20);

        if (logo || ready || green) {
            reportStep("Ready signal found");
        }
    });

    clearScreenCache();
}

Use cache when multiple screen-reading APIs inspect the same frame.


Prefer TextQuery for serious OCR workflows

Basic OCR:

var match = waitText("Continue", 5, false);

Better for control:

var match = text("Continue")
    .contains()
    .caseInsensitive()
    .normalizeSpaces()
    .in(createRegion(0, 900, getScreenWidth(), 900))
    .wait(5);

Use TextQuery when you need:

  • region filtering

  • regex

  • sorting

  • first match

  • nth match

  • case rules

  • normalized spaces

  • spatial matching

  • debug logs


Regex must be explicit

Wrong:

var price = text("(QAR\\s*)?\\d+(\\.\\d+)?")
    .find();

Correct:

var price = text("(QAR\\s*)?\\d+(\\.\\d+)?")
    .regex()
    .caseInsensitive()
    .normalizeSpaces()
    .find();

ArgusJS does not assume a string is regex. You must call .regex().


Use OCR normalization for messy text

OCR can return extra spaces or line breaks.

var total = text("Total QAR")
    .contains()
    .normalizeSpaces()
    .wait(5);

Use .trim() when leading or trailing spaces are a problem:

var done = text("Done")
    .exact()
    .trim()
    .wait(5);


Use nodes first for native Android UI

OCR version:

var login = waitText("Login", 5, false);

if (login) {
    click(login);
}

Node version:

var login = waitNode({ byText: "Login" }, 5);

if (login) {
    login.click();
}

Nodes are usually:

  • faster

  • less affected by screen quality

  • less affected by animation

  • better for native Android fields

Use dumpLayout() when you are not sure what nodes are available.


Node click vs touch click

Try native node click first:

var button = waitNode({ byText: "Submit" }, 5);

if (button) {
    button.click();
}

Use touch fallback when node click fails:

var button = waitNode({ byText: "Submit" }, 5);

if (button) {
    if (!button.click()) {
        click(button);
    }
}


Use typing APIs intentionally

Clipboard only:

setText("hello@example.com");

Paste:

paste("hello@example.com");

Focused typing:

type("hello@example.com");

Replace focused field:

typeOnFocus("hello@example.com");

Argus Keyboard typing:

imeType("hello@example.com");

Do not expect setText(...) to directly fill a field. It sets clipboard text.


Reset Argus Keyboard in finally

Fragile:

argusKeyboardShow();
imeType("hello@example.com");

Better:

try {
    argusKeyboardShow();
    sleep(500);

    imeType("hello@example.com");
    imeDone();
} finally {
    argusKeyboardReset();
}

Use finally when changing:

  • keyboard mode

  • overlays

  • watchers

  • timers

  • notifications

  • screen cache


Use dialogs for reusable scripts

Hardcoded:

var packageName = "com.example.app";
var timeout = 8;

Better:

var defaults = loadVar("runOptions", {
    packageName: "com.example.app",
    timeout: 8
});

dialogInit("Run Options");

addInput("packageName", "Package Name", defaults.packageName, "text", true);
addSlider("timeout", 1, 30, defaults.timeout);

var options = dialogShow({
    positiveButton: "Run",
    negativeButton: "Cancel"
});

if (!options) {
    scriptExit("Cancelled");
}

saveVar("runOptions", options);

This makes scripts easier to share and reuse.


Do not store secrets in persistent variables

Avoid storing:

  • passwords

  • API tokens

  • session cookies

  • private keys

  • personal identifiers

Acceptable persistent values:

  • selected mode

  • package name

  • timeout

  • retry count

  • last selected option

  • UI preference

  • calibration data

  • non-sensitive counters


Use config files for advanced scripts

Good for script users who prefer editing files instead of dialogs.

Example:

data/config.json

Content:

{
  "packageName": "com.example.app",
  "startText": "Start",
  "doneText": "Done",
  "timeout": 8
}

Script:

var configText = readFile("data/config.json");

if (!configText) {
    scriptExit("Missing data/config.json");
}

var config = JSON.parse(String(configText));

if (!ensureApp(config.packageName, { timeout: config.timeout }).success) {
    scriptExit("App did not open");
}


Keep project file paths simple

Use project-relative paths:

writeFile("logs/run.log", "Started\n");
saveColor(createRegion(0, 0, getScreenWidth(), getScreenHeight()), "debug/screen.png");

Avoid:

writeFile("../outside.txt", "bad");

Recommended folders:

images/
data/
debug/
reports/
logs/


Handle popups as part of the flow

Do not treat popups as rare exceptions. Treat them as expected states.

var popup = race([
    waitNodeAsync({ byText: "Not now" }, 2),
    waitNodeAsync({ byText: "Maybe later" }, 2),
    waitTextAsync("Skip", 2),
    waitAsync("close.png", 2)
]);

if (popup) {
    click(popup.result);
    sleep(500);
    reportStep("Popup closed");
}

Run popup handling:

  • after app launch

  • after login

  • after checkout

  • after network actions

  • before important clicks


Treat loading screens explicitly

Fragile:

click(start);
waitText("Home", 5, false);

Better:

click(start);

var loading = existsText("Loading", 2, false);

if (loading) {
    if (!waitVanishText("Loading", 15, false)) {
        saveDebug("loading_timeout");
        scriptExit("Loading did not finish");
    }
}

var home = waitText("Home", 8, false);

if (!home) {
    saveDebug("home_missing");
    reportError("Home not found");
}


Use watchers for background problems

Use watchers when a problem can appear during a long flow.

var errorFound = false;

watch("errorWatcher", function() {
    var error = text()
        .any(["Error", "Failed", "Try again"])
        .find();

    if (error) {
        errorFound = true;
        reportError("Error detected by watcher");
        return true;
    }

    return false;
}, 1000);

for (var step = 1; step <= 5; step++) {
    if (errorFound) {
        break;
    }

    reportStep("Running step " + step);
    sleep(1000);
}

stopWatcher("errorWatcher");

if (errorFound) {
    scriptExit("Stopped because an error appeared");
}


Clean up after timers and watchers

Fragile:

setInterval(function() {
    log("tick");
}, 1000);

Better:

var intervalId = setInterval(function() {
    log("tick");
}, 1000);

try {
    sleep(5000);
} finally {
    clearInterval(intervalId);
}

For watchers:

watch("popupWatcher", function() {
    return false;
}, 1000);

try {
    sleep(5000);
} finally {
    stopWatcher("popupWatcher");
}

For broad cleanup:

clearAllIntervals();
clearWatchers();


Use progress notifications for long scripts

notifyProgress("ArgusJS", "Starting", 0, 5, true);

try {
    for (var step = 1; step <= 5; step++) {
        notifyProgress("ArgusJS", "Step " + step + " of 5", step, 5, true);
        reportStep("Step " + step);

        sleep(1000);
    }
} finally {
    clearNotification();
}

This makes long-running scripts easier to monitor.


Be careful with HTTP and email

Before using network APIs, confirm the required settings are enabled.

Always check results:

var res = httpGet("https://example.com/status");

if (res && res.status === 200) {
    log(res.body);
} else {
    reportError("HTTP failed", res ? res.error : "no response");
}

Avoid sending sensitive screenshots or report data unless intended.


Use PixelSet for calibration-style scripts

PixelSet works well when users can calibrate a visual target once.

Capture:

var target = waitText("Target", 5, false);

if (target) {
    var pixelSet = getPixelSet(target, 40, false);

    if (pixelSet) {
        saveVar("targetPixelSet", String(pixelSet));
    }
}

Use later:

var saved = loadVar("targetPixelSet", "");

if (saved) {
    var found = findPixelSet(saved, 0.9);

    if (found) {
        click(found);
    }
}

Recalibrate when:

  • theme changes

  • resolution changes

  • UI scale changes

  • target design changes


Use PixelDNA only for stable visual targets

PixelDNA is useful for small stable visual signatures.

Good targets:

  • stable icons

  • fixed UI badges

  • consistent buttons

  • game UI elements

Poor targets:

  • animated objects

  • transparent overlays

  • changing text

  • shadows

  • video or moving backgrounds

Example:

var dna = getPixelDNA(createLocation(540, 1200), 1, 40, 10);

if (dna) {
    var found = waitPixelDNA(dna, 5);

    if (found) {
        click(found);
    }
}


Use ML Kit as a helper, not the main selector

Good use:

var qr = waitBarcode({ formats: "QR_CODE" }, 5);

if (qr) {
    log("QR value: " + qr.rawValue);
}

Risky use:

var objects = detectObjects({
    classify: true,
    multipleObjects: true
});

if (objects.length > 0) {
    click(objects[0]);
}

Better:

var objects = detectObjects({
    classify: true,
    multipleObjects: true
});

if (objects.length > 0) {
    var obj = objects[0];
    var area = createRegion(obj.x, obj.y, obj.w, obj.h);

    var confirm = text("Confirm")
        .contains()
        .in(area)
        .find();

    if (confirm) {
        click(confirm);
    }
}

ML Kit is useful as a search area reducer, not always as a final click target.


Use reports as a script timeline

Poor report:

reportStep("Clicked");

Better report:

reportStep("Login button clicked");
reportInfo("Login method", { method: "node", fallback: false });
reportError("Welcome text missing after login");

Reports should answer:

  • what happened?

  • what was expected?

  • what failed?

  • which fallback was used?

  • what screen state was detected?


Use clear naming

Poor:

var a = waitText("OK", 5, false);
var b = waitFor("x.png", 5);

Better:

var okButton = waitText("OK", 5, false);
var closeIcon = waitFor("close.png", 5);

Readable scripts are easier to debug and share.


Split large scripts into helpers

Hard to maintain:

// 300 lines of direct automation

Better:

function openApp() {
    return ensureApp("com.example.app", { timeout: 8 }).success;
}

function closePopupIfVisible() {
    var popup = existsText("Not now", 2, false);

    if (popup) {
        click(popup);
        sleep(500);
        return true;
    }

    return false;
}

function clickStart() {
    return smartClick("Start", "start_button.png", 5);
}

clearReport();

if (!openApp()) {
    scriptExit("App did not open");
}

closePopupIfVisible();

if (!clickStart()) {
    scriptExit("Start failed");
}


Test one layer at a time

When building a new script, test in this order:

  1. App launch

  2. Permission handling

  3. Popup handling

  4. First screen detection

  5. First click

  6. Result after first click

  7. Loading state handling

  8. Error handling

  9. Full flow

  10. Cleanup

Do not build the full script before testing the early screen states.


Suggested failure policy

Use reportError(...) when the script can continue.

reportError("Optional popup was not closed");

Use scriptExit(...) when continuing would be unsafe.

scriptExit("Login screen was not found");

Use debug output when failure needs investigation.

saveDebug("login_missing");
scriptExit("Login screen was not found");


Common mistake: clicking too early

Problem:

click(start);
click(next);

Fix:

click(start);
sleep(500);

var next = waitText("Next", 5, false);

if (next) {
    click(next);
}


Common mistake: full-screen OCR false positive

Problem:

var button = waitText("Continue", 5, false);

Fix:

var bottom = createRegion(
    0,
    Math.floor(getScreenHeight() * 0.70),
    getScreenWidth(),
    Math.floor(getScreenHeight() * 0.30)
);

var button = waitTextIn(bottom, "Continue", 5, false);


Common mistake: ignoring app state

Problem:

startApp("com.example.app");
click(540, 1200);

Fix:

if (!ensureApp("com.example.app", { timeout: 8 }).success) {
    scriptExit("App did not open");
}

var ready = waitText("Home", 8, false);

if (!ready) {
    saveDebug("home_missing");
    scriptExit("Home not found");
}


Common mistake: no cleanup

Problem:

argusKeyboardShow();
watch("errorWatcher", function() {
    return existsText("Error", 0, false);
}, 1000);

Fix:

try {
    argusKeyboardShow();

    watch("errorWatcher", function() {
        return existsText("Error", 0, false);
    }, 1000);

    // Main work here.
} finally {
    stopWatcher("errorWatcher");
    argusKeyboardReset();
    clearNotification();
    highlightAllOff();
    clearScreenCache();
}


Common mistake: no fallback

Problem:

var button = waitFor("button.png", 5);

if (button) {
    click(button);
}

Fix:

var button = race([
    waitNodeAsync({ byText: "Continue" }, 5),
    waitTextAsync("Continue", 5),
    waitAsync("button.png", 5)
]);

if (button) {
    click(button.result);
} else {
    saveDebug("continue_missing");
    scriptExit("Continue not found");
}


Common mistake: not checking return values

Problem:

click(target);
sendEmail({
    to: "me@example.com",
    subject: "Done",
    body: "Finished"
});

Fix:

if (!click(target)) {
    reportError("Click failed");
}

var sent = sendEmail({
    to: "me@example.com",
    subject: "Done",
    body: "Finished"
});

if (!sent) {
    reportError("Email failed");
}


Reliability checklist before publishing a script

Before sharing a script, test:

  • fresh app launch

  • app already open

  • slow loading

  • no network

  • popup appears

  • popup does not appear

  • permission appears

  • permission does not appear

  • dark mode if relevant

  • light mode if relevant

  • different screen scale if relevant

  • target image not found

  • target text not found

  • error screen

  • interrupted flow

  • cleanup after failure


Documentation style for shared recipes

When posting a recipe, include:

  • what the script does

  • required permissions

  • required image files

  • required config files

  • expected app package

  • expected screen resolution if relevant

  • known limitations

  • failure/debug files created

  • where users should replace values

Example:

Required files:
- images/start_button.png
- data/config.json

Replace:
- com.example.app
- Start
- Done
- me@example.com


Recommended final script structure

clearReport();
setReferenceResolution(1080, 1920);

function saveDebug(name) {
    createDir("debug");

    var full = createRegion(0, 0, getScreenWidth(), getScreenHeight());

    saveColor(full, "debug/" + name + "_screen.png");
    saveReport("debug/" + name + "_report.json");
    writeFile("debug/" + name + "_ocr.txt", String(readText()));
    writeFile("debug/" + name + "_layout.txt", String(dumpLayout()));
}

function openApp() {
    return ensureApp("com.example.app", {
        timeout: 8,
        retries: 2
    }).success;
}

function closePopupIfVisible() {
    var popup = race([
        waitTextAsync("Not now", 2),
        waitTextAsync("Skip", 2),
        waitAsync("close.png", 2)
    ]);

    if (popup) {
        click(popup.result);
        sleep(500);
    }
}

try {
    reportStep("Script started");

    if (!openApp()) {
        saveDebug("app_open_failed");
        scriptExit("App did not open");
    }

    closePopupIfVisible();

    var start = race([
        waitNodeAsync({ byText: "Start" }, 5),
        waitTextAsync("Start", 5),
        waitAsync("start_button.png", 5)
    ]);

    if (!start) {
        saveDebug("start_missing");
        scriptExit("Start not found");
    }

    click(start.result);
    reportStep("Start clicked");

    var done = waitText("Done", 15, false);

    if (!done) {
        saveDebug("done_missing");
        scriptExit("Done not found");
    }

    reportStep("Script completed");
    setSuccessMessage("Done");
} finally {
    clearNotification();
    highlightAllOff();
    clearScreenCache();
    clearWatchers();
    clearAllIntervals();
    argusKeyboardReset();
}


Final practical rules

  • Prefer stable signals over visual guesses.

  • Use regions whenever possible.

  • Use fallback detection for important actions.

  • Save debug files on important failures.

  • Use race(...) for uncertain screen states.

  • Use waitVanish(...) for loading screens.

  • Use try/finally for cleanup.

  • Use dialogs or config files for reusable scripts.

  • Keep secrets out of persistent variables and shared examples.

  • Test failure paths, not only success paths.

  • Make scripts explain themselves through reports.