跳到主要内容

窗口与渲染器约 6 分钟

后台启动与 agent 驱动

focus: false 与 show: false 让窗口不抢焦点。自动化并不需要焦点,因此 agent 可以驱动应用而不打断你。

focus: false 会以不抢占焦点的方式打开窗口。你正在输入的应用会保留光标与活跃标题栏。show: false 则更进一步,根本不打开任何窗口,于是进程带着一个存活的 React 树运行,但屏幕上什么都没有。

render(<App />, { title: 'Notes', focus: false })
平台 focus: false show: false
macOS 窗口排到最前但不成为 key 窗口,类似 open -g 生效
Windows SW_SHOWNOACTIVATE 生效
Linux 被忽略,窗口以聚焦状态打开 被忽略

在 macOS 上,进程仍然会获得一个 Dock 图标。GPUI 设置的是常规激活策略,因此目前还没有菜单栏代理(menu-bar-agent)模式。若要真正的后台守护进程,请从 ~/Library/LaunchAgents/ 下的 launchd agent 来运行应用;launchd 永远不会激活该进程。

让 agent 来驱动应用

通过环境变量让焦点变为「按需开启」,这样人类运行时行为正常,而 agent 运行时不会来打扰你:

render(<App />, {
  title: 'Notes',
  focus: process.env.GPUIX_BACKGROUND !== '1',
})
bun app.tsx                      # 你:窗口来到最前
GPUIX_BACKGROUND=1 bun app.tsx   # agent:窗口在你的编辑器之后打开

launch() 会直接透传 env,因此 agent 脚本只需设置一次,之后每一次截图、点击与断言都运行在一个永远不会打断你的窗口上:

import { launch } from '@gpuix/react/automation'

const app = await launch({
  command: 'bun',
  args: ['app.tsx'],
  env: { GPUIX_BACKGROUND: '1' },
})

await app.getByTestId('bump').waitFor()
await app.getByTestId('bump').click()
await app.screenshot({ path: 'tmp/after-click.png' })
await app.close()

唯一改变的是焦点。自动化并不需要焦点。 click() 命中最后一次绘制的边界框,screenshot() 读取 GPU 表面,因此两者在窗口位于你编辑器之后时也能工作,甚至在根本不在屏幕上的 show: false 窗口上也能工作。

  agent ──►  launch({ env: { GPUIX_BACKGROUND: '1' } })
                │
                ▼
           GPU window renders and paints without activation
                │
                ├──►  getByTestId(..).click()   ✓  hits the last painted bounds
                ├──►  screenshot({ path })      ✓  reads the GPU surface
                ├──►  fill() / press()          ✓  uses the live input pipeline
                └──►  close()

  you   ──►  keep typing, your editor stays frontmost the whole time

fill() 与 press() 使用的是实时的 GPUI 窗口输入管线。它们无需激活桌面窗口即可工作。Linux 会忽略 focus,因此那里的 agent 仍然会拿到一个获得焦点的窗口。

完整的自动化 API 见自动化。