Please refer to JavaScript Modules for more information.
How to Use
Overview:
This example demonstrates how to use Total Control to synchronously execute the same App operation flow across multiple Android devices. The script can automatically perform app launching, foreground verification, clicking, text input, paging, returning to the home screen, and closing the app. During execution, it records step execution time, automatically captures screenshots and saves failure states, and collects key runtime context information. It is suitable for multi-device automated testing, batch operations, and as a learning reference for beginners.
Before Running:
>> sigmaLoad("multiDeviceFlow.js");
Source Code
/**
* Unified App operation flow across multiple devices (copy & run)
* Launch -> verify foreground -> click -> input -> pgDn -> home -> close
*/
// ===================== Imports & injections =====================
var { getDevices, Device } = require("sigma/device");
// ===================== Config =====================
var TARGET_PKG = "com.sigma_rt.sigmatestapp";
var SEARCH_TEXT = "500";
var WAIT_LAUNCH_MS = 2500;
var SEARCH_X = 0.5352;
var SEARCH_Y = 0.135;
var LOG_DIR = "C:/tc_logs";
var IMG_TYPE = tcConst.IMG_JPG || tcConst.IMG_PNG; // if tcconst carries these; otherwise keep sigmaConst
// ===================== fs helpers (optional) =====================
var fs = null;
try {
fs = require("fs");
} catch (e) {
fs = null;
}
function ensureDir(path) {
if (!fs) return false;
try {
if (!fs.existsSync(path)) fs.mkdirSync(path, { recursive: true });
return true;
} catch (e) {
return false;
}
}
function appendFile(path, text) {
if (!fs) return false;
try {
fs.appendFileSync(path, text, { encoding: "utf8" });
return true;
} catch (e) {
return false;
}
}
// ===================== tiny utilities =====================
function nowMs() {
return Date.now();
}
function ts() {
// fewer helpers: build a compact timestamp quickly
var d = new Date();
function p(n) {
return (n < 10 ? "0" : "") + n;
}
return (
d.getFullYear() +
p(d.getMonth() + 1) +
p(d.getDate()) +
"_" +
p(d.getHours()) +
p(d.getMinutes()) +
p(d.getSeconds())
);
}
function safeFilePart(s) {
return String(s).replace(/[^\w\-.]+/g, "_");
}
function devName(d) {
return d.name || (d.getName && d.getName()) || d.SN || "UnknownDevice";
}
// Safe property/method fetcher
function safe(fn, fallback) {
try {
return fn();
} catch (e) {
return fallback;
}
}
// System key press wrapper
function press(d, keyConst) {
var ret = d.send(keyConst, tcConst.STATE_PRESS);
if (ret !== 0) throw new Error("send failed: " + lastError());
}
// Step wrapper (timing + uniform logs)
function stepRun(ctx, name, fn) {
var t0 = nowMs();
ctx.log(">> START " + name);
try {
var ret = fn();
ctx.log("<< END " + name + " (cost " + (nowMs() - t0) + " ms)");
return { ok: true, ret: ret };
} catch (e) {
var err = String(e && e.message ? e.message : e);
ctx.log(
"<< FAIL " +
name +
" (cost " +
(nowMs() - t0) +
" ms) err=" +
err,
);
return { ok: false, err: err };
}
}
// One helper to “run a step or fail fast”
function runStepOrFail(ctx, d, result, stepKey, stepName, fn) {
var s = stepRun(ctx, stepName, fn);
if (s.ok) return true;
return failAt(ctx, d, result, stepKey, s.err);
}
// Failure context (data-driven, less repetition)
function writeFailContext(ctx, d, stepKey, errText) {
ctx.log("---- FAIL CONTEXT BEGIN ----");
ctx.log("step=" + stepKey);
ctx.log("error=" + (errText || ""));
// foreground info
ctx.log("foregroundApp=" + (safe(() => d.getForegroundApp(), "") || ""));
ctx.log("activity=" + (safe(() => d.getActivity(), "") || ""));
// common device fields (add/remove here)
var fields = [
["battery", () => d.battery],
["name", () => d.name],
["manufacturer", () => d.manufacturer],
["model", () => d.model],
["SN", () => d.SN],
["IP", () => d.IP],
["DPI", () => d.DPI],
["resolution", () => d.width + "x" + d.height],
["androidVersionRelease", () => d.androidVersionRelease],
["androidVersionSdkInt", () => d.androidVersionSdkInt],
];
for (var i = 0; i < fields.length; i++) {
var k = fields[i][0];
var v = safe(fields[i][1], "");
ctx.log(k + "=" + (v === undefined || v === null ? "" : String(v)));
}
ctx.log("---- FAIL CONTEXT END ----");
}
function takeFailScreenshot(ctx, d, stepKey) {
var file =
LOG_DIR +
"/" +
safeFilePart(ctx.deviceName) +
"__" +
safeFilePart(stepKey) +
"__" +
ts() +
".jpg";
var ret = d.screenshot(file, IMG_TYPE);
if (ret === 0) ctx.log("!! Screenshot saved: " + file);
else ctx.log("!! Screenshot failed: " + lastError());
}
// Central fail handler (so you don’t repeat it)
function failAt(ctx, d, result, stepKey, errText) {
result.ok = false;
result.step = stepKey;
result.err = errText || "";
writeFailContext(ctx, d, stepKey, errText);
takeFailScreenshot(ctx, d, stepKey);
ctx.log("=== DEVICE END (FAIL) ===");
return false;
}
// Per-device logger
function makeCtx(d) {
var name = devName(d);
var canWrite = ensureDir(LOG_DIR) && !!fs;
var logFile = LOG_DIR + "/" + safeFilePart(name) + "__" + ts() + ".log";
return {
deviceName: name,
logFile: logFile,
canWrite: canWrite,
log: function (line) {
var msg = "[" + name + "] " + line;
print(msg);
if (this.canWrite) appendFile(this.logFile, msg + "\n");
},
};
}
// ===================== Single-device flow =====================
function runOnDevice(d) {
var ctx = makeCtx(d);
var result = { ok: false, step: "", err: "", fg: "", act: "" };
ctx.log("=== DEVICE BEGIN ===");
ctx.log(
"info: " +
safe(() => d.manufacturer, "") +
" " +
safe(() => d.model, "") +
" " +
safe(() => d.width, "") +
"x" +
safe(() => d.height, ""),
);
// 1) Launch
if (
!runStepOrFail(
ctx,
d,
result,
"runApp",
"Step1 app.runApp(" + TARGET_PKG + ")",
function () {
var r = d.runApp(TARGET_PKG);
if (r !== 0) throw new Error(lastError());
sleep(WAIT_LAUNCH_MS);
return 0;
},
)
)
return result;
// 2) Foreground verify
if (
!runStepOrFail(
ctx,
d,
result,
"foregroundCheck",
"Step2 verify foreground + getForegroundApp/getActivity",
function () {
var r = d.isAppForeground(TARGET_PKG);
if (r !== 0) throw new Error("not foreground: " + lastError());
result.fg = d.getForegroundApp() || "";
result.act = d.getActivity() || "";
ctx.log("foregroundApp=" + result.fg);
ctx.log("activity=" + result.act);
return 0;
},
)
)
return result;
// 3) Click search
if (
!runStepOrFail(
ctx,
d,
result,
"clickSearch",
"Step3 input.click2(searchBox)",
function () {
var r = d.click2(SEARCH_X, SEARCH_Y, { dx: 0.08, dy: 0.04 });
if (r !== 0) throw new Error(lastError());
sleep(300);
return 0;
},
)
)
return result;
// 4) Input + enter
if (
!runStepOrFail(
ctx,
d,
result,
"typeEnter",
"Step4 tests.inputText(SEARCH_TEXT + \\n)",
function () {
var r = d.inputText(SEARCH_TEXT + "\n");
if (r !== 0)
throw new Error("inputText failed: " + lastError());
sleep(1000);
return 0;
},
)
)
return result;
// 5) Paging
if (
!runStepOrFail(
ctx,
d,
result,
"navKeysPaging",
"Step5 navKeys.pgDn + navKeys.shiftPgDn",
function () {
var r1 = d.pgDn();
if (r1 !== 0) ctx.log("WARN pgDn ret=" + r1);
sleep(250);
var r2 = d.shiftPgDn();
if (r2 !== 0) ctx.log("WARN shiftPgDn ret=" + r2);
sleep(400);
return 0;
},
)
)
return result;
// 6) Back + Home
if (
!runStepOrFail(
ctx,
d,
result,
"backHome",
"Step6 send(KEY_BACK) + send(KEY_HOME)",
function () {
press(d, tcConst.KEY_BACK);
sleep(200);
press(d, tcConst.KEY_HOME);
sleep(400);
return 0;
},
)
)
return result;
// 7) Close app (non-fatal if it fails, but keep it as a step)
stepRun(ctx, "Step7 app.closeApp(" + TARGET_PKG + ")", function () {
var r = d.closeApp(TARGET_PKG);
if (r !== 0) ctx.log("WARN closeApp ret=" + r + " err=" + lastError());
return 0;
});
result.ok = true;
result.step = "done";
ctx.log("=== DEVICE END (OK) ===");
return result;
}
// ===================== main =====================
function main() {
if (Device.connectAll) Device.connectAll();
var devices = getDevices();
if (!devices || devices.length === 0) {
print("No selected devices. Please select devices first.");
return;
}
ensureDir(LOG_DIR);
print("Selected devices: " + devices.length);
var ok = 0;
for (var i = 0; i < devices.length; i++) {
var d = devices[i];
var name = devName(d);
print("\n==============================");
print("RUN " + (i + 1) + "/" + devices.length + " : " + name);
print("==============================");
var r = runOnDevice(d);
if (r.ok) {
ok++;
print("[" + name + "] OK");
} else {
print("[" + name + "] FAIL @" + r.step + " | " + r.err);
}
}
print("\n===== SUMMARY =====");
print("Success: " + ok + " / " + devices.length);
}
main();
Execution Result
Selected devices: 2
==============================
RUN 1/2 : TestDevice03
==============================
[TestDevice03] === DEVICE BEGIN ===
[TestDevice03] info: HUAWEI HLK-AL00 1080x2340
[TestDevice03] >> START Step1 app.runApp(com.sigma_rt.sigmatestapp)
[TestDevice03] << END Step1 app.runApp(com.sigma_rt.sigmatestapp) (cost 2591 ms)
[TestDevice03] >> START Step2 app.isAppForeground + getForegroundApp/getActivity
[TestDevice03] foregroundApp=com.sigma_rt.sigmatestapp
[TestDevice03] activity=com.sigma_rt.sigmatestapp/.MainActivity
[TestDevice03] << END Step2 app.isAppForeground + getForegroundApp/getActivity (cost 405 ms)
[TestDevice03] >> START Step3 input.click2(searchBox)
[TestDevice03] << END Step3 input.click2(searchBox) (cost 364 ms)
[TestDevice03] >> START Step4 tests.inputText(SEARCH_TEXT + \n)
[TestDevice03] << END Step4 tests.inputText(SEARCH_TEXT + \n) (cost 1032 ms)
[TestDevice03] >> START Step5 navKeys.pgDn + navKeys.shiftPgDn
[TestDevice03] << END Step5 navKeys.pgDn + navKeys.shiftPgDn (cost 716 ms)
[TestDevice03] >> START Step6 input.send(KEY_BACK) + input.send(KEY_HOME)
[TestDevice03] << END Step6 input.send(KEY_BACK) + input.send(KEY_HOME) (cost 653 ms)
[TestDevice03] >> START Step7 app.closeApp(com.sigma_rt.sigmatestapp)
[TestDevice03] << END Step7 app.closeApp(com.sigma_rt.sigmatestapp) (cost 254 ms)
[TestDevice03] === DEVICE END (OK) ===
[TestDevice03] OK
==============================
RUN 2/2 : T110
==============================
[T110] === DEVICE BEGIN ===
[T110] info: HUAWEI HLK-AL00 1080x2340
[T110] >> START Step1 app.runApp(com.sigma_rt.sigmatestapp)
[T110] << END Step1 app.runApp(com.sigma_rt.sigmatestapp) (cost 2578 ms)
[T110] >> START Step2 app.isAppForeground + getForegroundApp/getActivity
[T110] foregroundApp=com.sigma_rt.sigmatestapp
[T110] activity=com.huawei.android.launcher/.unihome.UniHomeLauncher
[T110] << END Step2 app.isAppForeground + getForegroundApp/getActivity (cost 483 ms)
[T110] >> START Step3 input.click2(searchBox)
[T110] << END Step3 input.click2(searchBox) (cost 343 ms)
[T110] >> START Step4 tests.inputText(SEARCH_TEXT + \n)
[T110] << END Step4 tests.inputText(SEARCH_TEXT + \n) (cost 1032 ms)
[T110] >> START Step5 navKeys.pgDn + navKeys.shiftPgDn
[T110] << END Step5 navKeys.pgDn + navKeys.shiftPgDn (cost 693 ms)
[T110] >> START Step6 input.send(KEY_BACK) + input.send(KEY_HOME)
[T110] << END Step6 input.send(KEY_BACK) + input.send(KEY_HOME) (cost 633 ms)
[T110] >> START Step7 app.closeApp(com.sigma_rt.sigmatestapp)
[T110] << END Step7 app.closeApp(com.sigma_rt.sigmatestapp) (cost 291 ms)
[T110] === DEVICE END (OK) ===
[T110] OK
===== SUMMARY =====
Success: 2 / 2
0