diff options
Diffstat (limited to 'qa/driver/browser-test.js')
| -rw-r--r-- | qa/driver/browser-test.js | 124 |
1 files changed, 122 insertions, 2 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js index 46a8f85..677ae54 100644 --- a/qa/driver/browser-test.js +++ b/qa/driver/browser-test.js @@ -11,6 +11,8 @@ const orderPosts = []; const orderPostStatuses = []; const confirmPosts = []; const confirmPostStatuses = []; +const redemptionPosts = []; +const redemptionConfirmResponses = []; let positionGets = 0; let lastPositionsResponse = null; let fundPostResponses = 0; @@ -20,6 +22,7 @@ let createdFund = null; let createdFundResponse = null; let lastOrdersResponse = null; let lastConfirmResponse = null; +let lastRedemptionConfirmResponse = null; function check(name, ok, detail) { results.push({ name, ok, detail: detail || "" }); @@ -106,7 +109,12 @@ async function summaryLabelExists(page, label, name) { } catch {} } if (r.url().includes("/orders") && r.request().method() === "POST") orderPostStatuses.push(r.status()); - if (r.url().includes("/confirm") && r.request().method() === "POST") { + if (r.url().includes("/confirm") && r.url().includes("/redemptions") && r.request().method() === "POST") { + try { + lastRedemptionConfirmResponse = await r.json(); + redemptionConfirmResponses.push(r.status()); + } catch {} + } else if (r.url().includes("/confirm") && r.request().method() === "POST") { confirmPostStatuses.push(r.status()); try { lastConfirmResponse = await r.json(); @@ -118,7 +126,7 @@ async function summaryLabelExists(page, label, name) { lastPositionsResponse = await r.json(); } catch {} } - if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/orders/.test(r.url())) { + if (r.status() >= 400 && !/\/api\/instruments\//.test(r.url()) && !/\/orders/.test(r.url()) && !/\/redemptions/.test(r.url())) { consoleErrors.push("resource " + r.status() + ": " + r.url()); } }); @@ -132,6 +140,9 @@ async function summaryLabelExists(page, label, name) { if (r.url().includes("/confirm") && r.method() === "POST") { confirmPosts.push({ url: r.url(), key: r.headers()["idempotency-key"] || "" }); } + if (r.url().includes("/redemptions") && r.method() === "POST" && !r.url().includes("/confirm")) { + redemptionPosts.push({ url: r.url(), key: r.headers()["idempotency-key"] || "", body: r.postData() || "" }); + } }); page.on("console", (m) => { if (/^DBG /.test(m.text())) console.log("[dbg] " + m.text()); @@ -155,11 +166,120 @@ async function summaryLabelExists(page, label, name) { await idempotentRetryScenario(page); await orderScenario(page); await confirmScenario(page); + await redeemScenario(page); } finally { check("G1 无浏览器控制台/页面错误", consoleErrors.length === 0, consoleErrors.slice(0, 3).join(" | ")); await browser.close(); } + +async function redeemScenario(page) { + // K 系列: 赎回提交(冻结份额) -> 确认(现金到账+份额核销),接 J 系列持仓 84.01949252 + await page.waitForSelector(".redeem-panel", { timeout: 10000 }); + const redeemEyebrow = ((await page.textContent(".redeem-panel .eyebrow")) || "").trim(); + check("K1 赎回面板 eyebrow 为 06 / REDEEM", redeemEyebrow === "06 / REDEEM", redeemEyebrow); + + await page.fill(".redemption-code-input", "000001"); + await page.fill(".redemption-units-input", "34.01949252"); + await page.fill(".redemption-fee-input", "1.00"); + await page.click(".redemption-submit-action"); + await page.waitForSelector(".pending-redemptions .order-row", { timeout: 15000 }); + const submittedRedemption = ((await page.textContent(".pending-redemptions")) || ""); + check( + "K2 提交赎回后显示已提交·待确认", + submittedRedemption.includes("已提交 · 待确认") && submittedRedemption.includes("份额 34.01949252"), + submittedRedemption.slice(0, 200) + ); + + // 提交后: 份额冻结但总份额与现金不变 + await page.waitForFunction( + () => { + const panel = document.querySelector(".positions-panel"); + return panel !== null && panel.textContent.includes("冻结份额 34.01949252"); + }, + null, + { timeout: 15000 } + ); + const frozenCash = await page.$$eval(".positions-panel .fund-detail-value", (els) => els.map((el) => el.textContent.trim())); + check( + "K3 提交后冻结份额且现金不变", + frozenCash.includes("4898.51") && frozenCash.includes("0.00"), + JSON.stringify(frozenCash) + ); + + await page.click(".redemption-confirm-action"); + for (let i = 0; i < 200 && (redemptionConfirmResponses.length === 0 || lastRedemptionConfirmResponse === null); i++) { + await page.waitForTimeout(100); + } + const redemptionStatus = lastRedemptionConfirmResponse ? lastRedemptionConfirmResponse.status : ""; + check("K4 赎回确认响应为 confirmed", redemptionStatus === "confirmed", redemptionStatus); + check( + "K5 赎回确认展示 NAV 与到账金额精确字符串", + lastRedemptionConfirmResponse.confirmedNav === "1.19020000" && + /^[0-9]+\.[0-9]{2}$/.test(lastRedemptionConfirmResponse.confirmedProceeds || "") && + /^[0-9]+\.[0-9]{2}$/.test(lastRedemptionConfirmResponse.confirmedCostReleased || ""), + JSON.stringify(lastRedemptionConfirmResponse).slice(0, 220) + ); + + await page.waitForFunction( + () => { + const panel = document.querySelector(".positions-panel"); + return panel !== null && panel.textContent.includes("份额 50.00000000"); + }, + null, + { timeout: 15000 } + ); + const proceeds = parseFloat(lastRedemptionConfirmResponse.confirmedProceeds); + const costReleased = parseFloat(lastRedemptionConfirmResponse.confirmedCostReleased); + const expectedAvailable = (4898.51 + proceeds).toFixed(2); + const expectedCost = (99.99 - costReleased).toFixed(2); + await page.waitForFunction( + (want) => { + const values = Array.from(document.querySelectorAll(".positions-panel .fund-detail-value")).map((el) => el.textContent.trim()); + return values.includes(want.available); + }, + { available: expectedAvailable }, + { timeout: 15000 } + ); + const settledCash = await page.$$eval(".positions-panel .fund-detail-value", (els) => els.map((el) => el.textContent.trim())); + const positionText = ((await page.textContent(".positions-panel")) || ""); + check( + "K6 确认后份额核销且现金按应收入账", + positionText.includes("份额 50.00000000") && + !positionText.includes("冻结份额") && + positionText.includes(expectedCost) && + settledCash.includes(expectedAvailable) && + settledCash.includes("0.00"), + JSON.stringify({ positionText: positionText.slice(0, 220), settledCash, expectedAvailable, expectedCost }) + ); + + const redemptionRowText = ((await page.textContent(".pending-redemptions")) || ""); + check( + "K7 赎回订单行显示已确认与到账金额", + redemptionRowText.includes("已确认") && redemptionRowText.includes(`赎回到账 ${lastRedemptionConfirmResponse.confirmedProceeds}`), + redemptionRowText.slice(0, 240) + ); + + // 份额上限校验: 超额赎回 409 且不产生冻结 + await page.fill(".redemption-units-input", "999999999.00000000"); + await page.fill(".redemption-fee-input", "0.00"); + await page.click(".redemption-submit-action"); + await page.waitForSelector(".error-banner", { timeout: 15000 }); + const insufficientText = ((await page.textContent(".error-banner")) || "").trim(); + check( + "K8 超额赎回被 409 拒绝", + insufficientText.includes("available holdings are not enough"), + insufficientText.slice(0, 140) + ); + const frozenAfterConflict = await page.$$eval(".positions-panel .fund-detail-value", (els) => els.map((el) => el.textContent.trim())); + check( + "K9 超额赎回后现金与冻结不变", + frozenAfterConflict.includes(expectedAvailable) && !((await page.textContent(".positions-panel")) || "").includes("冻结份额"), + JSON.stringify(frozenAfterConflict) + ); + await page.screenshot({ path: SHOTS + "/13-redemption-settled.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); |
