任务创建、执行与结果获取


更多信息请参阅 JavaScript Modules


如何使用

简介:

本示例展示如何通过 Tasks 接口,完整管理一次自动化任务的生命周期。
脚本依次完成任务创建(tasksCreate)、任务入库确认(tasksGet),并通过轮询方式持续查询任务状态,直到任务执行结束(RUNNING → STOPPED_*),最终输出任务执行结果与关键信息。
示例内置标准化的步骤日志(开始 / 结束 / 耗时统计),并在失败场景下自动采集前台应用、Activity、电量、设备信息及截图留证,便于问题定位与结果审计。
适用于 自动化测试调度、批量任务执行、平台集成与执行结果监控 等场景,可直接复制运行。

运行前:

  1. 下载并安装 Total Control 11.0(Update 20)及以上版本(下载
  2. 连接 Android 设备(支持 USB 连接 TCP 连接
  3. 在 Total Control 界面中勾选测试设备 (请注意:脚本中的 getDevices() 只会返回当前已选中的设备)
  4. [重要] 文件准备:请提前准备一个任务脚本文件
    • 路径:放置到 Total Control 默认脚本目录
    • 文件名:test.js
    • 内容示例:
      sleep(10000);
      print('-tasks-test-');
  5. 将示例代码保存为一个 .js 文件(如 TaskCreateSubmitPoll.js),并放置到 Total Control 默认脚本目录
  6. 打开脚本终端(主面板 → 脚本 → 脚本终端),执行:
    >> sigmaLoad("TaskCreateSubmitPoll.js");
    

注意:

  • tasksCreate() 会校验脚本路径必须存在,否则 "Script path not found"
  • tasks 的“执行器”不一定马上运行;如果 planTime 在未来会保持 SCHEDULED(-6)
  • 本示例用轮询 tasksGet 获取最终 status(-2/-3/-5 等)


源代码

/**
 * ============================================================
 * [TASK] Create -> Submit -> Get results
 * ============================================================
 * 目标:
 *  1) 创建任务 tasksCreate(...)
 *  2) 确认任务已入库 tasksGet(...)
 *  3) 轮询 tasksGet(...) 等待任务结束(RUNNING -> STOPPED_*)
 *  4) 打印结果(status / errorMessage / execDevice / sigmaTestStatus 等)
 *
 * 输出要求:
 *  - 每一步打印「开始/结束/耗时」
 *  - 失败:写上下文(前台包名+activity+电量+设备信息)+ 截图 + 写日志文件(若 fs 可用)
 *
 * 关键注意:
 *  - tasksCreate() 会校验脚本路径必须存在,否则 "Script path not found"
 *  - tasks 的“执行器”不一定马上运行;如果 planTime 在未来会保持 SCHEDULED(-6)
 *  - 本示例用轮询 tasksGet 获取最终 status(-2/-3/-5 等)
 */

// ===================== 引入与注入 =====================
var { getDevices } = require("sigma/device");
var tasks = require("sigma/tasks"); // tasks 模块

// ===================== 配置区 =====================
var LOG_DIR = "C:/tc_logs";

var IMG_TYPE = tcConst.IMG_JPG || tcConst.IMG_PNG;

// 任务名必须唯一(建议带时间戳)
function pad2(n) {
    return (n < 10 ? "0" : "") + n;
}
function ts() {
    var d = new Date();
    return (
        d.getFullYear() +
        pad2(d.getMonth() + 1) +
        pad2(d.getDate()) +
        "_" +
        pad2(d.getHours()) +
        pad2(d.getMinutes()) +
        pad2(d.getSeconds())
    );
}

var TASK_NAME = "demo_task_" + ts();

// 脚本必须存在、且扩展名必须 .js/.tst/.scp
// 例:var SCRIPT_PATH = "c:/file/test.js" 或者 test.js;
var SCRIPT_PATH = "test.js";
// 执行次数(iteration/iterCount)>=1
var ITERATION = 1;

// 执行时间策略:
// 1) 立刻创建(now):time = undefined, repeat = undefined
// 2) 指定一次性时间:time = "YYYY-MM-DD HH:mm[:ss]"
// 3) 周期:time="HH:mm[:ss]" + repeat=[0..6]
var TIME_MODE = "now"; // "now" | "runOnce" | "weekly"

// runOnce 示例(未来时间)
// var ONE_SHOT_TIME = "2026-01-14 23:50";

// weekly 示例:每周一三五 08:30
// var WEEKLY_TIME = "08:30";
// var WEEKLY_REPEAT = [1,3,5];

// 轮询等待最大时长(毫秒)
var WAIT_TIMEOUT_MS = 3 * 60 * 1000; // 3 分钟
var POLL_INTERVAL_MS = 1500;

// ===================== 小工具 =====================
function nowMs() {
    return Date.now();
}
function safeFilePart(s) {
    return String(s).replace(/[^\w\-.]+/g, "_");
}
function devName(d) {
    return d.name || (d.getName && d.getName()) || d.SN || "UnknownDevice";
}
function safeGet(fn, fallback) {
    try {
        return fn();
    } catch (e) {
        return fallback;
    }
}

// fs(有则写日志/锁)
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;
    }
}

// ===================== 日志 ctx =====================
function makeCtx() {
    var canWrite = fs != null && ensureDir(LOG_DIR);
    var logFile =
        LOG_DIR + "/TASK__" + safeFilePart(TASK_NAME) + "__" + ts() + ".log";

    return {
        logFile: logFile,
        canWrite: canWrite,
        log: function (line) {
            var msg = "[TASK] " + line;
            print(msg);
            if (this.canWrite) appendFile(this.logFile, msg + "\n");
        },
    };
}

// ===================== stepRun:开始/结束/耗时 =====================
function stepRun(ctx, stepName, fn) {
    var start = nowMs();
    ctx.log(">> START  " + stepName);
    try {
        var ret = fn();
        ctx.log(
            "<< END    " + stepName + "  (cost " + (nowMs() - start) + " ms)",
        );
        return { ok: true, ret: ret };
    } catch (e) {
        ctx.log(
            "<< FAIL   " +
                stepName +
                "  (cost " +
                (nowMs() - start) +
                " ms)  err=" +
                String(e && e.message ? e.message : e),
        );
        return { ok: false, err: String(e && e.message ? e.message : e) };
    }
}

// ===================== 失败上下文 + 截图 =====================
function writeFailContext(ctx, d, stepName, errText) {
    ctx.log("---- FAIL CONTEXT (" + devName(d) + ") BEGIN ----");
    ctx.log("step=" + stepName);
    ctx.log("error=" + (errText || ""));
    ctx.log(
        "foregroundApp=" +
            (safeGet(function () {
                return d.getForegroundApp();
            }, "") || ""),
    );
    ctx.log(
        "activity=" +
            (safeGet(function () {
                return d.getActivity();
            }, "") || ""),
    );
    ctx.log(
        "battery=" +
            (safeGet(function () {
                return d.battery;
            }, "") || ""),
    );
    ctx.log(
        "manufacturer=" +
            safeGet(function () {
                return d.manufacturer;
            }, ""),
    );
    ctx.log(
        "model=" +
            safeGet(function () {
                return d.model;
            }, ""),
    );
    ctx.log(
        "SN=" +
            safeGet(function () {
                return d.SN;
            }, ""),
    );
    ctx.log(
        "IP=" +
            safeGet(function () {
                return d.IP;
            }, ""),
    );
    ctx.log(
        "DPI=" +
            safeGet(function () {
                return d.DPI;
            }, ""),
    );
    ctx.log(
        "resolution=" +
            safeGet(function () {
                return d.width;
            }, "") +
            "x" +
            safeGet(function () {
                return d.height;
            }, ""),
    );
    ctx.log(
        "androidVersionRelease=" +
            safeGet(function () {
                return d.androidVersionRelease;
            }, ""),
    );
    ctx.log(
        "androidVersionSdkInt=" +
            safeGet(function () {
                return d.androidVersionSdkInt;
            }, ""),
    );
    ctx.log("---- FAIL CONTEXT (" + devName(d) + ") END ----");
}

function screenshotOne(ctx, d, tag) {
    var file =
        LOG_DIR +
        "/" +
        safeFilePart(devName(d)) +
        "__" +
        safeFilePart(tag) +
        "__" +
        ts() +
        ".jpg";
    var ret = d.screenshot(file, IMG_TYPE);
    if (ret === 0) ctx.log("!! Screenshot saved: " + file);
    else ctx.log("!! Screenshot failed (" + devName(d) + "): " + lastError());
}

// ===================== task status helper =====================
function statusToText(code) {
    var S = tasks.STATUS || {};
    // 常见值(文档里)
    if (code === 0) return "FILE_NOT_FOUND(0)";
    if (code === S.RUNNING || code === -1) return "RUNNING(-1)";
    if (code === S.STOPPED_NORMAL || code === -2) return "STOPPED_NORMAL(-2)";
    if (code === S.STOPPED_ERROR || code === -3) return "STOPPED_ERROR(-3)";
    if (code === S.PAUSED || code === -4) return "PAUSED(-4)";
    if (code === S.USER_TERMINATED || code === -5) return "USER_TERMINATED(-5)";
    if (code === S.SCHEDULED || code === -6) return "SCHEDULED(-6)";
    return String(code);
}

function isTerminalStatus(code) {
    return code === -2 || code === -3 || code === -5 || code === 0;
}

// ===================== main =====================
function main() {
    var ctx = makeCtx();

    // 可选:拿到设备,主要用于失败时上下文/截图
    var devices = getDevices();
    var list = [];
    if (devices && devices.length)
        devices.forEach(function (d) {
            list.push(d);
        });
    ctx.log("Selected devices: " + list.length);

    // Step0:准备脚本路径存在性提示
    stepRun(ctx, "Step0 Check script path string", function () {
        ctx.log("TASK_NAME=" + TASK_NAME);
        ctx.log("SCRIPT_PATH=" + SCRIPT_PATH);
        ctx.log("ITERATION=" + ITERATION);
        ctx.log("TIME_MODE=" + TIME_MODE);
    });

    // Step1:Create task
    var s1 = stepRun(ctx, "Step1 tasksCreate()", function () {
        var ret;

        if (TIME_MODE === "now") {
            // 立即(time/repeat 省略)
            ret = tasks.tasksCreate(TASK_NAME, SCRIPT_PATH, ITERATION);
        } else if (TIME_MODE === "runOnce") {
            if (typeof ONE_SHOT_TIME === "undefined")
                throw new Error("ONE_SHOT_TIME 未设置");
            // 一次性定时(自行把 ONE_SHOT_TIME 配出来)
            ret = tasks.tasksCreate(
                TASK_NAME,
                SCRIPT_PATH,
                ITERATION,
                ONE_SHOT_TIME,
            );
        } else if (TIME_MODE === "weekly") {
            if (typeof WEEKLY_TIME === "undefined")
                throw new Error("WEEKLY_TIME 未设置");
            if (typeof WEEKLY_REPEAT === "undefined")
                throw new Error("WEEKLY_REPEAT 未设置");
            // 周期(自行把 WEEKLY_TIME/WEEKLY_REPEAT 配出来)
            ret = tasks.tasksCreate(
                TASK_NAME,
                SCRIPT_PATH,
                ITERATION,
                WEEKLY_TIME,
                WEEKLY_REPEAT,
            );
        } else {
            throw new Error("Unknown TIME_MODE: " + TIME_MODE);
        }

        if (ret !== true && typeof ret !== "number") {
            // tasksCreate 文档:成功 true;失败 false(配合 lastError)
            throw new Error("tasksCreate FAIL: " + lastError());
        }

        ctx.log("tasksCreate OK, ret=" + ret);
        return ret;
    });

    if (!s1.ok) {
        ctx.log("[CREATE] Task create FAIL: " + TASK_NAME);
        ctx.log("[CREATE] err=" + s1.err);

        // 失败时:对每台设备补一份上下文+截图
        for (var i = 0; i < list.length; i++) {
            writeFailContext(ctx, list[i], "tasksCreate", s1.err);
            screenshotOne(ctx, list[i], "FAIL__tasksCreate");
        }

        // 额外:打印最近任务帮助排查
        var s1b = stepRun(ctx, "Step1b tasksList(recent 5)", function () {
            var recent = tasks.tasksList({
                limit: 5,
                orderBy: "updatedAt DESC",
            });
            ctx.log("recent tasks:\n" + JSON.stringify(recent, null, 2));
        });

        return;
    }

    // Step2:Get task record (submit/confirm)
    var taskRow = null;
    var s2 = stepRun(ctx, "Step2 tasksGet(taskName)", function () {
        var t = tasks.tasksGet(TASK_NAME);
        if (!t) throw new Error("tasksGet returned null (not found)");
        taskRow = t;
        // tasksGet 可能返回 object 或 [object](不同实现),这里统一一下
        if (Array.isArray(taskRow)) taskRow = taskRow[0];
        ctx.log("task snapshot:\n" + JSON.stringify(taskRow, null, 2));
        return taskRow;
    });
    if (!s2.ok) return;

    // Step3:Poll until terminal
    var s3 = stepRun(
        ctx,
        "Step3 Poll task until terminal or timeout",
        function () {
            var deadline = nowMs() + WAIT_TIMEOUT_MS;
            var lastStatus = null;

            while (nowMs() < deadline) {
                var t = tasks.tasksGet(TASK_NAME);
                if (Array.isArray(t)) t = t[0];
                if (!t) throw new Error("tasksGet null during polling");

                var st = t.status;
                if (st !== lastStatus) {
                    ctx.log(
                        "poll: status=" +
                            statusToText(st) +
                            " startTime=" +
                            t.startTime +
                            " endTime=" +
                            t.endTime,
                    );
                    lastStatus = st;
                }

                if (isTerminalStatus(st)) {
                    taskRow = t;
                    return t;
                }

                sleep(POLL_INTERVAL_MS);
            }

            // 超时:可能仍是 SCHEDULED/RUNNING
            throw new Error(
                "Timeout waiting task finish. lastStatus=" +
                    statusToText(lastStatus),
            );
        },
    );

    // Step4:Print result summary
    stepRun(ctx, "Step4 Print result summary", function () {
        var t = taskRow;
        if (Array.isArray(t)) t = t[0];

        ctx.log("===== RESULT =====");
        ctx.log("taskName=" + t.taskName);
        ctx.log("taskID=" + t.taskID);
        ctx.log("status=" + statusToText(t.status));
        ctx.log("errorMessage=" + (t.errorMessage || ""));
        ctx.log("execDevice=" + (t.execDevice || "-"));
        ctx.log("startTime=" + t.startTime);
        ctx.log("endTime=" + t.endTime);
        ctx.log(
            "sigmaTestStatus=" +
                (t.sigmaTestStatus || t.sigma_test_status || ""),
        );

        // 若失败:补一份设备上下文+截图
        if (t.status === -3 || t.status === 0) {
            ctx.log("Task ended with error, collecting device evidence...");
            for (var i = 0; i < list.length; i++) {
                writeFailContext(
                    ctx,
                    list[i],
                    "taskResult(" + statusToText(t.status) + ")",
                    t.errorMessage || "",
                );
                screenshotOne(ctx, list[i], "FAIL__taskResult");
            }
        }
    });

    // Step5:Optional - show recent list
    stepRun(ctx, "Step5 tasksList(recent 5)", function () {
        var recent = tasks.tasksList({ limit: 5, orderBy: "updatedAt DESC" });
        ctx.log("recent tasks:\n" + JSON.stringify(recent, null, 2));
    });

    ctx.log("DONE.");
}

main();


运行结果

[TASK] Selected devices: 2
[TASK] >> START  Step0 Check script path string
[TASK] TASK_NAME=demo_task_20260127_184910
[TASK] SCRIPT_PATH=test.js
[TASK] ITERATION=1
[TASK] TIME_MODE=now
[TASK] << END    Step0 Check script path string  (cost 155 ms)
[TASK] >> START  Step1 tasksCreate()
[TASK] tasksCreate OK, ret=true
[TASK] << END    Step1 tasksCreate()  (cost 165 ms)
[TASK] >> START  Step2 tasksGet(taskName)
[TASK] task snapshot:
{
  "id": 7,
  "autoConnect": false,
  "autoSelect": false,
  "startTime": 1769510950415,
  "endTime": null,
  "errorMessage": "",
  "execCount": 1,
  "execDevice": "-",
  "expiration": -31,
  "iterCount": 1,
  "reExec": false,
  "options": [
    {
      "device_names": [],
      "device_sn": [],
      "path": "C:\\Users\xxx\\Documents\\Scripts\\test.js"
    }
  ],
  "status": -1,
  "taskID": 17695109504181,
  "taskName": "demo_task_20260127_184910",
  "planTime": 1769510950415,
  "weekJson": null,
  "createdAt": 1769510950415,
  "updatedAt": 1769510950452,
  "timezone": "Asia/Shanghai",
  "sigmaTestStatus": null
}
[TASK] << END    Step2 tasksGet(taskName)  (cost 67 ms)
[TASK] >> START  Step3 Poll task until terminal or timeout
[TASK] poll: status=RUNNING(-1) startTime=1769510950415 endTime=null
[TASK] poll: status=STOPPED_NORMAL(-2) startTime=1769510950415 endTime=1769510960615
[TASK] << END    Step3 Poll task until terminal or timeout  (cost 10639 ms)
[TASK] >> START  Step4 Print result summary
[TASK] ===== RESULT =====
[TASK] taskName=demo_task_20260127_184910
[TASK] taskID=17695109504181
[TASK] status=STOPPED_NORMAL(-2)
[TASK] errorMessage=
[TASK] execDevice=-
[TASK] startTime=1769510950415
[TASK] endTime=1769510960615
[TASK] sigmaTestStatus=
[TASK] << END    Step4 Print result summary  (cost 310 ms)
[TASK] >> START  Step5 tasksList(recent 5)
[TASK] recent tasks:
[
  {
    "id": 7,
    "autoConnect": false,
    "autoSelect": false,
    "startTime": 1769510950415,
    "endTime": 1769510960615,
    "errorMessage": "",
    "execCount": 1,
    "execDevice": "-",
    "expiration": -10200,
    "iterCount": 1,
    "reExec": false,
    "options": [
      {
        "device_names": [],
        "device_sn": [],
        "path": "C:\\Users\xxx\\Documents\\Scripts\\test.js"
      }
    ],
    "status": -2,
    "taskID": 17695109504181,
    "taskName": "demo_task_20260127_184910",
    "planTime": 1769510950415,
    "weekJson": null,
    "createdAt": 1769510950415,
    "updatedAt": 1769510960619,
    "timezone": "Asia/Shanghai",
    "sigmaTestStatus": null
  },
  {
    "id": 6,
    "autoConnect": false,
    "autoSelect": false,
    "startTime": 1769520600000,
    "endTime": null,
    "errorMessage": "",
    "execCount": 1,
    "execDevice": "-",
    "expiration": 12714486,
    "iterCount": 1,
    "reExec": false,
    "options": [
      {
        "device_names": [
          "T110",
          "TestDevice03"
        ],
        "device_sn": [
          "GDB6R19819001063",
          "GDB6R19A18005229"
        ],
        "path": "C:/Users/XXX/Documents/Scripts/TaskCreateSubmitPoll.js"
      }
    ],
    "status": -6,
    "taskID": 17695078855150,
    "taskName": "form_fill_demo_20260127_175805",
    "planTime": 1769520600000,
    "weekJson": "[\"2\",\"4\"]",
    "createdAt": 1769507885514,
    "updatedAt": 1769507885514,
    "timezone": "Asia/Shanghai",
    "sigmaTestStatus": null
  },
  {
    "id": 5,
    "autoConnect": false,
    "autoSelect": false,
    "startTime": 1769520600000,
    "endTime": null,
    "errorMessage": "",
    "execCount": 1,
    "execDevice": "-",
    "expiration": 13809711,
    "iterCount": 1,
    "reExec": false,
    "options": [
      {
        "device_names": [
          "T110",
          "TestDevice03"
        ],
        "device_sn": [
          "GDB6R19819001063",
          "GDB6R19A18005229"
        ],
        "path": "C:/Users/XXX/Documents/Scripts/TaskCreateSubmitPoll.js"
      }
    ],
    "status": -6,
    "taskID": 17695067902980,
    "taskName": "form_fill_demo_20260127_173950",
    "planTime": 1769520600000,
    "weekJson": "[\"2\",\"4\"]",
    "createdAt": 1769506790289,
    "updatedAt": 1769506790289,
    "timezone": "Asia/Shanghai",
    "sigmaTestStatus": null
  },
  {
    "id": 4,
    "autoConnect": false,
    "autoSelect": false,
    "startTime": 1769520600000,
    "endTime": null,
    "errorMessage": "",
    "execCount": 1,
    "execDevice": "-",
    "expiration": 14652852,
    "iterCount": 1,
    "reExec": false,
    "options": [
      {
        "device_names": [
          "T110",
          "TestDevice03"
        ],
        "device_sn": [
          "GDB6R19819001063",
          "GDB6R19A18005229"
        ],
        "path": "C:/Users/XXX/Documents/Scripts/TaskCreateSubmitPoll.js"
      }
    ],
    "status": -6,
    "taskID": 17695059471538,
    "taskName": "form_fill_demo_20260127_172547",
    "planTime": 1769520600000,
    "weekJson": "[\"2\",\"4\"]",
    "createdAt": 1769505947148,
    "updatedAt": 1769505947148,
    "timezone": "Asia/Shanghai",
    "sigmaTestStatus": null
  },
  {
    "id": 3,
    "autoConnect": false,
    "autoSelect": false,
    "startTime": 1769504982779,
    "endTime": 1769504992938,
    "errorMessage": "",
    "execCount": 1,
    "execDevice": "-",
    "expiration": -10158,
    "iterCount": 1,
    "reExec": false,
    "options": [
      {
        "device_names": [],
        "device_sn": [],
        "path": "C:\\Users\xxx\\Documents\\Scripts\\test.js"
      }
    ],
    "status": -2,
    "taskID": 17695049827861,
    "taskName": "demo_task_20260127_170942",
    "planTime": 1769504982780,
    "weekJson": null,
    "createdAt": 1769504982779,
    "updatedAt": 1769504992940,
    "timezone": "Asia/Shanghai",
    "sigmaTestStatus": null
  }
]
[TASK] << END    Step5 tasksList(recent 5)  (cost 73 ms)
[TASK] DONE.
0

TCHelp