fix: 窗口组件条件字段渲染+选窗口按钮; imageSelector加文件浏览与屏幕截取; prompt()改confirm(); 后端窗口selector统一读取所有标题字段; 新增桌面键鼠人工验收流程与E2E脚本
This commit is contained in:
1 parent
68e16282de
commit
b5d6bef369
9 files changed
+373
-17
No files matched your search
@@ -239,7 +239,7 @@ function Find-RpaWindow([string]$winName, [string]$winClass, [string]$processNam
|
||||
$titleSb = New-Object System.Text.StringBuilder 512
|
||||
[void][Win32Rpa.RpaUser32Click]::GetWindowTextW($h, $titleSb, 512)
|
||||
$title = $titleSb.ToString()
|
||||
if ($winName -and $title -ne $winName) { return $true }
|
||||
if ($winName -and $title -notlike "*$winName*") { return $true }
|
||||
$classSb = New-Object System.Text.StringBuilder 256
|
||||
[void][Win32Rpa.RpaUser32Click]::GetClassNameW($h, $classSb, 256)
|
||||
$className = $classSb.ToString()
|
||||
@@ -257,7 +257,7 @@ function Find-RpaWindow([string]$winName, [string]$winClass, [string]$processNam
|
||||
if ($script:hitHwnd -ne [IntPtr]::Zero) {
|
||||
try { $win = [System.Windows.Automation.AutomationElement]::FromHandle($script:hitHwnd); if ($win) { return $win } } catch {}
|
||||
}
|
||||
$missing = @($winName, $winClass, $processName) | Where-Object { $_ } -join ","
|
||||
$missing = (@($winName, $winClass, $processName) | Where-Object { $_ }) -join ","
|
||||
throw "window_not_found:$missing"
|
||||
}
|
||||
function Restore-RpaWindow($win) {
|
||||
|
||||
@@ -153,22 +153,42 @@ const mouseRoll: CapabilityHandler = async (_ctx, node, args) => {
|
||||
};
|
||||
|
||||
const setActiveWindow: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const selector = text(args, ["match_title", "select_title", "title", "target_window", "window_title"]);
|
||||
const selector = text(args, ["window_obj", "match_title", "select_title", "title", "target_window", "window_title"]);
|
||||
if (!selector) throw new RuntimeError("validation_error", "窗口标题为空", { nodeId: node.id, componentId: node.componentId });
|
||||
const result = await powershell(`${USER32}; $p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1; if (-not $p) { throw '找不到窗口' }; [RpaUser32]::SetForegroundWindow($p.MainWindowHandle); $p.MainWindowTitle`);
|
||||
const result = await powershell(`${USER32}
|
||||
$p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1
|
||||
if (-not $p) { throw '找不到窗口' }
|
||||
Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd); [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h); [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr pid); [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a, uint b, bool attach); [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();' -Name RpaFocus2 -Namespace WinRpaFocus2 | Out-Null
|
||||
$h = $p.MainWindowHandle
|
||||
if ([WinRpaFocus2.RpaFocus2]::IsIconic($h)) { [void][WinRpaFocus2.RpaFocus2]::ShowWindow($h, 9) }
|
||||
if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) {
|
||||
[void][WinRpaFocus2.RpaFocus2]::keybd_event(0x12, 0, 0, [UIntPtr]::Zero)
|
||||
[void][WinRpaFocus2.RpaFocus2]::SetForegroundWindow($h)
|
||||
[void][WinRpaFocus2.RpaFocus2]::keybd_event(0x12, 0, 2, [UIntPtr]::Zero)
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) {
|
||||
$fgThread = [WinRpaFocus2.RpaFocus2]::GetWindowThreadProcessId([WinRpaFocus2.RpaFocus2]::GetForegroundWindow(), [IntPtr]::Zero)
|
||||
$thisThread = [WinRpaFocus2.RpaFocus2]::GetCurrentThreadId()
|
||||
$attached = [WinRpaFocus2.RpaFocus2]::AttachThreadInput($thisThread, $fgThread, $true)
|
||||
try { [void][WinRpaFocus2.RpaFocus2]::SetForegroundWindow($h) } finally { if ($attached) { [void][WinRpaFocus2.RpaFocus2]::AttachThreadInput($thisThread, $fgThread, $false) } }
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) { throw '前台切换失败' }
|
||||
$p.MainWindowTitle`);
|
||||
return result;
|
||||
};
|
||||
|
||||
const getActiveWindow: CapabilityHandler = async () => powershell(`${USER32}; $sb = New-Object System.Text.StringBuilder 512; [RpaUser32]::GetWindowText([RpaUser32]::GetForegroundWindow(),$sb,$sb.Capacity) | Out-Null; $sb.ToString()`);
|
||||
|
||||
const windowExists: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const selector = text(args, ["match_title", "select_title", "title", "target_window", "window_title"]);
|
||||
const selector = text(args, ["window_obj", "match_title", "select_title", "title", "target_window", "window_title"]);
|
||||
if (!selector) throw new RuntimeError("validation_error", "窗口标题为空", { nodeId: node.id, componentId: node.componentId });
|
||||
return (await powershell(`$p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1; [bool]$p`)).toLowerCase() === "true";
|
||||
};
|
||||
|
||||
const closeWindow: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const selector = text(args, ["window_obj", "title", "target_window", "window_title"]);
|
||||
const selector = text(args, ["window_obj", "match_title", "select_title", "title", "target_window", "window_title"]);
|
||||
if (!selector) throw new RuntimeError("validation_error", "窗口标题为空", { nodeId: node.id, componentId: node.componentId });
|
||||
return powershell(`$p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1; if (-not $p) { throw '找不到窗口' }; $p.CloseMainWindow() | Out-Null; $true`);
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("PDF high-level capabilities", () => {
|
||||
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
||||
const opened = await pdfjs.getDocument({ data: new Uint8Array(await readFile(encryptedPath)), password: "secret" }).promise;
|
||||
expect(opened.numPages).toBe(1);
|
||||
});
|
||||
}, 60000);
|
||||
});
|
||||
async function mkdtempSafe(): Promise<string> {
|
||||
const directory = await (await import("node:fs/promises")).mkdtemp(path.join(os.tmpdir(), "rpa-pdf-high-"));
|
||||
|
||||
+19
-1
@@ -1,5 +1,6 @@
|
||||
import { app } from "electron";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { FlowDefinition, FlowEdge, FlowNode, FlowSummary } from "../shared/types";
|
||||
|
||||
@@ -71,12 +72,29 @@ export function createFlow(name = "未命名流程"): FlowDefinition {
|
||||
};
|
||||
}
|
||||
|
||||
/** 演示流程浏览器类型:优先 Chrome,未安装时回退 Edge;两者都缺失则保持 chrome 由能力层给出友好错误 */
|
||||
function demoBrowserType(): string {
|
||||
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
||||
const chromePaths = [
|
||||
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
||||
path.join(localAppData, "Google", "Chrome", "Application", "chrome.exe")
|
||||
];
|
||||
if (chromePaths.some((p) => fs.existsSync(p))) return "chrome";
|
||||
const edgePaths = [
|
||||
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe"
|
||||
];
|
||||
if (edgePaths.some((p) => fs.existsSync(p))) return "edge";
|
||||
return "chrome";
|
||||
}
|
||||
|
||||
export function createDemoFlow(url: string): FlowDefinition {
|
||||
const flow = createFlow("本地网页订单演示");
|
||||
flow.description = "打开本地演示站,填写客户和金额,提交订单并读取结果。";
|
||||
flow.nodes = [
|
||||
flow.nodes[0],
|
||||
{ id: "node_open", componentId: "WebBrowser_CreateV3", position: { x: 180, y: 250 }, values: { type: "chrome", url, isRemote: "0", openNewTab: "1", waitingload: "1" }, options: { timeoutMs: 30000 } },
|
||||
{ id: "node_open", componentId: "WebBrowser_CreateV3", position: { x: 180, y: 250 }, values: { type: demoBrowserType(), url, isRemote: "0", openNewTab: "1", waitingload: "1" }, options: { timeoutMs: 30000 } },
|
||||
{ id: "node_name", componentId: "Element_SetValue_V6", position: { x: 180, y: 390 }, values: { uiSelector: "css:#name", designated_info: "0", designated_obj: "", text: "RPA 客户", input_type: "0", isEnter: "0", interval: "0" }, options: { timeoutMs: 10000 } },
|
||||
{ id: "node_amount", componentId: "Element_SetValue_V6", position: { x: 180, y: 530 }, values: { uiSelector: "css:#amount", designated_info: "0", designated_obj: "", text: "128", input_type: "0", isEnter: "0", interval: "0" }, options: { timeoutMs: 10000 } },
|
||||
{ id: "node_submit", componentId: "Element_Click_V6", position: { x: 180, y: 670 }, values: { uiSelector: "css:#submit", designated_info: "0", designated_obj: "", clickway: "0", clickfunc: "0", operation: "1" }, options: { timeoutMs: 10000 } },
|
||||
|
||||
Reference in new issue
Block a user