/** 冻结记录的响应乱序回归:只控制本地 HTTP 边界,页面与请求层使用生产实现。 */ import assert from "node:assert/strict"; import { test } from "node:test"; import { appOrigin, backendOrigin, eventually, login, scenario, } from "./helpers/console-browser.mjs"; for (const outcome of ["late success", "late failure", "newer read pending"]) { test( `finance freeze records ignore an older response with ${outcome}`, { timeout: 35000 }, (t) => scenario(t, { loginMode: "password-only" }, async (page) => { let freezeReads = 0; let releaseOlder; let releaseNewer; const olderHeld = new Promise((resolve) => { releaseOlder = resolve; }); const newerHeld = new Promise((resolve) => { releaseNewer = resolve; }); const freezePath = "/console/v1/account/credit/freeze"; const emptyPaths = [ "/console/v1/transaction", "/console/v1/credit", "/console/v1/withdraw", "/console/v1/order", "/console/v1/refund/pending/amount", ]; await page.route( (url) => url.origin === backendOrigin && (url.pathname === freezePath || emptyPaths.includes(url.pathname)), async (route) => { const path = new URL(route.request().url()).pathname; const fulfill = (data, status = 200) => route.fulfill({ status: 200, contentType: "application/json", headers: { "Access-Control-Allow-Origin": appOrigin }, body: JSON.stringify({ status, msg: status === 200 ? "" : "旧冻结请求失败", data, }), }); if (path === "/console/v1/refund/pending/amount") return fulfill({ amount: "0.00" }); if (path !== freezePath) return fulfill({ list: [], count: 0, total_amount: "0.00" }); const sequence = ++freezeReads; if (sequence === 2) await olderHeld; if (sequence === 3 && outcome === "newer read pending") await newerHeld; if (sequence === 2 && outcome === "late failure") return fulfill({}, 400); return fulfill({ list: [ { id: 9, amount: sequence === 2 ? "110.00" : "220.00", client_notes: sequence === 1 ? "初始冻结快照" : sequence === 2 ? "较早冻结快照" : "最新冻结快照", nickname: "audit-user", create_time: 1784619935, }, ], count: 1, }); }, ); try { await login(page, "/finance?tab=freeze"); await page.getByText("初始冻结快照", { exact: true }).waitFor(); const transaction = page.getByRole("tab", { name: "交易记录", exact: true, }); const freeze = page.getByRole("tab", { name: "冻结记录", exact: true, }); await transaction.click(); await freeze.click(); await eventually( () => freezeReads, (value) => value === 2, "Older freeze read is held at the HTTP boundary", ); await transaction.click(); await freeze.click(); await eventually( () => freezeReads, (value) => value === 3, "Newer freeze read has started", ); const panel = page.getByRole("tabpanel").filter({ visible: true }); const loading = panel.locator(".p-datatable-mask"); if (outcome === "newer read pending") await loading.waitFor({ state: "visible" }); else await page.getByText("最新冻结快照", { exact: true }).waitFor(); const olderResponse = page.waitForResponse( (response) => new URL(response.url()).pathname === freezePath, ); releaseOlder(); await (await olderResponse).finished(); // 等浏览器完成响应处理与绘制后再核对当前列表,不直接读取组件内部状态。 await page.evaluate( () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)), ), ); if (outcome === "newer read pending") { // 已开始淡出的遮罩仍暂时可见;等待它自身的过渡结束再判断是否仍在加载。 await loading.evaluateAll((nodes) => Promise.all( nodes.flatMap((node) => node .getAnimations() .map((animation) => animation.finished.catch(() => {})), ), ), ); assert.equal( await loading.isVisible(), true, "An older request must not finish the newer request's loading state", ); releaseNewer(); await page.getByText("最新冻结快照", { exact: true }).waitFor(); } assert.equal( await page.getByText("最新冻结快照", { exact: true }).isVisible(), true, ); assert.equal( await page.getByText("较早冻结快照", { exact: true }).isVisible(), false, ); assert.equal(await panel.locator(".p-message-error").count(), 0); await loading.waitFor({ state: "hidden" }); assert.equal(new URL(page.url()).searchParams.get("tab"), "freeze"); return { olderResponse: outcome, freezeReads, finalSnapshot: "latest", staleErrorVisible: false, loadingBelongsToLatestRead: true, }; } finally { releaseOlder(); releaseNewer(); } }), ); }