aboutsummaryrefslogtreecommitdiff
path: root/playwright-py/scripts/with_server.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-19 15:24:51 -0500
committerCraig Jennings <c@cjennings.net>2026-04-19 15:24:51 -0500
commit4ffa7417a359ef4eae09f61d7da4de06539462ca (patch)
treeb8eeb8aa5ec2344216c0f0cdcdcc82d0df307ce3 /playwright-py/scripts/with_server.py
parent11f5f003eef12bff9633ca8190e3c43c7dab6708 (diff)
downloadrulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.tar.gz
rulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.zip
refactor(playwright): split into playwright-js + playwright-py variants
Rename `playwright-skill/` → `playwright-js/` and add `playwright-py/` as a verbatim fork of Anthropic's official `webapp-testing` skill (Apache-2.0). Cross-pollinate: each skill gains patterns and helpers inspired by the other's strengths, with upstream semantics preserved. ## playwright-js (JS/TS stack) Renamed from playwright-skill; upstream lackeyjb MIT content untouched. New sections added (clearly marked, preserving upstream semantics): - Static HTML vs Dynamic Webapp decision tree (core Anthropic methodology) - Reconnaissance-Then-Action pattern (navigate → networkidle → inspect → act) - Console Log Capture snippet (page.on console/pageerror/requestfailed) Description updated to clarify JS/TS stack fit (React/Next/Vue/Svelte/Node) and reference `/playwright-py` as the Python sibling. ## playwright-py (Python stack) Verbatim fork of anthropics/skills/skills/webapp-testing; upstream SKILL.md and bundled `scripts/with_server.py` + examples kept intact. New scripts and examples added (all lackeyjb-style conveniences in Python): Scripts: scripts/detect_dev_servers.py Probe common localhost ports for HTTP servers; outputs JSON of found services. scripts/safe_actions.py safe_click, safe_type (retry-wrapped), handle_cookie_banner (common selectors), build_context_with_headers (env-var- driven: PW_HEADER_NAME / PW_HEADER_VALUE / PW_EXTRA_HEADERS='{…json…}'). Examples: examples/login_flow.py Login form + wait_for_url. examples/broken_links.py Scan visible external hrefs via HEAD. examples/responsive_sweep.py Multi-viewport screenshots to /tmp. SKILL.md gains 5 "Added:" sections documenting the new scripts, retry helpers, env-header injection, and /tmp script discipline. Attribution notes explicitly mark upstream vs local additions. ## Makefile SKILLS: playwright-skill → playwright-js + playwright-py deps target: extended Playwright step to install Python package + Chromium via `python3 -m pip install --user playwright && python3 -m playwright install chromium` when playwright-py/ is present. Idempotent (detected via `python3 -c "import playwright"`). ## Usage Both skills symlinked globally via `make install`. Invoke whichever matches the project stack — cross-references in descriptions route you to the right one. Run `make deps` once to install both runtimes.
Diffstat (limited to 'playwright-py/scripts/with_server.py')
-rwxr-xr-xplaywright-py/scripts/with_server.py106
1 files changed, 106 insertions, 0 deletions
diff --git a/playwright-py/scripts/with_server.py b/playwright-py/scripts/with_server.py
new file mode 100755
index 0000000..431f2eb
--- /dev/null
+++ b/playwright-py/scripts/with_server.py
@@ -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() \ No newline at end of file