跳转到内容

Webapp Testing:Playwright 自动化测试

webapp-testing 使用 Playwright 对 Web 应用进行端到端测试——支持元素发现、自动化测试、和本地开发服务器交互。

  • 🧪 基于 Playwright 的 Web 自动化测试
  • 🔍 智能元素发现和页面分析
  • 🖥️ 本地开发服务器集成(with_server.py)
  • 📋 测试场景示例:静态 HTML、console 日志、元素发现

webapp-testing uses Playwright for end-to-end web application testing — supporting element discovery, automated tests, and local dev server interaction.

  • 🧪 Playwright-based web automation testing
  • 🔍 Intelligent element discovery and page analysis
  • 🖥️ Local dev server integration (with_server.py)
  • 📋 Test scenario examples: static HTML, console logging, element discovery

webapp-testing 是一个轻量级脚本+示例驱动型 Skill,围绕 1 个核心脚本 + 3 个示例文件组织。

其结构体现了”小型化” Skill 的典型模式——一个入口脚本提供基础设施,几个示例展示使用模式,SKILL.md 通过决策树指导 Claude 选择正确方法。

webapp-testing is a lightweight script + example-driven Skill, organized around 1 core script + 3 example files.

Its structure embodies the typical “small-scale” Skill pattern — one entry script provides infrastructure, several examples demonstrate usage patterns, and SKILL.md uses a decision tree to guide Claude in choosing the right approach.

  • webapp-testing
    • SKILL.md 主入口 · ~90 行
    • LICENSE.txt Apache 2.0
    • scripts Python 脚本 · 1 个文件
      • with_server.py ~105 行 · ⭐⭐⭐ · 服务器生命周期管理器
    • examples 使用示例 · 3 个文件
      • console_logging.py 捕获浏览器控制台日志
      • static_html_automation.py 静态 HTML 文件自动化
      • element_discovery.py 页面元素发现

约 90 行的 SKILL.md 最突出的设计是决策树模式侦察-行动工作流

SKILL.md 的导航入口是一个二分决策树:

用户任务 → 是否静态 HTML?
├─ 是 → 直接读取 HTML → 编写 Playwright 脚本
└─ 否(动态 Web 应用)→ 服务是否在运行?
├─ 否 → 使用 with_server.py 启动
└─ 是 → 侦察-行动流程

这种设计让 Claude 能够根据用户场景自动选择合适的路径,无需用户指定”使用哪个工具”。

对于动态 Web 应用,SKILL.md 定义了 4 步侦察-行动流程:

  1. 导航:访问页面并等待 networkidle
  2. 截图:截取页面当前状态或检查 DOM
  3. 选择器发现:从渲染状态中识别选择器
  4. 执行:使用发现的选择器执行操作

with_server.py 管理服务器生命周期,Playwright 脚本处理浏览器自动化:

The ~90 line SKILL.md’s most prominent design features are the decision tree pattern and reconnaissance-action workflow:

The navigation entry in SKILL.md is a binary decision tree:

User task → Is it static HTML?
├─ Yes → Read HTML directly → Write Playwright script
└─ No (dynamic webapp) → Is the server already running?
├─ No → Use with_server.py
└─ Yes → Reconnaissance-action flow

This design allows Claude to automatically choose the correct path based on the user’s scenario, without requiring the user to specify “which tool to use”.

For dynamic web apps, SKILL.md defines a 4-step reconnaissance-action flow:

  1. Navigate: Load page and wait for networkidle
  2. Screenshot: Capture current page state or inspect DOM
  3. Selector Discovery: Identify selectors from rendered state
  4. Execute: Perform actions using discovered selectors

with_server.py manages the server lifecycle, while Playwright scripts handle browser automation:

webapp-testing 模块关系图

graph TD
  SKILL[SKILL.md] -->|决策树| Claude
  Claude -->|管理服务器| with_server[scripts/with_server.py]
  Claude -->|编写| Playwright[Playwright 自动化脚本]
  with_server -->|启动/停止| Server1[Server 1 :port]
  with_server -->|启动/停止| Server2[Server N :port]
  Playwright -->|浏览器自动化| Browser[Chromium Headless]

  subgraph examples [examples/]
      CL[console_logging.py]
      SA[static_html_automation.py]
      ED[element_discovery.py]
  end

  Claude -->|参考| examples
  SKILL -->|指导| Playwright

  style SKILL fill:#4fc3f7,stroke:#0288d1,color:#000
  style with_server fill:#81c784,stroke:#388e3c,color:#000
  style Playwright fill:#ffb74d,stroke:#f57c00,color:#000

| 脚本 | 语言 | 行数 | 复杂度 | 功能 | |------|------|------|--------|------| | with_server.py | Python | ~105 | ⭐⭐⭐ | 服务器生命周期管理器:启动、等待就绪、运行命令、清理 |


with_server.py — 服务器生命周期管理器

Section titled “with_server.py — 服务器生命周期管理器”

with_server.py 是 webapp-testing 的基础设施核心。它负责管理一个或多个本地开发服务器的生命周期——启动服务器、等待端口就绪、运行测试命令、最后清理所有子进程。

with_server.py is the infrastructure core of webapp-testing. It manages the lifecycle of one or more local dev servers — starting servers, waiting for ports to be ready, running test commands, and finally cleaning up all child processes.

with_server.py — 服务器生命周期管理器 ↗ 源文件
1 #!/usr/bin/env python3 2 """ 3 Start one or more servers, wait for them to be ready, 4 run a command, then clean up. 5 """ 6 7 import subprocess 8 import socket 9 import time 10 import sys 11 import argparse 12 13 14 def is_server_ready(port, timeout=30): 15 """Wait for server to be ready by polling the port.""" 16 start_time = time.time() 17 while time.time() - start_time < timeout: 18 try: 19 with socket.create_connection(('localhost', port), timeout=1): 20 return True 21 except (socket.error, ConnectionRefusedError): 22 time.sleep(0.5) 23 return False 24 25 26 def main(): 27 parser = argparse.ArgumentParser( 28 description='Run command with one or more servers') 29 parser.add_argument('--server', action='append', dest='servers', 30 required=True, help='Server command (can be repeated)') 31 parser.add_argument('--port', action='append', dest='ports', 32 type=int, required=True, help='Port for each server') 33 parser.add_argument('--timeout', type=int, default=30, 34 help='Timeout in seconds per server (default: 30)') 35 parser.add_argument('command', nargs=argparse.REMAINDER, 36 help='Command to run after server(s) ready') 37 38 args = parser.parse_args() 39 40 # Remove the '--' separator if present 41 if args.command and args.command[0] == '--': 42 args.command = args.command[1:] 43 44 if not args.command: 45 print("Error: No command specified to run") 46 sys.exit(1) 47 48 # Parse server configurations 49 if len(args.servers) != len(args.ports): 50 print("Error: Number of --server and --port arguments must match") 51 sys.exit(1) 52 53 servers = [] 54 for cmd, port in zip(args.servers, args.ports): 55 servers.append({'cmd': cmd, 'port': port}) 56 57 server_processes = [] 58 59 try: 60 # Start all servers 61 for i, server in enumerate(servers): 62 print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}") 63 64 # Use shell=True to support commands with cd and && 65 process = subprocess.Popen( 66 server['cmd'], 67 shell=True, 68 stdout=subprocess.PIPE, 69 stderr=subprocess.PIPE 70 ) 71 server_processes.append(process) 72 73 # Wait for this server to be ready 74 print(f"Waiting for server on port {server['port']}...") 75 if not is_server_ready(server['port'], timeout=args.timeout): 76 raise RuntimeError( 77 f"Server failed to start on port {server['port']}") 78 79 print(f"Server ready on port {server['port']}") 80 81 print(f" 82 All {len(servers)} server(s) ready") 83 84 # Run the command 85 print(f"Running: {' '.join(args.command)} 86 ") 87 result = subprocess.run(args.command) 88 sys.exit(result.returncode) 89 90 finally: 91 # Clean up all servers 92 print(f" 93 Stopping {len(server_processes)} server(s)...") 94 for i, process in enumerate(server_processes): 95 try: 96 process.terminate() 97 process.wait(timeout=5) 98 except subprocess.TimeoutExpired: 99 process.kill() 100 process.wait() 101 print(f"Server {i+1} stopped") 102 print("All servers stopped") 103 104 105 if __name__ == '__main__': 106 main()
代码解读
L3 文档字符串清晰描述了三步流程:启动服务器 → 等待就绪 → 运行命令 → 清理。这是优秀的 CLI 工具设计。"one or more" 暗示了多服务器支持。 L12 is_server_ready() 通过 socket 轮询检测端口是否开放。使用 time.time() + while 循环实现超时控制——简单可靠,不依赖外部依赖。 L14 socket.create_connection() 尝试连接 localhost:port。成功 = 服务器就绪,ConnectionRefusedError = 服务器尚未启动。time.sleep(0.5) 避免忙等待(busy waiting)。 L23 argparse.REMAINDER 捕获 -- 之后的所有参数,这样用户可以传递任意命令给测试脚本。 L26 action="append" 允许 --server 和 --port 重复出现。这是设计亮点:一个 --server/--port 对 = 一个后端服务,多个对 = 多服务栈。 L44 try/finally 是子进程生命周期管理的关键模式——无论测试命令成功还是失败,finally 块确保所有服务器都被停止。 L51 subprocess.Popen 启动服务器进程。shell=True 是关键——允许传递 "cd backend && python server.py" 这样的复合命令。 L61 启动每个服务器后立即轮询其端口,而不是全部启动后再检查。这可以快速失败——如果第一个服务器启动失败,用户不需要等待所有服务器。 L67 subprocess.run() 执行测试命令。使用 sys.exit(result.returncode) 传播测试命令的退出码,使脚本可以集成到 CI 管道中。 L73 finally 块的清理逻辑:先尝试优雅停止(terminate),超时后强制杀死(kill)。5 秒超时是合理的默认值——给进程足够时间做清理,但不会无限等待。

examples/ 目录包含 3 个 Playwright 示例文件,展示不同的使用模式:

  • console_logging.py:捕获浏览器控制台日志(console.log、错误、警告),用于调试前端应用
  • static_html_automation.py:使用 file:// URL 直接加载本地 HTML 文件,适用于无需服务器的静态页面测试
  • element_discovery.py:浏览页面并发现按钮、链接、输入框等可交互元素,适用于页面结构不明确时的侦察阶段

这些示例可作为 Claude 编写新测试脚本的模板。SKILL.md 建议 Claude 先尝试将这些脚本作为黑盒调用,必要时再阅读源码。

The examples/ directory contains 3 Playwright example files demonstrating different usage patterns:

  • console_logging.py: Captures browser console logs (console.log, errors, warnings) for debugging frontend applications
  • static_html_automation.py: Uses file:// URLs to directly load local HTML files, suitable for static page testing without a server
  • element_discovery.py: Browses pages and discovers interactive elements like buttons, links, and inputs — useful for the reconnaissance phase when page structure is unclear

These examples serve as templates for Claude when writing new test scripts. SKILL.md recommends that Claude first try calling these scripts as black boxes, only reading the source when necessary.

  1. 决策树引导流程:SKILL.md 中的二元决策树让 Claude 能自动选择静态 HTML 或动态 Web 应用的测试路径,无需用户指定
  2. 侦察-行动工作流:对动态 Web 应用先侦察再行动——先截图/检查 DOM 发现选择器,再执行操作。这模拟了人类测试者的”先观察再操作”模式
  3. 服务器生命周期管理:with_server.py 确保子进程在测试完成后总是被清理(try/finally + terminate/kill 双保险)
  4. 多服务器编排:通过 —server/—port 重复参数支持复杂的前后端架构(如后端 API + 前端 SPA)

“如果你想为其他领域创建测试工具…”

  1. 保留决策树:在你的 SKILL.md 中定义场景分支逻辑
  2. 保留 with_server.py:这是最通用的模式——启动服务、等待、测试、清理——适用于任何 Web 测试
  3. 替换 Playwright:将 Playwright 脚本替换为你的测试框架(Cypress、Selenium、Puppeteer)
  4. 添加使用示例:参照 examples/ 目录,为每个常见场景提供一个示例脚本
  5. 更新侦察模式:将 Playwright 特定的 DOM 检查替换为你的框架的等价操作

⚠️ 不要在使用 with_server.py 前省略 —help: SKILL.md 明确要求先运行 --help——脚本的参数(—server、—port、—timeout)有不同的组合方式

⚠️ networkidle 是必需的: 在动态 Web 应用上,必须在检查 DOM 前等待 networkidle——否则 DOM 可能尚未渲染完成

⚠️ shell=True 的安全风险: with_server.py 使用 shell=True 支持复合命令——如果 server 命令来自用户输入,存在命令注入风险

⚠️ 不要在自动化中重读源码: SKILL.md 提示 Claude 不要读取 with_server.py 源码——将其作为黑盒调用,避免浪费上下文窗口

  1. Decision Tree Guidance: The binary decision tree in SKILL.md lets Claude automatically choose between static HTML and dynamic webapp test paths without user specification
  2. Reconnaissance-Action Workflow: For dynamic webapps, recon first then act — screenshot/inspect DOM to discover selectors, then execute operations. This mimics human testers’ “observe then operate” pattern
  3. Server Lifecycle Management: with_server.py ensures child processes are always cleaned up after test completion (try/finally + terminate/kill double safety)
  4. Multi-server Orchestration: Supports complex frontend+backend architectures through repeatable —server/—port parameters

“If you want to create testing tools for other domains…”

  1. Keep Decision Tree: Define scenario branching logic in your SKILL.md
  2. Keep with_server.py: The most universal pattern — start, wait, test, cleanup — applicable to any web testing
  3. Replace Playwright: Swap Playwright scripts for your testing framework (Cypress, Selenium, Puppeteer)
  4. Add Usage Examples: Following the examples/ directory pattern, provide one example script per common scenario
  5. Update Recon Pattern: Replace Playwright-specific DOM inspection with your framework’s equivalent

⚠️ Don’t skip —help before using with_server.py: SKILL.md explicitly requires running —help first — the script’s arguments (—server, —port, —timeout) have different combinations

⚠️ networkidle is required: On dynamic webapps, must wait for networkidle before inspecting DOM — otherwise DOM may not be fully rendered

⚠️ shell=True security risk: with_server.py uses shell=True for compound commands — if server commands come from user input, there’s command injection risk

⚠️ Don’t re-read source in automation: SKILL.md advises Claude not to read with_server.py source — treat it as a black box to avoid wasting context window

模式说明适用于...
决策树引导SKILL.md 中的二分决策树自动路由到正确路径任何有分支场景的 skill
服务器生命周期管理器启动 → 等待就绪 → 执行 → 清理需要临时服务器的任何测试工具
侦察-行动先通过截图/DOM 检查发现选择器,再执行操作动态页面自动化测试
Socket 端口轮询不依赖 HTTP 请求检查端口任何需等待服务就绪的场景
PatternDescriptionApplies to...
Decision Tree GuidanceBinary decision tree in SKILL.md auto-routes to correct pathAny skill with branching scenarios
Server Lifecycle ManagerStart → wait for ready → execute → cleanupAny testing tool needing temporary servers
Reconnaissance-ActionDiscover selectors via screenshot/DOM inspection first, then actDynamic page automation testing
Socket Port PollingCheck ports without HTTP request dependencyAny scenario needing to wait for service readiness