feat: expand screeners and stabilize interactive feedback

This commit is contained in:
leefer
2026-07-28 22:47:50 +08:00
parent f4b2d7152a
commit 1cc80583b3
22 changed files with 2707 additions and 509 deletions
+53 -77
View File
@@ -1433,7 +1433,8 @@ test("regular account cannot see admin controls and member features are gated",
await expect(page.locator("#accountVipLabel")).toHaveText("非会员");
await page.locator('[data-view="screenerView"]').first().click();
await expect(page.locator("#screenerView .member-gate")).toBeVisible();
await expect(page.locator("#screenerRunButton")).toBeDisabled();
await page.locator('[data-screener-mode="quant"]').click();
await expect(page.locator("#quantRunButton")).toBeDisabled();
await page.locator("#assistantButton").click();
await expect(page.locator("#settingsDialog")).toBeHidden();
await expect(page.locator("#assistantDialog")).toBeVisible();
@@ -1817,7 +1818,8 @@ test("curated strategies and quant builder form independent screener workspaces"
await expect(page.locator("#curatedStrategyList .curated-strategy-card")).toHaveCount(1);
await expect(page.locator("#curatedStrategyName")).toHaveText("连续分红质量");
await expect(page.locator("#curatedFilterList .curated-rule-row")).toHaveCount(1);
await expect(page.locator("#curatedRunButton")).toBeEnabled();
await expect(page.locator("#curatedRunButton")).toHaveCount(0);
await expect(page.locator("#curatedHealthMetrics > div")).toHaveCount(4);
await page.locator('[data-screener-mode="quant"]').click();
await expect(page.locator('[data-screener-panel="curated"]')).toBeHidden();
@@ -1958,32 +1960,35 @@ test("screener redesign preserves three clear workspaces across desktop and mobi
});
await page.locator('[data-screener-mode="curated"]').click();
await expect(page.locator("#curatedStrategyList .curated-strategy-card")).toHaveCount(2);
await expect(page.locator("#curatedDetailDialog")).toBeHidden();
await expect(page.locator(".curated-detail-pane")).toBeVisible();
const secondStrategy = page.locator('#curatedStrategyList [data-curated-strategy="4"]');
await secondStrategy.locator(".curated-card-description").click();
await secondStrategy.click();
await expect(secondStrategy).toHaveClass(/active/);
await expect(page.locator('#curatedStrategyList [data-curated-strategy="2"]')).not.toHaveClass(/active/);
await secondStrategy.locator('[data-curated-inspect="4"]').click();
await expect(page.locator("#curatedDetailDialog")).toBeVisible();
const dialogBox = await page.locator("#curatedDetailDialog").boundingBox();
expect(Math.abs(dialogBox.x + dialogBox.width / 2 - 720)).toBeLessThanOrEqual(2);
expect(Math.abs(dialogBox.y + dialogBox.height / 2 - 450)).toBeLessThanOrEqual(2);
await page.locator("#closeCuratedDetailButton").click();
await expect(page.locator("#curatedStrategyName")).toHaveText("低波质量");
const [libraryBox, detailBox] = await Promise.all([
page.locator(".curated-library-pane").boundingBox(),
page.locator(".curated-detail-pane").boundingBox(),
]);
expect(detailBox.x).toBeGreaterThan(libraryBox.x + libraryBox.width - 2);
expect(Math.abs(detailBox.y - libraryBox.y)).toBeLessThanOrEqual(1);
expect(libraryBox.width).toBeLessThan(detailBox.width);
await expect(page.locator("#curatedHealthMetrics > div")).toHaveCount(4);
await page.screenshot({ path: "test-results/screener-stage15-strategy-1440.png", fullPage: true });
await page.locator('[data-screener-mode="quant"]').click();
await expect(page.locator("#quantScoreRows .quant-score-row")).toHaveCount(5);
await expect(page.locator("#screenerView .quant-intro-band")).toHaveCount(0);
await expect(page.getByText("执行设置", { exact: true })).toHaveCount(0);
await expect(page.locator("#screenerResultTitle")).toHaveText("打分结果");
await expect(page.locator("#screenerResultTitle")).toHaveText("自定义选股结果");
const [builderBox, summaryBox] = await Promise.all([
page.locator(".quant-builder-pane").boundingBox(),
page.locator(".quant-summary-pane").boundingBox(),
]);
expect(Math.abs(builderBox.y - summaryBox.y)).toBeLessThanOrEqual(1);
expect(summaryBox.x).toBeGreaterThan(builderBox.x + builderBox.width - 2);
expect(builderBox.width).toBeGreaterThanOrEqual(395);
expect(builderBox.width).toBeLessThanOrEqual(405);
expect(builderBox.width).toBeGreaterThanOrEqual(490);
expect(builderBox.width).toBeLessThanOrEqual(540);
const quantRunBox = await page.locator("#quantRunButton").boundingBox();
expect(quantRunBox.width).toBeLessThan(180);
expect((await page.locator("#quantFilterRows .quant-filter-row select").first().boundingBox()).width).toBeLessThanOrEqual(225);
@@ -1996,7 +2001,7 @@ test("screener redesign preserves three clear workspaces across desktop and mobi
await expect(page.locator("#screenerView .screener-mode-tabs")).toBeVisible();
});
test("screener stage completion follows its execution context and mode results stay isolated", async ({ page }) => {
test("automatic screener results stay read-only and mode results stay isolated", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await mockApplication(page, session("user", true));
await page.goto("/index.html");
@@ -2024,15 +2029,11 @@ test("screener stage completion follows its execution context and mode results s
await expect(page.locator('#screenerView .screener-step[data-state="complete"]')).toHaveCount(3);
await expect(page.locator("#screenerTableBody")).toContainText("阶段结果");
await page.locator('[data-regime="retreat"]').click();
await expect(page.locator('#screenerView .screener-step[data-state="complete"]')).toHaveCount(2);
await expect(page.locator("#screenerRunStatus")).toHaveText("等待执行");
await expect(page.locator("#backtestTaskStatus")).toHaveText("随选股执行");
await expect(page.locator("#screenerEmpty")).toContainText("当前阶段与策略");
await expect(page.locator("#screenerResultSource")).toBeHidden();
await page.locator('[data-regime="repair"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("阶段结果");
await expect(page.locator('[data-regime]')).toHaveCount(0);
await expect(page.locator("#screenerRunButton")).toHaveCount(0);
await expect(page.locator("#syncScreenerButton")).toHaveCount(0);
await expect(page.locator("#changeStrategyButton")).toHaveCount(0);
await expect(page.locator("#editStrategyButton")).toHaveCount(0);
await page.locator('[data-screener-mode="curated"]').click();
await expect(page.locator("#screenerEmpty")).toContainText("所选策略");
await page.evaluate(() => {
@@ -2044,10 +2045,10 @@ test("screener stage completion follows its execution context and mode results s
});
await expect(page.locator("#screenerTableBody")).toContainText("策略结果");
await expect(page.locator("#screenerTableBody")).not.toContainText("阶段结果");
await expect(page.locator("#screenerResultSource")).toHaveText("策略选股 · 修复 · 连续分红质量");
await expect(page.locator("#screenerResultSource")).toHaveText("策略选股 · 连续分红质量");
await page.locator('[data-screener-mode="quant"]').click();
await expect(page.locator("#screenerEmpty")).toContainText("量化选股");
await expect(page.locator("#screenerEmpty")).toContainText("自定义选股");
await page.evaluate(() => {
setScreenerResult("quant", {
meta: { run_id: 53, trade_date: "20260722", regime: "repair", strategy_name: "自定义量化公式" },
@@ -2056,7 +2057,7 @@ test("screener stage completion follows its execution context and mode results s
renderScreenerResult();
});
await expect(page.locator("#screenerTableBody")).toContainText("量化结果");
await expect(page.locator("#screenerResultSource")).toHaveText("量化选股 · 自定义因子权重");
await expect(page.locator("#screenerResultSource")).toHaveText("自定义选股 · 自定义因子权重");
await page.locator('[data-screener-mode="smart"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("阶段结果");
@@ -2064,20 +2065,34 @@ test("screener stage completion follows its execution context and mode results s
await expect(page.locator("#screenerTableBody")).not.toContainText("量化结果");
});
test("screener keeps results for each stage and curated strategy across switching and reload", async ({ page }) => {
test("screener restores automatic stage and curated pools across switching and reload", async ({ page }) => {
const formula = {
meta: { library: "smart" }, universe: {}, filters: [],
score: [{ field: "relative_strength", weight: 1, direction: "desc" }],
limit: 10, min_score: 0.5,
};
const candidate = (code, name) => ({
code, name, sector: "Test Sector", score_display: 80,
historical_probability: 50, probability_samples: 20, pct_chg: 1,
return_5d: 2, volume_ratio_5d: 1.2, sector_strength: 70,
reason: "Context result", risk_flags: [],
});
const result = (mode, runId, strategyName, row) => ({
meta: {
run_id: runId, trade_date: "20260722", regime: "repair",
strategy_name: strategyName, mode,
},
candidates: [row],
disclaimer: "Historical statistics do not predict future returns.",
backtest: null,
});
const smartResult = result("smart", 101, "修复确认", candidate("600001", "Smart Repair"));
const curatedA = result("curated", 102, "连续分红质量", candidate("600002", "Curated A"));
const curatedB = result("curated", 103, "Quality B", candidate("600003", "Curated B"));
const options = {
recentScreenerResults: [],
additionalScreenerRegimes: [{ id: "retreat", label: "Retreat" }],
latestScreenerResults: { smart: smartResult, curated: curatedA },
recentScreenerResults: [smartResult, curatedA, curatedB],
additionalScreenerStrategies: [
{
id: 3, name: "Retreat Defense", description: "Retreat-stage strategy",
regimes: ["retreat"], builtin: true, data_ready: true, missing_data: [], formula,
},
{
id: 4, name: "Quality B", description: "Second curated strategy",
regimes: ["repair"], builtin: true, data_ready: true, missing_data: [],
@@ -2088,66 +2103,27 @@ test("screener keeps results for each stage and curated strategy across switchin
},
],
};
options.screenerRunResult = (body) => {
const candidateName = body.mode === "smart"
? body.regime === "retreat" ? "Smart Retreat" : "Smart Repair"
: body.strategy_name === "Quality B" ? "Curated B" : "Curated A";
return {
meta: {
run_id: 100 + options.recentScreenerResults.length,
trade_date: "20260722",
regime: body.regime,
strategy_name: body.strategy_name,
mode: body.mode,
},
candidates: [{
code: `60000${options.recentScreenerResults.length + 1}`,
name: candidateName,
sector: "Test Sector",
score_display: 80,
historical_probability: 50,
probability_samples: 20,
pct_chg: 1,
return_5d: 2,
volume_ratio_5d: 1.2,
sector_strength: 70,
reason: "Context result",
risk_flags: [],
}],
disclaimer: "Historical statistics do not predict future returns.",
backtest: null,
};
};
await mockApplication(page, session("user", true), options);
await page.goto("/index.html?view=screenerView");
await page.locator("#screenerRunButton").click();
await expect(page.locator("#screenerTableBody")).toContainText("Smart Repair");
await page.locator('[data-regime="retreat"]').click();
await page.locator("#screenerRunButton").click();
await expect(page.locator("#screenerTableBody")).toContainText("Smart Retreat");
await page.locator('[data-regime="repair"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Smart Repair");
await page.locator('[data-regime="retreat"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Smart Retreat");
await expect(page.locator("#screenerRunButton")).toHaveCount(0);
await page.locator('[data-screener-mode="curated"]').click();
await page.locator('[data-curated-run="2"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Curated A");
await page.locator('[data-curated-run="4"]').click();
await page.locator('[data-curated-strategy="4"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Curated B");
await page.locator('[data-curated-strategy="2"] .curated-card-description').click();
await page.locator('[data-curated-strategy="2"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Curated A");
expect(options.screenerRunBodies || []).toHaveLength(0);
await page.reload();
await page.locator('[data-screener-mode="smart"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Smart Repair");
await page.locator('[data-regime="retreat"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Smart Retreat");
await page.locator('[data-screener-mode="curated"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Curated A");
await page.locator('[data-curated-strategy="4"] .curated-card-description').click();
await page.locator('[data-curated-strategy="4"]').click();
await expect(page.locator("#screenerTableBody")).toContainText("Curated B");
});