PRA-only-demo/scripts/install-desktop-manual-flow.mjs

107 lines
7.5 KiB
JavaScript
Raw Normal View History

import fs from "node:fs";
import path from "node:path";
const appData = process.env.APPDATA || path.join(process.env.USERPROFILE || "", "AppData", "Roaming");
const flowsDir = path.join(appData, "rpa-studio", "flows");
const id = "flow_manual_desktop_keymouse";
const title = "manual-desktop";
if (!fs.existsSync(flowsDir)) {
throw new Error(`找不到流程目录:${flowsDir}。请先启动一次 RPA Studio。`);
}
let y = 80;
let sequence = 0;
function node(componentId, values, label, outputVar, options = {}) {
const item = {
id: `n${String(++sequence).padStart(2, "0")}`,
componentId,
label,
position: { x: 260, y },
values,
options: { timeoutMs: 20000, logException: true, ...(outputVar ? { outputVar } : {}), ...options }
};
y += 140;
return item;
}
function dialog(text, title = "人工确认") {
return node("Dialog_Notice", { title, text }, title, undefined, { timeoutMs: 600000 });
}
const nodes = [
node("Trigger.Manual", { entryName: "开始桌面键鼠人工测试" }, "人工发起"),
node("System_ExecuteCommand", {
action: "$old=@(Get-Process notepad -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowTitle -like '*manual-desktop*' }); foreach($p in $old){ $p | Stop-Process -Force }; if($old.Count){ Start-Sleep -Milliseconds 500; 'closed:'+$old.Count } else { 'no-old' }"
}, "清理旧测试窗口", "cleanupOld"),
node("System_ExecuteCommand", {
action: `$d=Join-Path $env:TEMP 'rpa-manual-desktop'; New-Item -ItemType Directory -Force -Path $d | Out-Null; $f=Join-Path $d '${title}.txt'; Set-Content -LiteralPath $f -Value '' -Encoding UTF8; Write-Output $f`
}, "准备记事本文件", "noteFile"),
node("App_Run", { executeable: "notepad.exe", params: "${noteFile}" }, "启动记事本", "notepadPid"),
node("Basic_Delay", { mileSecond: 1200 }, "等待窗口", "waited1"),
node("System_ExecuteCommand", {
action: `$p=Get-Process notepad -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 -and $_.MainWindowTitle -like '*${title}*' } | Select-Object -First 1; if($p){ Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd);' -Name RpaManualMax -Namespace WinRpaManualMax | Out-Null; [void][WinRpaManualMax.RpaManualMax]::ShowWindow($p.MainWindowHandle,3); 'maximized' } else { 'no-window' }`
}, "最大化记事本", "maximized"),
node("Window_SetActive", { match_title: title }, "自动激活记事本", "activatedTitle", { skipOnError: true }),
node("Window_GetActive", {}, "读取前台窗口", "activeTitle1"),
node("Window_WindowExists", { switchType: "0", get_type: "2", window_obj: title, match_type: "1", match_title: title }, "检查窗口存在", "windowExists1"),
node("Basic_DebugOutput", { msg: "自动激活:${activatedTitle};前台:${activeTitle1};存在:${windowExists1}" }, "输出阶段1结果"),
dialog("阶段1 / 窗口能力\n\n预期:\n1. 屏幕上出现并最大化 manual-desktop.txt 记事本;\n2. 自动激活和前台标题包含 manual-desktop;\n3. 窗口存在为 true。\n\n确认无误后点确定。", "阶段1 · 窗口检查"),
dialog("阶段2 / 键盘准备\n\n请先点确定关闭本弹窗。\n关闭后 3 秒内,用鼠标点击一次记事本编辑区,但不要按键盘。", "阶段2 · 键盘准备"),
node("Basic_Delay", { mileSecond: 3000 }, "给手工聚焦留时间", "waited2"),
node("Keyboard_Send", { keys: "RPA-KEY-MANUAL-OK", words: "RPA-KEY-MANUAL-OK", interval: "0.01", fuctype: "1" }, "键盘输入英文", "keyboardResult"),
node("Keyboard_Send", { keys: "^a^c", words: "^a^c", interval: "0.01", fuctype: "1" }, "全选并复制", "shortcutResult"),
node("Clipboard_GetText", {}, "读取剪贴板", "clipboardText"),
node("Basic_DebugOutput", { msg: "键盘返回:${keyboardResult};剪贴板:${clipboardText}" }, "输出键盘结果"),
dialog("阶段2 / 键盘能力\n\n预期:\n1. 记事本显示 RPA-KEY-MANUAL-OK;\n2. 调试输出中的剪贴板也是 RPA-KEY-MANUAL-OK。\n\n确认后点确定。", "阶段2 · 键盘检查"),
node("Mouse_MoveTo", { position: "620,420" }, "鼠标移动", "mouseMoveResult"),
node("System_ExecuteCommand", {
action: "Add-Type -AssemblyName System.Windows.Forms; $p=[System.Windows.Forms.Cursor]::Position; Write-Output \"$($p.X),$($p.Y)\""
}, "核对光标位置", "realCursor"),
node("Mouse_Click", { position: "650,450" }, "鼠标左键点击", "mouseClickResult"),
node("Mouse_DragTo", { Start: "650,450", End: "760,520" }, "鼠标拖拽", "mouseDragResult"),
node("Mouse_RollTo", { RollNum: 1 }, "滚轮滚动", "mouseRollResult"),
dialog("阶段3 / 鼠标能力\n\n请回忆/观察:\n1. 光标曾移动到屏幕中部;\n2. 记事本编辑区收到左键点击;\n3. 拖拽执行且没有把窗口拖丢;\n4. 滚轮动作执行。\n\n确认后点确定。", "阶段3 · 鼠标检查"),
node("Element_ExistsGround", { uiSelector: `window=${title};type=Edit;index=1` }, "UIA 元素存在", "uiaExists", { skipOnError: true }),
node("Element_SetValueGround", { uiSelector: `window=${title};type=Edit;index=1`, value: "UIA-MANUAL-写入-OK" }, "UIA 写入编辑框", "uiaSetResult", { skipOnError: true }),
node("Element_GetValueGround", { uiSelector: `window=${title};type=Edit;index=1` }, "UIA 读取编辑框", "uiaValue", { skipOnError: true }),
node("Element_ClickGround", { uiSelector: `window=${title};type=Edit;index=1` }, "UIA 点击编辑框", "uiaClickResult", { skipOnError: true }),
node("Basic_DebugOutput", { msg: "UIA 存在:${uiaExists};读取:${uiaValue}" }, "输出UIA结果"),
dialog("阶段4 / UIA 能力\n\n预期:\n1. 记事本内容被写入 UIA-MANUAL-写入-OK;\n2. 调试输出中 UIA 存在为 true,读取值一致;\n3. UIA 点击执行后流程继续。\n\n确认后点确定。", "阶段4 · UIA检查"),
dialog("准备清理:流程会尝试清空内容并关闭记事本。点确定继续。", "清理确认"),
node("Window_SetActive", { match_title: title }, "清理前激活", "activatedTitle2", { skipOnError: true }),
node("Keyboard_Send", { keys: "^a{DEL}", words: "^a{DEL}", interval: "0.01", fuctype: "1" }, "清空文本", "clearResult"),
node("Window_CloseWindow", { title, window_obj: title }, "关闭记事本", "closeResult", { skipOnError: true }),
node("Basic_Delay", { mileSecond: 800 }, "等待退出", "waited3"),
node("Window_WindowExists", { switchType: "1", get_type: "2", window_obj: title, match_type: "1", match_title: title }, "确认窗口关闭", "windowExists2"),
node("Basic_DebugOutput", { msg: "关闭结果:${closeResult};关闭后窗口存在:${windowExists2}" }, "输出清理结果"),
dialog("阶段5 / 清理检查\n\n预期:记事本已关闭,关闭后窗口存在为 false。\n\n完成请点确定。", "阶段5 · 清理检查")
];
const flow = {
schemaVersion: 1,
id,
name: "桌面键鼠人工验收",
description: "分阶段验证窗口、键盘、鼠标、剪贴板、UIA 与清理;每个阶段有弹窗暂停,便于人工确认。",
enabled: true,
trigger: { type: "manual", label: "手动运行" },
variables: [],
nodes,
edges: nodes.slice(1).map((target, index) => ({ id: `e${index}`, source: nodes[index].id, target: target.id })),
meta: { createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), appVersion: "0.1.0" }
};
const target = path.join(flowsDir, `${id}.json`);
fs.writeFileSync(target, JSON.stringify(flow, null, 2), "utf8");
console.log(`已写入:${target}`);
console.log(`节点数:${nodes.length}`);