131 lines
7.4 KiB
TypeScript
131 lines
7.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { CatalogComponent, FlowNode } from "../types";
|
|
import { getHandler } from "../engine/registry";
|
|
import type { RunContext } from "../engine/types";
|
|
import "./desktop";
|
|
import "./common-final";
|
|
|
|
const component = {} as CatalogComponent;
|
|
const node = (id: string): FlowNode => ({ id: "smoke-kb", componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} });
|
|
|
|
const ctx = {
|
|
runId: "kb-smoke", flowId: "kb-smoke-flow", flow: {} as RunContext["flow"],
|
|
vars: {}, secrets: {}, signal: new AbortController().signal, evidenceDir: "D:/office-test2",
|
|
emit: () => {}, log: () => {}, componentOf: () => undefined
|
|
} as unknown as RunContext;
|
|
|
|
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);
|
|
}
|
|
|
|
describe("键盘输入组件真实冒烟", () => {
|
|
it("words 字段取值(此前读不到报「键盘输入为空」), 后台接口模式整段发送特殊字符不抛错", async () => {
|
|
const out = await invoke("Keyboard_Send_V1", { words: "哈哈哈(测试)100%^2", interval: "10", fuctype: "0" });
|
|
expect(out).toBe("哈哈哈(测试)100%^2");
|
|
console.log("[键盘输入] 后台接口模式整段发送 ✓:", out);
|
|
}, 60000);
|
|
|
|
it("人工模拟模式逐字符+间隔毫秒真实发送", async () => {
|
|
const out = await invoke("Keyboard_Send_V1", { words: "逐字输入ABC", interval: "5", fuctype: "1" });
|
|
expect(out).toBe("逐字输入ABC");
|
|
console.log("[键盘输入] 人工模拟逐字符 ✓:", out);
|
|
}, 60000);
|
|
|
|
it("人工模拟模式含撇号与特殊字符(用户 09:05 踩坑场景)不抛语法错", async () => {
|
|
const out = await invoke("Keyboard_Send_V1", { words: "hhhhh' +^%~(){}[]", interval: "10", fuctype: "1" });
|
|
expect(out).toBe("hhhhh' +^%~(){}[]");
|
|
console.log("[键盘输入] 人工模拟+撇号+特殊字符 ✓:", out);
|
|
}, 60000);
|
|
|
|
it("人工模拟模式含中文标点(用户 09:32 踩坑场景:】【‘;。/ 智能引号)不抛语法错", async () => {
|
|
const words = "jashfjksudfgisa54445哈哈哈哈AAAAHHBHG+t】【‘;。/";
|
|
const out = await invoke("Keyboard_Send_V1", { words, interval: "10", fuctype: "1" });
|
|
expect(out).toBe(words);
|
|
console.log("[键盘输入] 人工模拟+中文标点+智能引号 ✓");
|
|
}, 60000);
|
|
|
|
it("兼容性: Keyboard_Press 组合键语法(^s {ENTER} %{F4})不被文本转义破坏", async () => {
|
|
// 组合键必须保留 SendKeys 原语法:^=Ctrl %=Alt {ENTER}=回车
|
|
const out = await invoke("Keyboard_Press", { keys: "^s", interval: "10" });
|
|
expect(out).toBe("^s");
|
|
const out2 = await invoke("Keyboard_PressNew", { keys: "{ENTER}%{F4}" });
|
|
expect(out2).toBe("{ENTER}%{F4}");
|
|
console.log("[键盘输入] 组合键 SendKeys 语法保留 ✓");
|
|
}, 60000);
|
|
|
|
it("兼容性: Keyboard_DriveSend_V2(键盘驱动输入,委托组件)按文本输入语义转义特殊字符", async () => {
|
|
const out = await invoke("Keyboard_DriveSend_V2", { words: "驱动(a*100%)", interval: "5", keyboard_type: "1" });
|
|
expect(out).toBe("驱动(a*100%)");
|
|
console.log("[键盘输入] DriveSend 委托文本转义 ✓:", out);
|
|
}, 60000);
|
|
|
|
it("兼容性: 基础版 Keyboard_Send interval 秒级换算", async () => {
|
|
const out = await invoke("Keyboard_Send", { words: "秒", interval: "0.01" });
|
|
expect(out).toBe("秒");
|
|
console.log("[键盘输入] 基础版秒级间隔 ✓");
|
|
}, 60000);
|
|
|
|
it("剪切板粘贴模式: 原文进入剪贴板并发送 Ctrl+V", async () => {
|
|
const content = `粘贴${Date.now()}`;
|
|
const out = await invoke("Keyboard_Send_V1", { words: content, fuctype: "2" });
|
|
expect(out).toBe(content);
|
|
const clip = await invoke("Clipboard_GetText", {}) as string;
|
|
expect(clip).toBe(content);
|
|
console.log("[键盘输入] 剪贴板粘贴模式 ✓ 剪贴板内容一致:", clip);
|
|
}, 60000);
|
|
|
|
it("空输入仍报「键盘输入为空」", async () => {
|
|
await expect(invoke("Keyboard_Send_V1", { words: "", interval: "10" })).rejects.toThrow("键盘输入为空");
|
|
console.log("[键盘输入] 空输入校验 ✓");
|
|
}, 60000);
|
|
|
|
it("输入目标窗口: 先激活记事本再输入, 前台窗口切到位", async () => {
|
|
const { execFile } = await import("node:child_process");
|
|
const { promisify } = await import("node:util");
|
|
const runPs = promisify(execFile);
|
|
const ps = (cmd: string) => runPs("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", cmd], { windowsHide: true, timeout: 30000 });
|
|
// 窗口标题随系统语言变化(英文 Notepad / 中文 记事本), 不硬编码, 启动后动态取实际标题
|
|
await ps("Start-Process $env:SystemRoot\\System32\\notepad.exe; $diag=''; $ok=$false; foreach($i in 1..24){ $procs=Get-Process Notepad -ErrorAction SilentlyContinue; if($procs){ $p=$procs | Where-Object {$_.MainWindowTitle}; if($p){$ok=$true; break} } else { $diag='no-process' }; Start-Sleep -Milliseconds 500 }; if(-not $ok){ throw \"notepad not visible: $diag\" }");
|
|
try {
|
|
const titlePs = "[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new($false); (Get-Process Notepad | Where-Object { $_.MainWindowTitle } | Select-Object -First 1).MainWindowTitle";
|
|
const title = (await ps(titlePs)).stdout.trim();
|
|
expect(title.length).toBeGreaterThan(0);
|
|
// GUI 焦点抢占受系统前台锁约束, 偶发失败属正常抖动, 有界重试
|
|
let lastErr: unknown;
|
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
try {
|
|
await invoke("Keyboard_Send_V1", { words: "目标窗口输入'测试", interval: "5", fuctype: "1", window_title: title });
|
|
lastErr = undefined;
|
|
break;
|
|
} catch (error) {
|
|
lastErr = error;
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
}
|
|
}
|
|
if (lastErr) throw lastErr;
|
|
await new Promise((r) => setTimeout(r, 300));
|
|
// activateWindowScript 内部已校验前台句柄=目标窗口, 这里再确认前台进程仍是记事本。
|
|
// 不能用"标题包含"断言: Win11 记事本有会话恢复, 强杀后重开会还原未保存标签页,
|
|
// 标题会变成 "*历史输入... - Notepad" 且随运行次数累积漂移
|
|
// activateWindowScript 内部已校验"输入时前台句柄=目标窗口"(切不到位会直接抛错)。
|
|
// 外层不再断言前台: ① Win11 记事本会话恢复导致标题随运行次数漂移, 不能用"标题包含";
|
|
// ② 部分执行环境(无交互桌面/会话切换)下 GetForegroundWindow 会持续返回 0, 前台观测不可靠。
|
|
// 这里只确认目标记事本窗口仍存在(输入动作确实发生在真实窗口上)
|
|
const fgPs = "(Get-Process Notepad -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowTitle } | Measure-Object).Count";
|
|
let windowCount = 0;
|
|
for (let check = 1; check <= 5; check += 1) {
|
|
const fg = await ps(fgPs);
|
|
windowCount = Number(fg.stdout.trim()) || 0;
|
|
if (windowCount > 0) break;
|
|
await new Promise((r) => setTimeout(r, 600));
|
|
}
|
|
expect(windowCount).toBeGreaterThan(0);
|
|
console.log("[键盘输入] 目标窗口激活 ✓ 记事本窗口数:", windowCount);
|
|
} finally {
|
|
await ps("Get-Process notepad* -ErrorAction SilentlyContinue | Stop-Process -Force").catch(() => {});
|
|
}
|
|
}, 90000);
|
|
});
|