This commit is contained in:
2026-04-22 11:57:30 +08:00
commit 550bc2e75d
15 changed files with 5719 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
__pycache__
doc.xlsx
.env
.config.json
logs

24
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,24 @@
exclude: "^assets|^test/"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.11
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
args: [--py312-plus]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-added-large-files
- id: check-toml
- id: check-yaml
exclude: "^helm/"
args: [--unsafe]
- id: check-merge-conflict

2580
.well-known/schema.json Normal file

File diff suppressed because it is too large Load Diff

178
README Normal file
View File

@@ -0,0 +1,178 @@
# OPPO AI 能力自动化测试框架(测试用例部分)
## 1. OPPO测试用例项目简介
本项目旨在为 OPPO 移动设备提供一套全面的 AI 能力自动化测试解决方案。通过模拟真实用户操作,对 AI 社交、AI 办公、AI 学习、AI 影像及 AI 生活等多个核心场景的功能进行端到端验证,确保各项 AI 功能的稳定性与可靠性。
## 2. 项目结构
项目采用分层结构,将核心逻辑、测试用例与配置文件分离,以提高代码的可维护性与扩展性。
```
oppo/
├── .well-known/ # 此目录存放与服务器交互的规范文件(由框架自动生成,请勿手动维护。)
├── .pacakge/ # 此目录存放更为底层的框架封装(勿动!)
├── assets/ # 所需的测试素材与语料均保存在此目录中(请勿使用git维护此目录)
├── core/ # 核心业务逻辑与辅助模块
│ ├── album.py # 相册操作封装
│ ├── local_search.py # 本地搜索功能封装
│ ├── sound_recorder.py # 录音机功能封装
│ ├── ui_helper.py # UI 交互辅助工具
│ ├── xiaobu.py # 小布助手交互封装
│ └──...
├── scenarios/ # 自动化测试用例场景
│ ├── tc_01_social/ # AI 社交场景
│ ├── tc_02_office/ # AI 办公场景
│ ├── tc_03_study/ # AI 学习场景
│ ├── tc_04_visual/ # AI 影像场景
│ └── tc_05_daily_life/ # AI 生活场景
├── conftest.py # Pytest 配置文件,提供全局 Fixture
├── pyproject.toml # 项目配置文件 (PEP 621),定义项目依赖与元数据
├── README # 项目说明文档
└── .env # 环境变量配置(请勿提交至版本库)
```
### 2.1. `core` 目录
`core` 目录包含了项目所需的核心功能模块。这些模块提供了对设备原生应用如相册、录音机和系统级功能如UI操作、小布助手的原子化操作接口为上层测试用例的编写提供稳定支持。
### 2.2. `scenarios` 目录
`scenarios` 目录存放所有自动化测试用例。用例根据功能领域划分为不同的子目录,每个子目录对应一个二级测试场景。用例文件(`tc_*.py`)的命名遵循统一规范。
## 3. 测试场景概览
本项目覆盖了以下五大核心 AI 测试领域:
| 二级场景 | 编号前缀 | 描述 |
| :--- | :--- | :--- |
| AI 社交 | `tc_01` | 覆盖通话、短信、社交应用等场景下的 AI 功能。 |
| AI 办公 | `tc_02` | 覆盖日程管理、会议、文稿处理、全局搜索等办公场景。 |
| AI 学习 | `tc_03` | 覆盖问答、翻译、AI 教学等学习辅助功能。 |
| AI 影像 | `tc_04` | 覆盖 AI 拍摄、图片生成与处理、视频处理等影像能力。 |
| AI 生活 | `tc_05` | 覆盖生活问答、系统操作、出行、健康等日常应用场景。 |
每个场景下的具体测试用例(三级场景)均在对应的 `tc_*.py` 文件中实现。
## 4. 指标收集与发送机制
本项目包含一个完整的指标收集与发送机制,用于记录和传输测试过程中的关键性能指标和测试结果。
### 4.1 设计思想
指标管理器(`metric_manager.py`)的设计目标是提供一个灵活且易于使用的接口,用于收集和发送各种类型的测试指标。指标管理器支持多种指标类型,包括文本、数值、图像和视频。通过`conftest.py`中的`metric` fixture指标管理器被集成到测试框架中使得所有测试用例可以方便地使用它。
### 4.2 指标收集
指标管理器提供了以下方法来收集不同类型的指标:
- `add_text_metric(name: str, value: str, label: str | None = None)`: 添加文本类型的指标。
- `add_number_metric(name: str, value: float, label: str | None = None)`: 添加数值类型的指标。
- `add_image_metric(name: str, value: Path | str, label: str | None = None)`: 添加图像类型的指标。
- `add_video_metric(name: str, value: Path | str, label: str | None = None)`: 添加视频类型的指标。
此外,指标管理器还提供了一个`from_dict`方法,用于从字典中批量添加指标。
### 4.3 指标发送
在测试用例执行完成后,指标管理器会自动调用`send`方法,将收集到的所有指标发送到服务器。发送过程由`conftest.py`中的`metric` fixture管理确保在每个测试用例结束后指标能够及时发送。
### 4.4 使用示例
在测试用例中,可以通过`metric` fixture来使用指标管理器。以下是一个简单的示例
```python
import pytest
@pytest.mark.meta(id="TC-0101-01", description="AI社交/语音交流/AI代接/快递")
def test_tc_0101_01(metric):
metric.span(m_type="social", m_id="TC-0101-01", m_iter=1)
metric.add_text_metric(name="call_status", value="success", label="通话状态")
metric.add_number_metric(name="call_duration", value=120.5, label="通话时长")
metric.send()
```
在这个示例中,测试用例首先调用`span`方法开始一个新的指标收集周期,然后使用`add_text_metric`和`add_number_metric`方法添加文本和数值类型的指标,最后调用`send`方法将指标发送到服务器。
## 5. 开发规范与工具配置
为了确保代码质量和开发规范,项目中使用了`justfile`和`.pre-commit-config.yaml`两个重要配置文件。
### 5.1 `justfile` - 本地开发任务管理
`justfile` 是一个本地开发任务管理工具,类似于 Makefile但更加简洁和易用。它定义了一系列常用开发任务及其执行命令可以帮助你快速执行构建、清理、测试等操作。
#### 当前有的主要功能
- **任务分组**:任务按照功能分组,例如构建、测试、清理等。
- **命令简化**:通过简单的命令即可执行复杂的操作。
- **环境配置**:可以设置环境变量和默认参数。
后续,你可以参考现有命令自行拓展。
#### 使用示例
- **执行所有测试**
```shell
just run
```
- **清理缓存**
```shell
just clean-all
```
### 5.2 `.pre-commit-config.yaml` - 代码质量检查
`.pre-commit-config.yaml` 是 pre-commit 工具的配置文件,用于在每次提交代码前自动执行代码质量检查和格式化操作。
加入此工具的目的是为了代码风格的一致性和代码质量。
#### 主要功能
- **代码检查**:使用 Ruff 进行代码风格检查和格式化。
- **文件检查**检查大文件、YAML 文件、TOML 文件的格式。
- **冲突检测**:检测合并冲突。
#### 使用示例
- **安装 pre-commit 钩子**
```shell
pre-commit install
```
- **手动运行检查**
```shell
pre-commit run --all-files
```
## 6. 环境与依赖
本项目使用 `uv` 作为包管理工具,所有依赖项均在 `pyproject.toml` 文件中声明。
**环境设置步骤:**
1. 确保已安装 `uv`。
2. 在项目根目录下执行以下命令创建并激活虚拟环境:
```shell
uv venv
source .venv/bin/activate
```
3. 安装项目依赖:
```shell
uv pip install -e .
```
## 7. 执行测试
本项目测试框架基于 `pytest` 构建。在激活虚拟环境后,可使用标准 `pytest` 命令执行测试。
- **执行所有测试:**
```shell
pytest
```
- **执行指定场景的测试:**
```shell
pytest scenarios/tc_01_social/
```
- **执行单个测试文件:**
```shell
pytest scenarios/tc_01_social/tc_0101.py
```

0
__init__.py Normal file
View File

120
conftest.py Normal file
View File

@@ -0,0 +1,120 @@
from typing import Callable, Literal
from glrocky.framework.schemas import (
TestSessionRuntime,
Device as _Device,
TestCaseExecutionState,
TestCaseRuntime,
)
from glrocky.framework.const_stash_keys import CASE_RUNTIME_CONFIG_KEY
import pytest
from glrocky.framework.schemas import TestCaseMertic as MetricItem
from glrocky.framework.schemas import TestCaseMetrics as MetricPacked
from glrocky.core.logger import core_logger
@pytest.fixture(scope="function")
def device_info(
request: pytest.FixtureRequest,
) -> _Device:
runtime: TestSessionRuntime | None = getattr(request.config, "project", None)
if runtime is None:
raise RuntimeError("unreachable!!!!!")
ret = runtime.devices[0] if runtime.devices else None
if not ret:
raise RuntimeError("no device found")
return ret
@pytest.fixture(scope="function", autouse=True)
def metric(record_metric: Callable[[MetricPacked], None]):
"""usage:
def test_g(metric):
metric.span() # optional
metric.add()
metric.add()
metric.send_all() # optianl
"""
class M:
def __init__(self):
self.data: list[tuple[str, str, int, list[MetricItem]]] = []
self.span()
def add(
self,
name: str,
value: float | int | str,
label: str | None = None,
type: Literal["number", "image", "video", "text"] = "text",
) -> None:
metric = MetricItem(
name=name, value=value, label=label or name, unit=None, type=type
)
self.data[-1][-1].append(metric)
def span(
self,
material_type: str = "default",
material_id: str = "default",
material_iteration: int = 1,
):
# DO NOT ADD DUPLICATE ITEM!!
if len(self.data):
last_span = self.data[-1]
if all(
(
last_span[0] == material_type,
last_span[1] == material_id,
last_span[2] == material_iteration,
)
):
core_logger.debug(
f"SKIP DUPLICATE SPAN:{material_type=},{material_id=},{material_iteration=}"
)
return
self.data.append((material_type, material_id, material_iteration, []))
core_logger.info(
f"added new span for metric:{material_type=},{material_id=},{material_iteration=}"
)
def send_all(
self,
):
if len(self.data) < 1:
core_logger.debug("no materics to send")
return # no span at all,skip
data = self.data[::-1]
self.data.clear() # TODO: not safe,to be improved
core_logger.info(f"{len(data)} metrics for send.")
while True:
if len(data) == 0:
break
item = data.pop()
material_type, material_id, material_iteration, metrics = (
item[0],
item[1],
item[2],
item[3],
)
if len(metrics) == 0:
core_logger.info("skip empty metrics")
continue
for_send = MetricPacked(
type=material_type,
material=material_id,
iteration=material_iteration,
metrics=metrics,
)
core_logger.info(f"rebuild metric:{for_send=}")
core_logger.info("sending metrics to executor...")
record_metric(for_send)
core_logger.info("sent")
_m = M()
yield _m
try:
_m.send_all()
except Exception as e:
raise RuntimeError(f"Cloud not send metric to server,Detail: {e}")

0
core/__init__.py Normal file
View File

82
core/metric_manager.py Normal file
View File

@@ -0,0 +1,82 @@
from pathlib import Path
class MetricManager:
def __init__(self, metric):
self.metric = metric
def new_span(self, m_type: str = "default", m_id: str = "default", m_iter: int = 1):
self.metric.span(m_type, m_id, m_iter)
def _smart_name_label(self, name: str, label: str | None) -> tuple[str, str]:
"""retruns: (name,label)"""
if not name:
raise ValueError()
if not name.strip():
raise ValueError("Empty name")
if label:
return name, label
for sep in ("|", ":"):
if sep in name:
_n, _l = name.split(sep, maxsplit=1)
return (_n, _l)
return name, name
def add_text_metric(self, name: str, value: str, label: str | None = None):
_n, _l = self._smart_name_label(name, label)
self.metric.add(
name=_n,
label=_l,
value=value,
type="text",
)
def add_number_metric(self, name: str, value: float, label: str | None = None):
_n, _l = self._smart_name_label(name, label)
self.metric.add(
name=_n,
label=_l,
value=value,
type="number",
)
def add_image_metric(self, name: str, value: Path | str, label: str | None = None):
_n, _l = self._smart_name_label(name, label)
self.metric.add(
name=_n,
label=_l,
value=str(value),
type="image",
)
def add_video_metric(self, name: str, value: Path | str, label: str | None = None):
_n, _l = self._smart_name_label(name, label)
self.metric.add(
name=_n,
label=_l,
value=value,
type="video",
)
def send(self):
self.metric.send_all()
def from_dict(self, the_dict: dict):
if not the_dict:
raise ValueError("dict is null")
for k, v in the_dict.items():
if isinstance(v, (int, float)):
self.add_number_metric(name=k, value=v)
elif isinstance(v, str):
self.add_text_metric(name=k, value=v)
elif isinstance(v, Path):
if v.suffix.lower() in (".jpg", ".png", ".gif", ".bmp"):
self.add_image_metric(name=k, value=str(v.resolve()))
elif v.suffix.lower() in (".mp4", ".mkv"):
self.add_video_metric(name=k, value=str(v.resolve()))
else:
self.add_text_metric(name=k, value=str(v.resolve()))
else:
raise NotImplementedError(f"{type(v)} not supported")

Binary file not shown.

62
justfile Normal file
View File

@@ -0,0 +1,62 @@
set shell := ["powershell.exe","-NoLogo","-NoProfile", "-c"] # windows
set dotenv-load
set quiet :=true
@_:
just --list
[group("build")]
generate-schema:
rm {{quote(".well-known"/"*.json")}}
@fd -e json -H "schema"
uv run --reinstall-package glrocky-sdk \
python -m \
pytest --co --generate-schema --continue-on-collection-errors -q
mv {{ quote (".well-known" / "schema_autogenerated.json")}} {{ quote (".well-known" / "schema.json")}}
@fd -e json -H "schema"
[group("qa")]
lint:
uvx ruff check --fix --unsafe-fixes .
uvx ruff format
[group("run")]
[doc("run a specific test,eg: j run 0101_02")]
run TEST:
uv run python -m pytest -sqv -k {{TEST}}
[group("clean")]
clean-pycache:
@rm.exe -rf .pytest_cache .mypy_cache .ruff_cache .coverage
rm.exe -rf log out
@fd -td -I "__pycache__" -x rm -rf '{}'
@echo "✅ Done"
[group("clean")]
clean-cache:
@rm.exe -rf .pytest_cache .mypy_cache .ruff_cache .coverage
[group("clean")]
clean-out-and-log:
rm.exe -rf logs/ out/
[group("clean")]
clean-all:
just clean-cache
just clean-out-and-log
just clean-pycache
[group("dev")]
[doc("invoke ipython with u2")]
debug-u2:
uvx --with uiautomator2 --with .packages/glrocky_sdk-0.1.2-py3-none-any.whl --directory . ipython
[group("dev")]
[doc("show android ui")]
debug-ui:
uvx uiviewer
[group("dev")]
[doc("start test executor server,from glrocky framework")]
debug-srv:
uv run api-test-server

76
pyproject.toml Normal file
View File

@@ -0,0 +1,76 @@
[project]
name = "samsung-Phone-Test-Project-samsung"
version = "0.3.2"
description = "samsung音响用例集"
readme = "README.md"
authors = [{ name = "liumin", email = "liumin@glrocy.com" }]
requires-python = "==3.12.6"
dependencies = ["glrocky-sdk", "uvicorn>=0.34.2"]
[tool.pytest.ini_options]
minversion = "8.3.5"
#addopts = "--no-summary --no-header -rfEX --strict-markers --capture=tee-sys "
addopts = "--capture=tee-sys "
testpaths = ["scenarios"]
python_files = "tc_*.py"
markers = []
[tool.pyright]
venv = ".venv"
venvPath = "."
reportMissingTypeStubs = false
reportUnknownMemberType = false
[tool.uv.sources]
#glrocky-sdk = { path = "../../packages/glrocky-sdk" }
glrocky-sdk = { path = "./glrocky_sdk-0.4.3-py3-none-any.whl" }
[[tool.uv.index]]
url = "https://mirrors.aliyun.com/pypi/simple/"
default = true
[tool.glrocky]
remote_url = "https://gitea.glrocky.cn/aitest-scripts/samsung.git"
branch = "master"
uuid = "257f58111-aaa9-4636-b476-SMS936002"
[tool.glrocky.tags]
osName="android" #adb -s R3CX90P073R shell "getprop ro.build.version.release"
osVersion="15"
brand="samsung" #adb -s R3CX90P073R shell "getprop ro.product.brand"
model="SM-S9360" #adb -s R3CX90P073R shell "getprop ro.product.model"
manufacturer="samsung" #adb -s R3CX90P073R shell "getprop ro.product.manufacturer"
[tool.glrocky.commands]
generateSchema = "uv run --reinstall-package glrocky-sdk pytest --co --generate-schema --continue-on-collection-errors"
startTest = "uv run pytest"
[tool.glrocky.requirements]
devices = [{ type = "phone", os = "android", count = 1 }]
[tool.glrocky.project_settings]
screen-record-extra-commands = [
"--window-x=1380",
"--window-y=50",
"--always-on-top",
"--disable-screensaver",
"--window-borderless",
"-m",
"600",
"--video-codec",
"h264",
"--video-bit-rate",
"5M",
"--max-fps",
"60",
"--time-limit",
"3600",
"--print-fps",
"--video-buffer",
"100",
"--no-audio-playback",
"--no-audio",
]
[dependency-groups]
dev = ["ruff>=0.12.2"]

0
scenarios/__init__.py Normal file
View File

483
scenarios/tc_generator.py Normal file
View File

@@ -0,0 +1,483 @@
import os
import re
import sys
import json
import time
import concurrent.futures
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Any
import pytest
import yaml
from glrocky.core.logger import logger
from glrocky.framework.marks import Marks as M
from glrocky.framework.schemas import Device
from glrocky.services.dify.dify import run_workflow
from loguru._logger import Logger
from pydantic import BaseModel, ConfigDict, Field, field_serializer, create_model
import socket
PRODUCTION_ENV_NAME = "GLROCKY_PRODUCTION_MODE"
PRODUCTION_EXECUTOR = "192.168.0.139"
YAML_FILE = Path(__file__).parent / "test_cases.yaml"
def _get_local_ip() -> str:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(("8.8.8.8", 80))
ip: str = s.getsockname()[0]
return str(ip)
finally:
s.close()
def _is_production_mode() -> bool:
env_val = os.getenv(PRODUCTION_ENV_NAME)
if env_val is not None:
return env_val.lower() in ("true", "yes", "1")
return _get_local_ip() == PRODUCTION_EXECUTOR
def pre_process_material(input: list[str]) -> str:
assert isinstance(input, list)
if not input:
return json.dumps([], ensure_ascii=False)
result = []
for text in input:
splited = _Q_PATTERN.split(text, maxsplit=50)
for part in splited:
if not part.strip():
continue
result.append(part.strip())
return json.dumps(result, ensure_ascii=False)
IS_PRODUCTION = _is_production_mode()
METRIC_FIELD_MAPPING: dict[str, str] = {
"result": "result|结果",
"_dify_result": "_dify_result|业务完成",
"_dify_message": "_dify_message|业务错误信息",
"resultText": "resultText|文本结果",
"recordVideo": "recordVideo|录像",
"recordAudio": "recordAudio|录音",
"screenshotList": "screenshotList|截图",
"firstToken": "firstToken|首字符时长",
"timeSeries": "timeSeries|时间序列",
"fileList": "fileList|文件",
}
IMAGE_EXTENSIONS = {".jpg", ".png", ".gif", ".bmp"}
VIDEO_EXTENSIONS = {".mp4", ".mkv"}
_Q_PATTERN = re.compile(r"Q\d+\s*[:\.]+\s*", flags=re.MULTILINE)
@dataclass
class _TimeoutCtx:
client: Any = None
task_id: str | None = None
def stop(self):
if self.client and self.task_id:
try:
logger.info(f"Stopping workflow: {self.task_id}")
self.client.stop_workflow(self.task_id)
logger.info(f"Stopped workflow: {self.task_id}")
except Exception as e:
logger.debug(f"Stop workflow failed: {e}")
class MetricManager:
def __init__(self, metric):
self.metric = metric
def new_span(self, m_type: str = "default", m_id: str = "default", m_iter: int = 1):
self.metric.span(m_type, m_id, m_iter)
def _smart_name_label(self, name: str, label: str | None) -> tuple[str, str]:
"""retruns: (name,label)"""
if not name or not name.strip():
raise ValueError("Empty name")
if label:
return name, label
sep = next((s for s in ("|", ":") if s in name), None)
if sep:
_n, _l = name.split(sep, maxsplit=1)
return (_n, _l)
return name, name
def _add_metric(self, name: str, value: Any, m_type: str, label: str | None = None):
_n, _l = self._smart_name_label(name, label)
self.metric.add(name=_n, label=_l, value=value, type=m_type)
def add_text_metric(self, name: str, value: str, label: str | None = None):
self._add_metric(name, value, "text", label)
def add_number_metric(self, name: str, value: float, label: str | None = None):
self._add_metric(name, value, "number", label)
def add_image_metric(self, name: str, value: Path | str, label: str | None = None):
self._add_metric(name, str(value), "image", label)
def add_video_metric(self, name: str, value: Path | str, label: str | None = None):
self._add_metric(name, value, "video", label)
def send(self):
self.metric.send_all()
def from_dict(self, the_dict: dict[str, Any]):
if not the_dict:
raise ValueError("dict is null")
for _k, v in the_dict.items():
k = METRIC_FIELD_MAPPING.get(_k, _k)
logger.info(k)
match v:
case None:
self.add_text_metric(name=k, value="")
case int() | float():
self.add_number_metric(name=k, value=v)
case str():
self.add_text_metric(name=k, value=v)
case Path():
match v.suffix.lower():
case s if s in IMAGE_EXTENSIONS:
self.add_image_metric(name=k, value=str(v.resolve()))
case s if s in VIDEO_EXTENSIONS:
self.add_video_metric(name=k, value=str(v.resolve()))
case _:
self.add_text_metric(name=k, value=str(v.resolve()))
case _:
raise TypeError(f"{type(v)} not supported")
class DifySettings(BaseModel):
difyUrl: str = Field(
title="Dify服务地址",
)
difyWorkflowId: str = Field(
title="Dify工作流ID",
)
difyApiKey: str = Field(
title="Dify API密钥",
)
class MaterialForDify(BaseModel):
paramGroupUuid: list[str] = Field(
default_factory=list, title="UUID", description="UUID"
)
inputTextList: list[str] = Field(
default_factory=list,
title="对话列表,支持多轮对话",
description="对话列表,支持多轮对话",
)
prompt: list[str] = Field(
default_factory=list, title="提示词", description="提示词"
)
model_config = ConfigDict(arbitrary_types_allowed=True) # allows uuid extra...
class DifyPayload(BaseModel):
inputTextList: list[str] | str
prompt: list[str] | str
deviceId: str
address: str
caseId: str
parameters: dict[str, Any] |str| None = None
@field_serializer("inputTextList")
def serialize_input_text(self, value: list[str] | str) -> str:
if isinstance(value, list):
return pre_process_material(value)
return value
@field_serializer("prompt")
def serialize_prompt_as_single_str(self, value: list[str] | str) -> str:
if isinstance(value, list):
return value[0] if len(value) > 0 else ""
return value
def create_dynamic_model(model_name: str, parameters: list[dict]) -> type[BaseModel]:
field_definitions = {}
for idx, param in enumerate(parameters):
name: str = (
param.get("name", "").strip() if isinstance(param.get("name"), str) else ""
)
if not name:
raise ValueError(f"参数定义错误: 第 {idx + 1} 个参数缺少 'name' 字段或为空")
label = (
param.get("label", "").strip()
if isinstance(param.get("label"), str)
else name
)
description = (
param.get("description", label).strip()
if isinstance(param.get("description"), str)
else label
)
param_type = (
param.get("type", "string").strip()
if isinstance(param.get("type"), str)
else "string"
)
if param_type not in ("string",):
raise ValueError(
f"参数 '{name}' 不支持的类型 '{param_type}',目前仅支持 'string'"
)
default = param.get("defaultValue", "")
if default and isinstance(default, str):
default = default.strip()
field_definitions[name] = (
str,
Field(default=default, title=label, description=description),
)
try:
model = create_model(model_name, __base__=BaseModel, **field_definitions)
model.model_config["extra"] = "ignore"
model()
return model
except Exception as e:
raise ValueError(f"创建参数模型 '{model_name}' 失败: {e}")
def call_dify(
case_meta_info: dict[str, str],
logger: Logger,
device_info: Device,
material: list[MaterialForDify],
dify_cfg: DifySettings,
metric,
material_reporter,
parameters: dict[str, Any] | None = None,
timeout_sec: int | None = None,
):
# logger.info(device_info.device_serial)
logger.info(dify_cfg)
event_callbacks: dict[str, Callable[..., None]] = {}
def on_node_started(_, __, d: dict[str, Any]):
logger.info(f"开始执行:{d.get('title', '')}")
logger.info(f"输入节点参数:{d.get('inputs')}")
def on_node_finished(_, __, d):
logger.info(f"结束执行:{d.get('title', '')}")
logger.info(f"节点输出:{d.get('outputs')}")
event_callbacks["on_node_started"] = on_node_started
event_callbacks["on_node_finished"] = on_node_finished
if timeout_sec:
ctx = _TimeoutCtx()
def on_workflow_started(client, event_name, event_data):
logger.info("开始执行dify任务")
ctx.client = client
ctx.task_id = event_data.get("task_id")
event_callbacks["on_workflow_started"] = on_workflow_started
if not material:
raise RuntimeError("缺少素材")
else:
logger.info(f"下发素材:{material}")
metric_manager = MetricManager(metric)
local_ip = _get_local_ip()
for material_index, item in enumerate(material, 1):
paramGroupUuid = item.paramGroupUuid[0]
assert paramGroupUuid, "The param Group Uuid Value must be set."
material_reporter.begin(paramGroupUuid)
dify_final_status = False
try:
# IMPORTANT: the group uuid for params
payload_data = {
**item.model_dump(),
"deviceId": device_info.device_serial,
"address": local_ip,
"caseId": case_meta_info.get("id", "Unknown"),
}
if parameters:
payload_data["parameters"] = json.dumps(parameters)
payload = DifyPayload(**payload_data)
logger.info(f"payload send to dify:\n{payload.model_dump_json(indent=2)}\n")
metric_manager.new_span(
"default", f"default-{material_index}", material_index
)
def _run_workflow():
return run_workflow(
api_key=dify_cfg.difyApiKey,
base_url=dify_cfg.difyUrl,
workflow_id=dify_cfg.difyWorkflowId,
inputs=payload.model_dump(exclude_none=True),
event_callbacks=event_callbacks,
)
if timeout_sec:
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(_run_workflow)
try:
result = future.result(timeout=timeout_sec)
except concurrent.futures.TimeoutError:
logger.error(f"Workflow timeout after {timeout_sec}s,(yaml config)")
time.sleep(0.1)
logger.info("尝试停止工作流...")
ctx.stop()
raise TimeoutError(f"Workflow timeout after {timeout_sec}s")
else:
result = _run_workflow()
logger.info(f"工作流返回结果:{result.outputs}")
logger.info(f"工作流最终状态:{result.success}")
if result.outputs:
logger.info("提交结果到执行器")
metric_manager.from_dict(result.outputs)
metric.send_all()
logger.info("提交结果到执行器完成")
else:
logger.warning("dify 工作流无返回")
if not result.success:
logger.error(f"工作流执行失败:{result.error}")
dify_final_status = result.success
except Exception as e:
logger.error(e)
dify_final_status = False
continue # do not break next round
finally:
material_reporter.end_with(paramGroupUuid, dify_final_status)
def make_dify_test(
meta: dict[str, str],
dify_settings: DifySettings | None = None,
param_model: type[BaseModel] | None = None,
timeout: int | None = None,
) -> Callable[..., None]:
if param_model is None:
param_model = create_model("EmptyParams", __base__=BaseModel)
@pytest.mark.usefixtures("cfg")
def _func_impl(
logger: Logger,
device_info: Device,
material: list[MaterialForDify],
metric,
material_reporter,
cfg: param_model,
) -> None:
param_dict = cfg.model_dump() if cfg else {}
call_dify(
meta,
logger,
device_info,
material,
dify_cfg=dify_settings,
metric=metric,
material_reporter=material_reporter,
parameters=param_dict,
timeout_sec=timeout,
)
# always use cfg fixture
# _func_impl = pytest.mark.usefixtures("cfg")(_func_impl)
return M.meta(**meta)(_func_impl)
def make_skip_test(meta: dict[str, str]) -> Callable[..., None]:
@M.skip(reason=f"{meta['id']} 未实现")
@M.meta(**meta)
def _func(
logger: Logger,
device_info: Device,
material: list[MaterialForDify],
metric,
) -> None:
logger.error(f"此用例在手机{device_info.device_serial}上暂未实现")
assert False, "not implemented"
return _func
def generate_cases_from_yaml(module_name: str, yaml_path: Path):
if not yaml_path.exists():
logger.warning(f"Test case definition file not found: {yaml_path}")
return
try:
with open(file=yaml_path, mode="r", encoding="utf-8") as f:
all_cases: list[dict[str, Any]] = yaml.safe_load( # pyright: ignore[reportAny]
f
)
except yaml.YAMLError as e:
logger.error(f"Failed to parse YAML file {yaml_path}: {e}")
pytest.fail(f"YAML解析失败: {yaml_path}")
return
if not all_cases:
return
for case_info in all_cases.get("cases", []):
if not isinstance(case_info, dict):
logger.debug(f"Skipping non-dictionary item in YAML file: {case_info}")
continue
case_id: str = case_info.get("id", "")
if not case_id:
logger.warning(f"用例 缺少 case_id 字段。{case_info=}")
pytest.fail(f"用例 缺少 case_id 字段。{case_info=}")
description: str = case_info.get("description", "")
if not description:
logger.warning(f"用例{case_id} 缺少 description 字段。")
pytest.fail(f"用例{case_id} 缺少 description 字段。")
action: str = case_info.get("action", "skipped")
meta = {"id": case_id, "description": description}
fn_name = f"test_{case_id.lower().replace('-', '_')}"
if action == "dify":
dify_settings: DifySettings | None = None
param_model: type[BaseModel] | None = None
timeout = case_info.get("timeout")
if parameters_def := case_info.get("parameters"):
model_name = f"{case_id.replace('-', '_')}_Params"
param_model = create_dynamic_model(model_name, parameters_def)
if dify_config_block := case_info.get("dify_config"):
env_key = "production" if IS_PRODUCTION else "testing"
if env_config := dify_config_block.get(env_key):
dify_settings = DifySettings(
difyUrl=env_config.get("url"),
difyWorkflowId=env_config.get("workflow_id"),
difyApiKey=env_config.get("api_key"),
)
else:
logger.warning(
f"No '{env_key}' config found for {case_id}, will use default."
)
fn = make_dify_test(meta, dify_settings, param_model, timeout)
elif action == "skipped":
fn = make_skip_test(meta)
elif action == "custom":
logger.info(f"Case {case_id} is a custom test.")
continue
else:
logger.warning(f"Unknown action '{action}' for case {case_id}. Skipping.")
continue
setattr(sys.modules[module_name], fn_name, fn)
logger.info(f"Generated {fn_name} for {case_id} with action '{action}'")
generate_cases_from_yaml(__name__, YAML_FILE)

858
scenarios/test_cases.yaml Normal file
View File

@@ -0,0 +1,858 @@
dify_templates:
tianjin_dify_config: &testing_config
url: "http://192.168.0.163:8090/v1"
workflow_id: "d4a40bf2-99cb-440f-97be-3793e2274318"
api_key: "app-cvFr7cpcHytfYIS0q2LpdS0B"
guangzhou_dify_config: &production_config
url: "http://192.168.0.140:8090/v1"
workflow_id: "4169f511-872f-4b55-ac1e-6265b706f12b"
api_key: "app-bath5W4UY2kmj3DP3A3ukm1L"
cases:
- id: TC-0101-audio
description: AI代接
category: AI社交.语音交流
action: skipped
- id: TC-0102-audio
description: 通话实时翻译
category: AI社交.语音交流
action: skipped
- id: TC-0103-audio
description: 通话摘要
category: AI社交.语音交流
action: skipped
- id: TC-0104-txt
description: 文本通话
category: AI社交.语音交流
action: skipped
- id: TC-0105-audio
description: AI通话降噪
category: AI社交.语音交流
action: skipped
- id: TC-0106-audio
description: AI隐私保护防漏音
category: AI社交.语音交流
action: skipped
- id: TC-0107-audio
description: 对话翻译
category: AI社交.语音交流
action: skipped
- id: TC-0108-audio
description: 面对面同声传译
category: AI社交.语音交流
action: skipped
- id: TC-0109-txt
description: AI短信
category: AI社交.短信
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0110-txt
description: AR虚拟表情
category: AI社交.短信
action: skipped
- id: TC-0110-img
description: AR虚拟表情
category: AI社交.短信
action: skipped
- id: TC-0111-video
description: 视频电话AI鉴伪
category: AI社交.社交应用
action: skipped
- id: TC-0112-doc
description: 语音快传
category: AI社交.语音快传
action: skipped
- id: TC-0112-img
description: 语音快传
category: AI社交.语音快传
action: skipped
- id: TC-0201-img
description: 日程管理
category: AI办公.日程管理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0201-audio
description: 日程管理
category: AI办公.日程管理
action: skipped
- id: TC-0202-audio
description: 语音转写
category: AI办公.会议助手
action: skipped
- id: TC-0202-video
description: 语音转写
category: AI办公.会议助手
action: skipped
- id: TC-0203-doc
description: 智能纪要
category: AI办公.会议助手
action: skipped
- id: TC-0204-txt
description: 文稿创作
category: AI办公.工作文稿处理
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0204-img
description: 文稿创作
category: AI办公.工作文稿处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0205-txt
description: 文稿润色-文本
category: AI办公.工作文稿处理
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0205-doc
description: 文稿润色-文档
category: AI办公.工作文稿处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0206-txt
description: 文稿排版
category: AI办公.工作文稿处理
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0206-doc
description: 文稿排版
category: AI办公.工作文稿处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0207-txt
description: 长文本摘要
category: AI办公.工作文稿处理
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0208-doc
description: 文档摘要
category: AI办公.工作文稿处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0209-doc
description: 多语言摘要
category: AI办公.工作文稿处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0210-img
description: 图片转表格
category: AI办公.表格处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0211-img
description: 表格提取
category: AI办公.表格处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0212-txt
description: 文件搜索
category: AI办公.AI全局搜索
action: skipped
- id: TC-0213-txt
description: 图片搜索
category: AI办公.AI全局搜索
action: skipped
- id: TC-0214-txt
description: PPT生成
category: AI办公.PPT生成
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0214-doc
description: PPT生成
category: AI办公.PPT生成
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0215-doc
description: 思维导图
category: AI办公.思维导图生成
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0216-txt
description: 代码
category: AI办公.代码生成
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0301-txt
description: 用机问答
category: AI学习.学习问答
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0302-doc
description: 文档问答
category: AI学习.学习问答
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0303-txt
description: 对话问答
category: AI学习.学习问答
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0304-txt
description: 网页问答
category: AI学习.学习问答
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "33b0775c-7d9d-48f3-ba79-2be691d94bbe"
api_key: "app-cDWYmENtNPUaSQVWROoHB2KQ"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0305-img
description: 多模态问答
category: AI学习.学习问答
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0305-audio
description: 多模态问答
category: AI学习.学习问答
action: skipped
- id: TC-0305-video
description: 多模态问答
category: AI学习.学习问答
action: skipped
- id: TC-0306-txt
description: 深度推理
category: AI学习.学习问答
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0307-txt
description: 文本翻译
category: AI学习.翻译
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0308-doc
description: 文档翻译
category: AI学习.翻译
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "8c76d514-2899-4cd3-9aff-000ee327381f"
api_key: "app-5VUAZqCJoQ7PaRA7ZMdUPttd"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0309-txt
description: 网页翻译
category: AI学习.翻译
action: skipped
- id: TC-0310-audio
description: 口语陪练
category: AI学习.AI教学
action: skipped
- id: TC-0311-img
description: 图片解题
category: AI学习.AI教学
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0312-img
description: 拍摄解题
category: AI学习.AI教学
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0313-img
description: 试卷还原
category: AI学习.AI教学
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0314-img
description: 作业批改
category: AI学习.AI教学
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0315-img
description: 作业讲解
category: AI学习.AI教学
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0316-doc
description: 屏幕问答
category: AI学习.AI学习
action: skipped
- id: TC-0316-img
description: 屏幕问答
category: AI学习.AI学习
action: skipped
- id: TC-0401-txt
description: 智能体唤醒拍照
category: AI影像.AI拍摄
action: skipped
- id: TC-0401-audio
description: 智能体唤醒拍照
category: AI影像.AI拍摄
action: skipped
- id: TC-0402-img
description: 笑脸抓拍
category: AI影像.AI拍摄
action: skipped
- id: TC-0403-video
description: 录制视频(美颜/美化)
category: AI影像.AI拍摄
action: skipped
- id: TC-0404-video
description: 识文
category: AI影像.AI拍摄
action: skipped
- id: TC-0405-img
description: 识物
category: AI影像.AI拍摄
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0406-img
description: AR翻译
category: AI影像.AI拍摄
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0407-txt
description: 文生图
category: AI影像.图片生成
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0408-img
description: AI路人消除
category: AI影像.图片生成
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0409-img
description: AI物体消除
category: AI影像.图片生成
action: skipped
- id: TC-0410-img
description: AI扩图
category: AI影像.图片生成
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0411-img
description: 图片风格转化
category: AI影像.图片生成
action: skipped
- id: TC-0412-img
description: 涂鸦生图
category: AI影像.图片生成
action: skipped
- id: TC-0413-img
description: 图片翻译
category: AI影像.图片处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0414-img
description: AI搜图
category: AI影像.图片处理
action: skipped
- id: TC-0415-img
description: AI美颜
category: AI影像.图片处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0416-img
description: AI美化
category: AI影像.图片处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0417-img
description: 智能抠图
category: AI影像.图片处理
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0418-img
description: 闭眼修复
category: AI影像.图片处理
action: skipped
- id: TC-0419-img
description: 一键成片
category: AI影像.视频生成
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "5a3cdab7-6c86-4480-a3b1-bc13d55a213f"
api_key: "app-LGTDSIyuXT9szYlP1fnc2cdk"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0420-img
description: 视频字幕生成
category: AI影像.视频处理
action: skipped
- id: TC-0421-img
description: 视频翻译
category: AI影像.视频处理
action: skipped
- id: TC-0501-txt
description: 公共服务查询(天气/交通路线/节假日等)
category: AI生活.生活类问答
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0502-txt
description: 通信业务查询(话费/流量/短信等)
category: AI生活.生活类问答
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0503-txt
description: 生活费用查询(水/电/煤/物业等)
category: AI生活.生活类问答
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0504-txt
description: 生活建议(美食推荐/穿搭建议等)
category: AI生活.生活类问答
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0505-txt
description: 系统控制(开关机/音量/字体等)
category: AI生活.系统操作
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0506-txt
description: 应用管理
category: AI生活.系统操作
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0507-img
description: AI圈选
category: AI生活.系统操作
action: skipped
- id: TC-0508-audio
description: 智能打开乘车码(地铁/公交等)
category: AI生活.购物/支付
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "375edfd9-bf28-4792-8f03-399b9201f6cf"
api_key: "app-dHfCMjyNgVwvny9oODgZNKui"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
- id: TC-0509-txt
description: 商品比价
category: AI生活.购物/支付
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0509-img
description: 商品比价
category: AI生活.购物/支付
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "d15da81c-d0ff-4606-aa07-540d09fa9c0a"
api_key: "app-xbHHhDrcSfMwX21hA1Ca00p5"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0510-txt
description: AI导航
category: AI生活.出行
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0510-img
description: AI导航
category: AI生活.出行
action: skipped
- id: TC-0511-img
description: 网约车
category: AI生活.出行
action: skipped
- id: TC-0511-txt
description: 网约车
category: AI生活.出行
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0512-txt
description: 行程规划
category: AI生活.出行
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0513-txt
description: 健康问答
category: AI生活.运动健康
action: dify
dify_config:
production: *production_config
testing: *testing_config
- id: TC-0514-txt
description: AI读屏
category: AI生活.无障碍辅助
action: skipped
- id: TC-0515-txt
description: AI看见
category: AI生活.无障碍辅助
action: skipped
- id: TC-0516-txt
description: AI播客生成
category: AI生活.咨询传播
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "e01c29c1-2bb8-415e-9838-1e73da29e3c1"
api_key: "app-5uWRA2x6r8M1Ldr8SJCWtzjg"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0516-audio
description: AI播客生成
category: AI生活.咨询传播
action: dify
dify_config:
production:
url: "http://192.168.0.140:8090/v1"
workflow_id: "e01c29c1-2bb8-415e-9838-1e73da29e3c1"
api_key: "app-5uWRA2x6r8M1Ldr8SJCWtzjg"
testing:
url: "http://192.168.0.163:8090/v1"
workflow_id: "2c7809b7-baea-478f-9f19-c722a7a276c4"
api_key: "app-puoiD0ZqPRtFb7wrUimw4cSt"
- id: TC-0601-audio
description: 语音唤醒
category: 智能体基础测试.语音唤醒
action: skipped

1251
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff