웹앱 테스트
서버 오케스트레이션 도우미 및 문제 해결 팁이 포함된 Playwright 기반 웹 앱 테스트를 위한 에이전트 기술 핸드북입니다.
출처: 인류학/기술(MIT)에서 채택한 콘텐츠.
로컬 웹 애플리케이션을 테스트하려면 기본 Python Playwright 스크립트를 작성하세요.
사용 가능한 도우미 스크립트:
scripts/with_server.py- 서버 수명주기 관리(여러 서버 지원)
항상--help를 사용하여 스크립트를 먼저 실행하여 사용법을 확인하세요. 먼저 스크립트를 실행해보고 사용자 정의 솔루션이 반드시 필요하다는 것을 알기 전까지는 소스를 읽지 마십시오. 이러한 스크립트는 매우 클 수 있으므로 컨텍스트 창을 오염시킬 수 있습니다. 컨텍스트 창에 수집되지 않고 블랙박스 스크립트로 직접 호출되기 위해 존재합니다.
의사결정 트리: 접근 방식 선택
User task -> Is it static HTML?
Yes -> Read HTML file directly to identify selectors
Success -> Write Playwright script using selectors
Fails/Incomplete -> Treat as dynamic (below)
No (dynamic webapp) -> Is the server already running?
No -> Run: python scripts/with_server.py --help
Then use the helper + write simplified Playwright script
Yes -> Reconnaissance-then-action:
1. Navigate and wait for networkidle
2. Take screenshot or inspect DOM
3. Identify selectors from rendered state
4. Execute actions with discovered selectors예: with_server.py 사용
서버를 시작하려면 먼저--help를 실행한 다음 도우미를 사용하세요.
단일 서버:
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py여러 서버(예: 백엔드 + 프런트엔드):
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자동화 스크립트를 생성하려면 Playwright 로직만 포함하세요(서버는 자동으로 관리됩니다).
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
page = browser.new_page()
page.goto('http://localhost:5173') # Server already running and ready
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
# ... your automation logic
browser.close()정찰 후 조치 패턴
-
렌더링된 DOM 검사:
page.screenshot(path='/tmp/inspect.png', full_page=True) content = page.content() page.locator('button').all() -
검사 결과에서 선택자 식별
-
검색된 선택기를 사용하여 작업 실행
일반적인 함정
동적 앱에서networkidle를 기다리기 전에 DOM을 검사하지 마세요
Do 검사 전page.wait_for_load_state('networkidle')를 기다리세요
모범 사례
- 번들 스크립트를 블랙박스로 사용 - 작업을 수행하려면
scripts/에서 사용 가능한 스크립트 중 하나가 도움이 될 수 있는지 고려하세요. 이러한 스크립트는 컨텍스트 창을 어지럽히지 않고 일반적이고 복잡한 작업 흐름을 안정적으로 처리합니다.--help를 사용하여 사용법을 확인한 다음 직접 호출하세요. - 동기 스크립트에는
sync_playwright()사용 - 완료되면 항상 브라우저를 닫으세요.
- 설명 선택기 사용:
text=,role=, CSS 선택기 또는 ID - 적절한 대기 추가:
page.wait_for_selector()또는page.wait_for_timeout()
참조 파일
- 예/ - 일반적인 패턴을 보여주는 예:
element_discovery.py- 페이지에서 버튼, 링크 및 입력 검색static_html_automation.py- 로컬 HTML에 file:// URL 사용console_logging.py- 자동화 중 콘솔 로그 캡처
리소스 파일
라이센스.txt
바이너리 리소스
예제/console_logging.py
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")예제/element_discovery.py
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()예제/static_html_automation.py
예제 다운로드/static_html_automation.py
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', '[email protected]')
# 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!")스크립트/with_server.py
#!/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()GitHub에서 보기
Mcp 빌더
Model Context Protocol 서버 구축, 도구 정의, Claude Skills가 신뢰할 수 있는 평가 제품군 작성을 위한 에이전트 기술 매뉴얼입니다.
문서 공동 작성
문서 공동 작성을 위한 구조화된 워크플로를 사용자에게 안내합니다. 사용자가 문서, 제안서, 기술 사양, 의사 결정 문서 또는 유사한 구조의 콘텐츠를 작성하려고 할 때 사용합니다. 이 워크플로는 사용자가 효율적으로 컨텍스트를 전달하고, 반복을 통해 콘텐츠를 구체화하고, 문서가 독자에게 적합한지 확인하는 데 도움이 됩니다. 사용자가 문서 작성, 제안서 작성, 사양 초안 작성 또는 유사한 문서 작업을 언급할 때 트리거됩니다.
클로데스킬스 문서