桌面 GUI 外链修复 + 主题顺序调整(含结构扁平化进行中的工作区基线)
- 集成 tauri-plugin-opener:Cargo/lib.rs/capabilities/npm 四处接线,新增 src/openExternal.ts(桌面走 opener,浏览器回退 window.open,相对路径绝对化) - 关于页 4 个链接、活动栏官网菜单、代理错误横幅 2 处健康检查链接全部改走 openExternal,桌面端可正常跳转 - 设置→外观 主题顺序调整为 深色/浅色/跟随系统 - release 二进制重编并验证(浏览器实测关于页链接可开文档页) - 附带工作区中未提交的扁平化重构现状(另一会话进行中) - homepage 落地页与文档终检通过(8787 / 与 /docs 双 200)
This commit is contained in:
1 parent
6452026fd6
commit
2bb4ef7a95
299 files changed
+6059
-3322
No files matched your search
@@ -0,0 +1,137 @@
|
||||
import { executeOnce } from "./features/testing/execution";
|
||||
/**
|
||||
* 用例执行器:串行执行步骤 → 断言 → 变量提取 → 生成报告。
|
||||
* 与手动发送共用 buildHttpSpec(含环境变量与 Mock)。
|
||||
*/
|
||||
import type {
|
||||
CollectionNode,
|
||||
HttpSpec,
|
||||
RequestNode,
|
||||
TestRun,
|
||||
TestRunTrigger,
|
||||
TestStep,
|
||||
TestStepResult,
|
||||
} from "./core/index";
|
||||
import { evaluateAssertion, extractVariables } from "./core/index";
|
||||
import { buildHttpSpec } from "./send";
|
||||
import { activeEnvBaseUrl, activeEnvMap, useWorkspace } from "./workspaceStore";
|
||||
|
||||
async function getTransportFn() {
|
||||
const { detectTransport } = await import("./transport/index");
|
||||
const { transport } = detectTransport({ ginBaseUrl: "" });
|
||||
return transport.http.bind(transport);
|
||||
}
|
||||
|
||||
function stepLabel(step: TestStep, node: RequestNode | undefined): string {
|
||||
return node?.name ?? "(接口已被删除)";
|
||||
}
|
||||
|
||||
/** 执行单个用例并写入运行记录;返回本次报告 */
|
||||
export function runTestCase(caseId: string, trigger: TestRunTrigger): Promise<TestRun | null> {
|
||||
return executeOnce(caseId, () => executeTestCase(caseId, trigger));
|
||||
}
|
||||
|
||||
async function executeTestCase(caseId: string, trigger: TestRunTrigger): Promise<TestRun | null> {
|
||||
const tc = useWorkspace.getState().testCases.find((c) => c.id === caseId);
|
||||
if (!tc) return null;
|
||||
|
||||
const http = await getTransportFn();
|
||||
const started = performance.now();
|
||||
const stepResults: TestStepResult[] = [];
|
||||
|
||||
for (const step of tc.steps) {
|
||||
if (!step.enabled) continue;
|
||||
const node = findRequestNode(step.nodeId);
|
||||
if (!node || node.kind !== "http") {
|
||||
stepResults.push({
|
||||
stepId: step.id,
|
||||
name: stepLabel(step, node),
|
||||
method: "—",
|
||||
url: "—",
|
||||
ok: false,
|
||||
httpStatus: null,
|
||||
durationMs: null,
|
||||
error: node ? "仅支持 HTTP 步骤" : "接口不存在或已被删除",
|
||||
assertionResults: [],
|
||||
});
|
||||
if (!tc.continueOnFailure) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
const env = activeEnvMap();
|
||||
const { timeoutMs, mock } = useWorkspace.getState().settings;
|
||||
const spec: HttpSpec = buildHttpSpec(node.data, env, timeoutMs, mock, activeEnvBaseUrl());
|
||||
const t0 = performance.now();
|
||||
try {
|
||||
const res = await http(spec);
|
||||
const durationMs = Math.round(performance.now() - t0);
|
||||
const stepRes: TestStepResult = {
|
||||
stepId: step.id,
|
||||
name: node.name,
|
||||
method: spec.method,
|
||||
url: spec.url,
|
||||
ok: true,
|
||||
httpStatus: res.status,
|
||||
durationMs,
|
||||
error: null,
|
||||
assertionResults: step.assertions
|
||||
.filter((a) => a.enabled)
|
||||
.map((a) => evaluateAssertion(res, a)),
|
||||
};
|
||||
stepRes.ok = stepRes.assertionResults.every((a) => a.passed);
|
||||
|
||||
// 变量提取 → 写入当前环境
|
||||
const extracted = extractVariables(res, step.extracts);
|
||||
if (Object.keys(extracted).length) {
|
||||
useWorkspace.getState().setEnvValues(extracted);
|
||||
}
|
||||
stepResults.push(stepRes);
|
||||
if (!stepRes.ok && !tc.continueOnFailure) break;
|
||||
} catch (e) {
|
||||
stepResults.push({
|
||||
stepId: step.id,
|
||||
name: node.name,
|
||||
method: spec.method,
|
||||
url: spec.url,
|
||||
ok: false,
|
||||
httpStatus: null,
|
||||
durationMs: Math.round(performance.now() - t0),
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
assertionResults: [],
|
||||
});
|
||||
if (!tc.continueOnFailure) break;
|
||||
}
|
||||
}
|
||||
|
||||
const run: TestRun = {
|
||||
id: crypto.randomUUID(),
|
||||
caseId: tc.id,
|
||||
caseName: tc.name,
|
||||
at: Date.now(),
|
||||
trigger,
|
||||
totalMs: Math.round(performance.now() - started),
|
||||
passed: stepResults.length > 0 && stepResults.every((s) => s.ok),
|
||||
stepResults,
|
||||
};
|
||||
useWorkspace.getState().addTestRun(run);
|
||||
return run;
|
||||
}
|
||||
|
||||
function findRequestNode(id: string): RequestNode | undefined {
|
||||
const { collections } = useWorkspace.getState();
|
||||
const walk = (nodes: CollectionNode[]): RequestNode | undefined => {
|
||||
for (const n of nodes) {
|
||||
if (n.type === "request" && n.id === id) return n;
|
||||
if (n.type === "folder") {
|
||||
const hit = walk(n.children);
|
||||
if (hit) return hit;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
for (const c of collections) {
|
||||
const hit = walk(c.children);
|
||||
if (hit) return hit;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
Reference in new issue
Block a user