完善视觉回归边界与超长通知验收说明

This commit is contained in:
cieanyau committed 2026-11-06 03:17:10 +08:00
1 parent 21821ec6fa
commit bbd1f3b963
3 files changed
+88 -32

No files matched your search

+83 -31
View File
@@ -17,7 +17,7 @@ const maintenanceMessage =
async function layoutBoundary(
page,
{ maintenance = true, content = "help" } = {},
{ maintenance = true, content = "help", message = maintenanceMessage } = {},
) {
await page
.context()
@@ -33,21 +33,13 @@ async function layoutBoundary(
const body = await response.json();
Object.assign(body.data, {
maintenance_mode: Number(maintenance),
maintenance_mode_message: maintenanceMessage,
maintenance_mode_message: message,
});
return route.fulfill({ response, json: body });
}
if (path === "/menu")
return reply({
menu: [
{
id: 10,
name: "查看官网",
menu_type: "custom",
url: "/official-site",
},
],
});
if (["/ticket/status", "/ticket/type"].includes(path))
return reply({ list: [] });
if (path === "/ticket/statistic") return reply({});
if (path === "/official_site_cms/content") return reply({ content: {} });
if (path === "/help")
return reply({
@@ -202,26 +194,80 @@ test(
);
sizes.push({ width, noticeFits: true, headerBelowNotice: true });
}
await page.getByRole("button", { name: "打开菜单", exact: true }).click();
const menuVisible = await page
.getByRole("button", { name: "关闭菜单", exact: true })
.evaluate((button) => {
const r = button.getBoundingClientRect(),
hit = document.elementFromPoint(
r.left + r.width / 2,
r.top + r.height / 2,
);
return button.contains(hit);
});
assert.ok(
menuVisible,
"The notice must not cover the drawer close button",
// 关闭控件按既有设计只在键盘焦点时显现,因此从键盘入口验证它不被维护条遮挡。
const open = page.getByRole("button", { name: "打开菜单", exact: true });
await open.focus();
await page.keyboard.press("Enter");
await eventually(
() =>
page
.getByRole("button", { name: "关闭菜单", exact: true })
.evaluate((button) => {
const r = button.getBoundingClientRect(),
hit = document.elementFromPoint(
r.left + r.width / 2,
r.top + r.height / 2,
);
return button === document.activeElement && button.contains(hit);
}),
Boolean,
"The notice must not cover the focused drawer close button",
);
await page.keyboard.press("Escape");
return { sizes, drawerCloseReachable: true };
}),
);
test(
"oversized maintenance notices scroll without pushing navigation off screen",
{ timeout: 30000 },
(t) =>
scenario(t, {}, async (page) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await page.setViewportSize({ width: 320, height: 640 });
await layoutBoundary(page, { message: maintenanceMessage.repeat(30) });
await login(page, "/security/ssh-keys");
const banner = page.locator(".maintenance-banner");
await banner.waitFor();
const state = await eventually(
() =>
banner.evaluate((element) => {
const bounds = element.getBoundingClientRect(),
header = document
.querySelector(".topbar")
.getBoundingClientRect();
return {
height: bounds.height,
viewportHeight: innerHeight,
scrollable: element.scrollHeight > element.clientHeight,
belowBanner: header.top >= bounds.bottom - 0.5,
navigationVisible: header.bottom < innerHeight,
};
}),
(state) => state.belowBanner && state.navigationVisible,
"Even a long notice must leave navigation visible below it",
);
assert.ok(state.scrollable, "All notice text must remain scrollable");
assert.ok(state.height <= state.viewportHeight * 0.4 + 1);
const endReachable = await banner.evaluate((element) => {
element.scrollTop = element.scrollHeight;
const bounds = element.getBoundingClientRect(),
text = element.querySelector("span").firstChild,
end = document.createRange();
end.setStart(text, text.length - 1);
end.setEnd(text, text.length);
const last = end.getBoundingClientRect();
return (
element.scrollTop > 0 &&
last.top >= bounds.top &&
last.bottom <= bounds.bottom + 0.5
);
});
assert.ok(endReachable, "The end of a long notice must remain readable");
return { boundedHeight: true, navigationVisible: true, endReachable };
}),
);
for (const [name, path, selector] of [
["help", "/help/article?id=1", ".help-nav"],
["cart", "/cart", ".cart-foot"],
@@ -243,6 +289,8 @@ for (const [name, path, selector] of [
.first()
.click();
await page.locator(selector).waitFor();
if (name === "help")
await page.locator(".content-html p").first().waitFor();
await page.evaluate(() => window.scrollTo(0, 500));
const state = await eventually(
() =>
@@ -279,7 +327,7 @@ for (const [name, path, selector] of [
}
test(
"console layout releases its measured offsets when navigating to the official site",
"console layout releases its measured offsets after logging out",
{ timeout: 30000 },
(t) =>
scenario(t, {}, async (page) => {
@@ -298,8 +346,11 @@ test(
(top) => top > outsideConsoleTop,
"Global notifications must clear the maintenance header",
);
await page.getByRole("link", { name: "查看官网", exact: true }).click();
await page.locator(".official-site").waitFor();
await page.getByRole("button", { name: "用户菜单", exact: true }).click();
await page
.getByRole("menuitem", { name: "退出登录", exact: true })
.click();
await page.waitForURL((url) => url.pathname === "/login");
await eventually(
toastTop,
(top) => Math.abs(top - outsideConsoleTop) < 0.5,
@@ -307,7 +358,7 @@ test(
);
return {
notificationClearedConsole: consoleTop > outsideConsoleTop,
officialLayoutRestored: true,
blankLayoutRestored: true,
};
}),
);
@@ -322,6 +373,7 @@ for (const [path, name] of [
{ timeout: 25000 },
(t) =>
scenario(t, {}, async (page) => {
await layoutBoundary(page, { maintenance: false });
await login(page, path);
const links = page.getByRole("link", { name, exact: true });
await links.first().waitFor();