feat: refactor 30+ skills to Anthropic progressive disclosure pattern
- All SKILL.md files now <500 lines (avg reduction 69%) - Detailed content extracted to references/ subdirectories - Frontmatter standardised: only name + description (Anthropic standard) - New skills: brand-guidelines, spec-coauthor, report-templates, skill-creator - Design skills: anti-slop guidelines, premium-proposals reference - Removed non-standard frontmatter fields (triggers, version, author, category) Plugins affected: infraestrutura, marketing, dev-tools, crm-ops, gestao, core-tools, negocio, perfex-dev, wordpress, design-media Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
146
dev-tools/skills/webapp-testing/SKILL.md
Normal file
146
dev-tools/skills/webapp-testing/SKILL.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
name: webapp-testing
|
||||
description: Testes e automacao de aplicacoes web locais com Playwright — verificacao de frontend, debug de UI, screenshots, logs de consola e gestao de ciclo de vida de servidores.
|
||||
---
|
||||
|
||||
# Web Application Testing
|
||||
|
||||
Testar aplicacoes web locais escrevendo scripts Python nativos com Playwright.
|
||||
|
||||
**Complementaridade com /chrome:** Esta skill foca-se em **scripts Playwright headless** para testes automatizados e CI/CD. Para automacao de browser com sessao activa (sites autenticados, GIF recording, debug visual interactivo), usar a skill `/chrome` que integra os MCPs `chrome-devtools` e `claude-in-chrome`.
|
||||
|
||||
| Cenario | Skill |
|
||||
|---------|-------|
|
||||
| Testes automatizados headless | **/webapp-testing** (Playwright) |
|
||||
| CI/CD pipeline testing | **/webapp-testing** (Playwright) |
|
||||
| Debug visual com sessao activa | /chrome (MCP backends) |
|
||||
| Performance traces (LCP/TBT/CLS) | /chrome (DevTools MCP) |
|
||||
| Sites autenticados (Google, CRMs) | /chrome (Chrome extensao) |
|
||||
| Scraping com interaccao DOM programatica | **/webapp-testing** (Playwright) |
|
||||
|
||||
**Scripts auxiliares disponiveis:**
|
||||
- `scripts/with_server.py` - Gestao de ciclo de vida de servidores (suporta multiplos servidores)
|
||||
|
||||
**Executar sempre scripts com `--help` primeiro** para ver utilizacao. NAO ler o codigo fonte ate tentar executar o script e confirmar que uma solucao personalizada e absolutamente necessaria. Estes scripts podem ser extensos e poluir a context window. Existem para ser invocados como black-box.
|
||||
|
||||
## Arvore de decisao: Escolher abordagem
|
||||
|
||||
```
|
||||
Tarefa -> E HTML estatico?
|
||||
|-- Sim -> Ler ficheiro HTML directamente para identificar selectores
|
||||
| |-- Sucesso -> Escrever script Playwright com selectores
|
||||
| |-- Falha/Incompleto -> Tratar como dinamico (abaixo)
|
||||
|
|
||||
|-- Nao (webapp dinamica) -> Servidor ja esta a correr?
|
||||
|-- Nao -> Executar: python scripts/with_server.py --help
|
||||
| Depois usar helper + escrever script Playwright simplificado
|
||||
|
|
||||
|-- Sim -> Reconhecimento-depois-accao:
|
||||
1. Navegar e esperar por networkidle
|
||||
2. Tirar screenshot ou inspeccionar DOM
|
||||
3. Identificar selectores a partir do estado renderizado
|
||||
4. Executar accoes com selectores descobertos
|
||||
```
|
||||
|
||||
## Exemplo: Usar with_server.py
|
||||
|
||||
Iniciar um servidor: executar `--help` primeiro, depois usar o helper.
|
||||
|
||||
**Servidor unico:**
|
||||
```bash
|
||||
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
|
||||
```
|
||||
|
||||
**Multiplos servidores (ex: backend + frontend):**
|
||||
```bash
|
||||
python scripts/with_server.py \
|
||||
--server "cd backend && python server.py" --port 3000 \
|
||||
--server "cd frontend && npm run dev" --port 5173 \
|
||||
-- python your_automation.py
|
||||
```
|
||||
|
||||
Para criar um script de automacao, incluir apenas logica Playwright (servidores sao geridos automaticamente):
|
||||
```python
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True) # Sempre lancar chromium em modo headless
|
||||
page = browser.new_page()
|
||||
page.goto('http://localhost:5173') # Servidor ja a correr e pronto
|
||||
page.wait_for_load_state('networkidle') # CRITICO: Esperar JS executar
|
||||
# ... logica de automacao
|
||||
browser.close()
|
||||
```
|
||||
|
||||
## Padrao reconhecimento-depois-accao
|
||||
|
||||
1. **Inspeccionar DOM renderizado**:
|
||||
```python
|
||||
page.screenshot(path='/tmp/inspect.png', full_page=True)
|
||||
content = page.content()
|
||||
page.locator('button').all()
|
||||
```
|
||||
|
||||
2. **Identificar selectores** a partir dos resultados da inspeccao
|
||||
|
||||
3. **Executar accoes** usando selectores descobertos
|
||||
|
||||
## Erro comum
|
||||
|
||||
**NAO** inspeccionar o DOM antes de esperar por `networkidle` em apps dinamicas
|
||||
**SIM** esperar por `page.wait_for_load_state('networkidle')` antes de inspeccionar
|
||||
|
||||
## Boas praticas
|
||||
|
||||
- **Usar scripts bundled como black boxes** - Para cumprir uma tarefa, considerar se um dos scripts em `scripts/` pode ajudar. Estes scripts tratam workflows comuns e complexos de forma fiavel sem poluir a context window. Usar `--help` para ver utilizacao, depois invocar directamente.
|
||||
- Usar `sync_playwright()` para scripts sincronos
|
||||
- Fechar sempre o browser quando terminar
|
||||
- Usar selectores descritivos: `text=`, `role=`, selectores CSS, ou IDs
|
||||
- Adicionar waits apropriados: `page.wait_for_selector()` ou `page.wait_for_timeout()`
|
||||
- Screenshots guardar em `/tmp/` (nunca em directorio de projecto)
|
||||
- Para testes de aplicacoes Descomplicar: verificar `localhost:3000` (Next.js) ou `localhost:5173` (Vite)
|
||||
|
||||
## Ficheiros de referencia
|
||||
|
||||
- **examples/** - Exemplos com padroes comuns:
|
||||
- `element_discovery.py` - Descobrir botoes, links e inputs numa pagina
|
||||
- `static_html_automation.py` - Usar URLs file:// para HTML local
|
||||
- `console_logging.py` - Capturar logs de consola durante automacao
|
||||
|
||||
## Integracao Descomplicar
|
||||
|
||||
### MCPs complementares
|
||||
|
||||
| MCP | Uso com webapp-testing |
|
||||
|-----|----------------------|
|
||||
| `chrome-devtools` | Performance traces, network inspection, DOM/CSS debugging — usar via skill /chrome |
|
||||
| `claude-in-chrome` | Testes em sites autenticados com sessao activa — usar via skill /chrome |
|
||||
| `lighthouse` | Auditorias completas de performance, SEO, a11y — complementa testes Playwright |
|
||||
| `puppeteer` | Alternativa headless para automacao simples sem Playwright |
|
||||
|
||||
### Workflow tipico Descomplicar
|
||||
|
||||
1. **Desenvolvimento local**: Playwright (esta skill) para testes funcionais headless
|
||||
2. **Debug visual**: /chrome (DevTools MCP) para inspeccao DOM/CSS/network em tempo real
|
||||
3. **Performance**: /chrome (DevTools MCP traces) + MCP lighthouse para auditorias
|
||||
4. **CI/CD**: Scripts Playwright commitados no repositorio, executados em pipeline Gitea Actions
|
||||
|
||||
### Executar no servidor dev (regra #48)
|
||||
|
||||
Para testes que requerem Node.js/Python:
|
||||
```bash
|
||||
# Via SSH MCP para o servidor dev
|
||||
mcp__ssh-unified__ssh_execute server="dev" command="cd /root/Dev/projecto && python test_automation.py"
|
||||
```
|
||||
|
||||
### Instalacao Playwright (se necessario)
|
||||
|
||||
```bash
|
||||
pip install playwright
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
No servidor dev, Playwright ja esta disponivel em `/root/Dev`.
|
||||
|
||||
---
|
||||
**Versao**: 1.0.0 | **Autor**: Descomplicar®
|
||||
35
dev-tools/skills/webapp-testing/examples/console_logging.py
Normal file
35
dev-tools/skills/webapp-testing/examples/console_logging.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
# Example: Capturing console logs during browser automation
|
||||
|
||||
url = 'http://localhost:5173' # Replace with your URL
|
||||
|
||||
console_logs = []
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
|
||||
|
||||
# Set up console log capture
|
||||
def handle_console_message(msg):
|
||||
console_logs.append(f"[{msg.type}] {msg.text}")
|
||||
print(f"Console: [{msg.type}] {msg.text}")
|
||||
|
||||
page.on("console", handle_console_message)
|
||||
|
||||
# Navigate to page
|
||||
page.goto(url)
|
||||
page.wait_for_load_state('networkidle')
|
||||
|
||||
# Interact with the page (triggers console logs)
|
||||
page.click('text=Dashboard')
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
browser.close()
|
||||
|
||||
# Save console logs to file
|
||||
with open('/mnt/user-data/outputs/console.log', 'w') as f:
|
||||
f.write('\n'.join(console_logs))
|
||||
|
||||
print(f"\nCaptured {len(console_logs)} console messages")
|
||||
print(f"Logs saved to: /mnt/user-data/outputs/console.log")
|
||||
@@ -0,0 +1,40 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
# Example: Discovering buttons and other elements on a page
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page()
|
||||
|
||||
# Navigate to page and wait for it to fully load
|
||||
page.goto('http://localhost:5173')
|
||||
page.wait_for_load_state('networkidle')
|
||||
|
||||
# Discover all buttons on the page
|
||||
buttons = page.locator('button').all()
|
||||
print(f"Found {len(buttons)} buttons:")
|
||||
for i, button in enumerate(buttons):
|
||||
text = button.inner_text() if button.is_visible() else "[hidden]"
|
||||
print(f" [{i}] {text}")
|
||||
|
||||
# Discover links
|
||||
links = page.locator('a[href]').all()
|
||||
print(f"\nFound {len(links)} links:")
|
||||
for link in links[:5]: # Show first 5
|
||||
text = link.inner_text().strip()
|
||||
href = link.get_attribute('href')
|
||||
print(f" - {text} -> {href}")
|
||||
|
||||
# Discover input fields
|
||||
inputs = page.locator('input, textarea, select').all()
|
||||
print(f"\nFound {len(inputs)} input fields:")
|
||||
for input_elem in inputs:
|
||||
name = input_elem.get_attribute('name') or input_elem.get_attribute('id') or "[unnamed]"
|
||||
input_type = input_elem.get_attribute('type') or 'text'
|
||||
print(f" - {name} ({input_type})")
|
||||
|
||||
# Take screenshot for visual reference
|
||||
page.screenshot(path='/tmp/page_discovery.png', full_page=True)
|
||||
print("\nScreenshot saved to /tmp/page_discovery.png")
|
||||
|
||||
browser.close()
|
||||
@@ -0,0 +1,33 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
import os
|
||||
|
||||
# Example: Automating interaction with static HTML files using file:// URLs
|
||||
|
||||
html_file_path = os.path.abspath('path/to/your/file.html')
|
||||
file_url = f'file://{html_file_path}'
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
|
||||
|
||||
# Navigate to local HTML file
|
||||
page.goto(file_url)
|
||||
|
||||
# Take screenshot
|
||||
page.screenshot(path='/mnt/user-data/outputs/static_page.png', full_page=True)
|
||||
|
||||
# Interact with elements
|
||||
page.click('text=Click Me')
|
||||
page.fill('#name', 'John Doe')
|
||||
page.fill('#email', 'john@example.com')
|
||||
|
||||
# Submit form
|
||||
page.click('button[type="submit"]')
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# Take final screenshot
|
||||
page.screenshot(path='/mnt/user-data/outputs/after_submit.png', full_page=True)
|
||||
|
||||
browser.close()
|
||||
|
||||
print("Static HTML automation completed!")
|
||||
106
dev-tools/skills/webapp-testing/scripts/with_server.py
Executable file
106
dev-tools/skills/webapp-testing/scripts/with_server.py
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Start one or more servers, wait for them to be ready, run a command, then clean up.
|
||||
|
||||
Usage:
|
||||
# Single server
|
||||
python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py
|
||||
python scripts/with_server.py --server "npm start" --port 3000 -- python test.py
|
||||
|
||||
# Multiple servers
|
||||
python scripts/with_server.py \
|
||||
--server "cd backend && python server.py" --port 3000 \
|
||||
--server "cd frontend && npm run dev" --port 5173 \
|
||||
-- python test.py
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
def is_server_ready(port, timeout=30):
|
||||
"""Wait for server to be ready by polling the port."""
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
with socket.create_connection(('localhost', port), timeout=1):
|
||||
return True
|
||||
except (socket.error, ConnectionRefusedError):
|
||||
time.sleep(0.5)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Run command with one or more servers')
|
||||
parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)')
|
||||
parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)')
|
||||
parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)')
|
||||
parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Remove the '--' separator if present
|
||||
if args.command and args.command[0] == '--':
|
||||
args.command = args.command[1:]
|
||||
|
||||
if not args.command:
|
||||
print("Error: No command specified to run")
|
||||
sys.exit(1)
|
||||
|
||||
# Parse server configurations
|
||||
if len(args.servers) != len(args.ports):
|
||||
print("Error: Number of --server and --port arguments must match")
|
||||
sys.exit(1)
|
||||
|
||||
servers = []
|
||||
for cmd, port in zip(args.servers, args.ports):
|
||||
servers.append({'cmd': cmd, 'port': port})
|
||||
|
||||
server_processes = []
|
||||
|
||||
try:
|
||||
# Start all servers
|
||||
for i, server in enumerate(servers):
|
||||
print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}")
|
||||
|
||||
# Use shell=True to support commands with cd and &&
|
||||
process = subprocess.Popen(
|
||||
server['cmd'],
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
server_processes.append(process)
|
||||
|
||||
# Wait for this server to be ready
|
||||
print(f"Waiting for server on port {server['port']}...")
|
||||
if not is_server_ready(server['port'], timeout=args.timeout):
|
||||
raise RuntimeError(f"Server failed to start on port {server['port']} within {args.timeout}s")
|
||||
|
||||
print(f"Server ready on port {server['port']}")
|
||||
|
||||
print(f"\nAll {len(servers)} server(s) ready")
|
||||
|
||||
# Run the command
|
||||
print(f"Running: {' '.join(args.command)}\n")
|
||||
result = subprocess.run(args.command)
|
||||
sys.exit(result.returncode)
|
||||
|
||||
finally:
|
||||
# Clean up all servers
|
||||
print(f"\nStopping {len(server_processes)} server(s)...")
|
||||
for i, process in enumerate(server_processes):
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
print(f"Server {i+1} stopped")
|
||||
print("All servers stopped")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user