
Ferramenta de Debug para iOS Usando Claude Code
Um "por que não?" casual se tornou um Playwright para iOS nativo — escrito quase inteiramente por Claude Code.
1. Por que eu construí isso
Eu estava construindo um aplicativo iOS com Claude Code, e o fluxo de trabalho vinha com uma fricção à qual eu já tinha me acostumado: Claude muda uma tela, então eu pego meu telefone, navego pelo aplicativo, olho o resultado e tento descrever o que vi de volta para Claude. Claude estava codificando às cegas. Ele poderia escrever Swift o dia todo, mas não tinha olhos no aplicativo em execução e nem mãos para tocá-lo.
A ideia veio de observar Claude trabalhar na web. Com o Playwright conectado, ele controla um navegador por conta própria — abre uma página, clica, tira uma captura de tela, lê a página, corrige, repete — e verifica o resultado automaticamente, sem um humano no meio. Então o "por que não?" era simples: deixe Claude fazer a mesma coisa com meu aplicativo iOS. Tire uma captura de tela, veja a tela, toque em um botão, leia o estado interno. Faça isso, e todo o ciclo se fecha. Não mais eu passando mensagens entre telefone e modelo.
Então eu trouxe a ideia para Claude Code e conversamos sobre isso. A web tem o Playwright; iOS tem o XCUITest, mas isso é um executor de teste pesado e separado — não algo que um agente pode alcançar no meio de uma conversa.
Esse é o ponto chave: o que eu queria era o Playwright sem a estrutura de teste. Sem alvo de teste, sem configuração do XCTest, sem ciclo de compilar-e-executar-a-série — apenas uma ponte ao vivo para a versão de depuração já em execução no meu telefone, que Claude pode chamar a qualquer momento. Foi isso que construímos: um pequeno servidor HTTP dentro do aplicativo (apenas versões de depuração), um servidor MCP no Mac que o transforma em ferramentas, e um túnel USB entre eles. O agente ganha olhos e mãos; o aplicativo de produção não carrega nada disso.
Isso também resolveu silenciosamente a outra metade do problema: coletar dados de depuração. Capturas de tela, um instantâneo do estado ao vivo, até um vídeo gravado de um fluxo — Claude os captura por conta própria, sob demanda, em vez de eu capturar cada um e colar na conversa. Os dados que ele precisa para raciocinar sobre um bug agora vêm diretamente da fonte.
2. Como foi projetado
A visão geral
A ferramenta é uma cadeia de quatro pequenas partes. Claude Code chama as ferramentas MCP; um servidor MCP no Mac transforma cada chamada em uma solicitação HTTP; iproxy a transporta via USB para o telefone; e um pequeno servidor HTTP dentro do aplicativo faz o trabalho real e responde de volta.
Cada link é deliberadamente simples — um protocolo padrão sem nada personalizado no meio:
| Parte | Onde | Função |
|---|---|---|
| Claude Code | a conversa | Decide o que fazer; chama as ferramentas MCP |
app-debug-mcp.py |
no Mac | Um servidor FastMCP; envolve cada rota HTTP como uma ferramenta MCP |
iproxy |
terminal | Encaminha a porta 9876 do Mac → porta 9876 do dispositivo via USB |
AppDebugServer |
no aplicativo (#if DEBUG) |
Servidor HTTP que renderiza capturas de tela, executa ações, relata estado, grava vídeo |
Por que um túnel USB? No iOS, um NWListener só pode se vincular ao loopback (127.0.0.1), então o servidor do aplicativo não é acessível pela rede. O iproxy preenche essa lacuna — uma porta do lado do Mac mapeada diretamente para o dispositivo.
Dentro do aplicativo: o servidor de depuração
AppDebugServer é a única parte que importa no dispositivo, e permanece pequena. Um NWListener aceita uma conexão, a solicitação é analisada manualmente, um roteador despacha com base no método e no caminho, e cada rota alcança uma capacidade.
Seis rotas, uma ferramenta MCP cada:
| Ferramenta MCP | Rota | Retorna |
|---|---|---|
screenshot |
GET /screenshot |
PNG da tela atual |
list_actions |
GET /actions |
Identificadores registrados na tela atual |
activate |
POST /activate |
Executa um fechamento registrado pelo identificador |
app_state |
GET /state |
Instantâneo JSON do estado ao vivo do aplicativo |
record_start |
POST /record/start |
Inicia uma gravação do ReplayKit |
record_stop |
POST /record/stop |
Finaliza e retorna o MP4 |
Algumas notas sobre os cantos mais difíceis:
-
O servidor é minimalista. Ele analisa HTTP manualmente — lê até
\r\n\r\n, extrai método, caminho eContent-Length, e então lê o corpo. Sem biblioteca HTTP, apenasNetwork.framework. Tudo roda em@MainActor, porque capturas de tela e leituras de estado do SwiftUI precisam da thread principal. -
Capturas de tela renderizam a janela atual com
UIGraphicsImageRenderer; a ponte do Mac codifica em base64 o PNG para que Claude possa realmente vê-lo. Estado vem do provedor do aplicativo, que achata qualquer tipo nãoCodableem JSON limpo. -
Vídeo é a única parte complicada, por uma razão de concorrência do Swift 6. O ReplayKit dispara seus manipuladores em uma fila de fundo, enquanto o servidor vive em
@MainActor. Se aqueles fechamentos herdassem a isolação do ator principal, o tempo de execução falharia. Então as fábricas de manipuladores sãononisolated, cada quadro volta para o ator principal dentro de uma caixa@unchecked Sendable, e umAVAssetWritercostura os quadros em H.264.
A divisão é o ponto: esse código dentro do aplicativo é agnóstico ao aplicativo — ele importa apenas frameworks do sistema (Network, UIKit, AVFoundation, ReplayKit). As únicas partes específicas do aplicativo são os fechamentos de ação que suas visualizações registram e um pequeno provedor de estado que retorna um instantâneo Encodable. Portanto, tudo isso se encaixa em qualquer aplicativo iOS.
Registro, não coordenadas
Como o activate realmente toca em algo? A primeira ideia óbvia foi tocar em (x, y) — window.hitTest mais accessibilityActivate() — e isso é mantido como uma opção de fallback. Mas é instável: visualizações de gesto do SwiftUI sem traços de acessibilidade não respondem, e as coordenadas mudam com o dispositivo e o layout.
A registro é o que se mantém. Cada tela chama .debugAction("header.settings") { ... }
A ferramenta desenvolvida pode otimizar o processo de desenvolvimento de aplicativos iOS, permitindo que agentes de IA realizem testes e colete dados de forma autônoma. Isso pode reduzir o tempo de desenvolvimento e melhorar a eficiência na identificação de bugs, beneficiando empresas brasileiras no setor de tecnologia.



