diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-21 23:47:50 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-21 23:47:50 +0800 |
| commit | 36ee23a0c406c16e975276238a861299899ec79a (patch) | |
| tree | 7085f24566b7c53ad75c1ddf8130e563513f673a /qa/driver/browser-test.js | |
| parent | d4b539a2791a0cd4d098fecfece0039073a9f7e2 (diff) | |
| download | fund-lab-36ee23a0c406c16e975276238a861299899ec79a.tar.gz | |
Add rebalancing execution slice (3d-11)
Diffstat (limited to 'qa/driver/browser-test.js')
| -rw-r--r-- | qa/driver/browser-test.js | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js index 6b2d2ec..c7ee468 100644 --- a/qa/driver/browser-test.js +++ b/qa/driver/browser-test.js @@ -28,6 +28,10 @@ let lastRedemptionConfirmResponse = null; let lastReturnsResponse = null; let lastPlanRunResponse = null; let lastPlansResponse = null; +let lastRebalancePlansResponse = null; +let lastRebalancePreviewResponse = null; +let lastRebalanceExecuteResponse = null; +let lastRebalanceExecutionsResponse = null; function check(name, ok, detail) { results.push({ name, ok, detail: detail || "" }); @@ -147,6 +151,26 @@ async function summaryLabelExists(page, label, name) { lastPlansResponse = await r.json(); } catch {} } + if (/\/rebalance\/plans$/i.test(new URL(r.url()).pathname) && r.request().method() === "GET" && r.status() < 400) { + try { + lastRebalancePlansResponse = await r.json(); + } catch {} + } + if (/\/rebalance\/plans\/[0-9a-f-]+\/preview$/i.test(new URL(r.url()).pathname) && r.request().method() === "GET" && r.status() < 400) { + try { + lastRebalancePreviewResponse = await r.json(); + } catch {} + } + if (/\/rebalance\/plans\/[0-9a-f-]+\/execute$/i.test(new URL(r.url()).pathname) && r.request().method() === "POST" && r.status() < 400) { + try { + lastRebalanceExecuteResponse = await r.json(); + } catch {} + } + if (/\/rebalance\/executions$/i.test(new URL(r.url()).pathname) && r.request().method() === "GET" && r.status() < 400) { + try { + lastRebalanceExecutionsResponse = await r.json(); + } catch {} + } if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/orders/.test(r.url()) && !/\/redemptions/.test(r.url()) && !/\/capital/.test(r.url())) { consoleErrors.push("resource " + r.status() + ": " + r.url()); } @@ -195,6 +219,7 @@ async function summaryLabelExists(page, label, name) { await dividendScenario(page); await returnsScenario(page); await plansScenario(page); + await rebalanceScenario(page); } finally { check("G1 无浏览器控制台/页面错误", consoleErrors.length === 0, consoleErrors.slice(0, 3).join(" | ")); await browser.close(); @@ -574,6 +599,120 @@ async function plansScenario(page) { await page.screenshot({ path: SHOTS + "/17-investment-plans.png" }); } +async function rebalanceScenario(page) { + // R 系列: 再平衡最小闭环(接 P 场景:现金 5343.00,持仓 134.01949252 @1.1902) + await page.waitForSelector(".rebalance-panel", { timeout: 10000 }); + + const eyebrow = ((await page.textContent(".rebalance-panel .eyebrow")) || "").trim(); + check("P3 再平衡面板 eyebrow 为 12 / REBALANCE", eyebrow === "12 / REBALANCE", eyebrow); + await page.locator(".rebalance-target-code-input").first().fill("000001"); + await page.locator(".rebalance-target-percent-input").first().fill("100.00"); + await page.click(".rebalance-submit-action"); + const planRowAppeared = await page + .waitForSelector(".rebalance-plan-row", { timeout: 15000 }) + .then(() => true) + .catch(() => false); + + for (let i = 0; i < 200 && lastRebalancePlansResponse === null; i++) { + await page.waitForTimeout(100); + } + + const plans = Array.isArray(lastRebalancePlansResponse) ? lastRebalancePlansResponse : []; + const plan = plans.length > 0 ? plans[0] : {}; + const planText = ((await page.textContent(".rebalance-plans")) || ""); + + check( + "P4 目标权重计划创建(000001 100%)", + planRowAppeared && planText.includes("000001 100.00%") && Array.isArray(plan.targets) && plan.targets[0]?.targetPercent === "100.00", + `plans=${JSON.stringify(plans).slice(0, 200)}` + ); + + lastRebalancePreviewResponse = null; + await page.click(".rebalance-preview-action"); + for (let i = 0; i < 200 && lastRebalancePreviewResponse === null; i++) { + await page.waitForTimeout(100); + } + + const preview = lastRebalancePreviewResponse || {}; + const previewRows = Array.isArray(preview.rows) ? preview.rows : []; + const previewRow = previewRows.length > 0 ? previewRows[0] : {}; + const previewText = ((await page.textContent(".rebalance-preview")) || ""); + + check( + "P5 预览展示目标对比当前及差额买入", + previewRow.instrumentCode === "000001" && + previewRow.targetPercent === "100.00" && + previewRow.currentValue === "159.51" && + previewRow.currentPercent === "2.89" && + previewRow.action === "buy" && + previewRow.deltaAmount === "5343.00" && + preview.availableCash === "5343.00" && + preview.equity === "5502.51" && + previewText.includes("目标 100.00%") && + previewText.includes("当前 2.89%"), + `preview=${JSON.stringify(preview).slice(0, 260)}` + ); + + lastRebalanceExecuteResponse = null; + await page.click(".rebalance-execute-action"); + for (let i = 0; i < 200 && lastRebalanceExecuteResponse === null; i++) { + await page.waitForTimeout(100); + } + + const execution = lastRebalanceExecuteResponse || {}; + const outcomes = Array.isArray(execution.outcomes) ? execution.outcomes : []; + + for (let i = 0; i < 200 && (lastPositionsResponse === null || lastPositionsResponse.availableCash !== "0.01"); i++) { + await page.waitForTimeout(100); + } + + for (let i = 0; i < 200 && lastRebalanceExecutionsResponse === null; i++) { + await page.waitForTimeout(100); + } + + const executions = Array.isArray(lastRebalanceExecutionsResponse) ? lastRebalanceExecutionsResponse : []; + const boughtUnits = parseFloat(lastPositionsResponse?.positions?.[0]?.units || "0"); + + check( + "P6 执行再平衡按当日净值补足差额", + execution.runDate === "2026-09-21" && + outcomes.length === 1 && + outcomes[0].instrumentCode === "000001" && + outcomes[0].action === "buy" && + outcomes[0].amount === "5343.00" && + outcomes[0].status === "succeeded" && + lastPositionsResponse && + lastPositionsResponse.availableCash === "0.01" && + boughtUnits > 134.01949252 && + executions.length === 1 && + executions[0].action === "buy" && + executions[0].status === "succeeded", + `outcomes=${JSON.stringify(outcomes).slice(0, 200)} cash=${lastPositionsResponse?.availableCash} units=${lastPositionsResponse?.positions?.[0]?.units}` + ); + + const unitsAfterFirst = lastPositionsResponse?.positions?.[0]?.units; + + lastRebalanceExecuteResponse = null; + await page.click(".rebalance-execute-action"); + for (let i = 0; i < 200 && lastRebalanceExecuteResponse === null; i++) { + await page.waitForTimeout(100); + } + + const replay = lastRebalanceExecuteResponse || {}; + const replayOutcomes = Array.isArray(replay.outcomes) ? replay.outcomes : []; + + check( + "P7 再次执行容差内无新交易", + replayOutcomes.length === 0 && + lastPositionsResponse && + lastPositionsResponse.availableCash === "0.01" && + lastPositionsResponse.positions?.[0]?.units === unitsAfterFirst, + `outcomes=${JSON.stringify(replayOutcomes).slice(0, 160)} cash=${lastPositionsResponse?.availableCash}` + ); + + await page.screenshot({ path: SHOTS + "/18-rebalance.png" }); +} + const failed = results.filter((r) => !r.ok); console.log(`\n==== ${results.length - failed.length}/${results.length} passed ====`); process.exit(failed.length > 0 ? 1 : 0); |
