Files
worldquant-alpha-system/frontend/tests/semi-ai.spec.ts
T

189 lines
6.2 KiB
TypeScript
Raw Normal View History

import { choosePreparation } from "./preparation-helpers";
import { expect, test } from "@playwright/test";
// UI contract fixtures: no external model call or simulation is started by these tests.
const fixed = {
id: "semi-fixed-input",
name: "Semi 准备集合",
note: "",
version: 1,
field_count: 1,
dataset_count: 1,
dataset_ids: ["TEST_FIN"],
fields: [],
preparation_ref: { id: "semi-fixed-input", version: 1 },
field_ids: ["TEST_FIN_001"],
field_types: { TEST_FIN_001: "MATRIX" },
collection_version: "fixed-v1",
scope: {
instrument_type: "EQUITY",
region: "USA",
universe: "TOP3000",
delay: 1,
},
};
for (const mode of ["templates", "variants", "features"] as const) {
test(`${mode}: AI generation keeps its hypothesis, fixed inputs and failure draft`, async ({
page,
}) => {
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.route("**/api/v1/data-preparations?*", (route) =>
route.fulfill({
json: { items: [fixed], total: 1, limit: 25, offset: 0 },
}),
);
await page.route(
"**/api/v1/data-preparations/semi-fixed-input/selection?*",
(route) => route.fulfill({ json: fixed }),
);
await page.route(
"**/api/v1/research/input-snapshots/semi-fixed-input",
(route) =>
route.fulfill({ json: { ...fixed, preparation_ref: undefined } }),
);
await page.route(
"**/api/v1/research/assets/semi-generated/versions",
(route) => route.fulfill({ json: [{ version: 1 }] }),
);
await page.goto(`/#${mode}`);
await page
.getByLabel("密码", { exact: true })
.fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
const feature = mode === "features";
await page
.getByRole("button", {
name: feature ? "新建特征方案" : "新建",
exact: true,
})
.click();
const sheet = page.locator(".research-detail:visible");
const input = sheet.getByRole("textbox", {
name: feature ? "特征经济假设" : "本次研究假设",
exact: true,
});
const generate = sheet.getByRole("button", {
name: feature ? "AI 生成新方案" : "AI 生成模板",
exact: true,
});
await expect(generate).toBeDisabled();
await choosePreparation(
page,
fixed.name,
feature ? "选择特征数据准备" : "选择数据准备",
);
await input.click();
if (mode === "variants")
await sheet.getByLabel("种子 Alpha", { exact: true }).fill("TEST0001");
const hypothesis = "检验 <收益> 与规模\n第二轮保留原始假设";
await input.fill(hypothesis);
await expect(
sheet.locator("p").filter({ hasText: /Semi 准备集合 ·/ }),
).toBeVisible();
let release!: () => void;
const pending = new Promise<void>((resolve) => {
release = resolve;
});
let requestBody: Record<string, unknown> | undefined;
await page.route("**/api/v1/research/generate", async (route) => {
requestBody = route.request().postDataJSON();
await pending;
const template = {
name: "AI 生成验收",
description: "",
expression: "rank({field})",
category: "template",
variables: {
field: {
kind: "field",
field_type: "MATRIX",
values: ["TEST_FIN_001"],
},
},
};
await route.fulfill({
json: {
id: "semi-generated",
kind: feature ? "feature" : "template",
name: "AI 生成验收",
version: 1,
content: feature
? {
name: "AI 生成验收",
hypothesis,
input_ids: [fixed.id],
steps: [
{
name: "排名",
rationale: "降低尺度差异",
expression: "rank(TEST_FIN_001)",
},
],
template,
}
: template,
},
});
});
await input.press("Shift+Enter");
await expect
.poll(() => requestBody)
.toMatchObject({
hypothesis,
input_ids: [],
preparation_refs: [fixed.preparation_ref],
method: feature
? "feature"
: mode === "variants"
? "structure"
: "template",
});
if (mode === "variants")
expect(requestBody?.parent_alpha_ids).toEqual(["TEST0001"]);
await expect(generate).toBeDisabled();
await expect(input).toContainText("第二轮保留原始假设");
release();
await expect(
sheet.getByRole("textbox", { name: "模板名称", exact: true }),
).toHaveValue("AI 生成验收");
await expect(generate).toBeEnabled();
await expect(input).toContainText("检验 <收益> 与规模");
await page.screenshot({ path: `../output/playwright/semi-ai-${mode}.png` });
await page.setViewportSize({ width: 390, height: 900 });
await expect(generate).toBeInViewport();
expect(await sheet.evaluate((el) => el.scrollWidth <= el.clientWidth)).toBe(
true,
);
await page.screenshot({
path: `../output/playwright/semi-ai-${mode}-390.png`,
});
if (mode === "templates") {
await page.evaluate(() =>
document.body.setAttribute("theme-mode", "dark"),
);
await page.screenshot({
path: "../output/playwright/semi-ai-templates-dark.png",
});
await page.evaluate(() =>
document.body.setAttribute("theme-mode", "light"),
);
}
await page.route("**/api/v1/research/generate", (route) =>
route.fulfill({ status: 503, json: { detail: "模拟模型暂时不可用" } }),
);
await generate.click();
await expect(
sheet.getByText("模拟模型暂时不可用", { exact: true }),
).toBeVisible();
await expect(input).toContainText("第二轮保留原始假设");
await expect(generate).toBeEnabled();
await input.fill("a".repeat(10001));
await expect(generate).toBeDisabled();
await expect(
sheet.getByRole("alert").filter({ hasText: "10,000" }),
).toBeVisible();
expect(errors).toEqual([]);
});
}