feat: expand screeners and stabilize interactive feedback
This commit is contained in:
+53
-77
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user