import { expect, test } from "@playwright/test"; test("submission tabs, day selection, local correlation and reload", async ({ page, }) => { const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); const failedChecks = [ "LOW_SHARPE", "LOW_FITNESS", "LOW_GLB_AMER_SHARPE", "LOW_GLB_EMEA_SHARPE", "LOW_GLB_APAC_SHARPE", "LOW_2Y_SHARPE", ]; await page.route("**/api/v1/alphas?*", async (route) => { const response = await route.fetch(); const data = await response.json(); if (data.items?.length) data.items[0].failed_checks = failedChecks; await route.fulfill({ response, json: data }); }); await page.goto("/#alphas"); await page.getByLabel("密码", { exact: true }).fill("browser-test-password"); await page.getByRole("button", { name: "进入工作空间" }).click(); await expect( page.getByRole("tab", { name: "待提交", exact: true }), ).toBeVisible(); const headers = { "X-WQ-Request": "1" }; const credentials = await page.request.put("/api/v1/account/credentials", { headers, data: { email: "test@example.com", password: "synthetic-password" }, }); expect(credentials.ok()).toBe(true); expect( (await page.request.post("/api/v1/account/connect", { headers })).ok(), ).toBe(true); await expect .poll( async () => (await (await page.request.get("/api/v1/account")).json()) .connection_status, ) .toBe("connected"); await page.reload(); await page.getByRole("button", { name: "Alpha 管理", exact: true }).click(); await page.getByRole("tab", { name: "已提交", exact: true }).click(); await page.getByRole("button", { name: "按天同步", exact: true }).click(); await expect(page.getByRole("dialog")).toContainText("提交日期"); await page.getByRole("button", { name: "开始同步", exact: true }).click(); await expect(page.getByRole("alert")).toContainText("请选择有效的起止日期"); // Separate validation from submission; Semi ignores repeated OK clicks within 100 ms. await page .getByRole("dialog") .getByRole("button", { name: "cancel", exact: true }) .click(); await page.getByRole("button", { name: "按天同步", exact: true }).click(); await page.getByLabel("同步开始日期").fill("2025-02-02"); await page.getByLabel("同步结束日期").fill("2025-02-02"); const request = page.waitForResponse( (response) => response.url().endsWith("/api/v1/sync-jobs") && response.request().method() === "POST", ); await page.getByRole("button", { name: "开始同步", exact: true }).click(); const job = await (await request).json(); expect(job.payload).toMatchObject({ submission: "SUBMITTED", date_from: "2025-02-02", date_to: "2025-02-02", }); await expect .poll( async () => (await (await page.request.get(`/api/v1/sync-jobs/${job.id}`)).json()) .status, ) .toBe("completed"); const complete = await ( await page.request.get(`/api/v1/sync-jobs/${job.id}`) ).json(); expect(complete.processed).toBe(103); await expect(page.locator(".job-panel")).toContainText("2025-02-02"); await page.keyboard.press("Escape"); await page.getByRole("button", { name: "筛选 Alpha", exact: true }).click(); await page.getByRole("textbox", { name: "搜索 Alpha" }).fill("TEST0003"); await page.getByRole("button", { name: "查询", exact: true }).click(); await expect(page.locator(".alpha-link")).toHaveCount(1); const failedCell = page.locator(".alpha-failed-checks"); await failedCell.scrollIntoViewIfNeeded(); expect((await failedCell.boundingBox())!.height).toBeLessThanOrEqual(24); await failedCell.hover(); await expect(page.getByRole("tooltip")).toHaveText(failedChecks.join("\n")); await page.screenshot({ path: "../output/playwright/alpha-failed-checks-tooltip.png", animations: "disabled", }); await page.getByRole("tab", { name: "待提交", exact: true }).hover(); await page.getByRole("tab", { name: "待提交", exact: true }).click(); await page.getByRole("button", { name: "筛选 Alpha", exact: true }).click(); await expect(page.getByRole("textbox", { name: "搜索 Alpha" })).toHaveValue( "", ); await page.getByRole("button", { name: "关闭筛选", exact: true }).click(); await page.getByRole("button", { name: "按天同步", exact: true }).click(); await expect( page.getByRole("dialog", { name: "按天同步待提交 Alpha" }), ).toContainText("创建日期"); await page.getByLabel("同步开始日期").fill("2025-01-01"); await page.getByLabel("同步结束日期").fill("2025-01-01"); await page.getByRole("button", { name: "开始同步", exact: true }).click(); await expect .poll( async () => ( await ( await page.request.get("/api/v1/alphas?submission=UNSUBMITTED") ).json() ).total, ) .toBe(413); await page.keyboard.press("Escape"); await page.getByRole("button", { name: "筛选 Alpha", exact: true }).click(); await page.getByRole("textbox", { name: "搜索 Alpha" }).fill("TEST0004"); await page.getByRole("button", { name: "查询", exact: true }).click(); await page.route("**/api/v1/alphas/TEST0004", async (route) => { const response = await route.fetch(); const detail = await response.json(); detail.is_metrics.glbAmer = { yearly: { sharpe: 1.25, status: "PASS" }, samples: 120, enabled: false, }; detail.is_metrics.glbApac = { pnl: 439696, bookSize: 20000000, longCount: 215, turnover: 0.0242, sharpe: 0.85, }; detail.is_metrics.glbEmea = { pnl: 571980, fitness: 0.19, sharpe: 0.72 }; detail.is_metrics.investabilityConstrained = { pnl: 3696382, longCount: 890, turnover: 0.0919, }; detail.is_metrics.history = [ { status: "PASS", sharpe: 1.1 }, { status: "FAIL", sharpe: 0.2 }, ]; detail.is_metrics.emptyObject = {}; detail.is_metrics.emptyArray = []; detail.is_metrics.unavailable = null; await route.fulfill({ response, json: detail }); }); await expect(page.locator(".alpha-link")).toHaveCount(1); await page.locator(".alpha-link").click(); const sheet = page.locator(".alpha-detail"); await expect(sheet.getByRole("tab")).toHaveText([ "概览与表达式", "指标", "相关性检查", "基线比较", "变体关系", "研究信息", ]); const width = await sheet .locator(".semi-sidesheet-inner") .evaluate((element) => element.getBoundingClientRect().width); expect(width).toBeCloseTo(1440 * 0.8, 0); await expect( sheet.getByRole("button", { name: "研究此 Alpha 的变体" }), ).not.toBeVisible(); await sheet.getByRole("tab", { name: "指标", exact: true }).click(); const metrics = sheet.locator(".detail-section").filter({ has: page.getByRole("heading", { name: "IS 指标", exact: true }), }); await expect( metrics .locator(".detail-field-group") .filter({ has: page.locator("dt", { hasText: "yearly" }) }) .first(), ).toContainText("1.25"); await expect( metrics.locator(".semi-tag").filter({ hasText: "PASS" }).first(), ).toBeVisible(); await expect( metrics.locator(".semi-tag").filter({ hasText: "否" }), ).toBeVisible(); await expect(metrics).toContainText("[]"); await expect(metrics).toContainText("{}"); await expect(metrics).toContainText("未提供"); for (const [viewportWidth, columns] of [ [1440, 3], [900, 2], [600, 1], [390, 1], ]) { await page.setViewportSize({ width: viewportWidth, height: 1000 }); const grid = metrics.locator( ":scope > .detail-field-container > .detail-field-grid", ); await expect .poll(() => grid.evaluate( (element) => getComputedStyle(element).gridTemplateColumns.split(" ").length, ), ) .toBe(columns); const layout = await metrics.evaluate((element) => { const groups = Array.from( element.querySelectorAll(".detail-field-group"), ); return { fullRows: groups.every((group) => { const box = group.getBoundingClientRect(); const parent = group.parentElement!.getBoundingClientRect(); return ( Math.abs(box.x - parent.x) < 1 && Math.abs(box.width - parent.width) < 1 ); }), leftAligned: Array.from(element.querySelectorAll("dt, dd")).every( (cell) => getComputedStyle(cell).textAlign === "left", ), noOverflow: Array.from( element.querySelectorAll( ".detail-field-container, dt, dd", ), ).every((cell) => cell.scrollWidth <= cell.clientWidth + 1), }; }); expect(layout).toEqual({ fullRows: true, leftAligned: true, noOverflow: true, }); await page.screenshot({ path: `../output/playwright/alpha-grid-${viewportWidth}.png`, animations: "disabled", }); } await page.setViewportSize({ width: 1440, height: 1000 }); await sheet.getByRole("tab", { name: "变体关系", exact: true }).click(); await expect( sheet.getByRole("button", { name: "研究此 Alpha 的变体" }), ).toBeVisible(); await page.getByRole("tab", { name: "相关性检查", exact: true }).click(); await page.getByRole("button", { name: "检测自相关", exact: true }).click(); await expect .poll( async () => ( await ( await page.request.get("/api/v1/alphas/TEST0004/self-correlation") ).json() ).result?.status, { timeout: 20000 }, ) .toBe("high"); await page.keyboard.press("Escape"); await expect(page.locator(".job-panel")).not.toBeVisible(); await expect(page.locator(".correlation-panel")).toContainText("相关性偏高"); await expect(page.locator(".correlation-panel")).toContainText("1.0000"); const header = await sheet.locator(".detail-title").boundingBox(); const tabs = await sheet .getByRole("tab", { name: "相关性检查", exact: true }) .boundingBox(); const content = sheet.locator(".alpha-detail-tabs > .semi-tabs-content"); await content.evaluate((element) => { element.scrollTop = element.scrollHeight; }); expect( await content.evaluate((element) => element.scrollTop), ).toBeGreaterThan(0); expect((await sheet.locator(".detail-title").boundingBox())!.y).toBe( header!.y, ); expect( (await sheet .getByRole("tab", { name: "相关性检查", exact: true }) .boundingBox())!.y, ).toBe(tabs!.y); await expect( sheet.getByRole("textbox", { name: "regular Description", exact: true }), ).toBeVisible(); await page.screenshot({ path: "../output/playwright/alpha-detail-scrolled.png", }); await content.evaluate((element) => { element.scrollTop = 0; }); await page.screenshot({ path: "../output/playwright/local-correlation.png", animations: "disabled", }); await page.keyboard.press("Escape"); await page.reload(); await page.getByRole("button", { name: "筛选 Alpha", exact: true }).click(); await page.getByRole("textbox", { name: "搜索 Alpha" }).fill("TEST0004"); await page.getByRole("button", { name: "查询", exact: true }).click(); await expect(page.locator(".alpha-table:visible")).toContainText( "相关性偏高", ); await page.getByRole("tab", { name: "已提交", exact: true }).click(); await expect( page.getByRole("button", { name: "全量同步已提交" }), ).toBeVisible(); for (const width of [1440, 390]) { await page.setViewportSize({ width, height: 900 }); await expect( page.getByRole("tab", { name: "已提交", exact: true }), ).toBeVisible(); await expect( page.getByRole("button", { name: "按天同步", exact: true }), ).toBeVisible(); expect( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, ), ).toBe(true); await page.screenshot({ path: `../output/playwright/alpha-tabs-${width}.png`, animations: "disabled", }); } expect(errors).toEqual([]); });