feat: add versioned research templates and alpha variants

This commit is contained in:
yuxuanhui
2026-09-08 21:29:21 +08:00
parent b604e6050e
commit f89ae211d2
55 changed files with 5650 additions and 435 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ for (const renderer of ["future", "toString"]) {
.getByLabel("密码", { exact: true })
.fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
const chat = page.getByRole("complementary", { name: "AI 研究助手" });
await expect(chat.getByText("历史业务操作", { exact: true })).toBeVisible();
await expect(chat.getByRole("button", { name: "确认执行" })).toBeDisabled();
+30 -20
View File
@@ -6,7 +6,7 @@ async function login(page: Page) {
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
await expect(
page.getByRole("button", { name: "打开研究助手" }),
page.getByRole("button", { name: "切换研究助手" }),
).toBeVisible();
}
async function configure(page: Page) {
@@ -63,7 +63,7 @@ test("model settings, text stream, contextual query, confirmation and preserved
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await login(page);
await page.getByRole("button", { name: "个人信息", exact: true }).click();
await page.getByRole("button", { name: "配置", exact: true }).click();
await page
.getByRole("textbox", { name: "模型 Base URL" })
.fill("https://model.test/v1");
@@ -79,6 +79,7 @@ test("model settings, text stream, contextual query, confirmation and preserved
.getByText("启用研究助手(能力测试通过后可开启)", { exact: true })
.click();
await page.getByRole("button", { name: "保存模型配置" }).click();
await page.locator(".settings-drawer .semi-sidesheet-close").click();
await configure(page);
await page.getByRole("button", { name: "Alpha 管理", exact: true }).click();
await page.getByRole("textbox", { name: "搜索 Alpha" }).fill("TEST0001");
@@ -89,7 +90,7 @@ test("model settings, text stream, contextual query, confirmation and preserved
.getByRole("textbox", { name: "研究备注" })
.fill("尚未保存的人工研究草稿");
// A global launcher must remain reachable when the detail mask is open.
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
const chat = page.getByRole("complementary", { name: "AI 研究助手" });
await chat.getByRole("button", { name: "新会话", exact: true }).click();
await expect(chat.locator(".ai-context")).toContainText("TEST0001");
@@ -130,7 +131,7 @@ test("model settings, text stream, contextual query, confirmation and preserved
expect(JSON.stringify(history)).not.toContain("尚未保存的人工研究草稿");
expect(history.runs.at(-1).tools[0].result.filters.turnover_max).toBe(0.15);
await page.reload();
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
await expect(
chat.getByText("筛选换手率不超过 15% 的 Alpha", { exact: true }),
).toBeVisible();
@@ -150,16 +151,17 @@ test("collapse, navigation, reload, narrow viewport and cancellation preserve ex
}) => {
await login(page);
await configure(page);
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
const chat = page.getByRole("complementary", { name: "AI 研究助手" });
await chat.getByRole("button", { name: "新会话", exact: true }).click();
await chat.getByRole("textbox", { name: "发送给研究助手" }).fill("SLOW");
await chat.getByRole("button", { name: "发送", exact: true }).click();
await expect(chat.getByText("正在查询", { exact: true })).toBeVisible();
await chat.getByRole("button", { name: "收起研究助手" }).click();
await page.getByRole("button", { name: "个人信息", exact: true }).click();
await page.getByRole("button", { name: "用户菜单", exact: true }).click();
await page.getByRole("button", { name: "查看个人信息", exact: true }).click();
await page.reload();
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
await expect(
chat.getByText("正在查询,查询完成。", { exact: true }),
).toBeVisible({ timeout: 10000 });
@@ -174,6 +176,7 @@ test("collapse, navigation, reload, narrow viewport and cancellation preserve ex
await chat.getByRole("textbox", { name: "发送给研究助手" }).focus();
await page.keyboard.press("Escape");
await expect(chat).not.toBeVisible();
await page.getByRole("button", { name: "用户菜单" }).click();
await page.getByRole("button", { name: "退出登录" }).click();
await expect(
page.getByRole("heading", { name: "登录研究工作空间" }),
@@ -190,13 +193,10 @@ test("Lark workspace keeps pagination, account details and chat usable at every
await configure(page);
await page.getByRole("button", { name: "Alpha 管理", exact: true }).click();
await expect(page.locator(".alpha-link")).toHaveCount(25);
await expect(page.locator(".sidebar")).toHaveCSS(
await expect(page.locator(".app-sidebar")).toHaveCSS("width", "230px");
await expect(page.locator(".sidebar-nav-item.active")).toHaveCSS(
"background-color",
"rgb(249, 249, 249)",
);
await expect(page.locator(".nav-item.active")).toHaveCSS(
"background-color",
"rgba(31, 35, 41, 0.05)",
"rgb(234, 240, 255)",
);
const chat = page.getByRole("complementary", { name: "AI 研究助手" });
for (const width of [1920, 1440, 1280, 850, 390]) {
@@ -210,7 +210,7 @@ test("Lark workspace keeps pagination, account details and chat usable at every
.locator(".alpha-page-view:not([hidden]) .semi-table-body")
.evaluate((el) => el.scrollTo({ top: 1000 }));
expect((await footer.boundingBox())!.y).toBeCloseTo(before.y, 0);
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
await expect(chat).toBeVisible();
await expect(chat).toHaveCSS("background-color", "rgb(255, 255, 255)");
const box = (await chat.boundingBox())!;
@@ -237,7 +237,8 @@ test("Lark workspace keeps pagination, account details and chat usable at every
await chat.getByRole("button", { name: "收起研究助手" }).click();
}
await page.setViewportSize({ width: 1440, height: 1000 });
await page.getByRole("button", { name: "个人信息", exact: true }).click();
await page.getByRole("button", { name: "用户菜单", exact: true }).click();
await page.getByRole("button", { name: "查看个人信息", exact: true }).click();
await expect(page.getByRole("button", { name: "连接设置" })).toHaveAttribute(
"aria-expanded",
"false",
@@ -246,10 +247,12 @@ test("Lark workspace keeps pagination, account details and chat usable at every
await expect(page.getByRole("row", { name: /回测/ })).toContainText(
"10,000(本地设定)",
);
await page.getByRole("button", { name: "配置", exact: true }).click();
await page
.getByRole("textbox", { name: "模型标识", exact: true })
.fill("保留模型设置草稿");
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.locator(".settings-drawer .semi-sidesheet-close").click();
await page.getByRole("button", { name: "切换研究助手" }).click();
const resize = chat.getByRole("separator", { name: "调整助手宽度" });
await resize.focus();
for (let i = 0; i < 11; i++) await resize.press("ArrowLeft");
@@ -258,12 +261,19 @@ test("Lark workspace keeps pagination, account details and chat usable at every
const input = chat.getByRole("textbox", { name: "发送给研究助手" });
await input.fill("保留聊天草稿");
await page.getByRole("button", { name: "Alpha 管理", exact: true }).click();
await page.getByRole("button", { name: "个人信息", exact: true }).click();
await page.getByRole("button", { name: "用户菜单", exact: true }).click();
await page.getByRole("button", { name: "查看个人信息", exact: true }).click();
await expect(input).toHaveValue("保留聊天草稿");
await chat.getByRole("button", { name: "收起研究助手" }).click();
await page.getByRole("button", { name: "配置", exact: true }).click();
await expect(
page.getByRole("textbox", { name: "模型标识", exact: true }),
).toHaveValue("保留模型设置草稿");
await chat.getByRole("button", { name: "收起研究助手" }).click();
await page.screenshot({
path: "../output/playwright/settings-drawer.png",
animations: "disabled",
});
await page.locator(".settings-drawer .semi-sidesheet-close").click();
await page.screenshot({
path: "../output/playwright/lark-account.png",
fullPage: true,
@@ -277,7 +287,7 @@ test("Markdown streams incomplete code, finishes safely and restores history", a
page.on("pageerror", (error) => errors.push(error.message));
await login(page);
await configure(page);
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
const chat = page.getByRole("complementary", { name: "AI 研究助手" });
await chat.getByRole("button", { name: "新会话", exact: true }).click();
const prefix = "## 研究结论\n\n**正在生成**\n\n```python\nrank(close)";
@@ -391,7 +401,7 @@ test("Markdown streams incomplete code, finishes safely and restores history", a
).toBe(true);
await page.screenshot({ path: "../output/playwright/ai-markdown.png" });
await page.reload();
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
await expect(chat.getByRole("heading", { name: "研究结论" })).toBeVisible();
await expect(chat.getByRole("cell", { name: "1.2" })).toBeVisible();
expect(errors).toEqual([]);
+1 -1
View File
@@ -84,7 +84,7 @@ test("AI prepares one fixed preview, confirms once, and shows live run independe
data: { ...config, enabled: true },
});
await page.reload();
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
const chat = page.getByRole("complementary", { name: "AI 研究助手" });
await chat.getByRole("button", { name: "新会话", exact: true }).click();
const before = (
+1 -1
View File
@@ -279,7 +279,7 @@ test("平台选项失败可重试,地区联动限制 Delay 和 Universe", asyn
}),
);
await page.getByRole("button", { name: "Alpha 管理", exact: true }).click();
await page.getByRole("button", { name: "数据集", exact: true }).click();
await page.getByRole("button", { name: "数据目录", exact: true }).click();
await expect(page.getByText("测试平台选项不可用")).toBeVisible();
await expect(
page.getByRole("button", { name: "同步目录", exact: true }),
+2 -2
View File
@@ -17,7 +17,7 @@ test("fixed input, AI approval, backtest and research source remain connected",
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
await expect(
page.getByRole("button", { name: "打开研究助手" }),
page.getByRole("button", { name: "切换研究助手" }),
).toBeVisible();
const config = { base_url: "https://model.test/v1", model: "test-model" };
await page.request.put("/api/v1/ai/settings", {
@@ -103,7 +103,7 @@ test("fixed input, AI approval, backtest and research source remain connected",
);
expect(response.status()).toBe(200);
await page.reload();
await page.getByRole("button", { name: "打开研究助手" }).click();
await page.getByRole("button", { name: "切换研究助手" }).click();
const chat = page.getByRole("complementary", { name: "AI 研究助手" });
await expect(chat.getByRole("button", { name: "确认执行" })).toBeEnabled();
expect(
+91
View File
@@ -0,0 +1,91 @@
import { expect, test } from "@playwright/test";
test("sidebar geometry, recent navigation, user menu and compact tools", async ({
page,
}) => {
await page.goto("/");
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
const sidebar = page.locator(".app-sidebar");
const nav = page.getByRole("navigation", { name: "主导航", exact: true });
const recent = page.getByRole("navigation", {
name: "最近访问",
exact: true,
});
await expect(sidebar).toHaveCSS("width", "230px");
await expect(page.locator(".workspace")).toHaveCSS("padding-left", "230px");
await expect(page.getByRole("toolbar").getByRole("button")).toHaveCount(3);
await expect(page.locator(".topbar")).toHaveCount(0);
await page.getByRole("button", { name: "配置", exact: true }).click();
await expect(page.locator("#model-settings")).toBeInViewport();
await expect(
nav.getByRole("button", { name: "Alpha 管理", exact: true }),
).toHaveAttribute("aria-current", "page");
await page.locator(".settings-drawer .semi-sidesheet-close").click();
await page.getByRole("button", { name: "用户菜单", exact: true }).click();
await page.getByRole("button", { name: "查看个人信息", exact: true }).click();
await expect(page.locator(".account-page #model-settings")).toHaveCount(0);
await nav.getByRole("button", { name: "数据目录", exact: true }).click();
await recent
.getByRole("button", { name: "关闭最近访问:数据目录", exact: true })
.focus();
await page.keyboard.press("Enter");
await expect(page).toHaveURL(/#account$/);
await page.getByRole("button", { name: "全部关闭" }).click();
await expect(recent.locator(".recent-item")).toHaveCount(1);
await expect(
recent.getByRole("button", { name: "关闭最近访问:个人信息", exact: true }),
).toBeDisabled();
await page.screenshot({ path: "../output/playwright/sidebar-expanded.png" });
await page.getByRole("button", { name: "收起导航", exact: true }).click();
await expect(sidebar).toHaveCSS("width", "64px");
await expect(page.locator(".workspace")).toHaveCSS("padding-left", "64px");
await nav.getByRole("button", { name: "Alpha 管理", exact: true }).click();
await page.getByRole("button", { name: "用户菜单" }).click();
await expect(page.getByRole("button", { name: "退出登录" })).toBeVisible();
await page.getByRole("button", { name: "切换深色主题" }).click();
await expect(page.locator("body")).toHaveAttribute("theme-mode", "dark");
await page.screenshot({
path: "../output/playwright/sidebar-collapsed-dark.png",
});
await page.getByRole("button", { name: "切换浅色主题" }).click();
await expect(page.locator("body")).toHaveAttribute("theme-mode", "light");
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "展开导航", exact: true }).click();
await page.getByRole("button", { name: "快捷导航" }).click();
await page
.getByRole("dialog")
.getByRole("button", { name: "回测研究" })
.click();
await expect(
nav.getByRole("button", { name: "回测研究", exact: true }),
).toHaveAttribute("aria-current", "page");
await page.getByRole("button", { name: "同步任务", exact: true }).click();
await expect(page.locator(".job-panel")).toBeVisible();
await page.locator(".job-panel .semi-sidesheet-close").click();
await expect(page.locator(".job-panel")).not.toBeVisible();
await page.getByRole("button", { name: "切换研究助手" }).click();
await expect(page.locator(".ai-chat")).toBeVisible();
await page.getByRole("button", { name: "收起研究助手" }).click();
await page.setViewportSize({ width: 390, height: 900 });
await expect(sidebar).not.toBeVisible();
await expect(page.locator(".workspace")).toHaveCSS("padding-left", "0px");
await page.keyboard.press("Control+k");
await page
.getByRole("dialog")
.getByRole("button", { name: "Alpha 管理" })
.click();
await expect(
page.getByRole("tab", { name: "待提交", exact: true }),
).toBeVisible();
expect(
await page.evaluate(
() => document.documentElement.scrollWidth <= innerWidth,
),
).toBe(true);
await expect(page.getByRole("dialog")).not.toBeVisible();
await page.screenshot({
path: "../output/playwright/sidebar-mobile.png",
animations: "disabled",
});
});
+10 -4
View File
@@ -15,7 +15,8 @@ test("account → scoped sync → research → resync → PnL → filtered expor
await expect(page.getByText("用户名或密码不正确")).toBeVisible();
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
await page.getByRole("button", { name: "个人信息", exact: true }).click();
await page.getByRole("button", { name: "用户菜单", exact: true }).click();
await page.getByRole("button", { name: "查看个人信息", exact: true }).click();
const connectionSettings = page.getByRole("button", {
name: "连接设置",
exact: true,
@@ -81,7 +82,7 @@ test("account → scoped sync → research → resync → PnL → filtered expor
await page.keyboard.press("Escape");
await expect(page.locator(".job-panel")).not.toBeVisible();
await page.getByRole("tab", { name: "待提交", exact: true }).click();
await expect(page.locator(".library-stats strong").first()).toHaveText("620");
await expect(page.locator(".table-toolbar .count-pill")).toHaveText("413");
await page.screenshot({
path: "../output/playwright/alpha-library.png",
fullPage: true,
@@ -160,6 +161,7 @@ test("account → scoped sync → research → resync → PnL → filtered expor
expect(contents).toContain("TEST0001");
expect(contents).not.toContain("TEST0000");
expect((contents.match(/TEST\d{4}/g) ?? []).length).toBe(413);
await page.getByRole("button", { name: "用户菜单" }).click();
await page.getByRole("button", { name: "退出登录" }).click();
await expect(
page.getByRole("heading", { name: "登录研究工作空间" }),
@@ -175,7 +177,7 @@ test("batch tags, column visibility, server pagination and saved preferences", a
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
await expect(
page.getByRole("heading", { name: "Alpha 管理", exact: true }),
page.getByRole("tab", { name: "待提交", exact: true }),
).toBeVisible();
// Each test can run alone against the isolated browser acceptance server.
if ((await (await page.request.get("/api/v1/alphas")).json()).total !== 620) {
@@ -252,11 +254,15 @@ test("batch tags, column visibility, server pagination and saved preferences", a
const firstId = await page.locator(".alpha-link").first().innerText();
await page.locator(".semi-page-item").filter({ hasText: /^2$/ }).click();
await expect(page.locator(".alpha-link").first()).not.toHaveText(firstId);
await page.getByRole("button", { name: "个人信息", exact: true }).click();
await page.getByRole("button", { name: "用户菜单", exact: true }).click();
await page.getByRole("button", { name: "查看个人信息", exact: true }).click();
await expect(page.locator(".account-page .preferences-form")).toHaveCount(0);
await page.getByRole("button", { name: "配置", exact: true }).click();
await page.getByRole("textbox", { name: "显示名称" }).fill("Alpha 研究室");
await page.getByRole("button", { name: "保存偏好", exact: true }).click();
await expect(page.locator(".account-name")).toHaveText("Alpha 研究室");
await page.reload();
await page.getByRole("button", { name: "配置", exact: true }).click();
await expect(page.getByRole("textbox", { name: "显示名称" })).toHaveValue(
"Alpha 研究室",
);