Files

108 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 回测页 E2E:选策略 → 开始回测(自动取行情)→ 净值图 + 绩效表 + 成交记录。
//
// 行情来自 mock /bars(确定性合成 OHLCV),回测跑真实引擎;
// URL query 指定较短日期区间加快取数(页面 onMounted 回填 startDate/endDate)。
import { expect, test } from '@playwright/test'
test('回测全流程出净值图与绩效表', async ({ page }) => {
await page.goto('/backtest?startDate=2024-01-01&endDate=2025-12-31')
// 策略下拉加载出内置策略,默认 ma_cross
const strategySelect = page.locator('.strategy-picker select')
await expect(strategySelect).toHaveValue('ma_cross')
const optionCount = await strategySelect.locator('option').count()
expect(optionCount).toBeGreaterThanOrEqual(18)
// 取行情(mock /bars)+ 回测(真实引擎)
await page.getByRole('button', { name: '开始回测' }).click()
// 报告区:净值曲线(echarts canvas+ 绩效指标 + 成交记录
await expect(page.getByRole('heading', { name: '净值曲线与回撤' })).toBeVisible({ timeout: 60_000 })
await expect(page.locator('.report-section canvas').first()).toBeVisible()
await expect(page.getByRole('heading', { name: '绩效指标' })).toBeVisible()
await expect(page.getByRole('heading', { name: /成交记录(\d+ 笔)/ })).toBeVisible()
// 绩效表渲染出具体数值(总收益/夏普等指标行)
const perfSection = page.locator('.report-section', { hasText: '绩效指标' })
await expect(perfSection.locator('td, .metric-value, .mono').first()).toBeVisible()
})
test('勾选附加分析后出现 WF 逐窗柱状图与一条龙评估卡', async ({ page }) => {
await page.goto('/backtest?startDate=2023-01-01&endDate=2025-12-31')
// 勾选两个「附加分析」开关(v1.27 新增)
await page.getByLabel('Walk-Forward 样本外验证').check()
await expect(page.getByLabel('一条龙评估')).toBeVisible()
await page.getByLabel('一条龙评估').check()
await page.getByRole('button', { name: '开始回测' }).click()
// WF:先出现「验证中…」区块,随后逐窗柱状图(echarts canvas+ 稳定性汇总
await expect(page.getByRole('heading', { name: 'Walk-Forward 样本外验证' })).toBeVisible({
timeout: 60_000,
})
await expect(page.locator('.wf-chart canvas')).toBeVisible({ timeout: 120_000 })
// 收窄到汇总区:词条表里也有「盈利窗占比」(dt),全局 getByText 会触发 strict mode
await expect(page.locator('.wf-summary').getByText('盈利窗占比', { exact: true })).toBeVisible()
await expect(page.locator('.wf-summary .stat')).toHaveCount(6)
// 一条龙评估:综合评分 + 高适配徽标 + 基准对比
// 断言收窄到对应容器:名词解释按钮/词条文案里也含这些词,全局匹配会 strict mode
await expect(page.locator('.eval-panel')).toBeVisible({ timeout: 120_000 })
await expect(page.locator('.eval-header').getByText('综合评分', { exact: true })).toBeVisible()
await expect(page.locator('.eval-header').getByText('对比买入持有')).toBeVisible()
await expect(page.getByText(/适配性体检 \d+\/\d+/)).toBeVisible()
})
test('名词解释默认折叠,点击展开显示词条', async ({ page }) => {
await page.goto('/backtest?startDate=2023-01-01&endDate=2025-12-31')
await page.getByLabel('Walk-Forward 样本外验证').check()
await page.getByLabel('一条龙评估').check()
await page.getByRole('button', { name: '开始回测' }).click()
await expect(page.locator('.eval-panel')).toBeVisible({ timeout: 120_000 })
// 词条用 .g-term 类定位,与面板统计标签同名也不冲突
const wfTerm = page.locator('.wf-panel .g-term', { hasText: '每窗独立开仓' })
const evalTerm = page.locator('.eval-panel .g-term', { hasText: '高适配' })
const metricTerm = page.locator('.metric-wrap .g-term', { hasText: '卡玛比率' })
// 默认隐藏(内容在 DOM,但折叠框高度为 0)
await expect(wfTerm).toBeHidden()
await expect(evalTerm).toBeHidden()
await expect(metricTerm).toBeHidden()
// 点击「名词解释」按钮逐个展开
await page.locator('.wf-panel .help-toggle').click()
await expect(wfTerm).toBeVisible()
await page.locator('.eval-panel .help-toggle').click()
await expect(evalTerm).toBeVisible()
await page.locator('.metric-wrap .help-toggle').click()
await expect(metricTerm).toBeVisible()
})
test('AI 解读 Prompt 弹窗打包回测配置与各段报告', async ({ page }) => {
await page.goto('/backtest?startDate=2023-01-01&endDate=2025-12-31')
await page.getByLabel('Walk-Forward 样本外验证').check()
await page.getByLabel('一条龙评估').check()
await page.getByRole('button', { name: '开始回测' }).click()
await expect(page.locator('.eval-panel')).toBeVisible({ timeout: 120_000 })
await page.getByRole('button', { name: '🤖 AI 解读' }).click()
const area = page.locator('.ai-prompt-area')
await expect(area).toBeVisible()
// textarea 的内容在 value 属性而非文本节点,用 toHaveValue(正则=子串匹配)
await expect(area).toHaveValue(/# 角色设定/)
await expect(area).toHaveValue(/# 回测配置/)
await expect(area).toHaveValue(/SZ:000001(日线)/)
await expect(area).toHaveValue(/- 总收益率:/)
await expect(area).toHaveValue(/Walk-Forward 样本外验证/)
await expect(area).toHaveValue(/# 一条龙评估/)
await expect(area).toHaveValue(/# 评级(不看收益率/)
await expect(area).toHaveValue(/# 背景与免责/)
// 关闭弹窗回到报告
await page.getByRole('button', { name: '关闭' }).click()
await expect(area).toBeHidden()
})