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

107 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}`);