Electron Driver

Dirija aplicativos Electron a partir de agentes de IA via MCP - clique, digite, arraste, tire capturas de tela, avalie JS e muito mais.

Documentação

electron-driver

npm version license node version

Controle aplicativos Electron a partir de agentes de IA. Clique, digite, arraste, capture telas, avalie JavaScript no processo de renderização ou no processo principal, leia logs do console, gerencie aplicativos com múltiplas janelas, capture snapshots de acessibilidade — tudo através de um servidor MCP (Model Context Protocol) que se conecta ao Claude Code, Claude Desktop, Cursor e qualquer outro host de agente compatível com MCP.

https://github.com/user-attachments/assets/a95500d2-28d2-4ee1-9965-8f7e1ef54caa

Construído sobre a API experimental _electron do Playwright. Funciona com qualquer aplicativo Electron — React, Vue, Svelte, vanilla — desde que você possa apontá-lo para um entry-point do processo principal compilado.

Status: v0.3.0. Primeira versão pública. 38 ferramentas cobrindo fluxos de trabalho reais.

Por que isso existe

Agentes de IA podem raciocinar sobre o que um aplicativo desktop deveria fazer, mas não conseguem ver ou interagir com um por conta própria. Navegadores web têm várias opções de automação para agentes; Electron quase não tem nenhuma. Este pacote preenche essa lacuna: dê a um agente o caminho para seu aplicativo Electron compilado e ele poderá controlá-lo da mesma forma que um humano faria.

Casos de uso comuns:

  • Um agente verifica um recurso que acabou de implementar executando o aplicativo e verificando o resultado visível
  • Testes de regressão visual durante um refactor
  • Auditorias de acessibilidade via snapshots da árvore ARIA
  • Reprodução de bugs a partir de uma descrição em linguagem natural
  • Ensinar um subagente a iterar na interface até que uma especificação passe

Instalação

Requer Node 18+ e um aplicativo Electron que você já tenha compilado.

npm install electron-driver

Você não precisa instalar os navegadores do Playwright separadamente — _electron controla seu binário Electron diretamente.

Registre com seu host de agente

Claude Code (escopo do projeto)

Crie .mcp.json na raiz do repositório:

{
  "mcpServers": {
    "electron-driver": {
      "command": "npx",
      "args": ["electron-driver"]
    }
  }
}

Claude Code (escopo do usuário — disponível em todos os projetos)

claude mcp add electron-driver --scope user -- npx electron-driver

Claude Desktop / Cursor / outros

Adicione à configuração MCP do host, apontando para npx electron-driver ou o caminho absoluto para node_modules/electron-driver/index.mjs.

Ideia central

O servidor gerencia exatamente uma sessão Electron por vez. start_app a inicia, tudo o mais a controla, stop_app a encerra. Capturas de tela vão para um diretório de sessão que é limpo a cada start_app — sem acúmulo, sem artefatos obsoletos. Todas as chamadas de ferramentas são registradas em <project>/.electron-driver/driver.log durante uma sessão.

Erros carregam um campo estável code para que chamadores possam ramificar programaticamente sem usar regex em texto:

CódigoSignificado
NOT_RUNNINGUma ferramenta precisa de uma sessão em execução, mas não há nenhuma
ALREADY_RUNNINGstart_app chamado enquanto uma sessão existe
TIMEOUTUma ação atingiu seu tempo limite
NOT_FOUNDSeletor ou arquivo não correspondeu
FILE_NOT_FOUNDEntrada baseada em caminho aponta para um arquivo inexistente
BAD_ARGUMENTArgumentos falharam na validação
UNKNOWN_TOOLNome da ferramenta não reconhecido
ERRORTodo o resto

Ferramentas

Todas as 38 ferramentas agrupadas por propósito. Toda ferramenta baseada em seletor usa o mecanismo de seletores completo do Playwright: CSS, text=, role=, [aria-label=], :has-text(), escopo (main >> button), etc.

Ciclo de vida

start_app — inicia o aplicativo. Recebe main (caminho absoluto para o entry-point principal compilado), opcionalmente cwd, args, env, screenshotsDir, timeoutMs. Retorna { title, url, viewport, screenshotsDir, logFile }. Detecta o modo de falha de bloqueio de instância única e dá uma dica útil em vez de um erro bruto de desconexão.

stop_app — encerra de forma limpa. Seguro em uma sessão já interrompida.

info{ title, url, viewport: {width, height, devicePixelRatio}, uptimeMs }. O viewport é preenchido a partir de window.innerWidth/innerHeight.

Captura

screenshot — PNG de página inteira. Passe name (sem extensão) para controlar o nome do arquivo. Retorna { path }.

cleanup_screenshots — limpa o diretório de capturas de tela da sessão atual.

console_logs — mensagens recentes do console do renderizador (log/info/warn/error/ debug/pageerror) e stdout/stderr do processo principal. Buffer rolante de 1000 entradas. Filtre por source (renderer/main/all), type e limit. Passe clear: true para drenar após a leitura.

Interação

click — clica em um elemento. Opções: timeoutMs, button (left/right/middle), clickCount, force (pular verificações de capacidade de ação), position (clicar em um deslocamento dentro do elemento).

typefill um campo de texto, substituindo o conteúdo existente. Rápido, mas só funciona em inputs reais. Para editores/CodeMirror/contenteditables, use keyboard_type.

keyboard_type — digita como eventos reais de keydown por caractere. Passe focusSelector para clicar em um elemento primeiro. Avisa no resultado se nada estiver com foco e nenhum seletor de foco foi passado.

press — pressiona uma tecla ou combinação: "Escape", "Enter", "Control+S", "Shift+Tab", "Control+Shift+P".

press_sequence — alias para keyboard_type sem seletor de foco.

hover — passa o mouse sobre um elemento. Opções: timeoutMs, force.

drag — arrasta de um ponto a outro usando eventos reais de entrada do Chromium (via pipeline de mouse CDP do Playwright). Como são eventos de navegador confiáveis, o pipeline de ponteiro do Chromium gera PointerEvents correspondentes como efeito colateral, então onPointerDown do React, listeners nativos de pointerdown, setPointerCapture, CSS :hover/:active e qualquer outro consumidor de ponteiro vê o arraste exatamente como se um usuário real o tivesse feito. Coordenadas são em pixels CSS. Passe detectSelector e o driver medirá o elemento antes e depois do arraste e incluirá detect.moved no resultado — a única maneira confiável de detectar arrastes que silenciosamente atingem um limite mínimo/máximo.

{
  "from": { "x": 275, "y": 400 },
  "to":   { "x": 420, "y": 400 },
  "detectSelector": ".sidebar-resize-handle"
}

Se a estratégia principal não mover o alvo de detecção, o driver automaticamente recorre a invocar o handler do React diretamente via acesso à prop do fiber e despachar eventos de move/up tanto em document quanto em window — cobrindo todos os padrões conhecidos de splitters do React. Desative o fallback com fiberFallback: false. O resultado inclui strategy ("pointer-capture" ou "react-fiber").

clear_input — esvazia um input ou textarea.

select_option — seleciona de um <select> por value, label ou index.

check — marca um checkbox ou radio. Opções: timeoutMs, force.

uncheck — desmarca um checkbox.

scroll — rola um contêiner (passe selector) ou a janela. Suporta absoluto (x, y) ou delta (dx, dy).

scroll_into_view — garante que um elemento esteja visível. Seguro se já estiver.

drop_file — simula soltar um arquivo em um alvo via DragEvents sintéticos e um File reconstruído com DataTransfer. Funciona para aplicativos que leem o File via APIs web (FileReader, File.text(), etc.). Não popula file.path — aplicativos que dependem de webUtils.getPathForFile() devem usar eval_main para invocar seu próprio handler de IPC diretamente.

set_input_files — a maneira correta de testar UI de upload de arquivos. Define arquivos em um <input type="file"> sem diálogo nativo. Muito mais confiável que drop_file quando o aplicativo usa inputs de arquivo reais.

Espera

wait — pausa fixa em milissegundos. Prefira as outras.

wait_for_selector — espera até que um seletor atinja um estado (attached/detached/visible/hidden). Respeita timeoutMs. Retorna count, box e elapsedMs em caso de sucesso; o erro carrega elapsed vs requested em caso de timeout.

wait_for — faz polling de um predicado JavaScript (corpo da função, use return) até que retorne verdadeiro. Opções: timeoutMs, pollMs.

Verificação e leitura de estado

exists{ exists, count } verificação rápida, sem espera. Aceita o mecanismo de seletores completo.

get_text — conteúdo de texto da primeira correspondência. Aceita o mecanismo de seletores completo. Retorna { exists, text }.

get_attribute — lê um atributo HTML pelo nome. Retorna { exists, value }.

get_value — lê o valor atual de um input/textarea/select.

get_bbox — bounding box como { x, y, width, height } em pixels CSS. Use antes de arrastar ou clicar em um deslocamento.

get_computed_style — lê uma ou mais propriedades CSS computadas. Passe um array properties.

elements_list — enumera elementos que correspondem a um seletor com sua tag, id, classes, trecho de texto, box e atributos-chave. Ótimo para "quais botões existem nesta tela". Limitado a 50 por padrão; ajuste via limit.

focused_element — o que está com foco atualmente, com tag/id/classes/texto e bounding box. Retorna { focused: false } se nada significativo tiver foco.

accessibility_snapshot — captura a árvore ARIA como JSON. Útil para auditorias de a11y e para encontrar elementos por papel. Passe interestingOnly: false para incluir todos os nós. Passe root para capturar uma subárvore.

Multi-janela

windows_list — todas as BrowserWindow que o aplicativo tem abertas, com id, título, URL, flags de foco/visibilidade/estado.

switch_window — roteia chamadas de ferramentas subsequentes para uma janela diferente. Passe index ou titleMatch.

Diálogos

dialog_handler — instala um auto-respondedor para diálogos JavaScript (alert/confirm/prompt/beforeunload). Passe action: "accept" | "dismiss", opcionalmente text para prompt() e once: true (padrão) para auto-desinstalar após o primeiro diálogo.

Escape hatches de avaliação

Tanto eval_renderer quanto eval_main usam o mesmo contrato: passe um corpo de função, use return para produzir um valor, suporta async/await, e um payload opcional arg está disponível como a variável local arg.

eval_renderer — avalia no contexto do renderizador (página).

{
  "js": "return document.querySelectorAll(arg.selector).length",
  "arg": { "selector": ".item" }
}

eval_main — avalia no processo principal do Electron. O corpo recebe electron (o módulo Electron completo) e arg.

{
  "js": "return electron.app.getName()"
}
{
  "js": "const w = electron.BrowserWindow.getAllWindows()[0]; w.webContents.send('open-file', arg.path); return true",
  "arg": { "path": "C:/docs/README.md" }
}

Use eval_main como escape hatch para tudo que o lado do DOM não consegue alcançar: invocar handlers de IPC, ler caminhos de usuário, controlar janelas secundárias, contornar diálogos nativos para aplicativos que os usam.

Folha de referência de seletores

text=Open File              // exact text match
text=/^Save$/               // regex
[aria-label="Settings"]     // ARIA attribute
role=button[name="Close"]   // ARIA role
button:has-text("Save")     // CSS with text predicate
button.primary              // plain CSS
main >> text=Save           // scoped

Consistência do mecanismo de seletores

Toda ferramenta baseada em seletor (click, hover, wait_for_selector, get_text, get_attribute, get_value, get_bbox, exists, elements_list, scroll_into_view, select_option, check, uncheck, set_input_files) passa pelo mecanismo completo de locator do Playwright. Qualquer coisa que o Playwright aceita, essas ferramentas aceitam — incluindo text=, role= e :has-text().

Ferramentas que leem DOM de baixo nível via eval_renderer internamente (get_computed_style, scroll, focused_element, drop_file) usam document.querySelector nativo e só suportam CSS. Isso está documentado na descrição de cada ferramenta quando relevante.

Solução de problemas

"O processo Electron saiu imediatamente após o início." Outra cópia do seu aplicativo já está em execução e capturou o bloqueio de instância única — o segundo processo sai via app.requestSingleInstanceLock(). Feche a instância em execução (verifique sua barra de tarefas e processos em segundo plano) e tente novamente. drag retornou ok:true, mas detect.moved é false. A causa mais comum é um limite mínimo/máximo no alvo (por exemplo, uma barra lateral redimensionável no seu MAX_WIDTH). Tente arrastar na direção oposta para confirmar que o pipeline de arrastar está funcionando. Se realmente não for o limite, o fallback de fibra do React deve capturá-lo automaticamente — verifique o campo strategy no resultado. Se mesmo assim retornar moved:false, use eval_renderer ou eval_main para invocar a API de arrastar do próprio aplicativo diretamente.

type lança "Element is not an <input>..." Você está atingindo um botão, uma div ou um contenteditable. Use keyboard_type com um focusSelector em vez disso.

click expira em um elemento que está claramente lá. Algo está cobrindo-o — um fundo de modal, uma dica de ferramenta, um toast. Use exists primeiro para confirmar a contagem, depois tente force: true, ou use eval_renderer para verificar getComputedStyle(el).pointerEvents.

Diálogos nativos são invisíveis. O Playwright não consegue ver seletores de arquivo do sistema operacional, diálogos de salvar ou alertas do sistema. Use eval_main para invocar o mesmo manipulador de IPC que o botão da sua interface usa. Para diálogos JavaScript (alert/confirm/prompt), use dialog_handler para responder automaticamente.

Build de desenvolvimento vs aplicativo compilado. Isso executa o aplicativo compilado, não a saída do servidor de desenvolvimento. Execute seu comando de build antes de start_app e recompile

  • reinicie a sessão após alterações no código-fonte.

Uma sessão por vez. Chamar start_app enquanto uma sessão está em execução retorna ALREADY_RUNNING. Chame stop_app primeiro.

Logs. Cada chamada de ferramenta é registrada em <project>/.electron-driver/driver.log enquanto uma sessão está ativa. Útil ao depurar por que um agente ficou travado.

Localização das capturas de tela. O padrão é <project>/.electron-driver/screenshots, onde <project> é o diretório mais próximo que contém .git ou package.json. Substitua via screenshotsDir em start_app.

Segurança

Este servidor dá ao agente conectado controle total sobre um aplicativo Electron, incluindo execução arbitrária de código no processo principal (via eval_main). O processo principal do Electron tem acesso irrestrito ao Node.js — sistema de arquivos, rede, processos filhos, tudo. Isso é por design: é o que torna o driver poderoso o suficiente para executar aplicativos reais.

O que isso significa para você:

  • Use apenas via stdio (o padrão). Nunca exponha este servidor via HTTP, WebSocket ou qualquer transporte de rede. O stdio o vincula ao processo que o gerou — sua sessão local do Claude Code ou Claude Desktop.
  • Confie no agente. O agente que chama essas ferramentas pode fazer qualquer coisa na sua máquina via eval_main. Conecte apenas agentes em que você confia.
  • Não use em ambientes multi-tenant. Esta é uma ferramenta de usuário único, máquina local. Não foi projetada para servidores compartilhados, pipelines de CI com entrada não confiável ou qualquer contexto onde o chamador possa ser adversário.
  • drop_file e set_input_files leem arquivos locais e passam seus conteúdos para o renderizador. Os caminhos dos arquivos devem vir de fontes confiáveis.

Se você está executando isso com Claude Code, o perfil de risco é o mesmo que dar acesso ao terminal ao Claude Code (que você já tem). O driver não adiciona novas capacidades além do que eval em um terminal poderia fazer — apenas as torna convenientes para o agente usar.

Limitações conhecidas

  • O namespace _electron do Playwright é oficialmente experimental upstream. Timeouts ocasionais de inicialização em máquinas lentas; geralmente tentar novamente resolve.
  • Desenvolvido principalmente no Windows. Mac e Linux devem funcionar — o Playwright os suporta — mas são menos testados. Relatórios de bugs são bem-vindos.
  • switch_window roteia chamadas subsequentes para a janela selecionada, mas o buffer de log do console é preenchido a partir da janela inicial. Captura de console multi-janela é um item planejado para v0.4.
  • drop_file não preenche file.path. Aplicativos que usam webUtils.getPathForFile() devem usar eval_main com seu próprio IPC.
  • Sem captura de requisições de rede integrada ainda — planejada para v0.4.

Notas de implementação

Para qualquer pessoa curiosa ou contribuindo:

  • Sessão única do Electron, de propriedade do processo do servidor MCP.
  • Capturas de tela apagadas a cada start_app — intencional.
  • Logs do console capturados em um buffer rolante de 1000 entradas.
  • Cada chamada de ferramenta é registrada; erros carregam um campo code estável.
  • Mensagens de erro são reescritas para serem atribuídas à ferramenta do driver, não ao método subjacente do Playwright.
  • A detecção de bloqueio de instância única depende de "processo desconectado dentro de 5s após o lançamento", que é a forma real da falha.
  • Evals são envolvidos em IIFE assíncrono, então return funciona e await funciona.
  • Payloads de arg são coagidos no lado do servidor (JSON-parse em strings) para proteger contra clientes MCP que serializam campos de argumentos.
  • stderr é usado para mensagens de status; stdout é reservado para quadros de protocolo MCP.

Contribuindo

Issues e PRs são bem-vindos. Execute localmente com:

cd electron-driver
npm install
node index.mjs  # stdio MCP server

O servidor registra um banner de pronto no stderr e aguarda quadros MCP no stdin. Teste-o contra um aplicativo Electron real via qualquer cliente MCP.

Licença

MIT