更多信息请参阅 JavaScript Modules。
如何使用
简介:
本示例展示如何通过 Total Control 在多台 Android 设备上同步执行同一套 App 操作流程。脚本可自动完成应用启动、前台校验、点击、输入、翻页、返回主页及关闭应用,并在执行过程中记录步骤耗时、自动截图并保存失败现场,同时采集关键运行上下文信息,适合用于多设备自动化测试、批量操作及新手学习参考。
运行前:
>> sigmaLoad("multiDeviceFlow.js");
源代码
/**
* 多设备统一执行 App 操作流程(可直接复制运行)
* 启动 → 校验前台 → 点击 → 输入 → 翻页 → 返回主页 → 关闭
*/
// ===================== 导入与注入 =====================
var { getDevices, Device } = require("sigma/device");
// ===================== 配置 =====================
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; // 如果 tcConst 中包含这些常量;否则可保留 sigmaConst
// ===================== 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;
}
}
// ===================== 小工具函数 =====================
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";
}
// 安全获取属性/方法的包装器
function safe(fn, fallback) {
try {
return fn();
} catch (e) {
return fallback;
}
}
// 系统按键发送封装
function press(d, keyConst) {
var ret = d.send(keyConst, tcConst.STATE_PRESS);
if (ret !== 0) throw new Error("send failed: " + lastError());
}
// 步骤封装(计时 + 统一日志)
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 };
}
}
// 单步执行或快速失败的辅助函数
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);
}
// 失败上下文信息(数据驱动,减少重复)
function writeFailContext(ctx, d, stepKey, errText) {
ctx.log("---- FAIL CONTEXT BEGIN ----");
ctx.log("step=" + stepKey);
ctx.log("error=" + (errText || ""));
// 前台应用信息
ctx.log("foregroundApp=" + (safe(() => d.getForegroundApp(), "") || ""));
ctx.log("activity=" + (safe(() => d.getActivity(), "") || ""));
// 常用设备字段(可在此增删)
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());
}
// 统一失败处理器(避免重复代码)
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;
}
// 单设备日志上下文
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");
},
};
}
// ===================== 单设备执行流程 =====================
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) 启动应用
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) 前台校验
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) 点击搜索框
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) 输入并回车
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) 翻页
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) 返回 + 主页
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) 关闭应用(失败不致命,但仍作为一个步骤)
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;
}
// ===================== 主入口 =====================
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();
运行结果
Selected devices: 2
==============================
RUN 1/2 : SAMSUNG-SM-G975U1
==============================
[SAMSUNG-SM-G975U1] === DEVICE BEGIN ===
[SAMSUNG-SM-G975U1] info: samsung SM-G975U1 1440x3040
[SAMSUNG-SM-G975U1] >> START Step1 app.runApp(com.sigma_rt.sigmatestapp)
[SAMSUNG-SM-G975U1] << END Step1 app.runApp(com.sigma_rt.sigmatestapp) (cost 2662 ms)
[SAMSUNG-SM-G975U1] >> START Step2 verify foreground + getForegroundApp/getActivity
[SAMSUNG-SM-G975U1] foregroundApp=com.sigma_rt.sigmatestapp
[SAMSUNG-SM-G975U1] activity=com.sigma_rt.sigmatestapp/.MainActivity
[SAMSUNG-SM-G975U1] << END Step2 verify foreground + getForegroundApp/getActivity (cost 734 ms)
[SAMSUNG-SM-G975U1] >> START Step3 input.click2(searchBox)
[SAMSUNG-SM-G975U1] << END Step3 input.click2(searchBox) (cost 349 ms)
[SAMSUNG-SM-G975U1] >> START Step4 tests.inputText(SEARCH_TEXT + \n)
[SAMSUNG-SM-G975U1] << END Step4 tests.inputText(SEARCH_TEXT + \n) (cost 1031 ms)
[SAMSUNG-SM-G975U1] >> START Step5 navKeys.pgDn + navKeys.shiftPgDn
[SAMSUNG-SM-G975U1] << END Step5 navKeys.pgDn + navKeys.shiftPgDn (cost 693 ms)
[SAMSUNG-SM-G975U1] >> START Step6 send(KEY_BACK) + send(KEY_HOME)
[SAMSUNG-SM-G975U1] << END Step6 send(KEY_BACK) + send(KEY_HOME) (cost 633 ms)
[SAMSUNG-SM-G975U1] >> START Step7 app.closeApp(com.sigma_rt.sigmatestapp)
[SAMSUNG-SM-G975U1] << END Step7 app.closeApp(com.sigma_rt.sigmatestapp) (cost 1661 ms)
[SAMSUNG-SM-G975U1] === DEVICE END (OK) ===
[SAMSUNG-SM-G975U1] OK
==============================
RUN 2/2 : samsung-SM-S938U1
==============================
[samsung-SM-S938U1] === DEVICE BEGIN ===
[samsung-SM-S938U1] info: samsung SM-S938U1 1440x3120
[samsung-SM-S938U1] >> START Step1 app.runApp(com.sigma_rt.sigmatestapp)
[samsung-SM-S938U1] << END Step1 app.runApp(com.sigma_rt.sigmatestapp) (cost 2596 ms)
[samsung-SM-S938U1] >> START Step2 verify foreground + getForegroundApp/getActivity
[samsung-SM-S938U1] foregroundApp=com.sigma_rt.sigmatestapp
[samsung-SM-S938U1] activity=com.sigma_rt.sigmatestapp/.MainActivity
[samsung-SM-S938U1] << END Step2 verify foreground + getForegroundApp/getActivity (cost 600 ms)
[samsung-SM-S938U1] >> START Step3 input.click2(searchBox)
[samsung-SM-S938U1] << END Step3 input.click2(searchBox) (cost 343 ms)
[samsung-SM-S938U1] >> START Step4 tests.inputText(SEARCH_TEXT + \n)
[samsung-SM-S938U1] << END Step4 tests.inputText(SEARCH_TEXT + \n) (cost 1032 ms)
[samsung-SM-S938U1] >> START Step5 navKeys.pgDn + navKeys.shiftPgDn
[samsung-SM-S938U1] << END Step5 navKeys.pgDn + navKeys.shiftPgDn (cost 695 ms)
[samsung-SM-S938U1] >> START Step6 send(KEY_BACK) + send(KEY_HOME)
[samsung-SM-S938U1] << END Step6 send(KEY_BACK) + send(KEY_HOME) (cost 633 ms)
[samsung-SM-S938U1] >> START Step7 app.closeApp(com.sigma_rt.sigmatestapp)
[samsung-SM-S938U1] << END Step7 app.closeApp(com.sigma_rt.sigmatestapp) (cost 232 ms)
[samsung-SM-S938U1] === DEVICE END (OK) ===
[samsung-SM-S938U1] OK
===== SUMMARY =====
Success: 2 / 2