원클릭으로
testany-sync-case-env-from-source
从 OpenAPI 生成的 Testany case 源代码中自动识别入参和出参,并同步到 case 的环境变量列表中。适用于源代码存储在需要认证的 Git 仓库(如 Bitbucket)的场景。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
从 OpenAPI 生成的 Testany case 源代码中自动识别入参和出参,并同步到 case 的环境变量列表中。适用于源代码存储在需要认证的 Git 仓库(如 Bitbucket)的场景。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Testany platform case 编写助手 - 将传统测试场景拆解为 Testany platform cases,并生成可注册的 case packages
Testany 平台核心概念和配置参考
Testany pipeline 编排与 CRUD - 基于 automation design 或现有 case keys 创建执行与编排单元
Testany platform case 注册与 CRUD - 将已准备好的 platform case package 注册到平台,并管理 metadata、脚本与生命周期
分析 Testany 测试失败原因 - 排查问题、查看日志、定位根因
Testany Git 导入:把 Git 仓库里的测试脚本批量注册成 Testany platform cases,并持续同步、切 commit、演化 binding、配 webhook
| name | testany-sync-case-env-from-source |
| description | 从 OpenAPI 生成的 Testany case 源代码中自动识别入参和出参,并同步到 case 的环境变量列表中。适用于源代码存储在需要认证的 Git 仓库(如 Bitbucket)的场景。 |
本 Skill 用于从 OpenAPI definition 生成的 Testany case 源代码中自动提取入参和出参,并将它们同步更新到 Testany case 的 environment_variables 配置中。
当 Testany case 的源代码满足以下条件时使用本 Skill:
BASE_STRUCTURES_INFO 和 relay_request_fields 等特征结构用户首先选择操作模式:
| 模式 | 说明 |
|---|---|
| 单 Case 操作 | 直接输入一个 case key |
| 批量 Keys 操作 | 输入多个 case key(逗号分隔) |
| 搜索批量操作 | 通过条件搜索 case,对结果进行批量操作 |
使用 AskUserQuestion 工具让用户选择模式。
根据用户选择的模式,获取待处理的 case 列表:
case_keys = [user_input_case_key]
# 解析逗号分隔的 keys
case_keys = user_input_keys.split(',')
case_keys = [key.strip() for key in case_keys if key.strip()]
# 使用 AskUserQuestion 询问搜索条件
# 可选搜索条件:
# - key_or_name: case key 或名称关键词
# - workspaces: 工作空间列表
# - case_labels: 标签列表
# - runtime_uuid: 运行时 UUID
# 调用 testany_list_cases 获取匹配的 case 列表
cases = testany_list_cases(
key_or_name=search_keyword,
workspaces=selected_workspaces,
case_labels=selected_labels,
page_size=100 # 限制单页结果数量
)
case_keys = [case['key'] for case in cases]
搜索参数说明:
key_or_name: 模糊匹配 case key 或名称workspaces: 筛选指定工作空间的 case(需要先调用 testany_get_my_workspaces 获取可用工作空间列表)case_labels: 筛选包含指定标签的 case(需要先调用 testany_list_labels 获取可用标签列表)page_size: 单页返回数量,默认 20,批量操作建议设为 100遍历 case_keys,对每个 case 执行以下子流程:
case = testany_get_case(key=case_key)
if case is None or case.get('error'):
记录错误:Case 不存在
跳过此 case
continue
# 读取并隔离已有的 type=secrets 行
# 这类行是用户在 case 上直接维护的凭证绑定,不从源代码派生;
# sync 流程重建 environment_variables 时必须原样保留它们,避免静默丢失绑定。
existing_env_vars = (case.get('case_meta') or {}).get('environment_variables') or []
preserved_secrets = [row for row in existing_env_vars if row.get('type') == 'secrets']
preserved_secret_names = {row['name'] for row in preserved_secrets}
从 case['script_url'] 中提取:
URL 格式:
https://bitbucket.org/{owner}/{repo}/src/{branch}/{file_path}
显示解析结果供用户确认:
使用 Git Worktree(默认方案):
# 统一工作树目录
WORKTREE_BASE="/tmp/testany-worktrees"
# 首次访问某个分支时,创建工作树(如果不存在)
BRANCH_WORKTREE="${WORKTREE_BASE}/{branch_name}"
if [ ! -d "${BRANCH_WORKTREE}" ]; then
git fetch origin {branch_name}
git worktree add "${BRANCH_WORKTREE}" origin/{branch_name}
fi
# 从工作树中读取文件
FILE_PATH="${BRANCH_WORKTREE}/{file_path}"
优势:
从源代码的 BASE_STRUCTURES_INFO["structure"] 部分查找所有 ENV[变量名] 格式的占位符:
import re
env_pattern = r'ENV\[([^\]]+)\]'
input_params = set()
for section in ['path', 'query', 'header', 'body']:
if section in structure:
text = json.dumps(structure[section])
matches = re.findall(env_pattern, text)
input_params.update(matches)
# 跳过与已有 secret 绑定同名的变量:
# 这些名字已经由用户配置的 secret 提供,代码中直接读同名环境变量即可,
# sync 不应该再派生一条 type=env 把它覆盖掉。
input_collisions = input_params & preserved_secret_names
if input_collisions:
记录警告:f"以下变量与已有 secret 绑定同名,保留 secret,跳过 source 派生:{sorted(input_collisions)}"
input_params -= input_collisions
入参规范:
env(表示输入参数)从源代码的 relay_request_fields 数组中提取所有字段:
relay_request_fields = [
"NAME",
"DESCRIPTION",
"RUNTIME_UUID",
# ...
]
出参规范:
output(表示 relay 输出参数)与已有 secrets 的冲突处理:
如果 relay_request_fields 中某个字段与已有 secret 绑定同名,保留 secret 绑定,跳过该字段的 output 生成,并记录警告:output 字段 {NAME} 与已有 secret 绑定同名,跳过 output 派生。
(同一 case 内 environment_variables 的 name 必须唯一,不能既是 secrets 又是 output。)
从同一分支的 env 目录中读取环境变量配置文件,并将匹配的入参 value 从 "PLACEHOLDER" 替换为实际值。
环境变量文件路径规则:
| 元素 | 示例 |
|---|---|
| Python 文件路径 | testany-scripts/get_case_importHistory_id_payload/positive/test_get_case_importHistory_id_payload_positive.py |
| 提取的目录名 | get_case_importHistory_id_payload(testany-scripts/ 后的第一个目录) |
| 环境变量文件路径 | integration-test/testany-scripts/env/{目录名}_environment.txt |
| 完整路径 | integration-test/testany-scripts/env/get_case_importHistory_id_payload_environment.txt |
实现逻辑:
# 步骤 1:从 Python 文件路径中提取目录名
import re
from pathlib import Path
python_path = "testany-scripts/get_case_importHistory_id_payload/positive/xxx.py"
# 提取 testany-scripts/ 后的第一个目录名
match = re.match(r'testany-scripts/([^/]+)/', python_path)
if match:
directory_name = match.group(1) # get_case_importHistory_id_payload
else:
directory_name = None
# 步骤 2:构建环境变量文件路径
if directory_name:
env_file_path = f"integration-test/testany-scripts/env/{directory_name}_environment.txt"
# 在 worktree 中的完整路径
full_env_path = f"{BRANCH_WORKTREE}/{env_file_path}"
else:
full_env_path = None
# 步骤 3:读取并解析环境变量文件
env_values = {}
if full_env_path and Path(full_env_path).exists():
with open(full_env_path, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line and not line.startswith('#'):
key, value = line.split('=', 1)
env_values[key.strip()] = value.strip()
# 步骤 4:替换匹配的入参的 value
for param in input_params:
if param in env_values:
# 将 PLACEHOLDER 替换为实际值
param_value = env_values[param]
environment_variables.append({
"type": "env",
"name": param,
"value": param_value,
"description": f"Input parameter: {param}"
})
else:
# 文件中未找到该参数,保留 PLACEHOLDER
environment_variables.append({
"type": "env",
"name": param,
"value": "PLACEHOLDER",
"description": f"Input parameter: {param}"
})
环境变量文件格式:
# 示例:get_case_importHistory_id_payload_environment.txt
ENV_QUERY_KEY_1_00=IXX
ENV_PATH_ID_1_00=12345
ENV_HEADER_AUTH_TOKEN=abc123def456
注意:
KEY=VALUE,每行一个变量# 开头的注释行(忽略)value 保留 "PLACEHOLDER"value 保留 "PLACEHOLDER"显示将要更新的 environment_variables 数组:
入参(type=env):
- ENV_PATH_ID_1_00: Path parameter for import history id
出参(type=output):
- NAME: Output field from response: NAME
- DESCRIPTION: Output field from response: DESCRIPTION
...
如果 preserved_secrets 非空,额外展示保护提示(在入参/出参列表之前):
⚠️ 检测到 N 个 type=secrets 行,将原样保留(本次 sync 不会修改):
- DB_PASSWORD
- API_TOKEN
...
如果 input_collisions 或 output 冲突非空,同样展示跳过提示:
⚠️ 以下 source 变量与已有 secret 绑定同名,已跳过派生:
- DB_PASSWORD (source 中作为 ENV[...] 出现,保留 secret 绑定)
使用 AskUserQuestion 确认是否执行更新:
# 注意:environment_variables 已经在子流程 3.6 中填充了入参
# 这里只需要添加出参(出参不替换 PLACEHOLDER)
# 添加出参
for field in relay_request_fields:
environment_variables.append({
"type": "output",
"name": field,
"value": "PLACEHOLDER",
"description": f"Output field from response: {field}"
})
# 合并:从 source 派生的 env/output 行 + 子流程 3.1 中保留的 type=secrets 行
# 必须带上 preserved_secrets,否则整集合替换语义会把已有 secret 绑定删除
final_env_vars = environment_variables + preserved_secrets
# 更新 case
result = testany_update_case(
key=case_key,
case_meta={
"environment_variables": final_env_vars
}
)
记录更新结果(成功/失败/错误信息)。
所有 case 处理完成后,显示汇总报告:
同步完成汇总:
----------------
总处理数:10
成功:8
失败:2
失败的 Case:
- ABC12345: Case 不存在
- DEF67890: 源代码拉取失败(分支不存在)
是否清理 worktree 缓存?(缓存可复用,建议保留)
提供清理选项:
| 代码特征 | 含义 |
|---|---|
BASE_STRUCTURES_INFO | 请求结构模板 |
structure.path | 路径参数 |
structure.query | 查询参数 |
structure.header | 请求头参数 |
structure.body | 请求体参数 |
ENV[变量名] | 需要解析的环境变量(入参) |
| 代码特征 | 含义 |
|---|---|
relay_request_fields | 出参字段列表(全大写) |
response_fields | 响应字段定义(用于 process_response_data) |
share_test_data(processed_data) | 调用 relay 服务传递出参 |
testany_get_case 的返回值git fetch 时检查返回值BASE_STRUCTURES_INFO 和 relay_request_fields 是否存在testany_update_case 的错误信息E400002(case_secrets_feature_disabled),提示用户:该 workspace 的 secrets 功能可能未开启,请联系管理员确认/testany-sync-case-env-from-source
→ 选择操作模式:单 Case 操作
→ 输入 case key: B9121897
→ 执行同步流程...
/testany-sync-case-env-from-source
→ 选择操作模式:批量 Keys 操作
→ 输入 case keys(逗号分隔): B9121897,5C5FF07F,ABC12345
→ 执行同步流程...
/testany-sync-case-env-from-source
→ 选择操作模式:搜索批量操作
→ 选择搜索条件:按标签搜索
→ 选择标签: swagger-backend
→ 找到 15 个匹配的 case
→ 执行同步流程...
/testany-sync-case-env-from-source
→ 选择操作模式:搜索批量操作
→ 搜索关键词: importHistory
→ 选择工作空间: demo1
→ 选择标签: swagger-backend
→ 找到 8 个匹配的 case
→ 执行同步流程...
/tmp/testany-worktrees/{branch_name} 创建工作树testany_list_cases 默认返回 20 条结果page_size=100 或更高integration-test/testany-scripts/env/{目录名}_environment.txt 文件中读取实际值替换 PLACEHOLDER
ENV_QUERY_KEY_1_00=IXX)KEY=VALUE,每行一个变量# 开头的注释行(自动忽略)integration-test/testany-scripts/env/{目录名}_environment.txttestany-scripts/ 后的第一个目录名获取OpenAPI 生成的典型测试代码结构:
BASE_STRUCTURES_INFO = {
"structure": {
"path": {
"id": "ENV[ENV_PATH_ID_1_00]"
},
"query": {},
"header": {},
"body": None
}
}
# ... 其他代码 ...
relay_request_fields = [
"NAME",
"DESCRIPTION",
"RUNTIME_UUID",
"SCRIPT_ADDRESS",
"CASE_META",
"CASE_LABELS",
"CASE_VERSION",
"IS_PRIVATE",
"WORKSPACE_KEYS",
"CREDENTIALS",
]
response_fields = [
# ... 响应字段定义 ...
]
手动清理所有 worktree 缓存:
# 列出所有 worktree
git worktree list
# 删除特定 worktree
git worktree remove /tmp/testany-worktrees/{branch_name}
# 删除所有 worktree(谨慎使用)
for worktree in /tmp/testany-worktrees/*/; do
git worktree remove "$worktree"
done