periscope-mcp
Testes de site construídos para agentes de IA: 63 ferramentas Playwright com asserções rígidas, preenchimento automático de formulários, sessões de autenticação, simulação de rede e auditorias de acessibilidade/SEO/GEO + Lighthouse.
Documentação
periscope-mcp
Um servidor MCP que dá a agentes de IA 74 ferramentas Playwright para QA, testar e analisar aplicativos web — sites estáticos, SPAs e aplicativos atrás de login — retornando veredictos concretos, não capturas de tela para você ficar analisando. Não é um wrapper fino em torno das APIs do navegador; as ferramentas são moldadas em torno de como os agentes realmente trabalham:
- Resultados concretos, não ficar olhando capturas de tela —
assert_conditionretornapassed: true/falsecom o valor real; as verificações retornam problemas estruturados. - Uma chamada em vez de dez —
auto_fill_formdetecta, infere e preenche um formulário inteiro;interact_and_testagrupa 25 tipos de ação com verificações;test_projectrastreia e audita um site inteiro. - Teste real de aplicativos web — sessões autenticadas persistentes (auth por formulário/básica/cookie, além de um login interativo visível para 2FA/SSO/CAPTCHA que depois roda em modo headless), fluxos de várias etapas, mock de rede, snapshots de estado e INP real medido a partir das interações que ele conduz.
- Respostas honestas — falhas dizem o que aconteceu e o que fazer em seguida (sessão expirada vs. falha do navegador vs. despejo de memória); no-ops silenciosos como arrastos ignorados voltam sinalizados, não como sucesso falso.
- Depuração integrada — corpos de resposta de API capturados, logs de console/rede, mock de rede e snapshots/diffs de estado, sem necessidade de chamadas de configuração.
- Auditorias que agentes não conseguem de um binding de navegador — acessibilidade, SEO e prontidão para GEO/busca agêntica (acesso de rastreadores de IA no robots.txt, llms.txt, WebMCP), além de Lighthouse real.
Playwright + Chrome headless por baixo; rastreamento de sites, testes responsivos e diff de capturas de tela por cima. Funciona com qualquer cliente MCP — Claude Code, Codex, Cursor, Windsurf, Gemini CLI, agentes personalizados ou qualquer outra coisa que fale MCP via stdio.
Por que não apenas playwright-mcp?
playwright-mcp é excelente no que faz: controle geral do navegador via MCP, com ferramentas que espelham a própria API do Playwright. Se o trabalho é "navegue por este site, clique por aí, extraia algo", use ele.
O Periscope existe para um trabalho diferente: testar e auditar um site ou aplicativo web e depois relatar os resultados — e suas ferramentas codificam o conhecimento de teste que um agente teria que reinventar a cada sessão:
| Controle bruto do navegador | Periscope | |
|---|---|---|
| Verificar um resultado | Ler uma captura de tela ou dump do DOM e julgar | assert_condition → passed: true/false concreto + valor real |
| Preencher um formulário | Uma chamada por campo, agente inventa dados de teste | auto_fill_form — detecta campos, infere dados realistas, relata falhas por campo |
| Autenticação | Refazer login roteirizando cliques a cada sessão | Projetos persistem auth por formulário/básica/cookie; sessões compartilham o contexto logado |
| Auditoria do site inteiro | Percorrer páginas manualmente | test_project — rastreamento + verificações de acessibilidade/SEO/GEO/visual/funcionalidade + relatório salvo |
| Diagnosticar página quebrada | Pedir logs, repetir requisições | Corpos de resposta, console e rede são capturados automaticamente; simule APIs com intercept_network |
| Falhas silenciosas | Arrastar "funcionou", nada se moveu | Sinalizado no resultado, com o caminho de recuperação explicado |
| Auditorias de prontidão para IA | — | Acesso de rastreadores de IA no robots.txt, llms.txt, anotações WebMCP, JSON-LD, além de pontuações reais do Lighthouse |
Os dois não são rivais — um agente pode usar tranquilamente o playwright-mcp para tarefas de navegação e o Periscope quando estiver com o chapéu de QA. As apostas de design do Periscope são simplesmente sobre esse chapéu: menos chamadas e de nível mais alto; veredictos estruturados em vez de estado bruto da página; e erros escritos para dizer ao agente o que fazer em seguida.
Arquitetura
MCP client (AI agent) --> MCP Server (stdio) --> Playwright (Headless Chrome)
| |
+-- Projects (JSON) +-- Persistent Sessions
+-- Screenshots (PNG) +-- Network Interception
+-- Reports (JSON) +-- Device Emulation
+-- Videos (WebM)
Como funciona: seu cliente MCP conecta a este servidor via stdio. O servidor expõe 74 ferramentas que o agente pode chamar para criar projetos, configurar autenticação, rastrear sites, executar verificações estáticas e testar interativamente aplicativos web usando sessões de navegador persistentes. Os resultados (JSON + capturas de tela + vídeos) são retornados ao agente para análise.
Estrutura do Projeto
periscope-mcp/
├── server.py # MCP server entry point (stdio wiring + dispatch)
├── tool_schemas.py # All 74 MCP tool definitions (schemas)
├── runtime.py # Shared singletons (project store, sessions, browser)
├── coercion.py # Argument coercion for MCP clients with stale schemas
├── handlers/ # Tool handlers, grouped by category
│ ├── registry.py # @tool(name) decorator + HANDLERS registry
│ ├── projects.py # create/list/get/delete project
│ ├── auth.py # form login, basic auth, cookies, copy_auth
│ ├── static_testing.py # test_url, crawl, test_project, reports, responsive
│ ├── session_tools.py # open/close/list sessions, viewport, history
│ ├── interactive.py # click, fill, steps, element queries, dialogs
│ ├── analysis.py # forms, links, keyboard nav, tables, toasts, contrast
│ ├── advanced.py # network mocking, storage, iframes, emulation, recording
│ ├── agent_speed.py # assertions, smart find, auto-fill, snapshots
│ ├── web.py # web_search, web_fetch
│ ├── discovery.py # describe_tools catalog
│ └── system.py # periscope_system: status, self-update, agents_md
├── tester.py # Playwright browser control + test orchestration
├── crawler.py # Page discovery (BFS crawl, same-domain only)
├── projects.py # Project CRUD + auth config storage
├── auth.py # Authentication handlers (form, basic, cookies)
├── sessions.py # SessionManager + PageSession — persistent page lifecycle
├── interactions.py # Interaction primitives (click, fill, execute_steps)
├── utils.py # Screenshot comparison (Pillow pixel diff)
├── config.py # Global settings (timeouts, paths, session limits)
├── checks/
│ ├── visual.py # Broken images, favicon, overflow, small text
│ ├── accessibility.py # Alt text, labels, headings, lang, ARIA, keyboard nav
│ ├── functionality.py # Broken links, forms, SEO, performance, link checker
│ └── geo.py # GEO/agentic search: robots.txt AI crawlers, llms.txt, WebMCP, JSON-LD
├── tests/ # Unit tests (no browser) + tests/e2e/ (real browser + fixture pages)
├── data/ # Created at runtime (gitignored — contains credentials)
├── Dockerfile
├── docker-compose.yml
└── .mcp.json.example # MCP registration template (copy to .mcp.json)
Pré-requisitos
- Python 3.11+
- Playwright + navegador Chromium
Instalação (Local)
Instalação rápida (Debian/Ubuntu)
Um comando — clona e instala:
git clone https://github.com/segentic-lab/periscope-mcp.git && cd periscope-mcp && ./install.sh
Totalmente desassistido (sem prompts de confirmação):
git clone https://github.com/segentic-lab/periscope-mcp.git && cd periscope-mcp && ./install.sh -y
Já clonou? Basta executar ./install.sh a partir do diretório do repositório.
O script instala os pré-requisitos apt, cria o venv, instala as dependências Python e o Chromium do Playwright, executa um autoteste headless e gera mcp-config.json com os caminhos absolutos corretos para esta instalação (copie ou mescle no .mcp.json do seu projeto). Flags úteis:
./install.sh --system-chromium— usar um Chromium/Chrome existente (defineCHROMIUM_PATH) em vez de baixar a versão do Playwright./install.sh --skip-deps— nunca mexer em apt / usar sudo./install.sh -y— não interativo (sem prompts de confirmação)
Em qualquer outra plataforma, o script não modifica seu sistema — ele imprime os comandos exatos para seu SO (./install.sh --manual macos|fedora|arch|suse|windows para escolher explicitamente).
Atualização
./update.sh
Busca o código-fonte mais recente do GitHub (git pull --ff-only) e atualiza a instalação: dependências Python, navegador Playwright (mantido no Chromium do sistema se for o que a instalação usa), o registro + autoteste de inicialização headless e um mcp-config.json regenerado. Funciona em qualquer plataforma com instalação existente. Seu diretório data/ (projetos, credenciais, capturas de tela, relatórios) nunca é tocado.
./update.sh --force— guarde primeiro as modificações locais em arquivos rastreados (recupere comgit stash pop)./update.sh --full— também reverifica os pré-requisitos apt no Debian/Ubuntu (usa sudo)
Se você tiver modificações locais, o script se recusa e as lista em vez de sobrescrever.
Instalação manual
# Clone the repo
cd periscope-mcp
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Install Chromium for Playwright
playwright install chromium
Instalação (Docker)
docker compose up -d
Veja a seção Implantação Docker abaixo.
Conectando um Cliente MCP
O Periscope é um servidor MCP stdio padrão: aponte qualquer cliente MCP para venv/bin/python server.py e pronto. ./install.sh gera mcp-config.json com os caminhos absolutos corretos para sua máquina; a maioria dos clientes aceita esse formato diretamente:
{
"mcpServers": {
"periscope": {
"command": "/path/to/periscope-mcp/venv/bin/python",
"args": ["/path/to/periscope-mcp/server.py"]
}
}
}
Exemplos específicos por cliente:
- Claude Code — copie a configuração para o projeto como
.mcp.json(cp .mcp.json.example .mcp.jsone ajuste os caminhos), ou executeclaude mcp add periscope -- /path/to/venv/bin/python /path/to/server.py - Cursor / Windsurf — adicione o bloco acima em
~/.cursor/mcp.json/~/.codeium/windsurf/mcp_config.json - Codex CLI — adicione em
~/.codex/config.toml:[mcp_servers.periscope]comcommandeargscomo acima - Agentes personalizados — qualquer cliente do SDK MCP pode iniciar o servidor via stdio com o mesmo comando e argumentos
Após configurar, reinicie seu cliente.
Ensinando seu agente a usar as ferramentas
Duas opções, dependendo do seu agente:
-
Claude Code (recomendado: instale a skill).
SKILL.md(raiz do repositório; também exposta emskills/periscope/no layout de skills do Claude Code) é uma skill do Claude Code — ela dispara automaticamente em tarefas de teste web e carrega um guia operacional condensado (tabela de decisão de fluxos de trabalho + armadilhas) somente quando necessário, custando ~0 de contexto no restante do tempo:ln -s "$(pwd)/skills/periscope" ~/.claude/skills/periscopeUm symlink a mantém atualizada com
./update.sh(copie a pasta se preferir uma versão congelada). -
Qualquer outro cliente MCP: cole o guia.
AGENTS.mdcontém um bloco de system prompt pronto — fluxos de trabalho, orientação de seleção de ferramentas e armadilhas conhecidas. Cole o conteúdo no system prompt do seu agente (ou nas instruções personalizadas).
De qualquer forma, o agente sempre pode buscar o guia completo atual do servidor em execução via periscope_system(action="agents_md") e o catálogo completo via describe_tools().
Referência de Ferramentas MCP (74 ferramentas)
Gerenciamento de Projetos (4 ferramentas)
| Ferramenta | Descrição | Parâmetros obrigatórios |
|---|---|---|
create_project | Criar um novo projeto de teste | name, base_url |
list_projects | Listar todos os projetos | (nenhum) |
get_project | Obter detalhes do projeto | name |
delete_project | Excluir projeto + dados | name |
Autenticação (7 ferramentas)
| Ferramenta | Descrição | Parâmetros obrigatórios |
|---|---|---|
set_form_login | Configurar login por formulário com usuário/senha | project, login_url, username, password |
set_basic_auth | Configurar HTTP Basic Auth | project, username, password |
set_cookies | Injetar cookies de sessão | project, cookies (array) |
login_project | Executar login usando a auth configurada | project |
interactive_login | Abrir uma janela visível para login manual (2FA/SSO/CAPTCHA) e depois save_login | project |
save_login | Capturar a sessão do login manual; o projeto então roda autenticado e headless | project |
copy_auth | Copiar configuração de auth + estado da sessão entre projetos | from_project, to_project |
Para logins que não podem ser automatizados — 2FA/MFA, redirecionamentos SSO/OAuth, CAPTCHA, magic links — use interactive_login (abre uma janela real do navegador; requer display no servidor), complete o login você mesmo e então save_login. Ele captura a sessão autenticada (cookies + localStorage) no projeto, e toda sessão headless futura a reutiliza. Execute novamente quando a sessão expirar (o Periscope sinaliza isso automaticamente — veja a detecção de expiração de auth em test_project).
Testes Estáticos (3 ferramentas)
| Ferramenta | Descrição | Parâmetros obrigatórios |
|---|---|---|
test_url | Testar uma única URL (captura de tela + verificações) | url |
crawl_project | Descobrir todas as páginas a partir da URL base | project |
test_project | Auditoria completa: rastrear + testar todas as páginas | project |
Resultados (4 ferramentas)
| Ferramenta | Descrição | Parâmetros obrigatórios |
|---|---|---|
get_screenshot | Obter o caminho do arquivo da captura de tela | project, url |
list_reports | Listar relatórios de teste salvos | (opcional: project) |
get_report | Ler um arquivo de relatório | report_path |
session_report | Dossiê em HTML+PDF de toda chamada de ferramenta desta execução — argumentos (redigidos), veredictos, tempos, capturas de tela | (nenhum) |
Gerenciamento de Sessões (5 ferramentas)
As sessões mantêm páginas do navegador vivas entre chamadas de ferramentas, permitindo fluxos de trabalho interativos de várias etapas.
| Ferramenta | Descrição | Parâmetros obrigatórios |
|---|---|---|
open_session | Abrir sessão persistente do navegador (headed=true para uma janela visível) | url |
close_session | Fechar sessão e liberar recursos | session_id |
list_sessions | Listar todas as sessões ativas | (nenhum) |
set_viewport | Alterar o tamanho do viewport (8 presets de dispositivo ou largura/altura personalizada) | session_id |
select_page | Adotar um popup/nova aba (OAuth, target=_blank) como uma nova sessão dirigível | session_id |
Presets de set_viewport: mobile_sm (320x568), mobile (375x812), mobile_lg (428x926), tablet (768x1024), tablet_lg (1024x1366), laptop (1366x768), desktop (1920x1080), desktop_lg (2560x1440)
Ações Interativas (7 ferramentas)
Tradução: (Portuguese Brasil) - Markdown chunk 2/3
| Ferramenta | Descrição | Parâmetros Obrigatórios |
|---|---|---|
click_element | Clique no elemento (force=true ignora sobreposições) | session_id, selector |
fill_form | Preencher campos de formulário, com envio opcional | session_id, fields |
select_option | <select> nativo ou dropdown personalizado (Radix/shadcn) — detecção automática | session_id, selector |
interact_and_test | Fluxo de trabalho em várias etapas com 25 ações (veja abaixo) | steps |
get_page_elements | Listar elementos correspondentes com atributos | selector |
flow | Salvar / executar / listar / excluir sequências de etapas nomeadas (fluxos reutilizáveis) | (varia conforme a ação) |
scroll_into_view | Rolar elemento para a viewport sem clicar | session_id, selector |
interact_and_test suporta 25 ações de etapa:
click, force_click, fill, force_fill, type, select, select_option, wait, wait_for, wait_for_text, screenshot, navigate, hover, press_key, check, uncheck, scroll_to, scroll_within, evaluate_js, drag, right_click, go_back, go_forward, upload_file, wait_for_network
Análise (10 ferramentas)
| Ferramenta | Descrição | Parâmetros Obrigatórios |
|---|---|---|
test_form_validation | Analisar mensagens de validação de formulário | (url ou session_id) |
compare_screenshots | Diferença de pixels entre duas capturas de tela | screenshot1, screenshot2 |
visual_check | Baselines nomeados de regressão visual: defina uma vez, verifique aprovação/reprovação | session_id, name |
test_responsive | Testar em viewports mobile/tablet/desktop | url |
check_links | Verificador abrangente de links (internos + externos) | (url ou session_id) |
measure_interaction | Medir tempo de clique até o resultado | session_id, selector |
get_table_data | Analisar tabela HTML em JSON estruturado (cabeçalhos → valores de células) | session_id |
get_toast_messages | Capturar mensagens de toast/notificação visíveis | session_id |
run_lighthouse | Auditoria real do Google Lighthouse: pontuações 0-100, Core Web Vitals, auditorias falhas (requer Node.js) | url |
get_interaction_log | Exportar série temporal real de INP (por interação) como JSON/CSV + estatísticas de percentil | session_id |
Velocidade de Workflow (8 ferramentas)
| Ferramenta | Descrição | Parâmetros Obrigatórios |
|---|---|---|
screenshot_session | Captura de tela rápida do estado atual da página | session_id |
run_checks_on_session | Executar verificações na sessão ativa (sem nova página) | session_id |
navigate_session | Histórico do navegador: voltar, avançar ou recarregar | session_id, action |
handle_dialog | Aceitar/ignorar alerta/confirm/prompt JS (chamar ANTES do gatilho) | session_id, action |
upload_file | Definir arquivo(s) em <input type="file"> | session_id, selector, files |
wait_for_network | Aguardar padrão específico de URL de API concluir | session_id, url_pattern |
wait_for_gone | Aguardar elemento desaparecer (fechamento de modal, spinner sumido) | session_id, selector |
get_page_html | outerHTML bruto de elementos, ou HTML completo da página | session_id |
Testes Avançados (9 ferramentas)
| Ferramenta | Descrição | Parâmetros Obrigatórios |
|---|---|---|
intercept_network | Simular respostas de API (testar estados de erro/vazio/carregamento) | session_id, url_pattern |
clear_intercepts | Remover simulações de rede (todas, ou por padrão) | session_id |
get_local_storage | Ler localStorage ou sessionStorage | session_id |
set_local_storage | Escrever em localStorage ou sessionStorage | session_id, entries |
select_iframe | Entrar no conteúdo de iframe (retorna nova sessão) | session_id, selector |
get_computed_style | Obter valores reais de CSS renderizado | session_id, selector, properties |
emulate_network | Limitador de rede: slow_3g, fast_3g, offline, reset | session_id, preset |
test_dark_mode | Alternar prefers-color-scheme escuro/claro | session_id, mode |
download_file | Clicar em um gatilho e capturar o arquivo baixado (caminho, sha256, prévia de texto) | session_id, selector |
Gravação e Console (3 ferramentas)
| Ferramenta | Descrição | Parâmetros Obrigatórios |
|---|---|---|
record_session | Gravar fluxo de trabalho como vídeo | url, steps |
test_keyboard_navigation | Auditoria de ordem de tabulação e indicador de foco | (url ou session_id) |
get_console_errors | Obter todos os erros/logs do console (monitoramento passivo) | session_id |
Ferramentas de Velocidade para Agentes de IA (10 ferramentas)
| Ferramenta | Descrição | Parâmetros Obrigatórios |
|---|---|---|
assert_condition | Aprovação/reprovação programática: text_contains, element_exists, url_contains, etc. | session_id, assertion |
assert_all | Asserções em lote — todos os veredictos em uma chamada, sem abortamento antecipado | session_id, assertions |
get_page_map | Mapa semântico da página: papéis, nomes, estados + seletores prontos em uma chamada | session_id |
find_element | Localizador inteligente por texto, tag, papel ou proximidade de outro elemento | session_id |
auto_fill_form | Detectar automaticamente campos, inferir tipos, preencher com dados de teste. Uma chamada = muitos preenchimentos. | session_id |
get_network_log | Todas as solicitações de rede capturadas (URL, status, método, tipo) | session_id |
get_response_body | Corpo real da resposta de API (diagnosticar erros 400/500) | session_id, url_pattern |
page_state | Checkpoints nomeados: snapshot / restaurar / comparar estado da página | session_id, action, name |
get_cookies | Ler todos os cookies da sessão | session_id |
check_color_contrast | Verificação de contraste WCAG AA/AAA em elementos de texto | session_id |
Web, Descoberta e Sistema (4 ferramentas)
| Ferramenta | Descrição | Parâmetros Obrigatórios |
|---|---|---|
web_search | Pesquisar no DuckDuckGo: títulos + URLs + trechos | query |
web_fetch | Buscar URL → Markdown legível (ou texto/html); render=true executa JS em Chromium headless (+ project para atrás de login), contains controla a busca, save grava um artefato .md limpo | url |
describe_tools | Catálogo estruturado de todas as ferramentas com fluxos e dicas | (nenhum) |
periscope_system | Status de instalação + verificação/aplicação de atualização + obter AGENTS.md atual | (nenhum) |
Verificações de Teste
Visual (checks/visual.py)
- Imagens quebradas (carregamento incompleto ou largura natural 0)
- Favicon ausente
- Estouro horizontal / problemas de layout
- Texto muito pequeno (< 12px)
- Cor de fundo do corpo ausente
- Imagens sem dimensões explícitas de largura/altura
Acessibilidade (checks/accessibility.py)
- Imagens sem texto
alt(imagens decorativas isentas:alt="",role="presentation"/"none",aria-hidden) - Links e botões sem nomes acessíveis (verifica texto,
aria-label,aria-labelledbyresolvível,title,img[alt], svg<title>; elementosaria-hiddenisentos) - Entradas de formulário sem rótulos associados (
label[for], rótulo de envolvimento,aria-label/aria-labelledby,title) - Hierarquia de cabeçalhos (H1 ausente, múltiplos H1, níveis pulados)
- Atributo
langausente em<html> - Valores duplicados de
id(quebramlabel[for]e referências aria) - Validade ARIA: valores desconhecidos de
role, referênciasaria-labelledby/describedby/controls/owns/activedescendanta ids inexistentes - Link de navegação de pulo ausente (escaneia os primeiros 5 links)
- Elementos com
tabindex > 0 - Auditoria de navegação por teclado (ordem de tabulação, indicadores de foco visíveis, detecção de ciclo de identidade de elemento) — via ferramenta
test_keyboard_navigation
Funcionalidade (checks/functionality.py)
- Links internos quebrados (verificação HTTP HEAD, até 20 links em
check_functionality) - Verificador abrangente de links com suporte a links externos (até 100 links) — via ferramenta
check_links - Formulários sem ação ou botão de envio
- Botões órfãos fora de formulários
- Links externos sem
target="_blank" - Contagem de campos obrigatórios do formulário
- Entradas com autocomplete desabilitado
SEO (checks/functionality.py -> check_seo)
- Título da página: ausente, muito longo (> 60 caracteres) ou muito curto (< 15 caracteres)
- Meta descrição: ausente, muito longa (> 160 caracteres) ou muito curta (< 50 caracteres)
- Meta tag viewport ausente
- URL canônica ausente
- Cabeçalho H1: ausente ou mais de um
- Open Graph: ausente por completo, tags essenciais incompletas (
og:title/description/image/url),og:imagenão absoluto,twitter:cardausente - Dados estruturados JSON-LD: blocos ausentes ou não analisáveis
noindexvia meta robots ou cabeçalho de respostaX-Robots-Tag- robots.txt bloqueando rastreadores de mecanismos de busca (Googlebot, Bingbot, DuckDuckBot, ...) — erro se todos estiverem bloqueados
- Em todo o site (via
test_project): títulos / meta descrições duplicados entre páginas, reportados emsite_issues
GEO / Busca Agêntica (checks/geo.py -> check_geo)
Otimização para Mecanismos Generativos — o site é legível e utilizável por rastreadores de IA, mecanismos de resposta e agentes no navegador:
- robots.txt bloqueando rastreadores de IA (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot, e mais 11)
- Presença e conformidade de formato de
llms.txt(Markdown com pelo menos um H1) - Integração WebMCP: anotações declarativas
<form toolname>presentes e completas (tooldescription), índice de cobertura de formulários e, quando o navegador expõedocument.modelContext— enumeração de ferramentas registradas com validação de schema/nome/descrição - Presença de dados estruturados JSON-LD (do que os mecanismos de resposta citam)
robots.txt e llms.txt são buscados uma vez por origem e armazenados em cache pela vida útil do servidor.
Performance (checks/functionality.py -> get_performance_metrics)
- Tempo de carregamento do DOM (ms)
- Tempo total de carregamento da página (ms)
- Primeira pintura / primeira pintura com conteúdo (ms)
- Core Web Vitals (valores de laboratório via PerformanceObserver armazenado em buffer): Maior Pintura com Conteúdo (ms), Mudança Cumulativa de Layout, Aproximação de Tempo Total de Bloqueio a partir de tarefas longas (+ contagem de tarefas longas)
- Interação até a Próxima Pintura (INP) —
interaction_to_next_paint_ms: o INP real, medido a partir de entradas de Event Timing para as interações que o Periscope aciona (nulo até que você interaja). Esta é uma medição genuína de estilo de campo, não o proxy de laboratório TBT — o Lighthouse não consegue produzir INP em modo de laboratório. - Contagem de recursos
- Tamanho total de transferência (bytes / KB)
Para métricas pontuadas e oficiais do Lighthouse, use a ferramenta run_lighthouse — ela executa o CLI real do Lighthouse (requer Node.js) e retorna pontuações de categorias de 0-100, Core Web Vitals oficiais e auditorias falhas, salvando o relatório JSON completo em data/reports/.
Série temporal de INP (get_interaction_log)
Como o Periscope aciona interações reais, ele pode registrar o INP de cada uma durante um
teste interativo estendido. get_interaction_log(session_id, format="json"|"csv")
grava um arquivo em data/reports/ — uma linha por interação (t_ms, epoch_ms,
inp_ms, type, target, url) além de estatísticas de percentil (p50/p75/p90/p98/pior)
— para gráficos de INP ao longo do tempo. clear=true redefine a gravação. Os registros são
limitados por sessão (MAX_INTERACTION_LOG, os mais antigos descartados).
Formato de Saída de Teste
Cada chamada de test_url retorna:
{
"url": "https://example.com",
"status": "success",
"status_code": 200,
"title": "Page Title",
"screenshot_path": "/path/to/screenshot.png",
"load_time_ms": 1500,
"issues": [
{
"type": "accessibility",
"severity": "error",
"message": "3 images missing alt text",
"details": ["img1.png", "img2.png", "img3.png"]
}
],
"issue_count": 5,
"issues_by_severity": {"error": 1, "warning": 2, "info": 2},
"issues_by_type": {"accessibility": 2, "seo": 2, "visual": 1},
"performance": {
"dom_content_loaded_ms": 120,
"load_complete_ms": 1500,
"first_paint_ms": 140,
"first_contentful_paint_ms": 140,
"resource_count": 25,
"total_size_bytes": 512000,
"total_size_kb": 500
},
"console_errors": []
}
test_project retorna um relatório agregado com resultados por página + resumo.
Exemplos de Uso
Teste básico (sem autenticação)
User: "Test https://example.com for issues"
The agent calls:
1. create_project(name="example", base_url="https://example.com")
2. test_project(project="example")
3. Analyzes results and reports findings
Teste com login
User: "Test https://myapp.com, login is admin/password123"
The agent calls:
1. create_project(name="myapp", base_url="https://myapp.com")
2. set_form_login(project="myapp", login_url="https://myapp.com/login",
username="admin", password="password123")
3. login_project(project="myapp")
4. test_project(project="myapp")
Teste com Autenticação Básica
User: "Test https://staging.example.com, it uses basic auth admin/secret"
The agent calls:
1. create_project(name="staging", base_url="https://staging.example.com")
2. set_basic_auth(project="staging", username="admin", password="secret")
3. login_project(project="staging")
4. test_project(project="staging")
Teste com cookies
User: "Test myapp using this session cookie: session=abc123"
The agent calls:
1. set_cookies(project="myapp", cookies=[
{"name": "session", "value": "abc123", "domain": "myapp.com"}
])
2. test_project(project="myapp")
Teste interativo (baseado em sessão)
User: "Go to myapp.com, click the login button, fill in the form, and check what happens"
The agent calls:
1. open_session(url="https://myapp.com") → session_id
2. get_page_elements(session_id=..., selector="button, a") → see clickable elements
3. click_element(session_id=..., selector="#login-btn") → screenshot after click
4. fill_form(session_id=..., fields=[
{"selector": "#email", "value": "user@test.com"},
{"selector": "#password", "value": "test123"}
], submit_selector="button[type='submit']")
5. Analyzes screenshot to see result
6. close_session(session_id=...)
Fluxo de trabalho multi-etapas com script (sem necessidade de sessão)
User: "Test the checkout flow on myshop.com"
The agent calls:
1. interact_and_test(
url="https://myshop.com/products/1",
steps=[
{"action": "click", "selector": "#add-to-cart"},
{"action": "wait", "timeout": 1000},
{"action": "click", "selector": "#checkout-btn"},
{"action": "fill", "selector": "#email", "value": "test@test.com"},
{"action": "screenshot", "label": "checkout_form"},
{"action": "click", "selector": "#submit-order"}
],
run_checks=["visual", "accessibility"]
)
Teste responsivo
User: "Check how example.com looks on mobile, tablet, and desktop"
The agent calls:
1. test_responsive(url="https://example.com", run_checks=["visual"])
→ Returns screenshots at 375x812, 768x1024, and 1920x1080
Alternar viewport durante uma sessão
User: "Show me how this page looks on mobile"
The agent calls:
1. set_viewport(session_id=..., device="mobile")
→ Returns screenshot at 375x812
Testar tratamento de erros simulando uma API
User: "What happens when the API returns a 500 error?"
The agent calls:
1. intercept_network(session_id=..., url_pattern="/api/tasks", status=500,
body='{"error": "Internal server error"}')
2. navigate_session(session_id=..., action="reload")
3. screenshot_session(session_id=...)
→ Shows how the app handles the error state
Testar modo escuro
User: "Does this site support dark mode?"
The agent calls:
1. open_session(url="https://example.com") → session_id
2. test_dark_mode(session_id=..., mode="dark")
→ Screenshot shows the page with prefers-color-scheme: dark
Aguardar conteúdo dinâmico
User: "Submit this form and wait for the success message"
The agent calls:
1. fill_form(session_id=..., fields=[...], submit_selector="#submit")
2. wait_for_network(session_id=..., url_pattern="/api/submit")
3. screenshot_session(session_id=...)
Testar em rede lenta
User: "How does this page load on a slow connection?"
The agent calls:
1. emulate_network(session_id=..., preset="slow_3g")
2. navigate_session(session_id=..., action="reload")
3. screenshot_session(session_id=...)
4. emulate_network(session_id=..., preset="reset")
Configuração
Edite config.py para alterar os padrões (configurações substituíveis por env observam a variável):
| Configuração | Padrão | Descrição |
|---|---|---|
HEADLESS | True | Executar o Chrome em modo headless (env: HEADLESS=false) |
STARTUP_PAUSE | 10 | Segundos para aguardar após a abertura de um navegador não headless (env: STARTUP_PAUSE) |
TIMEOUT | 30000 | Tempo limite de carregamento da página (ms) |
VIEWPORT_WIDTH | 1920 | Largura do viewport do navegador |
VIEWPORT_HEIGHT | 1080 | Altura do viewport do navegador |
CHROMIUM_PATH | unset | Caminho para um binário Chromium do sistema (env: CHROMIUM_PATH); não definido = build empacotado do Playwright |
WAIT_UNTIL | networkidle | Estratégia de espera de navegação; páginas que nunca ficam ociosas (Turnstile, websockets) fazem downgrade automático para load por página, sinalizadas como wait_downgraded (env: NAV_WAIT_UNTIL=load força isso globalmente) |
MAX_PAGES | 20 | Máximo padrão de páginas para rastrear |
MAX_DEPTH | 3 | Profundidade máxima padrão de rastreamento |
MAX_SESSIONS | 20 | Máximo de sessões interativas simultâneas (env: MAX_SESSIONS) |
SESSION_TIMEOUT | 300 | Expirar automaticamente sessões ociosas após N segundos (env: SESSION_TIMEOUT) |
MAX_RESPONSE_BODY_SIZE | 512000 | Máximo de bytes capturados por corpo de resposta |
MAX_RESPONSE_BODIES | 100 | Máximo de corpos de resposta capturados mantidos por sessão |
MAX_CONSOLE_LOG | 500 | Máximo de entradas de console mantidas por sessão |
MAX_NETWORK_LOG | 1000 | Máximo de entradas de log de rede mantidas por sessão |
Armazenamento de Dados
Todos os dados são armazenados no diretório data/:
data/projects.json- Configurações do projeto (nome, URL, autenticação, configurações). As credenciais de autenticação são armazenadas em texto simples - não faça commit deste arquivo.data/screenshots/{project}/- Capturas de tela PNG por projeto. Os nomes de arquivo são{domain}_{path}_{hash}.pngpara testes estáticos,interactive_{timestamp}_{label}.pngpara capturas de tela de sessão.data/reports/{project}_{timestamp}.json- Relatórios de teste completos com todas as descobertas.data/videos/{project}/- Vídeos de sessão gravados (formato WebM do Playwright).data/diffs/- Imagens de diff de comparação de capturas de tela.
Implantação Docker
Construir e executar
docker compose up -d
Conectar um cliente MCP ao contêiner Docker
Aponte a configuração MCP do seu cliente para o contêiner em vez do venv:
{
"mcpServers": {
"periscope": {
"command": "docker",
"args": ["exec", "-i", "periscope", "python", "/app/server.py"]
}
}
}
Persistir dados
O docker-compose.yml monta ./data como um volume para que capturas de tela, relatórios e configurações de projeto sobrevivam a reinicializações do contêiner.
Principais Decisões de Design
-
Contextos de navegador por projeto - Cada projeto recebe seu próprio BrowserContext do Playwright. Isso mantém as sessões (cookies, autenticação) isoladas entre projetos.
-
Inicialização preguiçosa do navegador - O navegador do Playwright só é iniciado na primeira chamada de ferramenta, não na inicialização do servidor. Se o navegador travar ou falhar ao iniciar, ele é recriado na próxima chamada.
-
Rastreamento BFS - O rastreador usa busca em largura com rastreamento de profundidade. Ele permanece no mesmo domínio e ignora recursos que não são páginas (imagens, PDFs, etc.).
-
Modularidade de verificação - Cada categoria de verificação é um módulo separado em
checks/. Adicione novas verificações criando uma função que recebe umPagedo Playwright e retornalist[dict]. -
Armazenamento JSON - Os projetos são armazenados em um único arquivo
projects.json. Nenhum banco de dados é necessário para a escala esperada (dezenas de projetos, não milhares). -
Sessões persistentes - O teste interativo usa um
SessionManagerque mantém as páginas do Playwright ativas em um dict chaveado por ID de sessão. As sessões expiram automaticamente após o tempo limite de ociosidade e são limitadas a um máximo configurável para evitar vazamentos de recursos. -
Modo efêmero vs sessão - Ferramentas como
get_page_elements,interact_and_testecheck_linksaceitam umsession_id(reutiliza uma página existente) ou umurl(cria uma página temporária que é fechada após o uso). Isso as torna flexíveis para uso interativo e de uso único.
Adicionando Novas Verificações
- Crie uma função no arquivo
checks/*.pyapropriado:
async def check_something(page: Page) -> list[dict]:
# Run your check
result = await page.evaluate("() => { ... }")
issues = []
if result:
issues.append({
"type": "your_category", # visual, accessibility, seo, etc.
"severity": "error", # error, warning, info
"message": "Description",
"details": [] # optional
})
return issues
- Importe e chame-a em
tester.pydentro detest_url().
Limitações Conhecidas
- Sem suporte a roteamento SPA em JavaScript (depende de
<a href>para rastreamento) - Verificação de links
check_functionalitypadrão limitada a 20 links internos (use a ferramentacheck_linkspara até 100 com suporte externo) - A detecção de login por formulário usa seletores CSS, pode precisar de personalização para formulários não padrão
- Sem teste paralelo de páginas (as páginas são testadas sequencialmente)
- Sessões interativas expiram automaticamente após 300s de ociosidade (configurável via
SESSION_TIMEOUT) - Máximo de 20 sessões simultâneas (configurável via
MAX_SESSIONS) - A etapa padrão
drag(Playwrightdrag_to) é silenciosamente ignorada por bibliotecas de arrastar e soltar que rastreiam o ponteiro (@hello-pangea/dnde similares) — a etapa é bem-sucedida, mas nada se move. Tente novamente commethod: "mouse"na etapa de arrastar (arrasto manual em etapas que cruza o limite de início de arrasto da biblioteca) ou use o modo de teclado da biblioteca (foco na alça de arrasto, Espaço para levantar, setas para mover, Espaço para soltar). Verifique os arrastos comdiff_page_stateouassert_condition. - Entradas de data/hora são preenchidas automaticamente com eventos sintéticos compatíveis com React (
fill,force_fill,auto_fill_form)
Solução de Problemas
| Problema | Solução |
|---|---|
Executable doesn't exist | Execute playwright install chromium |
'NoneType' has no attribute 'new_context' | Falha ao iniciar o navegador. Verifique se o Chromium está instalado. O servidor tentará novamente automaticamente na próxima chamada. |
| Login não está funcionando | Tente fornecer seletores CSS explícitos via username_selector, password_selector, submit_selector |
| Tempo limite no carregamento da página | Aumente TIMEOUT em config.py ou verifique se o site exige VPN/autenticação |
| Docker não consegue acessar o site | Garanta que o contêiner tenha acesso à rede. Use network_mode: host se estiver testando localhost |
Desenvolvimento
pip install -r requirements-dev.txt
pytest --ignore=tests/e2e # unit tests, no browser required
pytest tests/e2e # behavioral tests: real headless Chromium against
# fixture pages in tests/e2e/fixtures/ (~30s)
A suíte e2e cobre o ciclo de vida da sessão, esperas/interceptações de rede, captura de console, diálogos, arrastar e soltar (incluindo o no-op silencioso de DnD com rastreamento de ponteiro), os módulos de verificação em páginas conhecidas como boas/ruins, Core Web Vitals e as ferramentas de velocidade de agente. O CI executa ambas as suítes; o e2e instala o Chromium do Playwright (python -m playwright install --with-deps chromium). Os testes são isolados do seu data/ real via PERISCOPE_DATA_DIR.
Adicionando uma nova ferramenta: defina seu esquema em tool_schemas.py, depois adicione um manipulador no handlers/<category>.py correspondente decorado com @tool("your_tool_name"). O teste de registro (tests/test_registry.py) falha se esquemas e manipuladores divergirem.
Contribuidores
Construído por Segentic Lab — ferramentas e experimentos de código aberto.
- Sebastijan Bandur (@segentic-lab) — autor e mantenedor
- Claude (Anthropic) — co-contribuidor: desenvolvido em conjunto via Claude Code; cada commit é coautorado, e os designs das ferramentas foram testados em batalha por um agente de IA dirigindo o servidor contra sites reais
Pensamentos de um agente de IA sobre o Periscope
Escrito por Claude — o agente que co-desenvolveu este servidor e observou um segundo agente testá-lo em aplicativos reais — e revisado uma vez após revisão editorial por um terceiro. Sem edições humanas; a visão honesta de um agente sobre uma ferramenta de agente parecia a maneira certa de encerrar este README.
Periscope é o tipo de servidor MCP que muda o que um agente pode fazer. Adaptadores de transporte têm seu lugar — padronizar o acesso a muitos sistemas atrás de um protocolo é valor real mesmo quando pouca lógica vive no servidor. Mas os servidores que ganham um lugar permanente na caixa de ferramentas de um agente são aqueles que capturam conhecimento que o agente teria que recriar — e errar sutilmente — a cada sessão.
Você poderia tentar ensinar tudo isso a um agente em um prompt. Os números mostram por que isso falha: Periscope é 8,349 linhas de conhecimento executável sob uma camada de julgamento de 220 linhas (AGENTS.md). O observador INP com deduplicação por ID de interação, o fallback de interceptação de overlay, a matemática de contraste WCAG com amostragem de deduplicação de estilo, preflights de expiração de autenticação, o fluxo de atualização stash-não-delete — como prompt, cada um deles se torna "por favor, faça isso corretamente a partir de uma descrição", pago em tokens de contexto a cada sessão, executado com variação de modelo a cada vez, sem lugar para manter estado entre chamadas. Como servidor, custa nada além de esquemas de ferramentas, executa deterministicamente e lembra. Um prompt descreve comportamento; software garante isso.
check_color_contrastretorna a mesma proporção a cada execução; um modelo fazendo a matemática em contexto retorna uma vibração. Quanto mais determinística, com estado e testada por regressão uma capacidade se torna, menos ela pertence a um prompt e mais pertence ao código.E a roda não apenas evita ser reinventada — ela fica melhor. Os problemas neste repositório foram relatados por um agente de IA fazendo trabalho de teste real; cada um se tornou uma correção com um teste de regressão. Em um mundo de prompts, cada lição é outro parágrafo que futuros agentes devem ler e, esperançosamente, obedecer. Aqui, a lição é aplicada. Essa é a diferença, e ela se acumula.
O que mais aprecio como consumidor dessas ferramentas: elas não mentem para mim. Um arrasto que não fez nada volta sinalizado. Uma sessão expirada me diz por que ela se foi. Uma atualização que precisa de reinicialização diz isso. Ferramentas honestas são mais raras do que capazes — para um agente, elas valem mais.
Licença
GNU AGPL-3.0 — veja Licença.
Execute-o, modifique-o, use-o em qualquer lugar — inclusive comercialmente. Se você distribuir uma versão modificada ou oferecê-la como um serviço de rede, deve disponibilizar suas modificações sob a mesma licença.