PC 桌面自动化支持

Midscene 可以驱动原生键盘和鼠标控制,在 Windows、macOS 和 Linux 上支持 PC 桌面自动化。

通过采用视觉模型方案,自动化流程可以适配任何桌面应用程序——无论是用 Electron、Qt、WPF 还是原生技术构建的。开发者在调试 UI 自动化脚本时,只需关注最终的用户体验。

PC 桌面自动化方案具备 Midscene 的所有特性:

  • 支持使用 Playground 进行零代码试用
  • 支持 JavaScript SDK 进行脚本编写
  • 支持 YAML 格式的自动化脚本和命令行工具
  • 支持 HTML 报告回放所有操作路径
  • 跨 Windows、macOS 和 Linux 平台
  • 支持 Linux CI 无头模式(通过 Xvfb,无需物理显示器)
  • 多显示器支持复杂设置

案例展示

Prompt (macOS): Help me post a tweet promoting Midscene's support for AutoGLM through safari, with the following requirements:

  1. Text content: Midscene now supports AutoGLM!
  2. Media content: Use the AutoGLM video from the download folder!

查看此次任务的完整报告:report.html

Prompt (Windows): 打开 Sauce Demo 电商网站,登录并添加商品到购物车

查看此次任务的完整报告:report.html

Prompt (macOS): 打开 Google 查询圣何塞明天天气温度

查看此次任务的完整报告:report.html

Prompt (Linux): 打开 TodoMVC,添加多个任务并筛选

查看此次任务的完整报告:report.html

查看更多案例:showcases

本指南将引导您完成使用 Midscene 自动化 PC 桌面应用所需的一切:安装依赖、配置模型凭据并运行您的第一个 JavaScript 脚本。

配置 AI 模型服务

将你的模型配置写入环境变量,可参考 模型策略 了解更多细节。

export MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1"
export MIDSCENE_MODEL_API_KEY="替换为你的 API Key"
export MIDSCENE_MODEL_NAME="替换为你的模型名称"
export MIDSCENE_MODEL_FAMILY="替换为你的模型系列"

更多配置信息请参考 模型策略模型配置

系统要求

Node.js

需要 Node.js 18.19.0 或更高版本。

平台特定依赖

macOS:需要辅助功能权限才能控制键盘和鼠标。首次运行脚本时,macOS 会提示你授予访问权限。前往 系统设置 > 隐私与安全性 > 辅助功能,为运行脚本的应用程序(如 Terminal、iTerm2、VS Code、WebStorm 或其他 IDE)启用权限。更多详情请参阅 nut.js macOS 设置

Windows:操作普通程序无需额外配置。但 Windows 会按权限级别隔离输入(UIPI):未提权的进程无法向以管理员身份运行(已提权)的窗口发送鼠标或键盘输入,输入会被静默丢弃——光标仍会移动到正确位置,但点击和按键不生效。优先尝试让目标程序不要以管理员权限运行;如果目标程序必须保持提权状态,再同样以管理员身份运行启动 Midscene 的终端或 Node.js,让两个进程处于相同的权限级别。详见 Windows:点击对某些程序不生效

Linux:需要安装 ImageMagick 用于截图功能。

无头 Linux(CI 环境):要在无头 Linux 服务器(如 GitHub Actions)上运行桌面自动化,需安装 Xvfb 及其依赖,然后启用 headless 模式:

# 安装依赖
sudo apt-get install -y xvfb x11-xserver-utils imagemagick
// 方式 1:传入 headless 选项
const agent = await agentForComputer({ headless: true });

// 方式 2:设置环境变量
// MIDSCENE_COMPUTER_HEADLESS_LINUX=true npx tsx example.ts

Xvfb 会创建一个虚拟显示器,使鼠标、键盘和截图操作在没有物理显示器的情况下正常工作。详见 API 参考

试用 Playground(零代码)

Playground 是验证连接、观察 AI Agent 的最快方式,无需编写代码。它与 @midscene/computer 共享相同的代码实现,因此在 Playground 上验证通过的流程,用脚本运行时也完全一致。

  1. 启动 Playground CLI:
npx --yes @midscene/computer-playground
  1. 点击 Playground 窗口中的齿轮按钮,粘贴你的 API Key 配置。如果还没有 API Key,请回到 模型配置 获取。

开始体验

配置完成后,你可以立即开始体验 Midscene。它提供了多个关键操作 Tab:

  • Act: 与网页进行交互,这就是自动规划(Auto Planning),对应于 aiAct 方法。比如
在搜索框中输入 Midscene,执行搜索,跳转到第一条结果
填写完整的注册表单,注意主要让所有字段通过校验
  • Query: 从界面中提取 JSON 结构的数据,对应于 aiQuery 方法。

类似的方法还有 aiBoolean(), aiNumber(), aiString(),用于直接提取布尔值、数字和字符串。

提取页面中的用户 ID,返回 { id: string } 结构的 JSON 数据
  • Assert: 理解页面,进行断言,如果不满足则抛出错误,对应于 aiAssert 方法。
页面上存在一个登录按钮,它的下方有一个用户协议的链接
  • Tap: 在某个元素上点击,这就是即时操作(Instant Action),对应于 aiTap 方法。
点击登录按钮

关于自动规划(Auto Planning)和即时操作(Instant Action)的区别,请参考 API 文档。

与 Midscene Agent 集成

当 Playground 运行正常后,就可以切换到可复用的 JavaScript 脚本。

步骤 1. 安装依赖

npm
yarn
pnpm
bun
deno
npm install @midscene/computer

步骤 2. 编写您的第一个脚本

创建 example.ts

import { agentForComputer } from '@midscene/computer';

(async () => {
  // 创建 agent
  const agent = await agentForComputer({
    aiActionContext: '你正在控制一台桌面计算机。',
  });

  // 截图并查询信息
  const screenInfo = await agent.aiQuery(
    '{width: number, height: number}, 获取屏幕分辨率'
  );
  console.log('屏幕分辨率:', screenInfo);

  // 移动鼠标到中心
  await agent.aiAct('将鼠标移动到屏幕中心');

  // 断言屏幕有内容
  await agent.aiAssert('屏幕有可见内容');

  console.log('桌面自动化完成!');
})();

步骤 3. 运行脚本

npx tsx example.ts

通过 RDP 连接远程 Windows 桌面

@midscene/computer 也可以通过专用的 agentForRDPComputer() 工厂,直接经由 RDP 协议控制远程 Windows 桌面。

前提条件

  1. 一台已开启 RDP 且网络可达的 Windows 机器。
  2. 在运行脚本的机器上安装 FreeRDP

示例

import { agentForRDPComputer } from '@midscene/computer';

const agent = await agentForRDPComputer({
  aiActionContext:
    'You are controlling a remote Windows desktop over the RDP protocol.',
  host: '10.75.166.249',
  port: 3389,
  username: 'Admin',
  password: 'replace-with-your-password',
  ignoreCertificate: true,
});

await agent.aiWaitFor('The remote Windows desktop is visible');
await agent.aiAct('Click the Windows Start button');
await agent.aiAct('Open Settings');
await agent.aiAssert('The Windows Settings window is visible');

常用 RDP 选项

  • host:远程 Windows 主机名或 IP。
  • port:RDP 端口,默认是 3389
  • username / password:远程会话使用的账号凭据。
  • domain:可选的 Windows 域。
  • ignoreCertificate:用于跳过自签名证书校验。
  • desktopWidth / desktopHeight:请求指定远程桌面分辨率。
  • adminSession:当服务端允许时,请求远程管理员会话。

对于 Midscene 来说,一个 RDP 会话会被视为单个远程显示器。你仍然可以像本机桌面自动化一样,使用 aiActaiQueryaiAssert 和报告能力。

多显示器支持

如果您有多个显示器,可以控制特定的显示器:

import { ComputerDevice, agentForComputer } from '@midscene/computer';

// 列出所有显示器
const displays = await ComputerDevice.listDisplays();
console.log('可用显示器:', displays);

// 连接到特定显示器
const agent = await agentForComputer({
  displayId: displays[0].id,
});

使用示例

基本鼠标操作

// 在屏幕中心点击
await agent.aiAct('在屏幕中心点击鼠标');

// 移动鼠标到特定位置
await agent.aiAct('将鼠标移动到左上角');

// 双击
await agent.aiAct('双击桌面图标');

// 右键
await agent.aiAct('右键打开上下文菜单');

键盘操作

// 输入文本
await agent.aiAct('输入 "你好世界"');

// 按快捷键
if (process.platform === 'darwin') {
  await agent.aiAct('按 Cmd+Space 打开 Spotlight');
  await agent.aiAct('输入 "计算器" 并按回车');
} else {
  await agent.aiAct('按 Windows 键');
  await agent.aiAct('输入 "计算器" 并按回车');
}

// 按功能键
await agent.aiAct('按 Escape');
await agent.aiAct('按 Enter');

查询信息

// 提取屏幕信息
const info = await agent.aiQuery(
  '{hasDesktop: boolean, visibleApps: string[]}, 检查桌面是否可见并列出可见应用'
);

// 定位元素
const position = await agent.aiLocate('文件菜单');
console.log('文件菜单位置:', position);

复杂工作流

// 打开应用并与之交互
await agent.aiAct('打开访达');
await agent.aiWaitFor('访达窗口可见');

await agent.aiAct('点击文稿文件夹');
await agent.aiAct('按 Cmd+N 创建新文件夹');
await agent.aiAct('输入 "我的项目"');
await agent.aiAct('按回车');

await agent.aiAssert('存在名为 "我的项目" 的文件夹');

环境检查

您可以检查系统是否正确配置:

import { checkComputerEnvironment } from '@midscene/computer';

const env = await checkComputerEnvironment();
console.log('平台:', env.platform);
console.log('可用:', env.available);
console.log('显示器数量:', env.displays);

if (!env.available) {
  console.error('环境不可用:', env.error);
}

常见问题

macOS:脚本无法控制鼠标或键盘

macOS 需要辅助功能权限才能控制键盘和鼠标。请前往 系统设置 > 隐私与安全性 > 辅助功能,为运行脚本的应用程序(如 Terminal、iTerm2、VS Code 或 WebStorm)开启权限。

如果已经授权但仍然无法控制,可以尝试将该应用从辅助功能列表中移除后重新添加——macOS 有时会缓存过期的权限。

Windows:点击对某些程序不生效

如果光标能移动到正确位置,但对某个程序点击或按键毫无反应,而其他程序却正常,请检查目标程序是否以管理员身份运行(已提权)。Windows 的 UIPI 机制会拦截由低权限进程注入到高权限窗口的输入,并静默丢弃,且不报任何错误。

优先尝试降低目标程序的权限级别,例如不要使用“以管理员身份运行”启动它,或关闭总是提权启动的相关设置。如果目标程序必须保持提权状态,再以管理员身份运行启动 Midscene 的终端(或 Node.js),使其与目标程序处于相同的权限级别,然后重试。像 Win+Tab 这类系统级快捷键由 shell 处理,即使在这种情况下也照常生效——这也是为什么有时键盘快捷键看起来能用、但程序内的点击却不行。

在 Windows 上,如果 Midscene 未以管理员身份运行,连接时的健康检查日志会打印这条排查文档链接。

Linux:在无头服务器上截图或交互失败

无头 Linux 环境(如 CI)没有物理显示器,需要安装 Xvfb 和 ImageMagick,并启用无头模式:

sudo apt-get install -y xvfb x11-xserver-utils imagemagick
const agent = await agentForComputer({ headless: true });

或通过环境变量设置:

MIDSCENE_COMPUTER_HEADLESS_LINUX=true npx tsx example.ts

API 参考

本节记录了 @midscene/computer 提供的 PC 桌面特定 API。

有关适用于所有平台的通用 API,请参阅 通用 API 参考

Agent 创建

agentForComputer(opts?): Promise<ComputerAgent>

创建用于本机桌面自动化的 agent。

向后兼容:agentFromComputer 仍可作为别名使用。

agentForRDPComputer(opts): Promise<ComputerAgent<RDPDevice>>

创建用于通过 RDP 控制远程 Windows 桌面的 agent。

参数:

interface BaseComputerAgentOpt {
  // Agent 选项(继承自 AgentOpt)
  aiActionContext?: string;
  cache?: boolean;
  // ... 其他 AgentOpt 属性

  customActions?: DeviceAction<any>[];
}

interface LocalComputerAgentOpt extends BaseComputerAgentOpt {

  // 本机桌面选项
  displayId?: string;
  headless?: boolean;
  xvfbResolution?: string;
}

interface RDPComputerAgentOpt extends BaseComputerAgentOpt {
  host: string;
  port?: number;
  username?: string;
  password?: string;
  domain?: string;
  adminSession?: boolean;
  ignoreCertificate?: boolean;
  securityProtocol?: 'auto' | 'tls' | 'nla' | 'rdp';
  desktopWidth?: number;
  desktopHeight?: number;
}

本机桌面选项

  • displayId(可选):指定要控制的显示器。使用 ComputerDevice.listDisplays() 获取可用显示器。
  • customActions(可选):向设备添加自定义操作。
  • headless(可选,仅 Linux):设为 true 时通过 Xvfb 启动虚拟显示器,使桌面自动化能在无物理显示器的 Linux 服务器和 CI 环境中运行。也可通过环境变量 MIDSCENE_COMPUTER_HEADLESS_LINUX=true 设置。
  • xvfbResolution(可选):Xvfb 虚拟显示器的分辨率,默认为 '1920x1080x24'

RDP 选项

  • host:远程 Windows 主机名或 IP。
  • port:RDP 端口,默认是 3389
  • username / password:远程会话凭据。
  • domain:可选的 Windows 域。
  • adminSession:当服务端允许时,请求远程管理员会话。
  • ignoreCertificate:用于跳过自签名证书校验。
  • securityProtocol:可选 'auto''tls''nla''rdp'
  • desktopWidth / desktopHeight:请求指定远程桌面分辨率。
示例:在无头 Linux CI 中测试 Electron 应用

使用 @midscene/computer 在无头 Linux CI 中测试 Obsidian(Electron 应用)的完整示例:https://github.com/web-infra-dev/midscene-example/tree/main/computer/electron-demo

示例:

import { agentForComputer } from '@midscene/computer';

// 连接到主显示器
const agent = await agentForComputer({
  aiActionContext: '你正在自动化一个桌面应用。',
});

// 连接到特定显示器
const displays = await ComputerDevice.listDisplays();
const agent2 = await agentForComputer({
  displayId: displays[1].id,
});

示例:通过 RDP 连接远程 Windows 桌面

import { agentForRDPComputer } from '@midscene/computer';

const agent = await agentForRDPComputer({
  aiActionContext:
    'You are controlling a remote Windows desktop over the RDP protocol.',
  host: '10.75.166.249',
  port: 3389,
  username: 'Admin',
  password: 'replace-with-your-password',
  // 可选:把 TCP 连接绑定到这个本地源 IP。
  localAddress: '10.75.166.10',
  ignoreCertificate: true,
});

await agent.aiWaitFor('The remote Windows desktop is visible');
await agent.aiAct('Click the Windows Start button');
await agent.aiAct('Open Settings');
示例:通过 RDP 控制远程 Windows 桌面

一个可直接运行的 Demo:连接远程 Windows,打开「设置」并进入「Windows 更新」,最后输出一份结构化报告:https://github.com/web-infra-dev/midscene-example/tree/main/computer/rdp-demo

当运行 Midscene 的机器存在多条出站路由,且 RDP 服务器要求从指定本地源 IP 访问时,可以使用 localAddress。这里传入的是 IP 地址,不是网卡名。

设备管理

ComputerDevice.listDisplays(): Promise<DisplayInfo[]>

列出所有可用显示器。

返回:

interface DisplayInfo {
  id: string;
  name: string;
  primary?: boolean;
}

示例:

import { ComputerDevice } from '@midscene/computer';

const displays = await ComputerDevice.listDisplays();
console.log('可用显示器:', displays);
// [
//   { id: '0', name: '内置显示器', primary: true },
//   { id: '1', name: '外接显示器', primary: false }
// ]

checkComputerEnvironment(): Promise<EnvironmentCheck>

检查计算机环境是否正确配置。

返回:

interface EnvironmentCheck {
  available: boolean;
  error?: string;
  platform: string;
  displays: number;
}

示例:

import { checkComputerEnvironment } from '@midscene/computer';

const env = await checkComputerEnvironment();
console.log('环境检查:', env);

if (!env.available) {
  console.error('环境错误:', env.error);
}

ComputerAgent

ComputerAgent 类继承自 PageAgent<ComputerDevice>,并继承所有通用 agent 方法:

  • aiAct(action: string):使用 AI 执行操作
  • aiQuery(query: string):使用 AI 提取信息
  • aiAssert(assertion: string):使用 AI 断言条件
  • aiWaitFor(condition: string):等待条件
  • aiLocate(description: string):定位元素
  • 更多...

定位元素后,还可以使用即时操作(Instant Action)进行直接、确定性的控制:

  • aiTap()aiDoubleClick()aiRightClick()aiHover():鼠标操作
  • aiInput()aiClearInput()aiKeyboardPress():键盘操作
  • aiScroll():滚动操作

详见 通用 API 参考

可用操作

ComputerDevice 支持以下操作:

鼠标操作

Tap(点击)

在目标位置单击。

await agent.aiAct('点击文件菜单');
await agent.aiAct('点击屏幕中心');
DoubleClick(双击)

在目标位置双击。

await agent.aiAct('双击桌面图标');
RightClick(右键)

右键点击打开上下文菜单。

await agent.aiAct('右键点击桌面');
await agent.aiAct('右键点击文件');
MouseMove(移动鼠标 / 悬停)

将鼠标移动到目标元素上,也就是鼠标悬停(hover),例如用于触发悬停菜单或提示框。

// 自然语言形式(移动鼠标 / 悬停)
await agent.aiAct('移动鼠标到菜单项');

// 即时操作:一次调用完成定位并悬停
await agent.aiHover('菜单项「Products」');
DragAndDrop(拖放)

从一个位置拖动并放到另一个位置。

await agent.aiAct('将文件拖到文件夹');

键盘操作

KeyboardPress(按键)

按键盘按键,可选修饰键。

支持的按键:

  • 普通键:a-z0-9EnterEscapeSpaceTab
  • 方向键:ArrowUpArrowDownArrowLeftArrowRight
  • 功能键:F1-F12
  • 修饰键:Command/Cmd(macOS)、Control/CtrlAltShiftWin(Windows)
  • 媒体键:VolumeUpVolumeDownMute

示例:

// 简单按键
await agent.aiAct('按 Enter');
await agent.aiAct('按 Escape');

// 组合键(平台特定)
if (process.platform === 'darwin') {
  // macOS
  await agent.aiAct('按 Cmd+Space');  // 打开 Spotlight
  await agent.aiAct('按 Cmd+Tab');    // 应用切换器
  await agent.aiAct('按 Cmd+C');      // 复制
  await agent.aiAct('按 Cmd+V');      // 粘贴
} else {
  // Windows/Linux
  await agent.aiAct('按 Windows 键'); // 开始菜单
  await agent.aiAct('按 Alt+Tab');    // 应用切换器
  await agent.aiAct('按 Ctrl+C');     // 复制
  await agent.aiAct('按 Ctrl+V');     // 粘贴
}

// 方向键
await agent.aiAct('按 ArrowDown');
await agent.aiAct('按 ArrowUp');

// 功能键
await agent.aiAct('按 F5');  // 刷新
Input(输入)

在输入框中输入文本。

await agent.aiAct('在搜索框输入 "你好世界"');
await agent.aiAct('输入 "我的文档.txt"');
ClearInput(清空输入)

清空输入框内容。

await agent.aiAct('清空文本框');

滚动操作

滚动屏幕或特定区域。

// 滚动方向
await agent.aiAct('向下滚动');
await agent.aiAct('向上滚动');
await agent.aiAct('向左滚动');
await agent.aiAct('向右滚动');

// 滚动到位置
await agent.aiAct('滚动到顶部');
await agent.aiAct('滚动到底部');

显示器操作

ListDisplays(列出显示器)

获取所有已连接显示器的信息。

const displays = await ComputerDevice.listDisplays();

使用 RDP 时,ListDisplays 会把当前远程会话视为单个显示器返回。

示例

打开应用并导航

import { agentForComputer } from '@midscene/computer';

const agent = await agentForComputer();

// 打开应用
if (process.platform === 'darwin') {
  await agent.aiAct('按 Cmd+Space');
  await agent.aiAct('输入 "文本编辑" 并按回车');
} else {
  await agent.aiAct('按 Windows 键');
  await agent.aiAct('输入 "记事本" 并按回车');
}

await agent.aiWaitFor('文本编辑器窗口可见');

// 输入内容
await agent.aiAct('输入 "你好,Midscene!"');

// 保存文件
if (process.platform === 'darwin') {
  await agent.aiAct('按 Cmd+S');
} else {
  await agent.aiAct('按 Ctrl+S');
}

多显示器工作流

import { ComputerDevice, agentForComputer } from '@midscene/computer';

// 列出显示器
const displays = await ComputerDevice.listDisplays();
console.log(`找到 ${displays.length} 个显示器`);

// 控制主显示器
const agent1 = await agentForComputer({
  displayId: displays[0].id,
});
await agent1.aiAct('将鼠标移动到屏幕中心');

// 控制副显示器
if (displays.length > 1) {
  const agent2 = await agentForComputer({
    displayId: displays[1].id,
  });
  await agent2.aiAct('将鼠标移动到屏幕中心');
}

Web 浏览器自动化

import { agentForComputer } from '@midscene/computer';

const agent = await agentForComputer();

// 打开浏览器
if (process.platform === 'darwin') {
  await agent.aiAct('按 Cmd+Space');
  await agent.aiAct('输入 "Safari" 并按回车');
} else {
  await agent.aiAct('按 Windows 键');
  await agent.aiAct('输入 "Chrome" 并按回车');
}

await agent.aiWaitFor('浏览器窗口已打开');

// 导航
await agent.aiAct('点击地址栏');
await agent.aiAct('输入 "example.com" 并按回车');
await agent.aiWaitFor('页面已加载');

// 提取信息
const title = await agent.aiQuery('string, 获取页面标题');
console.log('页面标题:', title);

TypeScript 类型

import type {
  ComputerAgent,
  ComputerAgentOpt,
  ComputerDevice,
  ComputerDeviceOpt,
  DisplayInfo,
  EnvironmentCheck,
} from '@midscene/computer';

另请参阅