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
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ódigo | Significado |
|---|---|
NOT_RUNNING | Uma ferramenta precisa de uma sessão em execução, mas não há nenhuma |
ALREADY_RUNNING | start_app chamado enquanto uma sessão existe |
TIMEOUT | Uma ação atingiu seu tempo limite |
NOT_FOUND | Seletor ou arquivo não correspondeu |
FILE_NOT_FOUND | Entrada baseada em caminho aponta para um arquivo inexistente |
BAD_ARGUMENT | Argumentos falharam na validação |
UNKNOWN_TOOL | Nome da ferramenta não reconhecido |
ERROR | Todo 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).
type — fill 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_fileeset_input_filesleem 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
_electrondo 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_windowroteia 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_filenão preenchefile.path. Aplicativos que usamwebUtils.getPathForFile()devem usareval_maincom 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
codeestá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
returnfunciona eawaitfunciona. - Payloads de
argsã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