返回 DeepSeek-Reasonix
session-navigation-smoke.mjs
根目录 / desktop / packaging / session-navigation-smoke.mjs
1 // Real packaged sidebar -> navigation owner -> service -> transcript regression.
2 // Usage: node desktop/packaging/session-navigation-smoke.mjs /path/Reasonix.app
3 import assert from "node:assert/strict";
4 import { createRequire } from "node:module";
5 import { createServer } from "node:http";
6 import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
7 import { join } from "node:path";
8 import { tmpdir } from "node:os";
9 import { packagedSmokeEnv } from "./smoke-env.mjs";
10 import { waitForSmokeCondition } from "./smoke-poll.mjs";
11
12 const require = createRequire(new URL("../electron/package.json", import.meta.url));
13 const { _electron } = require("playwright");
14 const home = mkdtempSync(join(tmpdir(), "reasonix-sidebar-navigation-"));
15 const server = createServer(async (req, res) => {
16 let raw = "";
17 for await (const chunk of req) raw += chunk;
18 const request = JSON.parse(raw || "{}");
19 const messages = JSON.stringify(request.messages ?? []);
20 const marker = messages.includes("NAV_BETA") ? "NAV_BETA" : "NAV_ALPHA";
21 res.writeHead(200, { "Content-Type": "text/event-stream" });
22 res.end(`data: ${JSON.stringify({ id: "fixture", choices: [{ index: 0, delta: { content: `ANSWER_${marker}` }, finish_reason: null }] })}\n\n`
23 + `data: ${JSON.stringify({ id: "fixture", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}\n\ndata: [DONE]\n\n`);
24 });
25 await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
26 writeFileSync(join(home, "config.toml"), `default_model = "fixture/model"\n[desktop]\nprovider_access = ["fixture"]\n[[providers]]\nname = "fixture"\nkind = "openai"\nbase_url = "http://127.0.0.1:${server.address().port}/v1"\nmodels = ["model"]\ndefault = "model"\napi_key_env = "SIDEBAR_FIXTURE_KEY"\n`);
27 let application;
28 try {
29 application = await _electron.launch({ executablePath: join(process.argv[2], "Contents/MacOS/Reasonix"),
30 env: { ...packagedSmokeEnv(process.env, home), SIDEBAR_FIXTURE_KEY: "local-fixture" } });
31 const page = await application.firstWindow();
32 page.setDefaultTimeout(15000);
33 const errors = [];
34 page.on("pageerror", error => errors.push(error.message));
35 await page.waitForFunction(() => Boolean(window.reasonixDesktop));
36 const invoke = (method, args = []) => page.evaluate(({ method, args }) => window.reasonixDesktop.invoke(method, args), { method, args });
37 const active = async () => (await invoke("ListTabs")).find(tab => tab.active);
38 const transcriptContains = (text, expected = true) => page.waitForFunction(({ text, expected }) =>
39 (document.querySelector(".chat-transcript")?.textContent?.includes(text) ?? false) === expected, { text, expected });
40 const refs = {};
41 await invoke("CreateSession", ["global"]);
42 for (const marker of ["NAV_ALPHA", "NAV_BETA"]) {
43 console.log("Seeding", marker);
44 if (marker === "NAV_BETA") {
45 await page.locator(".sidebar__quick-action").click();
46 await transcriptContains("ANSWER_NAV_ALPHA", false);
47 }
48 const composer = page.locator("textarea").first();
49 for (let attempt = 0; attempt < 100; attempt++) {
50 await composer.fill(marker);
51 if (await page.locator(".composer__btn--send").isEnabled()) break;
52 await page.waitForTimeout(100);
53 }
54 await page.locator(".composer__btn--send").click();
55 await transcriptContains(`ANSWER_${marker}`);
56 await waitForSmokeCondition(async () => (await invoke("ListTabs")).every(tab => !tab.running));
57 refs[marker] = (await active()).session;
58 await invoke("RenameCanonicalSession", [refs[marker], marker]);
59 }
60 assert.notEqual(refs.NAV_ALPHA.sessionId, refs.NAV_BETA.sessionId);
61 const sessionRow = marker => page.locator(".project-tree__topic-main").filter({ has: page.getByText(marker, { exact: true }) });
62 for (const marker of ["NAV_ALPHA", "NAV_BETA", "NAV_ALPHA"]) {
63 await sessionRow(marker).click();
64 await transcriptContains(`ANSWER_${marker}`);
65 const other = marker === "NAV_ALPHA" ? "NAV_BETA" : "NAV_ALPHA";
66 await transcriptContains(`ANSWER_${other}`, false);
67 assert.equal((await active()).session.sessionId, refs[marker].sessionId);
68 assert.equal(await sessionRow(marker).locator("xpath=..").evaluate(node => node.classList.contains("project-tree__topic--active")), true);
69 }
70 await page.evaluate(markers => {
71 for (const marker of markers) {
72 const label = [...document.querySelectorAll(".project-tree__topic-label")]
73 .find(candidate => candidate.textContent?.trim() === marker);
74 const row = label?.closest(".project-tree__topic-main");
75 if (!row) throw new Error(`missing project tree row ${marker}`);
76 row.click();
77 }
78 }, ["NAV_BETA", "NAV_ALPHA", "NAV_BETA"]);
79 await transcriptContains("ANSWER_NAV_BETA");
80 await transcriptContains("ANSWER_NAV_ALPHA", false);
81 assert.equal((await active()).session.sessionId, refs.NAV_BETA.sessionId);
82 assert.deepEqual(errors, []);
83 console.log("PASS packaged sidebar: create after completed turn, A/B/A selection and transcript agree, rapid clicks keep the last target; no page errors");
84 } finally {
85 await application?.close();
86 server.closeAllConnections();
87 await new Promise(resolve => server.close(resolve));
88 rmSync(home, { recursive: true, force: true });
89 }
90
90 lines Plain Text