前端验证 / verify (push) Failing after 3m34s
通过表格原生渲染统一生成手机布局标签,删除全页监听与后补扫描。动态等级列使用单一标题并保留颜色。 补齐首次插入、深链刷新、动态表头与首页延迟响应几何回归。全量214项通过,最终专项16项通过;不声明全站所有场景零位移。
87 lines
3.2 KiB
JavaScript
87 lines
3.2 KiB
JavaScript
/** 回归缺口:异步列表替换骨架时,已显示的相邻卡片不应因同样五行内容而上下跳动。 */
|
|
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { backendOrigin, login, scenario } from "./helpers/console-browser.mjs";
|
|
|
|
for (const width of [1440, 390]) {
|
|
test(
|
|
`home list reserves its rendered row geometry at ${width}px`,
|
|
{ timeout: 30000 },
|
|
(t) =>
|
|
scenario(t, {}, async (page) => {
|
|
await page.setViewportSize({ width, height: 900 });
|
|
await page.emulateMedia({ reducedMotion: "reduce" });
|
|
let release;
|
|
const held = new Promise((resolve) => {
|
|
release = resolve;
|
|
});
|
|
await page.route(`${backendOrigin}/console/v1/**`, async (route) => {
|
|
const path = new URL(route.request().url()).pathname;
|
|
if (![/\/ticket$/, /\/announcement$/].some((end) => end.test(path)))
|
|
return route.fallback();
|
|
await held;
|
|
return route.fulfill({
|
|
json: {
|
|
status: 200,
|
|
msg: "",
|
|
data: {
|
|
count: 5,
|
|
list: Array.from({ length: 5 }, (_, index) => ({
|
|
id: index + 1,
|
|
title: `本地布局验收 ${index + 1}`,
|
|
create_time: 1730000000,
|
|
post_time: 1730000000,
|
|
status: "待回复",
|
|
color: "#0284c7",
|
|
})),
|
|
},
|
|
},
|
|
});
|
|
});
|
|
const geometry = () =>
|
|
page.locator(".home-tickets, .home-notices").evaluateAll((cards) =>
|
|
cards.map((card) => {
|
|
const box = card.getBoundingClientRect();
|
|
return {
|
|
card: card.classList.contains("home-tickets")
|
|
? "tickets"
|
|
: "notices",
|
|
top: box.top + scrollY,
|
|
height: box.height,
|
|
};
|
|
}),
|
|
);
|
|
let before;
|
|
try {
|
|
await login(page, "/");
|
|
await page
|
|
.locator(".home-tickets .home-row-skeleton")
|
|
.first()
|
|
.waitFor();
|
|
await page.evaluate(() => document.fonts.ready);
|
|
// 只比较本次控制的两个响应,避免混入其他独立面板的正常数据替换。
|
|
await page.waitForFunction(
|
|
() =>
|
|
!document.querySelector(
|
|
".home-servers .home-row-skeleton, .home-expiring .home-row-skeleton, .home-referral .p-skeleton",
|
|
),
|
|
);
|
|
before = await geometry();
|
|
} finally {
|
|
release();
|
|
}
|
|
await page.locator(".home-notices .notice-row").last().waitFor();
|
|
await page.locator(".home-tickets .home-row").last().waitFor();
|
|
const after = await geometry();
|
|
for (let index = 0; index < before.length; index++) {
|
|
for (const dimension of ["height", "top"])
|
|
assert.ok(
|
|
Math.abs(before[index][dimension] - after[index][dimension]) <= 1,
|
|
`${before[index].card} ${dimension} changed: ${before[index][dimension]} -> ${after[index][dimension]}`,
|
|
);
|
|
}
|
|
return { width, sameRowGeometryAfterData: true };
|
|
}),
|
|
);
|
|
}
|