- engine/(registry/types/runtime/expression)物理迁入包, 解耦shared/types(域类型归包, 宿主type-only re-export) - 31个纯Node能力+27个测试迁入包; 宿主留守Electron/浏览器/AI依赖能力, index.ts经@rpa/core/capabilities拉起 - 新增createEngine()/defaultEngine: excel缓存/Excel句柄/db连接/FTP客户端/OCR实例/拟真设置挂引擎实例 - remaining-core子流程依赖改为host-bridge注入, 包内不再import宿主flows - 宿主main/index.ts启动时注入子流程源; electron-vite/vitest/tsconfig配@rpa/core源码别名 - 验证: typecheck绿, 快照909个id diff为零, 332测试过(2个桌面冒烟因会话无交互SetForegroundWindow被拒, 环境阻塞)
95 lines
4.7 KiB
TypeScript
95 lines
4.7 KiB
TypeScript
import { execFile } from "node:child_process";
|
||
import { promisify } from "node:util";
|
||
import { afterEach, describe, expect, it } from "vitest";
|
||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||
import { getHandler } from "@rpa/core";
|
||
import type { RunContext } from "@rpa/core";
|
||
import "./window-extended";
|
||
|
||
const run = promisify(execFile);
|
||
const component = {} as CatalogComponent;
|
||
const node = (id: string): FlowNode => ({ id: "smoke-mouse", componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} });
|
||
|
||
const ctx = {
|
||
runId: "mouse-smoke", flowId: "mouse-smoke-flow", flow: {} as RunContext["flow"],
|
||
vars: {}, secrets: {}, signal: new AbortController().signal, evidenceDir: "D:/office-test2",
|
||
emit: () => {}, log: () => {}, componentOf: () => undefined
|
||
} as unknown as RunContext;
|
||
|
||
const PS_API = `Add-Type @'
|
||
using System; using System.Runtime.InteropServices;
|
||
public static class RpaMouseSmoke {
|
||
[StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; }
|
||
[DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT p);
|
||
[DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr v);
|
||
}
|
||
'@; try { [void][RpaMouseSmoke]::SetProcessDpiAwarenessContext([IntPtr](-4)) } catch {}; $c = [RpaMouseSmoke+POINT]::new(); [void][RpaMouseSmoke]::GetCursorPos([ref]$c); "$($c.X),$($c.Y)"`;
|
||
|
||
async function cursorPos(): Promise<[number, number]> {
|
||
const { stdout } = await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", PS_API], { windowsHide: true });
|
||
return stdout.trim().split(",").map(Number) as [number, number];
|
||
}
|
||
|
||
/** 共享桌面环境下光标可能被其他进程短暂挪动,轮询等待落位(容差 px),超时返回最后一次读数 */
|
||
async function waitForCursor(target: [number, number], tolerance = 0, attempts = 10, gapMs = 100): Promise<[number, number]> {
|
||
let pos = await cursorPos();
|
||
for (let i = 0; i < attempts; i += 1) {
|
||
if (Math.abs(pos[0] - target[0]) <= tolerance && Math.abs(pos[1] - target[1]) <= tolerance) return pos;
|
||
await new Promise((r) => setTimeout(r, gapMs));
|
||
pos = await cursorPos();
|
||
}
|
||
return pos;
|
||
}
|
||
|
||
async function invoke(componentId: string, args: Record<string, unknown>): Promise<unknown> {
|
||
const handler = getHandler(componentId);
|
||
expect(handler).toBeTypeOf("function");
|
||
return handler!(ctx, node(componentId), args, component);
|
||
}
|
||
|
||
let original: [number, number];
|
||
afterEach(async () => {
|
||
if (original) await invoke("Mouse_MoveToV2", { location: { x: original[0], y: original[1] }, position: "0", move_type: "2", speed: 100 });
|
||
});
|
||
|
||
describe("移动鼠标组件真实冒烟", () => {
|
||
it("对象坐标 x/y JSON 不再报无效, 瞬间移动真实落位", async () => {
|
||
original = await cursorPos();
|
||
// 用户屏幕框选生成的浮点对象坐标(此前直接报「鼠标坐标无效」)
|
||
const out = await invoke("Mouse_MoveToV2", { location: { x: 1397, y: 227.60000610351562 }, position: "0", move_type: "2", speed: 100 }) as { x: number; y: number };
|
||
expect(out).toEqual({ x: 1397, y: 228 });
|
||
const pos = await waitForCursor([1397, 228]);
|
||
expect(pos[0]).toBe(1397);
|
||
expect(pos[1]).toBe(228);
|
||
console.log("[移动鼠标] 对象坐标解析 + 瞬间移动落位 ✓ 实际光标:", pos);
|
||
}, 60000);
|
||
|
||
it("模拟人工移动(默认方式)平滑到达目标", async () => {
|
||
original = await cursorPos();
|
||
const out = await invoke("Mouse_MoveToV2", { location: "[300,300]", position: "0", move_type: "0", speed: 60 }) as { x: number; y: number };
|
||
expect(out).toEqual({ x: 300, y: 300 });
|
||
const pos = await waitForCursor([300, 300], 2);
|
||
expect(Math.abs(pos[0] - 300)).toBeLessThanOrEqual(2);
|
||
expect(Math.abs(pos[1] - 300)).toBeLessThanOrEqual(2);
|
||
console.log("[移动鼠标] 模拟人工移动 ✓ 实际光标:", pos);
|
||
}, 60000);
|
||
|
||
it("匀速直线移动与字符串数组坐标", async () => {
|
||
original = await cursorPos();
|
||
const out = await invoke("Mouse_MoveToV2", { location: "[500,400]", position: "0", move_type: "1", speed: 80 }) as { x: number; y: number };
|
||
expect(out).toEqual({ x: 500, y: 400 });
|
||
const pos = await waitForCursor([500, 400]);
|
||
expect(pos[0]).toBe(500);
|
||
expect(pos[1]).toBe(400);
|
||
console.log("[移动鼠标] 匀速直线 + 字符串坐标 ✓ 实际光标:", pos);
|
||
}, 60000);
|
||
|
||
it("相对激活窗口左上角模式返回有效坐标", async () => {
|
||
original = await cursorPos();
|
||
const out = await invoke("Mouse_MoveToV2", { location: { x: 10, y: 10 }, position: "1", move_type: "2", speed: 100 }) as { x: number; y: number };
|
||
expect(Number.isFinite(out.x)).toBe(true);
|
||
expect(Number.isFinite(out.y)).toBe(true);
|
||
console.log("[移动鼠标] 相对激活窗口模式 ✓ 目标:", out);
|
||
}, 60000);
|
||
});
|