Files
PRA-only-demo/packages/rpa-core/src/capabilities/app-system-smoke.test.ts
T
yu 0fcb1ecc0e test: 真桌面冒烟测试补 win32 平台守卫 + 平台判断收拢
迁移前置项(指南五件小事 #3/#4/#5):
- 键盘/鼠标/应用系统冒烟/回归/手工加密 5 个测试文件补 skipIf(非 win32),
  Mac/Linux CI 可跑纯逻辑部分
- 新增 driver/platform.ts 平台判断唯一入口(dev-conventions 规则 4 例外白名单),
  utility-extended 两处 process.platform 散写改引 isWindows
- 能力分组核查:COM 类能力(excelCom/word/wordExtended)在 DOMAINS 已是独立分组,可整组过滤
2026-11-05 18:03:06 +08:00

158 lines
8.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { getHandler } from "../engine/registry";
import type { CatalogComponent, FlowNode } from "../types";
import type { RunContext } from "../engine/types";
import "./data";
import "./desktop";
import "./system-extended";
import "./utility-extended";
import "./common-final";
import "./control";
import "./windows-system";
import "./basic-encryption";
const ctx = {
signal: new AbortController().signal,
log: () => {},
vars: {},
evidenceDir: "D:/office-test2",
runId: "smoke-run",
flow: {
id: "smoke-flow", name: "冒烟测试流程", description: "", enabled: true,
trigger: null, meta: { appVersion: "dev", createdAt: "", updatedAt: "" },
nodes: []
}
} as unknown as RunContext;
const node = { id: "smoke-app", componentId: "smoke", position: { x: 0, y: 0 }, values: {}, options: {} } as unknown as FlowNode;
async function run(componentId: string, args: Record<string, unknown>, ctxOverride?: RunContext): Promise<unknown> {
const handler = getHandler(componentId);
if (!handler) throw new Error(`组件 ${componentId} 未注册运行时`);
const n = { ...node, componentId } as unknown as FlowNode;
return handler(ctxOverride ?? ctx, n, args, {} as CatalogComponent);
}
describe.skipIf(process.platform !== "win32")("应用与系统组件冒烟", () => {
it("数据类型判断族", async () => {
expect(await run("Basic_IsNumber", { val: "12.5" })).toBe(true);
expect(await run("Basic_IsNumber", { val: "abc" })).toBe(false);
expect(await run("Basic_IsNull", { val: "" })).toBe(true);
expect(await run("Basic_IsNull", { val: "x" })).toBe(false);
expect(await run("Basic_IsArray", { val: [1, 2] })).toBe(true);
expect(await run("Basic_IsDictionary", { val: { a: 1 } })).toBe(true);
expect(await run("Basic_Type", { val: [1] })).toBe("list");
expect(await run("Basic_Type", { val: "s" })).toBe("string");
expect(await run("Basic_JudgeDataType", { val: 5, into_type: "int" })).toBe(true);
expect(await run("Basic_JudgeDataType", { val: 5.5, into_type: "int" })).toBe(false);
console.log("[数据判断族] 通过");
});
it("数据转换族", async () => {
expect(await run("Basic_ConvertString", { val: 123 })).toBe("123");
expect(await run("Basic_ConvertInt", { val: "12.9" })).toBe(12);
expect(await run("Basic_ConvertBool", { val: 1 })).toBe(true);
expect(await run("Basic_ConvertNumber", { val: "3.14" })).toBe(3.14);
expect(await run("Basic_DataTypeConversion", { val: "是", into_type: "bool" })).toBe(true);
expect(await run("Basic_DataTypeConversion", { val: 8, into_type: "string" })).toBe("8");
console.log("[数据转换族] 通过");
});
it("复制/随机/密文", async () => {
const src = { a: [1, { b: 2 }] };
const cloned = (await run("Basic_Clone", { val: src })) as typeof src;
expect(cloned).toEqual(src);
expect(cloned).not.toBe(src);
const ri = (await run("Basic_RandomInt", { min: 5, max: 5 })) as number;
expect(ri).toBe(5);
const rnd = (await run("Basic_Rnd", {})) as number;
expect(rnd).toBeGreaterThanOrEqual(0);
expect(rnd).toBeLessThan(1);
expect(await run("Basic_Dodecryption", { words: Buffer.from("hello-世界", "utf8").toString("base64") })).toBe("hello-世界");
console.log("[复制/随机/密文] 通过");
});
it("延迟/调试输出/设置变量", async () => {
const logs: string[] = [];
const ctxLog = { ...ctx, log: (m: string) => logs.push(m), vars: {} } as unknown as RunContext;
const t0 = Date.now();
expect(await run("Basic_Delay", { mileSecond: 100 })).toBe(0.1);
expect(Date.now() - t0).toBeGreaterThanOrEqual(95);
await run("Basic_DebugOutput", { msg: "冒烟输出" }, ctxLog);
expect(logs.join()).toContain("冒烟输出");
const setNode = { ...node, options: { outputVar: "v_test" } } as unknown as FlowNode;
const handler = getHandler("Basic_SetVariable_V1")!;
expect(await handler(ctxLog, setNode, { value: "42", variableType: "rpa_int" }, {} as CatalogComponent)).toBe(42);
expect((ctxLog as unknown as { vars: Record<string, unknown> }).vars.v_test).toBe(42);
console.log("[延迟/输出/变量] 通过");
});
it("自定义配置与业务计数", async () => {
const cfg = (await run("CustomConfig_GetConfig", {
jsonlist: JSON.stringify([{ name: "host", value: "127.0.0.1" }]),
configName: "host"
})) as string;
expect(cfg).toBe("127.0.0.1");
const ctxCount = { ...ctx, vars: {} } as unknown as RunContext;
expect(await run("Basic_BusinessOrderCount", { start_count: 100, count: 3 }, ctxCount)).toBe(103);
expect(await run("Basic_BusinessOrderCount", { start_count: 100, count: 3 }, ctxCount)).toBe(106);
console.log("[配置/计数] 通过");
});
it("系统路径与环境变量", async () => {
expect(String(await run("System_GetTempPath", {}))).toMatch(/Temp|tmp/i);
expect(String(await run("System_GetHomePath", {}))).toMatch(/Users|home/i);
expect(String(await run("System_GetSystemPath", { path_name: "desktop" }))).toMatch(/Desktop/);
expect(await run("System_GetEnv", { name: "PATH" })).toBeTruthy();
expect(await run("System_SetEnv", { name: "RPA_SMOKE", value: "ok" })).toBe("ok");
expect(await run("System_GetEnv", { name: "RPA_SMOKE" })).toBe("ok");
expect(await run("Basic_GetProcessInfo", {})).toBeTruthy();
console.log("[系统路径/环境变量] 通过");
});
it("输入法状态获取", async () => {
const st = (await run("System_GetInputMethodState", {})) as { culture: string; layout: string };
console.log("[输入法]", JSON.stringify(st));
expect(st.culture).toBeTruthy();
}, 60000);
it("执行命令行两种组件", async () => {
const out1 = String(await run("System_ExecuteCommand", { command: "echo rpa-smoke-123" }));
expect(out1).toContain("rpa-smoke-123");
const out2 = String(await run("System_ExecuteCommandV1", { command_type: "1", dos_command: "echo dos-ok-456" }));
expect(out2).toContain("dos-ok-456");
const out3 = String(await run("System_ExecuteCommandV1", { command_type: "3", ps_command: "Write-Output 'ps-ok-789'" }));
expect(out3).toContain("ps-ok-789");
console.log("[执行命令] 通过");
}, 120000);
it("应用生命周期: 启动→查PID→关闭", async () => {
const pid = (await run("App_Run", { executeable: "notepad.exe" })) as number;
expect(Number.isInteger(pid)).toBe(true);
await new Promise((r) => setTimeout(r, 1200));
// Win11 notepad.exe 为应用别名:Start-Process 返回的启动器 PID 会立即退出,真实进程另获新 PID,
// 因此不比较具体 PID,只验证「能查到运行中的 notepad 进程」
const single = (await run("App_GetPid", { pattern: "notepad" })) as number | null;
expect(single).toBeTruthy();
const all = (await run("App_GetPids", { p_name: "notepad" })) as number[];
expect(all.length).toBeGreaterThan(0);
const killed = await run("App_KillV1", { close_type: "name", p_name: "notepad" });
console.log("[应用生命周期] 启动PID:", pid, "查询到:", all, "关闭:", JSON.stringify(killed));
await new Promise((r) => setTimeout(r, 800));
const after = (await run("App_GetPids", { pattern: "notepad" })) as number[];
expect(after.length).toBe(0);
}, 120000);
it("Windows凭据: 添加→读取→删除", async () => {
const target = "RPA_SMOKETEST_GENERIC";
expect((await run("System_AddCredential", { application: target, name: "smoke-user", password: "p@ss" })) as { added: boolean }).toEqual({ added: true, target });
const read = (await run("System_ReadCredential", { application: target })) as { exists: boolean; detail: string };
expect(read.exists).toBe(true);
expect(read.detail).toContain("smoke-user");
expect((await run("System_DeleteCredential", { application: target })) as { deleted: boolean }).toEqual({ deleted: true, target });
const readAfter = (await run("System_ReadCredential", { application: target })) as { exists: boolean };
expect(readAfter.exists).toBe(false);
console.log("[凭据闭环] 通过");
}, 120000);
});