桌面 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:
logan committed 2026-10-16 03:03:36 +08:00
1 parent 6452026fd6
commit 2bb4ef7a95
299 files changed
+6059 -3322

No files matched your search

+137
View File
@@ -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;
}