import { expect, test } from "@playwright/test"; const headers = { "X-WQ-Request": "1" }; const scope = { instrument_type: "EQUITY", region: "USA", universe: "TOP3000", delay: 1, }; test("fixed input, AI approval, backtest and research source remain connected", async ({ page, }) => { const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); await page.goto("/#datasets"); await page.getByLabel("密码", { exact: true }).fill("browser-test-password"); await page.getByRole("button", { name: "进入工作空间" }).click(); await expect( page.getByRole("button", { name: "打开研究助手" }), ).toBeVisible(); const config = { base_url: "https://model.test/v1", model: "test-model" }; await page.request.put("/api/v1/ai/settings", { headers, data: { ...config, api_key: "synthetic-key" }, }); expect( ( await ( await page.request.post("/api/v1/ai/settings/test", { headers }) ).json() ).ready, ).toBe(true); await page.request.put("/api/v1/ai/settings", { headers, data: { ...config, enabled: true }, }); await page.request.put("/api/v1/account/credentials", { headers, data: { email: "test@example.com", password: "synthetic-password" }, }); await page.request.post("/api/v1/account/connect", { headers }); await expect .poll( async () => (await (await page.request.get("/api/v1/account")).json()) .connection_status, ) .toBe("connected"); for (const dataset_id of [null, "TEST_FIN"]) { const response = await page.request.post("/api/v1/catalog/sync-jobs", { headers, data: { scope, dataset_id }, }); expect(response.status()).toBe(202); const job = await response.json(); await expect .poll( async () => (await (await page.request.get(`/api/v1/sync-jobs/${job.id}`)).json()) .status, ) .toBe("completed"); } const params = new URLSearchParams( Object.entries(scope).map(([k, v]) => [k, String(v)]), ); const fields = await ( await page.request.get(`/api/v1/catalog/datasets/TEST_FIN/fields?${params}`) ).json(); const saved = await page.request.post("/api/v1/catalog/inputs", { headers, data: { scope, dataset_id: "TEST_FIN", collection_version: fields.collection_version, selection: "all", }, }); expect(saved.status()).toBe(201); const input = await saved.json(); const conversation = await ( await page.request.post("/api/v1/ai/conversations", { headers }) ).json(); const before = ( await (await page.request.get("/api/v1/backtests/runs")).json() ).total; const response = await page.request.post( `/api/v1/ai/conversations/${conversation.id}/runs`, { headers, data: { request_id: "research-navigation", message: "研究此输入", context: { page: "datasets", catalog_scope: scope, dataset_id: "TEST_FIN", template_input_id: input.id, }, }, }, ); expect(response.status()).toBe(200); await page.reload(); await page.getByRole("button", { name: "打开研究助手" }).click(); const chat = page.getByRole("complementary", { name: "AI 研究助手" }); await expect(chat.getByRole("button", { name: "确认执行" })).toBeEnabled(); expect( (await (await page.request.get("/api/v1/backtests/runs")).json()).total, ).toBe(before); await chat.getByRole("button", { name: "确认执行" }).click(); await expect(chat.getByText("已保存 1/1 · 平台失败 0")).toBeVisible({ timeout: 15000, }); await chat.getByRole("button", { name: "打开回测详情", exact: true }).click(); await expect(page.getByText("1 / 1 已保存", { exact: true })).toBeVisible(); // Follow the persisted source from the business detail, then back to its fixed input. await chat.getByRole("button", { name: "收起研究助手" }).click(); await page .locator(".backtest-sheet .research-source") .getByRole("button", { name: "打开研究会话", exact: true }) .click(); await expect(chat.locator(".ai-message.user .ai-text")).toHaveText( "研究此输入", ); await chat .getByRole("button", { name: "查看研究输入", exact: true }) .last() .click(); await expect(chat).not.toBeVisible(); const inputSheet = page.getByRole("dialog", { name: "模板输入草稿", exact: true, }); await expect( inputSheet.getByRole("heading", { name: "输入草稿已保存", exact: true }), ).toBeVisible(); await inputSheet .getByRole("button", { name: "用此输入研究", exact: true }) .click(); await expect(chat.locator(".ai-context")).toContainText("固定研究输入"); expect(errors).toEqual([]); });