166 lines
6.5 KiB
TypeScript
166 lines
6.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const powershellMock = vi.hoisted(() => vi.fn(async () => '{"clicked":true,"value":"cached"}'));
|
|
|
|
vi.mock("../driver/windows/powershell", () => ({
|
|
powershell: powershellMock,
|
|
psString: (value: unknown) => JSON.stringify(String(value ?? ""))
|
|
}));
|
|
|
|
import { getHandler } from "../../src/engine/registry";
|
|
import { parseUiaSelector } from "../../src/capabilities/desktop-uia";
|
|
|
|
|
|
describe("desktop UIA ground capabilities", () => {
|
|
it.each([
|
|
"Element_ClickGround",
|
|
"Element_SetValueGround",
|
|
"Element_ExistsGround",
|
|
"Element_GetValueGround",
|
|
"Element_WaitElementGround",
|
|
"Element_GetCheckGround",
|
|
"Element_SetCheckGround",
|
|
"Element_RegionGround",
|
|
"Element_GetSelectGround",
|
|
"Element_SetSelectGround",
|
|
"Element_GetAttributeGround",
|
|
"Element_SetAttributeGround",
|
|
"Element_GetButtonEnableGround",
|
|
"Element_ShotGround",
|
|
"Element_MouseToGround",
|
|
"Element_ScrollIntoViewGround",
|
|
"Element_ChildrenGround",
|
|
"Element_DesktopChildren",
|
|
"Element_RestoreWindowGround",
|
|
"Element_ActivateWindowGround",
|
|
"Element_MoveWindowGround"
|
|
])("registers %s", (id) => {
|
|
expect(getHandler(id), id).toBeTruthy();
|
|
});
|
|
|
|
it("parses name selector", () => {
|
|
const s = parseUiaSelector("name=添加供应商");
|
|
expect(s.name).toBe("添加供应商");
|
|
expect(s.index).toBe(1);
|
|
});
|
|
|
|
it("parses window, id, type, class and index", () => {
|
|
const s = parseUiaSelector("window=CC Switch;id=btn-save;type=button;class=btn;index=2");
|
|
expect(s.window).toBe("CC Switch");
|
|
expect(s.id).toBe("btn-save");
|
|
expect(s.type).toBe("Button");
|
|
expect(s.className).toBe("btn");
|
|
expect(s.index).toBe(2);
|
|
});
|
|
|
|
it("strips uia: prefix and accepts object form", () => {
|
|
const s = parseUiaSelector({ value: "uia:name=确定" });
|
|
expect(s.name).toBe("确定");
|
|
});
|
|
|
|
it("parses AI screen coordinate selector", () => {
|
|
const s = parseUiaSelector("screen=100,200,320,80;window= MyApp;ai=1");
|
|
expect(s.screen).toEqual({ x: 100, y: 200, w: 320, h: 80 });
|
|
expect(s.window).toBe("MyApp");
|
|
});
|
|
|
|
it("parses enriched AI relocation metadata", () => {
|
|
const selector = parseUiaSelector(
|
|
"screen=100,200,320,80;window=App;winClass=Chrome_WidgetWin_1;process=chrome;rel=20,30,320,80;name=login;id=loginButton;type=Button;class=Button;aiType=button;aiText=login;enriched=1;ai=1"
|
|
);
|
|
expect(selector.screen).toEqual({ x: 100, y: 200, w: 320, h: 80 });
|
|
expect(selector.rel).toEqual({ x: 20, y: 30, w: 320, h: 80 });
|
|
expect(selector.window).toBe("App");
|
|
expect(selector.winClass).toBe("Chrome_WidgetWin_1");
|
|
expect(selector.process).toBe("chrome");
|
|
expect(selector).toMatchObject({ name: "login", id: "loginButton", type: "Button", className: "Button", aiType: "button", aiText: "login", enriched: true, ai: true });
|
|
});
|
|
|
|
it("rejects invalid selector", () => {
|
|
expect(() => parseUiaSelector("")).toThrow();
|
|
expect(() => parseUiaSelector("screen=1,2,3")).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("desktop UIA selector compatibility", () => {
|
|
beforeEach(() => {
|
|
powershellMock.mockClear();
|
|
powershellMock.mockImplementation(async () => '{"clicked":true,"value":"cached"}');
|
|
});
|
|
|
|
it("maps compound .NET ControlType names explicitly and preserves raw input", () => {
|
|
expect(parseUiaSelector("type=combobox")).toMatchObject({ type: "ComboBox", typeRaw: "combobox" });
|
|
expect(parseUiaSelector("type=listitem")).toMatchObject({ type: "ListItem", typeRaw: "listitem" });
|
|
expect(parseUiaSelector("type=datagrid")).toMatchObject({ type: "DataGrid", typeRaw: "datagrid" });
|
|
});
|
|
|
|
it("parses a stable ancestor path", () => {
|
|
const selector = parseUiaSelector("window=App;path=Pane>Edit[name=用户名;index=2]>Button[name=确定]");
|
|
expect(selector.path).toEqual([
|
|
{ type: "Pane" },
|
|
{ type: "Edit", name: "用户名", index: 2 },
|
|
{ type: "Button", name: "确定" }
|
|
]);
|
|
});
|
|
|
|
const context = { log: vi.fn() } as any;
|
|
const node = { id: "n1", componentId: "Element_ClickGround" } as any;
|
|
|
|
it("resolves screen selectors through UIA/window before coordinate fallback", async () => {
|
|
const args = {
|
|
selector: "screen=100,200,320,80;ai=1",
|
|
clickway: "1"
|
|
};
|
|
await expect(getHandler("Element_ClickGround")!(context, node, args, {} as any)).resolves.toEqual({ clicked: true, value: "cached" });
|
|
const script = String((powershellMock.mock.calls as unknown as Array<[string]>)[0]?.[0] ?? "");
|
|
expect(script).toContain("RpaScreenInput");
|
|
expect(script).toContain("$x = [int]($rect.x + $rect.w / 2)");
|
|
expect(script).toContain("SetCursorPos($x, $y)");
|
|
expect(script).toContain("Resolve-RpaScreenRect");
|
|
expect(script).toContain("Find-RpaWindow");
|
|
});
|
|
|
|
it("元素定位到但 clicked=false 时必须抛错,不允许静默成功(用户 NVIDIA 覆盖层踩坑场景)", async () => {
|
|
powershellMock.mockImplementation(async () => '{"clicked":false,"method":"invoke_pattern","name":"","x":0,"y":0,"w":1919,"h":1080}');
|
|
let error: any = null;
|
|
try {
|
|
await getHandler("Element_ClickGround")!(
|
|
context,
|
|
node,
|
|
{ selector: "window=NVIDIA GeForce Overlay DT;type=Document;id=RootWebArea" },
|
|
{} as any
|
|
);
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
expect(error?.code).toBe("desktop_error");
|
|
// recommendation 必须给出可执行建议
|
|
expect(String(error?.recommendation ?? "")).toContain("桌面拾取");
|
|
});
|
|
|
|
it("dispatches screen setValue to coordinate paste", async () => {
|
|
await getHandler("Element_SetValueGround")!(context, node, {
|
|
selector: "screen=10,20,30,40",
|
|
value: "hello"
|
|
}, {} as any);
|
|
const script = String((powershellMock.mock.calls as unknown as Array<[string]>)[0]?.[0] ?? "");
|
|
expect(script).toContain("resolvedMethod = $rect.method");
|
|
expect(script).toContain("hello");
|
|
expect(script).toContain("Find-RpaWindow");
|
|
});
|
|
|
|
it("validates a picked screen rectangle for exists/wait", async () => {
|
|
powershellMock.mockImplementation(async () => '{"found":true,"method":"window_rel"}');
|
|
await expect(getHandler("Element_ExistsGround")!(context, node, {
|
|
selector: "screen=1,2,3,4"
|
|
}, {} as any)).resolves.toBe(true);
|
|
await expect(getHandler("Element_WaitElementGround")!(context, node, {
|
|
selector: "screen=1,2,3,4"
|
|
}, {} as any)).resolves.toBe(true);
|
|
expect(powershellMock).toHaveBeenCalled();
|
|
const script = String((powershellMock.mock.calls as unknown as Array<[string]>).at(-1)?.[0] ?? "");
|
|
expect(script).toContain("Resolve-RpaScreenRect");
|
|
});
|
|
});
|
|
|