Please refer to JavaScript Modules for more information.
How to Use
Overview:
This script example demonstrates how to use ArrayOutput to collect execution results from each device in a multi-device batch workflow, and how to read and analyze those results in a unified way using ArrayOutputReturn.
The example covers result structures, common access patterns, error checking, and debugging techniques, and can be directly copied, executed, and applied to real-world projects.
Before Running:
>> sigmaLoad("TestArrayOutput.js");
Source Code
/**
* ArrayOutput Full-Feature Example: Multi-Device Batch Execution + Detailed Walkthrough
* (Ready to copy and run)
*
* What you will learn / see:
* 1) ArrayOutput:
* - Used to collect execution results from each device (value + error)
* 2) ArrayOutputReturn:
* - Used to read/query results (read-only wrapper)
* 3) global.showOutputFlag:
* - When enabled, getReturn() automatically prints a structured table
* - Also assigns the latest ArrayOutputReturn to global.ao (global debug alias)
* 4) Three forms of ret.get():
* - ret.get() => Returns the full records array
* (each item includes name / object / id / value / error)
* - ret.get(device) => Returns the value for the specified device
* - ret.get([device...]) => Returns a value array aligned with the input order
* 5) Dynamic helpers (objects / values / errors / ids / names):
* - No arguments: returns the entire column as an array
* - One argument: returns the field value for the specified object
* 6) success():
* - Returns true if all errors are empty
* 7) lastError():
* - Returns the last record whose error is not empty
* (scanned from bottom to top)
* 8) findObj():
* - Accepts either a device object
* - Or a String(device) (i.e. the device id)
*
* Notes:
* - The runFlow() below uses common Total Control APIs
* (runApp / isAppForeground / click2 / inputText / ...)
* - If some API names differ in your environment, simply replace them;
* this does not affect the ArrayOutput demonstration logic
*/
var { getDevices, Device } = require("sigma/device");
// ===================== Config: App and Actions Used in This Example =====================
var TARGET_PKG = "com.sigma_rt.sigmatestapp";
var SEARCH_TEXT = "500";
var WAIT_LAUNCH_MS = 1200;
var SEARCH_X = 0.5352;
var SEARCH_Y = 0.135;
// ===================== Utilities: Device Names, Logs, and Key Actions =====================
function devName(d) {
// Handles different device object fields: name, getName(), or SN
return d.name || (d.getName && d.getName()) || d.SN || "UnknownDevice";
}
function logDev(d, s) {
print("[" + devName(d) + "] " + s);
}
function press(d, keyConst) {
// Common Total Control usage: send(KEY_*, STATE_PRESS)
var ret = d.send(keyConst, tcConst.STATE_PRESS);
if (ret !== 0) throw new Error("send failed: " + lastError());
}
/**
* step(): A simple "step wrapper"
* - Purpose: Wraps try/catch and stringifies errors for cleaner workflow code
* - Returns: { ok: true, ret: any } or { ok: false, err: string }
*/
function step(label, fn) {
try {
var r = fn();
return { ok: true, ret: r };
} catch (e) {
return { ok: false, err: String(e && e.message ? e.message : e) };
}
}
// ===================== Single-Device Flow: Returns value (object) on success, throws on failure (errors collected externally) =====================
/**
* The return value of runFlow(d) will be used as the value in ArrayOutput.add(d, value).
* If runFlow throws an exception, the outer catch will call out.add(d, null, errorText).
*/
function runFlow(d) {
// This is the final "value" to be returned and collected (structured for easier subsequent analysis)
var result = {
ok: false,
step: "", // Records the step where a failure or termination occurred
fg: "", // Foreground package name (on success)
act: "", // Activity name (on success)
};
logDev(d, "=== DEVICE BEGIN ===");
// 1) Launch the App
var s1 = step("runApp", function () {
var r = d.runApp(TARGET_PKG);
if (r !== 0) throw new Error(lastError());
sleep(WAIT_LAUNCH_MS);
return 0;
});
if (!s1.ok) {
result.step = "runApp";
throw new Error("runApp failed: " + s1.err);
}
// 2) Verify foreground + retrieve foreground info
var s2 = step("foregroundCheck", function () {
var r = d.isAppForeground(TARGET_PKG);
if (r !== 0) throw new Error("not foreground: " + lastError());
result.fg = d.getForegroundApp() || "";
result.act = d.getActivity() || "";
return 0;
});
if (!s2.ok) {
result.step = "foregroundCheck";
throw new Error("foregroundCheck failed: " + s2.err);
}
// 3) Click the search box
var s3 = step("clickSearch", function () {
var r = d.click2(SEARCH_X, SEARCH_Y, { dx: 0.08, dy: 0.04 });
if (r !== 0) throw new Error(lastError());
sleep(250);
return 0;
});
if (!s3.ok) {
result.step = "clickSearch";
throw new Error("clickSearch failed: " + s3.err);
}
// 4) Enter text and press Enter
var s4 = step("inputText", function () {
var r = d.inputText(SEARCH_TEXT + "\n");
if (r !== 0) throw new Error("inputText failed: " + lastError());
sleep(600);
return 0;
});
if (!s4.ok) {
result.step = "inputText";
throw new Error("inputText failed: " + s4.err);
}
// 5) Turn page (example: failure is non-fatal, no throw here; just demonstrating continuation)
step("paging", function () {
d.pgDn();
sleep(200);
d.shiftPgDn();
sleep(300);
return 0;
});
// 6) Go back / return to home
var s6 = step("backHome", function () {
press(d, tcConst.KEY_BACK);
sleep(200);
press(d, tcConst.KEY_HOME);
sleep(300);
return 0;
});
if (!s6.ok) {
result.step = "backHome";
throw new Error("backHome failed: " + s6.err);
}
// 7) Close the app (failure is non-fatal)
step("closeApp", function () {
d.closeApp(TARGET_PKG);
return 0;
});
result.ok = true;
result.step = "done";
logDev(d, "=== DEVICE END (OK) ===");
return result;
}
// ===================== Main Entry: Collection + Return Wrapper + Full-Feature Read Demonstration =====================
function main() {
/**
* Purpose of showOutputFlag:
* - When calling out.getReturn(), the module prints a simplified table:
* [{ name, object: id, value, error }, ...]
* - Also assigns global.ao to the latest return object
* (so you can conveniently use ao.values() directly in the console)
*/
global.showOutputFlag = true;
if (Device.connectAll) Device.connectAll();
var devices = getDevices();
if (!devices || devices.length === 0) {
print("No selected devices. Please select devices first.");
return;
}
print("Selected devices: " + devices.length);
// 1) Create the collector
var out = new ArrayOutput();
// 2) Batch execution: each device generates a value or an error
for (var i = 0; i < devices.length; i++) {
var d = devices[i];
print("\n==============================");
print("RUN " + (i + 1) + "/" + devices.length + " : " + devName(d));
print("==============================");
try {
// Success: value is a structured object (recommended)
var value = runFlow(d);
out.add(d, value); // error is omitted, defaulting to null
} catch (e) {
// Failure: value is null, error is a string
out.add(d, null, String(e && e.message ? e.message : e));
}
}
// 3) Obtain the read-only return object (core functionality)
var ret = out.getReturn();
// 4) Verify the global alias
print("\n-- global alias check --");
print("ret === global.ao ? " + (ret === global.ao));
// =========================================================
// A. Status APIs: toString / success / lastError
// =========================================================
print("\n-- status APIs --");
print(ret.toString()); // "ArrayOutputReturn: N elements"
print("success() = " + ret.success()); // Returns true only if there are no errors
var lastErr = ret.lastError(); // {object, id, error} or undefined
if (lastErr) {
print("lastError.id=" + lastErr.id);
print("lastError.error=" + lastErr.error);
} else {
print("lastError() = undefined (no errors)");
}
// =========================================================
// // B. get() in three forms (the most important access method)
// =========================================================
print("\n-- get() forms --");
// 1) ret.get(): Retrieve all records (each containing: name / object / id / value / error)
var all = ret.get();
print("get() records length = " + all.length);
// Example structure of all[0]:
// {
// name: "DeviceName",
// object: ,
// id: "String(deviceObject)",
// value: ,
// error:
// }
// 2) ret.get(device): Retrieve the value for a single device (not the full record)
var d0 = devices[0];
print("get(device0) value = " + JSON.stringify(ret.get(d0)));
// 3) ret.get([device...]): Retrieve values for multiple devices (aligned with the input array order)
var pick = devices.slice(0, Math.min(3, devices.length));
print("get([device0..]) values = " + JSON.stringify(ret.get(pick)));
// =========================================================
// C. Dynamic helpers: objects / values / errors / ids / names
// =========================================================
print("\n-- helpers (no args) --");
// No arguments: returns the entire column as an array
// Note: objects() returns the device objects themselves; may appear as [object ...] when printed
print("objects() = " + ret.objects());
print("values() = " + JSON.stringify(ret.values()));
print("errors() = " + JSON.stringify(ret.errors()));
print("ids() = " + JSON.stringify(ret.ids()));
print("names() = " + JSON.stringify(ret.names()));
print("\n-- helpers (single target) --");
// Single argument: retrieve a specific field for a given device
print("values(device0) = " + JSON.stringify(ret.values(d0)));
print("errors(device0) = " + JSON.stringify(ret.errors(d0)));
print("ids(device0) = " + JSON.stringify(ret.ids(d0)));
print("names(device0) = " + JSON.stringify(ret.names(d0)));
print("objects(device0)= " + ret.objects(d0));
// =========================================================
// D. findObj: Locate index (supports either object or string id)
// =========================================================
print("\n-- findObj() --");
// 1) Pass an object: match using identity or String(obj)
var idxByObj = ret.findObj(d0);
print("findObj(device0) = " + idxByObj);
// 2) Pass a string: match using String(this.output[i].object)
// This corresponds to the id stored as String(obj) when using add()
var id0 = String(d0);
var idxById = ret.findObj(id0);
print("findObj(String(device0)) = " + idxById + ' (id="' + id0 + '")');
// =========================================================
// E. Global ao: Quick debugging access (same object as ret)
// =========================================================
print("\n-- global ao usage --");
print("ao.values() = " + JSON.stringify(ao.values()));
print("ao.errors() = " + JSON.stringify(ao.errors()));
print("ao.success() = " + ao.success());
// =========================================================
// F. Common practical use: generate a more readable summary output
// =========================================================
print("\n===== SUMMARY (computed) =====");
var names = ret.names();
var errs = ret.errors();
var vals = ret.values();
var okCount = 0;
for (var k = 0; k < names.length; k++) {
if (!errs[k]) {
okCount++;
print("[" + names[k] + "] OK step=" + (vals[k] && vals[k].step));
} else {
print("[" + names[k] + "] FAIL err=" + errs[k]);
}
}
print("Success: " + okCount + " / " + names.length);
/**
* Additional tips (common usage recommendations):
* - It is recommended to store structured objects in value
* (e.g., { ok, step, fg, act, ... }) for easier later analysis and troubleshooting
* - It is recommended to store errors as a string: String(err)
* - On failure, you can include more information in the error text or value
* (e.g., screenshot paths, execution time, key parameters)
*/
}
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