Skip to content

7.9 Webapp Testing Skill - Web 应用测试工具包

概述

Webapp Testing Skill 提供了一套使用 Playwright 与本地 Web 应用交互和测试的工具包。它支持验证前端功能、调试 UI 行为、捕获浏览器截图以及查看浏览器日志。

核心功能

  • 验证前端功能
  • 调试 UI 行为
  • 捕获浏览器截图
  • 查看浏览器控制台日志
  • 自动化测试流程

技术基础

这个 Skill 基于 Playwright——一个现代化的浏览器自动化库,支持:

  • Chromium、Firefox、WebKit
  • 同步和异步 API
  • 自动等待机制
  • 截图和视频录制

决策树:选择方法

用户任务 --> 是静态 HTML 吗?
    |
    +-- 是 --> 直接读取 HTML 文件识别选择器
    |          |
    |          +-- 成功 --> 使用选择器编写 Playwright 脚本
    |          |
    |          +-- 失败/不完整 --> 按动态应用处理(见下)
    |
    +-- 否(动态 webapp)--> 服务器已运行吗?
              |
              +-- 否 --> 运行: python scripts/with_server.py --help
              |          然后使用辅助脚本 + 编写简化的 Playwright 脚本
              |
              +-- 是 --> 侦察-然后-行动模式:
                         1. 导航并等待 networkidle
                         2. 截图或检查 DOM
                         3. 从渲染状态识别选择器
                         4. 使用发现的选择器执行操作

辅助脚本

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

这个脚本自动管理服务器的启动、就绪检测和清理工作。

重要提示:始终先运行 --help 查看用法,不要先阅读源代码。这些脚本作为黑盒工具使用,避免污染上下文窗口。

单服务器模式

bash
python scripts/with_server.py \
  --server "npm run dev" \
  --port 5173 \
  -- python your_automation.py

多服务器模式

当需要同时运行后端和前端时:

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

脚本工作原理

+---------------+     +----------------+     +---------------+     +---------------+
|   启动服务器  | --> |  等待端口就绪  | --> |  运行自动化   | --> |   清理服务器  |
+---------------+     +----------------+     +---------------+     +---------------+
| subprocess    |     | socket 轮询    |     | subprocess    |     | terminate()   |
| Popen()       |     | 超时 30s       |     | run()         |     | wait()        |
+---------------+     +----------------+     +---------------+     +---------------+

核心模式:侦察-然后-行动

对于动态 Web 应用,采用"侦察-然后-行动"模式:

Step 1: 检查渲染后的 DOM

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()

    page.goto('http://localhost:5173')
    page.wait_for_load_state('networkidle')  # 关键:等待 JS 执行完毕

    # 方法 1:截图查看
    page.screenshot(path='/tmp/inspect.png', full_page=True)

    # 方法 2:获取页面内容
    content = page.content()

    # 方法 3:查找特定元素
    buttons = page.locator('button').all()

    browser.close()

Step 2: 从检查结果识别选择器

分析截图或 DOM 内容,确定:

  • 按钮的文本或类名
  • 输入框的 name 或 id
  • 链接的 href 或文本

Step 3: 使用发现的选择器执行操作

python
page.click('text=Submit')
page.fill('#username', 'testuser')
page.select_option('select#country', 'China')

常用选择器

Playwright 支持多种选择器语法:

选择器类型语法示例
文本选择器text=page.click('text=登录')
CSS 选择器CSSpage.click('.btn-primary')
ID 选择器#idpage.fill('#email', 'test@example.com')
角色选择器role=page.click('role=button[name="提交"]')
Placeholderplaceholder=page.fill('placeholder=请输入密码', '123456')

示例代码

示例 1:元素发现

发现页面上的按钮、链接和输入框:

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()

    # 导航并等待页面完全加载
    page.goto('http://localhost:5173')
    page.wait_for_load_state('networkidle')

    # 发现所有按钮
    buttons = page.locator('button').all()
    print(f"找到 {len(buttons)} 个按钮:")
    for i, button in enumerate(buttons):
        text = button.inner_text() if button.is_visible() else "[隐藏]"
        print(f"  [{i}] {text}")

    # 发现链接
    links = page.locator('a[href]').all()
    print(f"\n找到 {len(links)} 个链接:")
    for link in links[:5]:  # 显示前 5 个
        text = link.inner_text().strip()
        href = link.get_attribute('href')
        print(f"  - {text} -> {href}")

    # 发现输入框
    inputs = page.locator('input, textarea, select').all()
    print(f"\n找到 {len(inputs)} 个输入框:")
    for input_elem in inputs:
        name = input_elem.get_attribute('name') or \
               input_elem.get_attribute('id') or "[未命名]"
        input_type = input_elem.get_attribute('type') or 'text'
        print(f"  - {name} ({input_type})")

    # 截图保存
    page.screenshot(path='/tmp/page_discovery.png', full_page=True)
    print("\n截图已保存到 /tmp/page_discovery.png")

    browser.close()

示例 2:控制台日志捕获

捕获浏览器自动化过程中的控制台日志:

python
from playwright.sync_api import sync_playwright

url = 'http://localhost:5173'
console_logs = []

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page(viewport={'width': 1920, 'height': 1080})

    # 设置控制台日志捕获
    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)

    # 导航到页面
    page.goto(url)
    page.wait_for_load_state('networkidle')

    # 与页面交互(触发控制台日志)
    page.click('text=Dashboard')
    page.wait_for_timeout(1000)

    browser.close()

# 保存控制台日志到文件
with open('/tmp/console.log', 'w') as f:
    f.write('\n'.join(console_logs))

print(f"\n捕获了 {len(console_logs)} 条控制台消息")

示例 3:静态 HTML 自动化

使用 file:// URL 自动化本地 HTML 文件:

python
from playwright.sync_api import sync_playwright
import os

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})

    # 导航到本地 HTML 文件
    page.goto(file_url)

    # 截图
    page.screenshot(path='/tmp/static_page.png', full_page=True)

    # 与元素交互
    page.click('text=Click Me')
    page.fill('#name', 'John Doe')
    page.fill('#email', 'john@example.com')

    # 提交表单
    page.click('button[type="submit"]')
    page.wait_for_timeout(500)

    # 最终截图
    page.screenshot(path='/tmp/after_submit.png', full_page=True)

    browser.close()

print("静态 HTML 自动化完成!")

常见陷阱与最佳实践

常见陷阱

错误做法正确做法
在动态应用上检查 DOM 前不等待先等待 networkidle 再检查
使用硬编码延迟使用智能等待 wait_for_selector()
不关闭浏览器始终在完成后 browser.close()

最佳实践

  1. 使用打包脚本作为黑盒

    • 先运行 --help 查看用法
    • 直接调用,不读取源码
    • 避免污染上下文窗口
  2. 使用同步 API

    python
    # 推荐
    from playwright.sync_api import sync_playwright
    with sync_playwright() as p:
        # ...
  3. 始终关闭浏览器

    python
    try:
        # 自动化逻辑
    finally:
        browser.close()
  4. 使用描述性选择器

    python
    # 好
    page.click('text=提交订单')
    page.fill('input[name="email"]', 'test@example.com')
    
    # 避免
    page.click('div > div > button:nth-child(3)')
  5. 添加适当的等待

    python
    page.wait_for_load_state('networkidle')
    page.wait_for_selector('#dynamic-content')
    page.wait_for_timeout(500)  # 仅在必要时使用

完整工作流示例

测试一个带后端的 React 应用:

bash
# Step 1: 准备自动化脚本
cat > test_app.py << 'EOF'
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()

    # 访问前端
    page.goto('http://localhost:5173')
    page.wait_for_load_state('networkidle')

    # 登录测试
    page.fill('#username', 'testuser')
    page.fill('#password', 'password123')
    page.click('text=登录')

    # 等待登录完成
    page.wait_for_selector('.dashboard')

    # 截图验证
    page.screenshot(path='/tmp/dashboard.png')
    print("登录成功,仪表板截图已保存")

    browser.close()
EOF

# Step 2: 使用 with_server.py 运行测试
python scripts/with_server.py \
  --server "cd backend && python server.py" --port 3000 \
  --server "cd frontend && npm run dev" --port 5173 \
  -- python test_app.py

总结

Webapp Testing Skill 提供了一套完整的 Web 应用测试工具链:

核心工具

  • Playwright Python API
  • with_server.py 服务器管理脚本

核心模式

  • 侦察-然后-行动(Reconnaissance-then-action)
  • 等待 networkidle 后再操作

关键原则

  1. 始终先等待页面完全加载
  2. 使用描述性选择器
  3. 打包脚本作为黑盒使用
  4. 始终关闭浏览器

参考示例

  • element_discovery.py - 元素发现
  • static_html_automation.py - 静态 HTML 自动化
  • console_logging.py - 控制台日志捕获

这个 Skill 是 Web Artifacts Builder 的完美补充——先用 Web Artifacts Builder 创建应用,再用 Webapp Testing 验证功能。

基于 MIT 许可证发布。内容版权归作者所有。