返回 DeepSeek-Reasonix
manual-creation.mjs
根目录 / desktop / frontend / bench / manual-creation.mjs
1 import assert from "node:assert/strict";
2 import { mkdir } from "node:fs/promises";
3 import { tmpdir } from "node:os";
4 import path from "node:path";
5 import { fileURLToPath } from "node:url";
6 import { createServer } from "vite";
7 import { newSessionButton } from "./app-page-actions.mjs";
8
9 const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10 process.env.PLAYWRIGHT_BROWSERS_PATH ??= path.join(root, ".pw-browsers");
11 const { chromium } = await import("playwright");
12 const server = await createServer({ root, logLevel: "error", server: { host: "127.0.0.1", port: 0 } });
13 await server.listen();
14 let browser;
15 try {
16 browser = await chromium.launch({ headless: true, executablePath: process.env.CHROME_EXECUTABLE });
17 const page = await browser.newPage({ viewport: { width: 1280, height: 900 }, reducedMotion: "reduce" });
18 const errors = [];
19 page.on("pageerror", error => errors.push(error.message));
20 const address = server.httpServer.address();
21 await page.goto(`http://127.0.0.1:${address.port}/?mock=bench&bench=1`);
22 const input = page.locator("textarea.composer__input:not([aria-hidden=true])");
23 await input.waitFor();
24 await page.evaluate(async () => {
25 const { app } = await import("/src/lib/bridge.ts");
26 const { DESKTOP_COMMANDS } = await import("/src/generated/desktopContract.generated.ts");
27 const { installDesktopHostStub } = await import("/src/__tests__/desktopHostStub.ts");
28 const fallback = Object.fromEntries(DESKTOP_COMMANDS.map(key => [key, app[key]]));
29 const operations = new Map();
30 const observedReady = new Set();
31 const reads = new Map();
32 const operationFor = meta => [...operations.values()].find(op => op.ref.sessionId === (meta.session?.sessionId || meta.sessionId));
33 const startup = meta => {
34 const operation = operationFor(meta);
35 return operation?.phase === "starting" ? { ...meta, ready: false } : meta;
36 };
37 installDesktopHostStub({ ...fallback,
38 BeginManualSessionCreation: async request => {
39 // The mock creates the durable identity; hold runtime readiness behind
40 // an explicit fixture completion, just like the host's reserved tab.
41 const result = await fallback.BeginManualSessionCreation(request);
42 const operation = { ...result, phase: "starting", surfaceReady: true };
43 operations.set(request.operationId, operation);
44 return structuredClone(operation);
45 },
46 GetManualSessionCreation: async id => {
47 reads.set(id, (reads.get(id) || 0) + 1);
48 const operation = operations.get(id);
49 if (operation.phase === "ready") observedReady.add(id);
50 return structuredClone(operation);
51 },
52 ListManualSessionCreations: async () => [...operations.values()].filter(op => op.phase !== "ready"),
53 ListTabs: async () => (await fallback.ListTabs()).map(startup),
54 MetaForTab: async id => startup(await fallback.MetaForTab(id)),
55 });
56 window.manualCreationFixture = {
57 operations: () => [...operations.values()],
58 finish: id => { operations.get(id).phase = "ready"; },
59 observedReady: id => observedReady.has(id),
60 reads: id => reads.get(id) || 0,
61 active: async () => (await app.ListTabs()).find(tab => tab.active)?.session?.sessionId,
62 };
63 });
64 await input.fill("Existing session input");
65 await newSessionButton(page).click();
66 await page.waitForFunction(() => window.manualCreationFixture.operations().length === 1);
67 await input.waitFor({ state: "visible" });
68 await page.waitForFunction(() => {
69 const input = document.querySelector("textarea.composer__input:not([aria-hidden=true])");
70 return input && !input.closest("[inert]") && !input.disabled && input.value === "";
71 });
72 const first = await page.evaluate(() => window.manualCreationFixture.operations()[0]);
73 assert.equal(first.phase, "starting");
74 await input.fill("First new session input while starting");
75 assert.equal(await page.locator(".composer__btn--send").isDisabled(), true, "typing never sends before the new controller is ready");
76 const notice = page.getByTestId("creation-notice");
77 await notice.getByText(/You can type now/).waitFor();
78 assert.equal(await notice.locator("button").count(), 0, "preparation does not ask for any user choice");
79 const noticeBox = await notice.boundingBox();
80 const inputBox = await input.boundingBox();
81 assert.ok(noticeBox.y + noticeBox.height <= inputBox.y, "preparation explanation is adjacent to and above the composer");
82 const evidence = process.env.REASONIX_CREATION_EVIDENCE ?? path.join(tmpdir(), "reasonix-manual-creation-evidence");
83 await mkdir(evidence, { recursive: true });
84 await page.screenshot({ path: path.join(evidence, "starting-editable.png") });
85
86 await newSessionButton(page).click();
87 await page.waitForFunction(() => window.manualCreationFixture.operations().length === 2);
88 await page.waitForFunction(() => {
89 const input = document.querySelector("textarea.composer__input:not([aria-hidden=true])");
90 return input && !input.closest("[inert]") && input.value === "";
91 });
92 const second = await page.evaluate(() => window.manualCreationFixture.operations()[1]);
93 assert.notEqual(second.ref.sessionId, first.ref.sessionId, "each explicit new action allocates its own session");
94 await input.fill("Second new session input while starting");
95 const firstReads = await page.evaluate(id => window.manualCreationFixture.reads(id), first.operationId);
96 await page.evaluate(id => window.manualCreationFixture.finish(id), first.operationId);
97 assert.equal(await page.evaluate(() => window.manualCreationFixture.active()), second.ref.sessionId);
98 assert.equal(await input.inputValue(), "Second new session input while starting");
99 await page.evaluate(id => window.manualCreationFixture.finish(id), second.operationId);
100 await page.waitForFunction(id => window.manualCreationFixture.observedReady(id), second.operationId);
101 await page.waitForFunction(() => !document.querySelector(".composer__btn--send")?.disabled);
102 await notice.waitFor({ state: "detached" });
103 assert.equal(await input.inputValue(), "Second new session input while starting", "runtime completion preserves typed input");
104 assert.equal(await page.evaluate(() => window.manualCreationFixture.active()), second.ref.sessionId, "out-of-order startup never changes the user's selection");
105 assert.equal(await page.evaluate(id => window.manualCreationFixture.reads(id), first.operationId), firstReads, "leaving a starting session stops its request polling while the host continues");
106 assert.deepEqual(errors, []);
107 await page.screenshot({ path: path.join(evidence, "ready-input-preserved.png") });
108 console.log(`PASS manual creation: editable starting input, isolated new identities, send readiness and out-of-order completion. Evidence: ${evidence}`);
109 } finally {
110 await browser?.close();
111 await server.close();
112 }
113
113 lines Plain Text