Files
magpie/internal/gui/tests/agent-cli-missing.test.cjs
T
yetone 5268a3308e agent, gui: an agent whose CLI is gone while its settings are left says so, and Install another agent offers it again (evilgaoshu, #843: 卸载了 DeepSeek Harness,但 ~/.dsh 还在,Agents 页面依然把它当作已安装列出来,也不会出现在 Install another agent 里) — an agent was here when its config or folder was, so dsh, uninstalled with ~/.dsh kept, was listed as installed, and Install another agent left out every agent listed. Agent.CLIMissing: its folder or config is here, its CLI isn't, neither on PATH nor where users' tools go (proc.FindTool, as #839's Claude Code is found on Windows); only for an agent that is its CLI and has an install command magpie knows, not one whose folder an app or editor extension keeps too (Codex, Claude Code, Gemini CLI, OpenCode, Cline, Goose), nor a WSL one. Its row stays, settings kept and nothing deleted, with "CLI not found" where its version goes (its tooltip names the folder and points to the install command), and Install another agent lists it, marked "CLI not found" under its name. dsh now has its npm package, @deepseek-ai/dsh, so it is offered with npm install -g @deepseek-ai/dsh, and its version and updates show like the other CLIs'. zh, ja, de strings. TestCLIMissing (dsh with ~/.dsh and no dsh is missing and offered; codex's folder, an agent not here, a WSL one aren't; dsh in ~/.local/bin off PATH isn't) fails on the old code; agent-cli-missing.test.cjs (WebKit, en and zh: the row's note, its height kept, Install another agent lists it with its command) fails on the old code. agent-update-all, agent-layout, agent-start-pick, click-scroll, gui-ja, gui-de and import-row pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 09:25:01 +08:00

90 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Run with Node's test runner and Playwright on the module path; see README.md.
// #843 (evilgaoshu): DeepSeek Harness uninstalled, its ~/.dsh left behind,
// was listed on the Agents page as if it were installed, and Install
// another agent didn't offer it. Now its row says its CLI isn't found (its
// settings are kept, the row stays), and Install another agent has it with
// its install command, marked the same. No backend: the API is faked here.
const assert = require("node:assert/strict");
const fs = require("node:fs/promises");
const path = require("node:path");
const { test } = require("node:test");
const { chromium, webkit } = require("playwright");
const assets = path.resolve(__dirname, "../assets");
const models = ["model-a", "model-b"].map((m) => ({ value: m, label: m }));
const agent = (id, name, more = {}) => ({
id, name, path: "~/." + id + "/config.yaml", fields: [{ key: "model", label: "model", value: "model-a", options: models }], ...more,
});
const AGENTS = [agent("crush", "Crush"), agent("dsh", "DeepSeek Harness", { cliMissing: true })];
const CLIS = { crush: { version: "0.3.0", latest: "0.3.0", via: "brew", command: "brew upgrade crush" } };
const INSTALLS = [
{ id: "dsh", name: "DeepSeek Harness", icon: "deepseek-color", missing: true, commands: [{ via: "npm", command: "npm install -g @deepseek-ai/dsh" }] },
{ id: "opencode", name: "OpenCode", icon: "opencode", commands: [{ via: "npm", command: "npm install -g opencode-ai" }] },
];
function server(lang) {
return async (route) => {
const req = route.request(), url = new URL(req.url());
if (url.pathname === "/boot.js") return route.fulfill({ contentType: "text/javascript", body: `window.bootPrefs = {lang:"${lang}",theme:"light",web:true};` });
if (url.pathname === "/wails/runtime.js") return route.fulfill({ contentType: "text/javascript", body: "export const Window = {};" });
if (url.pathname === "/api/state") return route.fulfill({ json: { agents: AGENTS, profiles: [], settings: { lang, theme: "light" } } });
if (url.pathname === "/api/agents/cli" && req.method() === "GET") return route.fulfill({ json: { agents: CLIS, pending: false } });
if (url.pathname === "/api/agents/install") return route.fulfill({ json: INSTALLS });
if (url.pathname === "/api/usage/quotas") return route.fulfill({ json: [] });
if (url.pathname === "/api/groups") return route.fulfill({ json: { groups: [] } });
if (url.pathname === "/api/providers") return route.fulfill({ json: { providers: [], gateway: { running: true } } });
if (url.pathname.startsWith("/api/")) return route.fulfill({ json: {} });
const file = path.join(assets, url.pathname === "/" ? "index.html" : url.pathname);
const contentType = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".png": "image/png" }[path.extname(file)];
await route.fulfill({ body: await fs.readFile(file), contentType });
};
}
const row = (id) => `.row.agent[data-id="${id}"]`;
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
test(engine + ": an agent whose CLI is gone says so, and is offered again", async (t) => {
assert(["chromium", "webkit"].includes(engine), "BROWSER must be chromium or webkit");
const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" }));
const errors = [];
t.after(async () => {
if (errors.length) console.log(errors);
await browser.close();
});
for (const [lang, said, also] of [["en", "CLI not found", "Install another agent (2)"], ["zh", "未找到 CLI", "安装其他 Agent(2)"]]) {
await t.test(lang, async () => {
const page = await (await browser.newContext({ viewport: { width: 980, height: 700 } })).newPage();
page.setDefaultTimeout(5000);
page.on("pageerror", (e) => errors.push(e.message));
await page.route("**/*", server(lang));
await page.goto("http://magpie.test/");
await page.locator(`${row("crush")} .ag-ver`).waitFor();
// the row stays, its settings kept, and says its CLI isn't found
const tag = page.locator(`${row("dsh")} .ag-missing`);
assert.equal(await tag.textContent(), said);
assert.match(await tag.getAttribute("title"), /~\/\.dsh\/config\.yaml/);
assert.equal(await page.locator(`${row("dsh")} .ag-ver`).count(), 0);
assert.equal(await page.locator(`${row("crush")} .ag-missing`).count(), 0, "an installed one says nothing of it");
// on its name's line, not pushing the row taller than crush's
const h = (id) => page.locator(row(id)).evaluate((r) => r.getBoundingClientRect().height);
assert(Math.abs(await h("dsh") - await h("crush")) < 0.5, "the row grew");
// Install another agent offers it, though it is listed above
const box = page.locator("#agentsInstall");
assert.equal(await box.locator(".ag-install-head").textContent(), also);
await box.locator(".ag-install-head").click();
assert.deepEqual(await box.locator(".ag-install-row").evaluateAll((rs) => rs.map((r) => r.dataset.id)), ["dsh", "opencode"]);
const dsh = box.locator('.ag-install-row[data-id="dsh"]');
assert.equal(await dsh.locator("b").textContent(), "DeepSeek Harness");
assert.equal(await dsh.locator(".ag-install-missing").textContent(), said);
assert.equal(await dsh.locator("code").textContent(), "npm install -g @deepseek-ai/dsh");
assert.equal(await box.locator('.ag-install-row[data-id="opencode"] .ag-install-missing').count(), 0);
// the name isn't cut by the note under it
assert(await dsh.locator("b").evaluate((b) => b.scrollWidth <= b.clientWidth + 1), "name cut");
await page.screenshot({ path: path.join(process.env.SHOTS || "/tmp", `cli-missing-${engine}-${lang}.png`), fullPage: true }).catch(() => {});
await page.context().close();
});
}
assert.deepEqual(errors, []);
});
}