返回 DeepSeek-Reasonix
approval-animation.mjs
根目录 / desktop / frontend / bench / approval-animation.mjs
1 #!/usr/bin/env node
2
3 import path from "node:path";
4 import { fileURLToPath } from "node:url";
5 import { startPreviewServer } from "./vite-preview-server.mjs";
6
7 const frontendDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8 process.env.PLAYWRIGHT_BROWSERS_PATH = !process.env.PLAYWRIGHT_BROWSERS_PATH || process.env.PLAYWRIGHT_BROWSERS_PATH === ".pw-browsers"
9 ? path.join(frontendDir, ".pw-browsers")
10 : process.env.PLAYWRIGHT_BROWSERS_PATH;
11 const { chromium } = await import("playwright");
12 const port = Number(process.env.REASONIX_APPROVAL_BROWSER_PORT ?? 4620);
13 const url = `http://127.0.0.1:${port}/`;
14
15 function assert(condition, message) {
16 if (!condition) throw new Error(message);
17 process.stdout.write(` PASS ${message}\n`);
18 }
19
20 const preview = await startPreviewServer(frontendDir, port);
21
22 let browser;
23 try {
24 browser = await chromium.launch({ headless: true });
25 const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
26 const pageErrors = [];
27 page.on("pageerror", (error) => pageErrors.push(String(error)));
28 await page.addInitScript(() => {
29 window.__reasonixApprovalAnimationCalls = [];
30 const original = Element.prototype.animate;
31 Element.prototype.animate = function (frames, options) {
32 if (this instanceof HTMLElement && this.querySelector(".prompt-shelf")) {
33 window.__reasonixApprovalAnimationCalls.push({
34 easing: typeof options === "object" && options ? String(options.easing ?? "") : "",
35 });
36 }
37 return original.call(this, frames, options);
38 };
39 });
40
41 const response = await page.goto(url, { waitUntil: "domcontentloaded" });
42 assert(response?.ok(), `approval preview serves the frontend (HTTP ${response?.status()})`);
43 await page.waitForFunction(() => !document.querySelector(".startup-splash"), undefined, { timeout: 30_000 });
44 const composer = page.locator("#composer-input");
45 await composer.waitFor({ state: "visible", timeout: 30_000 });
46 await composer.fill("/mock-tool-approval");
47 await page.locator(".composer__btn--send").click();
48
49 const action = page.locator(".prompt-shelf__actions .prompt-action").first();
50 await action.waitFor({ state: "visible", timeout: 30_000 });
51 await action.click();
52 await page.locator(".decision-confirm-bar__confirm").click();
53 try {
54 await page.waitForFunction(() => !document.querySelector(".prompt-shelf--tool-approval"), undefined, { timeout: 10_000 });
55 } catch (error) {
56 const diagnostics = await page.evaluate(() => {
57 const shelf = document.querySelector(".prompt-shelf--tool-approval");
58 const wrapper = shelf?.parentElement;
59 return {
60 shelfText: shelf?.textContent?.replace(/\s+/g, " ").trim(),
61 wrapperStyle: wrapper?.getAttribute("style"),
62 disabledActions: [...document.querySelectorAll(".prompt-shelf button:disabled")].map((button) => button.textContent?.trim()),
63 notices: [...document.querySelectorAll(".toast, .notice, [role='alert']")].map((notice) => notice.textContent?.replace(/\s+/g, " ").trim()),
64 };
65 });
66 throw new Error(`approval card did not resolve: ${JSON.stringify(diagnostics)}`, { cause: error });
67 }
68
69 const calls = await page.evaluate(() => window.__reasonixApprovalAnimationCalls ?? []);
70 assert(calls.length === 1, `approval invokes one native Web Animation (${JSON.stringify(calls)})`);
71 assert(calls[0]?.easing === "cubic-bezier(0.8, 0, 0.8, 0.28)", `approval uses the CSS easing contract (${calls[0]?.easing})`);
72 assert(pageErrors.length === 0, `approval interaction completes without page errors (${JSON.stringify(pageErrors)})`);
73 process.stdout.write("\napproval animation browser gate passed\n");
74 } finally {
75 await browser?.close();
76 await new Promise((resolve) => preview.httpServer.close(resolve));
77 }
78
78 lines Plain Text