95 lines
4.8 KiB
TypeScript
95 lines
4.8 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 "../engine/registry";
|
|
import type { RunContext } from "../engine/types";
|
|
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);
|
|
});
|