90 lines
5.9 KiB
JavaScript
90 lines
5.9 KiB
JavaScript
// 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, []);
|
||
});
|
||
}
|