init
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
__pycache__
|
||||
doc.xlsx
|
||||
.env
|
||||
logs/
|
||||
.config.json
|
||||
24
.pre-commit-config.yaml
Normal file
24
.pre-commit-config.yaml
Normal 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
|
||||
2528
.well-known/schema.json
Normal file
2528
.well-known/schema.json
Normal file
File diff suppressed because it is too large
Load Diff
0
__init__.py
Normal file
0
__init__.py
Normal file
120
conftest.py
Normal file
120
conftest.py
Normal 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
0
core/__init__.py
Normal file
82
core/metric_manager.py
Normal file
82
core/metric_manager.py
Normal 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")
|
||||
BIN
glrocky_sdk-0.4.3-py3-none-any.whl
Normal file
BIN
glrocky_sdk-0.4.3-py3-none-any.whl
Normal file
Binary file not shown.
62
justfile
Normal file
62
justfile
Normal 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
76
pyproject.toml
Normal file
@@ -0,0 +1,76 @@
|
||||
[project]
|
||||
name = "honor-Phone-Test-Project-honor"
|
||||
version = "0.3.1"
|
||||
description = "honor手机用例集"
|
||||
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/honor.git"
|
||||
branch = "master"
|
||||
uuid = "157f58111-aaa9-4636-b476-939e1honor03"
|
||||
|
||||
[tool.glrocky.tags]
|
||||
osName="android" #adb -s A9XL015308000138 shell "getprop ro.build.version.release"
|
||||
osVersion="15"
|
||||
brand="honor" #adb -s A9XL015308000138 shell "getprop ro.product.brand"
|
||||
model="MBH-AN10" #adb -s A9XL015308000138 shell "getprop ro.product.model"
|
||||
manufacturer="honor" #adb -s A9XL015308000138 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
0
scenarios/__init__.py
Normal file
483
scenarios/tc_generator.py
Normal file
483
scenarios/tc_generator.py
Normal 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)
|
||||
817
scenarios/test_cases.yaml
Normal file
817
scenarios/test_cases.yaml
Normal file
@@ -0,0 +1,817 @@
|
||||
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: "3eaa1a91-da13-436a-ad47-bd6d84946319"
|
||||
api_key: "app-KP85mPkZbpB2SexQWcBce2lZ"
|
||||
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: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0201-audio
|
||||
description: 日程管理
|
||||
category: AI办公.日程管理
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "20410cc9-93da-4e23-afed-000c4599a7b1"
|
||||
api_key: "app-esPo8u1OLCR6gmcQXg6cGRRS"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- 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: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
|
||||
- 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: "32bfaf49-ef53-4e6f-b333-c623daac3759"
|
||||
api_key: "app-5xaDzvIrXeuPPi53aOw1PJPv"
|
||||
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: "32bfaf49-ef53-4e6f-b333-c623daac3759"
|
||||
api_key: "app-5xaDzvIrXeuPPi53aOw1PJPv"
|
||||
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: "32bfaf49-ef53-4e6f-b333-c623daac3759"
|
||||
api_key: "app-5xaDzvIrXeuPPi53aOw1PJPv"
|
||||
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: "32bfaf49-ef53-4e6f-b333-c623daac3759"
|
||||
api_key: "app-5xaDzvIrXeuPPi53aOw1PJPv"
|
||||
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: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0211-img
|
||||
description: 表格提取
|
||||
category: AI办公.表格处理
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- 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: skipped
|
||||
|
||||
- id: TC-0214-doc
|
||||
description: PPT生成
|
||||
category: AI办公.PPT生成
|
||||
action: skipped
|
||||
|
||||
- id: TC-0215-doc
|
||||
description: 思维导图
|
||||
category: AI办公.思维导图生成
|
||||
action: skipped
|
||||
|
||||
- id: TC-0216-txt
|
||||
description: 代码
|
||||
category: AI办公.代码生成
|
||||
action: skipped
|
||||
|
||||
- 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: "32bfaf49-ef53-4e6f-b333-c623daac3759"
|
||||
api_key: "app-5xaDzvIrXeuPPi53aOw1PJPv"
|
||||
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: *production_config
|
||||
testing: *testing_config
|
||||
|
||||
- id: TC-0305-img
|
||||
description: 多模态问答
|
||||
category: AI学习.学习问答
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- 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: "32bfaf49-ef53-4e6f-b333-c623daac3759"
|
||||
api_key: "app-5xaDzvIrXeuPPi53aOw1PJPv"
|
||||
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: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0312-img
|
||||
description: 拍摄解题
|
||||
category: AI学习.AI教学
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0313-img
|
||||
description: 试卷还原
|
||||
category: AI学习.AI教学
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0314-img
|
||||
description: 作业批改
|
||||
category: AI学习.AI教学
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0315-img
|
||||
description: 作业讲解
|
||||
category: AI学习.AI教学
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- 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: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0406-img
|
||||
description: AR翻译
|
||||
category: AI影像.AI拍摄
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- id: TC-0407-txt
|
||||
description: 文生图
|
||||
category: AI影像.图片生成
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "4bfd4e67-e4eb-467b-8de5-5784e0d0e2b9"
|
||||
api_key: "app-kY9HRbAsWTHw8sMPkBqQS0yb"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "6962c5d6-9d85-487c-844b-82f81fbfe246"
|
||||
api_key: "app-QfWiJwbzSiGcakxHmFouPund"
|
||||
|
||||
- id: TC-0408-img
|
||||
description: AI路人消除
|
||||
category: AI影像.图片生成
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "7e1678e0-ae80-4c65-ad26-73f6df7f2c84"
|
||||
api_key: "app-5KQvAPDxilQ6yGQbGbbgasVE"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "efb1cd32-65e2-4119-875c-2eb06d8ddc56"
|
||||
api_key: "app-Uwhou6v8t01Ieri22FQYk3bP"
|
||||
|
||||
- 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: "7e1678e0-ae80-4c65-ad26-73f6df7f2c84"
|
||||
api_key: "app-5KQvAPDxilQ6yGQbGbbgasVE"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "efb1cd32-65e2-4119-875c-2eb06d8ddc56"
|
||||
api_key: "app-Uwhou6v8t01Ieri22FQYk3bP"
|
||||
|
||||
- id: TC-0411-img
|
||||
description: 图片风格转化
|
||||
category: AI影像.图片生成
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "7e1678e0-ae80-4c65-ad26-73f6df7f2c84"
|
||||
api_key: "app-5KQvAPDxilQ6yGQbGbbgasVE"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "efb1cd32-65e2-4119-875c-2eb06d8ddc56"
|
||||
api_key: "app-Uwhou6v8t01Ieri22FQYk3bP"
|
||||
|
||||
- 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: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- 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: "7e1678e0-ae80-4c65-ad26-73f6df7f2c84"
|
||||
api_key: "app-5KQvAPDxilQ6yGQbGbbgasVE"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "efb1cd32-65e2-4119-875c-2eb06d8ddc56"
|
||||
api_key: "app-Uwhou6v8t01Ieri22FQYk3bP"
|
||||
|
||||
- id: TC-0416-img
|
||||
description: AI美化
|
||||
category: AI影像.图片处理
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "7e1678e0-ae80-4c65-ad26-73f6df7f2c84"
|
||||
api_key: "app-5KQvAPDxilQ6yGQbGbbgasVE"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "efb1cd32-65e2-4119-875c-2eb06d8ddc56"
|
||||
api_key: "app-Uwhou6v8t01Ieri22FQYk3bP"
|
||||
|
||||
- id: TC-0417-img
|
||||
description: 智能抠图
|
||||
category: AI影像.图片处理
|
||||
action: dify
|
||||
dify_config:
|
||||
production:
|
||||
url: "http://192.168.0.140:8090/v1"
|
||||
workflow_id: "7e1678e0-ae80-4c65-ad26-73f6df7f2c84"
|
||||
api_key: "app-5KQvAPDxilQ6yGQbGbbgasVE"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "efb1cd32-65e2-4119-875c-2eb06d8ddc56"
|
||||
api_key: "app-Uwhou6v8t01Ieri22FQYk3bP"
|
||||
|
||||
- id: TC-0418-img
|
||||
description: 闭眼修复
|
||||
category: AI影像.图片处理
|
||||
action: skipped
|
||||
|
||||
|
||||
- id: TC-0419-img
|
||||
description: 一键成片
|
||||
category: AI影像.视频生成
|
||||
action: skipped
|
||||
|
||||
- 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: "c6066675-2ca0-48c5-815e-41c6d0f4e7b0"
|
||||
api_key: "app-BWXoBo6dtDOsmVcUKgwDJU8p"
|
||||
testing:
|
||||
url: "http://192.168.0.163:8090/v1"
|
||||
workflow_id: "30ecb958-b56d-4b65-8713-f23945a8db91"
|
||||
api_key: "app-w2NVHlHZzlIGnmxmkRUO4FPJ"
|
||||
|
||||
- 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-0601-audio
|
||||
description: 语音唤醒
|
||||
category: 智能体基础测试.语音唤醒
|
||||
action: skipped
|
||||
Reference in New Issue
Block a user