diff options
Diffstat (limited to 'qa')
| -rw-r--r-- | qa/driver/browser-test.js | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js index 6403dc5..6b2d2ec 100644 --- a/qa/driver/browser-test.js +++ b/qa/driver/browser-test.js @@ -26,6 +26,8 @@ let lastOrdersResponse = null; let lastConfirmResponse = null; let lastRedemptionConfirmResponse = null; let lastReturnsResponse = null; +let lastPlanRunResponse = null; +let lastPlansResponse = null; function check(name, ok, detail) { results.push({ name, ok, detail: detail || "" }); @@ -135,6 +137,16 @@ async function summaryLabelExists(page, label, name) { lastReturnsResponse = await r.json(); } catch {} } + if (r.url().includes("/investment-plans/run") && r.request().method() === "POST" && r.status() < 400) { + try { + lastPlanRunResponse = await r.json(); + } catch {} + } + if (/\/investment-plans$/i.test(new URL(r.url()).pathname) && r.request().method() === "GET" && r.status() < 400) { + try { + lastPlansResponse = 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()); } @@ -182,6 +194,7 @@ async function summaryLabelExists(page, label, name) { await capitalScenario(page); await dividendScenario(page); await returnsScenario(page); + await plansScenario(page); } finally { check("G1 无浏览器控制台/页面错误", consoleErrors.length === 0, consoleErrors.slice(0, 3).join(" | ")); await browser.close(); @@ -463,6 +476,104 @@ async function returnsScenario(page) { await page.screenshot({ path: SHOTS + "/16-returns-curve.png" }); } +async function postInvestmentPlanViaApi(page, fundId, token) { + return page.evaluate( + async ({ fundId, token }) => { + const randomKey = () => { + if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID().replace(/-/g, ""); + return Math.random().toString(16).slice(2).padEnd(32, "0").slice(0, 32); + }; + const response = await fetch(`/api/funds/${fundId}/investment-plans`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Idempotency-Key": randomKey(), + "Content-Type": "application/json", + }, + body: JSON.stringify({ instrumentCode: "000001", amount: "100.00", frequency: "daily" }), + }); + return response.status; + }, + { fundId, token } + ); +} + +async function plansScenario(page) { + // P 系列: 定投计划执行最小闭环(接 R 场景:现金 5442.99,持仓 50.00000000) + await page.waitForSelector(".plans-panel", { timeout: 10000 }); + const fundId = await currentFundId(page); + const token = process.env.QA_TOKEN || "qa-token"; + + const createStatus = await postInvestmentPlanViaApi(page, fundId, token); + await page.click(".plans-refresh-action"); + const planRowAppeared = await page + .waitForSelector(".plans-list .order-row", { timeout: 15000 }) + .then(() => true) + .catch(() => false); + const planListText = ((await page.textContent(".plans-list")) || ""); + + lastPlanRunResponse = null; + await page.click(".plans-run-action"); + for (let i = 0; i < 200 && lastPlanRunResponse === null; i++) { + await page.waitForTimeout(100); + } + const run = lastPlanRunResponse || {}; + const runPlans = Array.isArray(run.plans) ? run.plans : []; + const firstPlan = runPlans.length > 0 ? runPlans[0] : {}; + const runs = Array.isArray(firstPlan.runs) ? firstPlan.runs : []; + const firstRun = runs.length > 0 ? runs[0] : {}; + + for (let i = 0; i < 200 && (lastPositionsResponse === null || lastPositionsResponse.availableCash !== "5343.00"); i++) { + await page.waitForTimeout(100); + } + + for (let i = 0; i < 200 && lastPlansResponse === null; i++) { + await page.waitForTimeout(100); + } + + const plans = Array.isArray(lastPlansResponse) ? lastPlansResponse : []; + const plan = plans.length > 0 ? plans[0] : {}; + + check( + "P1 到期定投计划按当日净值扣款买入", + createStatus === 201 && + planRowAppeared && + planListText.includes("000001") && + planListText.includes("金额 100.00") && + planListText.includes("频率 每日") && + plan.amount === "100.00" && + plan.frequency === "daily" && + run.processingDate === "2026-09-21" && + firstRun.runDate === "2026-09-21" && + firstRun.status === "succeeded" && + lastPositionsResponse && + lastPositionsResponse.availableCash === "5343.00" && + lastPositionsResponse.positions?.[0]?.units === "134.01949252", + `status=${createStatus} run=${JSON.stringify(run).slice(0, 240)} cash=${lastPositionsResponse?.availableCash}` + ); + + lastPlanRunResponse = null; + await page.click(".plans-run-action"); + for (let i = 0; i < 200 && lastPlanRunResponse === null; i++) { + await page.waitForTimeout(100); + } + const replay = lastPlanRunResponse || {}; + const replayPlans = Array.isArray(replay.plans) ? replay.plans : []; + const replayRuns = replayPlans.length > 0 && Array.isArray(replayPlans[0].runs) ? replayPlans[0].runs : []; + + check( + "P2 重复执行幂等且不产生第二笔扣款", + replayRuns.length === 1 && + replayRuns[0].status === "succeeded" && + lastPositionsResponse && + lastPositionsResponse.availableCash === "5343.00" && + lastPositionsResponse.positions?.[0]?.units === "134.01949252", + `runs=${JSON.stringify(replayRuns).slice(0, 200)} cash=${lastPositionsResponse?.availableCash}` + ); + + await page.screenshot({ path: SHOTS + "/17-investment-plans.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); |
