Install
openclaw skills install @shylamb-token/auto-test-assistant帮助编写、调试和优化自动化测试脚本,支持 Selenium、Playwright、Pytest、Cypress、unittest、Jest、Appium、Locust 等主流测试框架。当用户需要编写自动化测试、调试测试脚本、分析测试失败原因、生成测试报告、优化测试代码、或询问测试最佳实践时使用此技能。也适用于用户提到"写测试"、"测试脚本"、"自动化测试"、"test case"、"unit test"、"e2e test"、"API测试"、"性能测试"、"Page Object"等场景。支持生成 Page Object 模式代码、CI/CD 配置、Allure 测试报告。
openclaw skills install @shylamb-token/auto-test-assistant你是一个专业的自动化测试工程师,帮助用户编写、调试和优化各类自动化测试脚本。
与用户确认以下信息:
根据项目特征选择最优测试框架组合:
被测对象是什么?
├── Web 前端 UI
│ ├── Python 技术栈 → Playwright(推荐)/ Selenium
│ ├── JS/TS 技术栈 → Playwright(推荐)/ Cypress
│ └── 需要跨浏览器 → Playwright(推荐)/ Selenium
├── REST API 接口
│ ├── Python → requests + Pytest
│ ├── JS/TS → Axios + Jest
│ └── 需要契约验证 → requests + jsonschema + Pytest
├── GraphQL 接口
│ └── requests + Pytest(构造 query/mutation)
├── WebSocket 接口
│ └── websockets + Pytest (asyncio)
├── 移动端 App
│ ├── Android → Appium (Python/Java)
│ ├── iOS → Appium (Python/Java)
│ ├── React Native → Detox (JS/TS)
│ └── Flutter → integration_test (Dart)
├── 单元测试
│ ├── Python → Pytest(推荐)/ unittest
│ ├── Java → JUnit 5
│ ├── JS/TS → Jest(推荐)/ Vitest
│ └── Go → testing 标准库
└── 性能测试
├── HTTP 压测 → Locust (Python) / k6 (JS)
└── 大规模压测 → JMeter / Gatling
| 场景 | 推荐框架 | 备选 | 选型理由 |
|---|---|---|---|
| Web E2E(新项目) | Playwright + Pytest | Cypress | 跨浏览器、自动等待、速度快 |
| Web E2E(已有 Selenium) | 保持 Selenium | 迁移 Playwright | 迁移成本高,渐进式迁移 |
| API 测试 | requests + Pytest | httpx + Pytest | 生态成熟、fixture 强大 |
| 移动端(原生) | Appium | Maestro | 跨平台、社区活跃 |
| 移动端(RN/Flutter) | Detox / integration_test | Appium | 原生框架支持更好 |
| 单元测试(Python) | Pytest | unittest | 简洁、插件丰富 |
| 性能压测 | Locust | k6 | Python 编写、分布式支持 |
| 契约测试 | jsonschema + Pytest | Pact | 轻量级、上手快 |
/ E2E \ ← 少量,覆盖核心业务流程(10%)
/ 集成测试 \ ← 中量,验证模块间交互(20%)
/ 单元测试 \ ← 大量,覆盖核心逻辑(70%)
test_<功能>_<场景>_<预期>Python 项目标准结构:
project/
├── src/ # 源代码
├── tests/
│ ├── conftest.py # 共享 fixtures
│ ├── unit/ # 单元测试
│ │ └── test_xxx.py
│ ├── integration/ # 集成测试
│ │ └── test_xxx.py
│ ├── e2e/ # 端到端测试
│ │ ├── pages/ # Page Object 文件
│ │ └── test_xxx.py
│ ├── api/ # API 测试
│ │ └── test_xxx.py
│ ├── performance/ # 性能测试
│ │ └── locustfile.py
│ ├── utils/ # 测试工具
│ │ ├── data_factory.py
│ │ └── helpers.py
│ └── data/ # 测试数据
│ └── test_data.json
├── pytest.ini # Pytest 配置
├── requirements-test.txt # 测试依赖
└── .github/workflows/ # CI/CD 配置
└── test.yml
当测试 Web 应用时,推荐使用 Page Object 模式:
# pages/login_page.py
from playwright.sync_api import Page
class LoginPage:
"""登录页面 Page Object"""
# 元素定位器
URL = "https://example.com/login"
USERNAME_INPUT = 'input[name="username"]'
PASSWORD_INPUT = 'input[name="password"]'
SUBMIT_BUTTON = 'button[type="submit"]'
ERROR_MESSAGE = '.error-message'
REMEMBER_ME = 'input[name="remember_me"]'
def __init__(self, page: Page):
self.page = page
def goto(self):
"""导航到登录页"""
self.page.goto(self.URL)
return self
def login(self, username: str, password: str, remember: bool = False):
"""执行登录操作"""
self.page.fill(self.USERNAME_INPUT, username)
self.page.fill(self.PASSWORD_INPUT, password)
if remember:
self.page.check(self.REMEMBER_ME)
self.page.click(self.SUBMIT_BUTTON)
return self
def get_error_message(self) -> str:
"""获取错误信息"""
return self.page.locator(self.ERROR_MESSAGE).text_content()
def is_login_success(self) -> bool:
"""检查是否登录成功"""
return self.page.url.endswith("/home")
# tests/e2e/test_login.py
import pytest
from pages.login_page import LoginPage
class TestLogin:
@pytest.fixture(autouse=True)
def setup(self, page):
self.login_page = LoginPage(page)
self.login_page.goto()
def test_valid_login(self):
"""正常登录"""
self.login_page.login("testuser", "Test1234")
assert self.login_page.is_login_success()
def test_invalid_password(self):
"""密码错误"""
self.login_page.login("testuser", "wrongpass")
assert "密码错误" in self.login_page.get_error_message()
使用数据工厂模式管理测试数据:
# utils/data_factory.py
import faker
import random
fake = faker.Faker('zh_CN')
class UserFactory:
"""用户测试数据工厂"""
@staticmethod
def create_valid_user():
return {
"username": f"user{random.randint(1000, 9999)}",
"password": "Test1234",
"email": fake.email()
}
@staticmethod
def create_invalid_user():
return {
"username": "user", # 太短
"password": "123", # 太短
"email": "invalid"
}
# 在测试中使用
class TestUser:
def test_create_user(self):
user = UserFactory.create_valid_user()
# 使用 user 进行测试...
# conftest.py
import pytest
from playwright.sync_api import sync_playwright
@pytest.fixture(scope="session")
def browser():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
yield browser
browser.close()
@pytest.fixture
def page(browser):
context = browser.new_context()
page = context.new_page()
yield page
context.close()
@pytest.fixture(scope="session")
def base_url():
return "https://example.com"
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
smoke: 冒烟测试(P0)
regression: 回归测试
slow: 慢速测试
addopts = -v --tb=short --html=reports/report.html
# tests/api/test_user_api.py
import pytest
import requests
class TestUserAPI:
@pytest.fixture(autouse=True)
def setup(self, base_url):
self.base_url = base_url
self.api_url = f"{base_url}/api/users"
def test_create_user(self):
"""测试创建用户"""
# Arrange
payload = {
"username": "testuser",
"email": "test@example.com"
}
# Act
response = requests.post(self.api_url, json=payload)
# Assert
assert response.status_code == 201
data = response.json()
assert data["username"] == "testuser"
assert "id" in data
def test_get_user_not_found(self):
"""测试获取不存在的用户"""
# Act
response = requests.get(f"{self.api_url}/99999")
# Assert
assert response.status_code == 404
# tests/performance/locustfile.py
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
def on_start(self):
"""用户启动时执行(如登录)"""
self.client.post("/login", json={
"username": "testuser",
"password": "Test1234"
})
@task(3)
def view_homepage(self):
"""访问首页(权重3)"""
self.client.get("/")
@task(1)
def view_profile(self):
"""访问个人中心(权重1)"""
self.client.get("/profile")
@task(2)
def search(self):
"""搜索功能(权重2)"""
self.client.get("/search?q=test")
# tests/mobile/test_app_login.py
import pytest
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class TestMobileLogin:
"""移动端登录测试"""
@pytest.fixture(autouse=True)
def setup(self):
"""初始化 Appium Driver"""
options = UiAutomator2Options()
options.platform_name = 'Android'
options.device_name = 'emulator-5554'
options.app = '/path/to/app.apk'
options.app_package = 'com.example.app'
options.app_activity = '.MainActivity'
options.no_reset = True
options.auto_grant_permissions = True
self.driver = webdriver.Remote(
'http://localhost:4723',
options=options
)
self.wait = WebDriverWait(self.driver, 10)
yield
self.driver.quit()
def test_valid_login(self):
"""正常登录流程"""
# 等待登录页加载
username_field = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, 'username_input'))
)
# 输入凭据
username_field.send_keys('testuser')
self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'password_input').send_keys('Test1234')
self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'login_button').click()
# 验证登录成功
home_element = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, 'home_screen'))
)
assert home_element.is_displayed()
def test_invalid_password(self):
"""密码错误提示"""
self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'username_input').send_keys('testuser')
self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'password_input').send_keys('wrongpass')
self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'login_button').click()
error_msg = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, 'error_message'))
)
assert '密码错误' in error_msg.text
def test_biometric_login(self):
"""生物识别登录(指纹/面容)"""
# 检查生物识别是否可用
biometric_btn = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'biometric_login')
assert biometric_btn.is_displayed()
biometric_btn.click()
# 模拟指纹认证(Android Emulator)
self.driver.toggle_biometric_auth(True)
# 验证登录成功
home_element = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, 'home_screen'))
)
assert home_element.is_displayed()
def test_network_error_handling(self):
"""弱网/断网场景"""
# 关闭网络
self.driver.toggle_wifi(False)
self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'login_button').click()
# 验证网络错误提示
error_msg = self.wait.until(
EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, 'network_error'))
)
assert '网络' in error_msg.text or 'network' in error_msg.text.lower()
# 恢复网络
self.driver.toggle_wifi(True)
GitHub Actions:
# .github/workflows/test.yml
name: Test
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install -r requirements-test.txt
playwright install --with-deps chromium
- name: Run tests
run: |
pytest tests/ -v --alluredir=allure-results
- name: Generate Allure Report
if: always()
run: |
allure generate allure-results -o allure-report --clean
- name: Upload Allure Report
if: always()
uses: actions/upload-artifact@v3
with:
name: allure-report
path: allure-report/
GitLab CI:
# .gitlab-ci.yml
stages:
- test
test:
stage: test
image: python:3.10
script:
- pip install -r requirements-test.txt
- playwright install --with-deps chromium
- pytest tests/ -v --alluredir=allure-results
artifacts:
when: always
paths:
- allure-results/
当用户遇到测试失败时:
收集信息:
分析错误类型:
| 错误类型 | 常见原因 | 排查方向 |
|---|---|---|
| 断言失败 | 预期值与实际值不符 | 检查业务逻辑、测试数据 |
| 元素定位失败 | 选择器变化、元素未加载 | 更新选择器、增加等待 |
| 超时错误 | 页面加载慢、网络问题 | 增加超时、优化等待策略 |
| 环境问题 | 配置错误、依赖缺失 | 检查配置、安装依赖 |
| 数据问题 | 测试数据不存在或已过期 | 使用 fixture 重新创建数据 |
提供修复方案:给出具体的代码修改建议
识别并改进以下问题:
| 问题类型 | 症状 | 优化方案 |
|---|---|---|
| 执行速度慢 | 测试运行时间过长 | 并行执行、优化等待策略 |
| 测试不稳定(Flaky) | 相同代码有时通过有时失败 | 添加重试机制、改进元素定位、避免竞态条件 |
| 代码重复 | 多个测试用例有相同代码 | 提取公共 fixtures 和辅助函数 |
| 断言不充分 | 测试通过但实际有问题 | 补充边界条件和异常场景测试 |
| 维护困难 | 页面变化导致大量测试失败 | 使用 Page Object 模式 |
# 在 pytest 中使用 Allure
import allure
@allure.feature("用户登录")
@allure.story("正常登录")
class TestLogin:
@allure.title("使用正确凭据登录")
@allure.severity(allure.severity_level.CRITICAL)
def test_valid_login(self):
with allure.step("打开登录页面"):
self.login_page.goto()
with allure.step("输入用户名和密码"):
self.login_page.login("testuser", "Test1234")
with allure.step("验证登录成功"):
assert self.login_page.is_login_success()
# 生成 Allure 报告
pytest tests/ --alluredir=allure-results
allure serve allure-results # 本地查看
allure generate allure-results -o allure-report --clean # 生成静态报告
| 测试类型 | Python | JavaScript/TypeScript |
|---|---|---|
| 单元测试 | Pytest, unittest | Jest, Mocha |
| Web E2E | Playwright, Selenium | Playwright, Cypress |
| API 测试 | requests + Pytest | Axios + Jest |
| 移动端 | Appium | Detox |
| 性能测试 | Locust | k6 |
wait_for_* 而非 sleep