fix: 修复首页用户 ID 复制按钮换行问题
前端验证 / verify (push) Failing after 15m2s

- 禁止用户 ID 在按钮内拆行,确保长用户名场景下按钮整体换行
- 添加 Chromium 和 Firefox 在不同视口宽度下的布局回归测试
- 更新 CI 浏览器安装配置及测试文档
- 忽略包含生产服务器结构信息的本地运维技能目录
This commit is contained in:
cneicy committed 2026-11-06 19:40:42 +08:00
1 parent e28fbbd9da
commit 6041d717f7
5 files changed
+101 -2

No files matched your search

+1 -1
View File
@@ -38,7 +38,7 @@ CHROME_PATH=/absolute/path/to/chrome npm test
- `review-purchase.e2e.test.mjs`:镜像订单未付复用、取消后重建、已付授权延迟不重复收费;级联候选与默认、结算、购物车恢复一致。
- `review-fields.e2e.test.mjs`:常见 ASCII 规则本地拦截、PCRE 专有规则交后端校验;实例随机密码实际提交及安全源失效、无偏采样边界。
- `review-theme.e2e.test.mjs`:认证页三态主题文案、持久化与跟随系统变化。
- `review-style-layout.e2e.test.mjs`:维护条随内容与视口变化、超长通知内部滚动、帮助/购物车/机型/配置摘要吸顶避让,退出布局后的偏移清理与导航单一键盘焦点。
- `review-style-layout.e2e.test.mjs`:维护条随内容与视口变化、超长通知内部滚动、帮助/购物车/机型/配置摘要吸顶避让,退出布局后的偏移清理与导航单一键盘焦点;首页用户 ID 复制按钮在各宽度下不拆行(该问题只在 Firefox 出现,场景额外启动 Playwright Firefox,需先执行 `npx playwright install firefox`)。
- `review-style-fields.e2e.test.mjs`:深色商品说明对比度、提现样式隔离、实名认证与转账凭证上传名称;实际升级弹窗的窄屏网格。
- `review-style-motion.e2e.test.mjs`:实例监控 Canvas 初始/刷新/加载反馈跟随系统减动偏好,并验证偏好切换及离页后无运行时错误。
- `review-online-auth.e2e.test.mjs`:真实密码输入的自动填充语义、键盘显隐且不误提交、浅暗主题按钮对比度;注册、找回、第三方绑定与账户新绑/改绑手机号固定 `+86`,保留旧号码真实区号和地址国家选项。登录发码与登录协议由原有认证场景共同覆盖。
+93
View File
@@ -4,6 +4,7 @@
*/
import assert from "node:assert/strict";
import { test } from "node:test";
import { firefox } from "playwright";
import {
appOrigin,
backendOrigin,
@@ -479,3 +480,95 @@ test(
return { navigationActions: 2, singleKeyboardStop: true };
}),
);
// 首页用户 ID 按钮继承标题的任意断行时,Firefox 会把 “#” 和数字拆到两行;按钮必须作为整体排版。
// Chromium 下该问题不出现,因此同一场景还要在 Firefox 中验证真实渲染。
async function routeHomeAccount(context) {
await context.route(`${backendOrigin}/console/v1/index`, async (route) => {
if (route.request().method() !== "GET") return route.fallback();
const response = await route.fetch();
const body = await response.json();
Object.assign(body.data.account, { id: 6, username: "测试用户" });
return route.fulfill({ response, json: body });
});
}
async function userIdChipGeometry(page) {
const sizes = [];
const chip = page.locator(".welcome-title .copy-id");
await chip.waitFor();
for (const width of [1440, 375, 320]) {
await page.setViewportSize({ width, height: 900 });
const state = await eventually(
() =>
chip.evaluate((button) => {
const text = [...button.childNodes].find(
(node) => node.nodeType === Node.TEXT_NODE,
),
value = text.textContent,
glyph = (index) => {
const range = document.createRange();
range.setStart(text, index);
range.setEnd(text, index + 1);
return range.getBoundingClientRect();
},
first = glyph(value.indexOf("#")),
last = glyph(value.length - 1),
box = button.getBoundingClientRect(),
lineHeight = parseFloat(getComputedStyle(button).lineHeight);
return {
label: value.trim(),
sameLine: Math.abs(first.top - last.top) <= 0.5,
singleLineHeight: box.height < lineHeight * 1.5,
};
}),
(state) => state.sameLine && state.singleLineHeight,
"The user id must not wrap inside its copy button",
);
assert.equal(state.label, "#6");
sizes.push({ width, singleLine: true });
}
return sizes;
}
test(
"home user id chip stays on one line beside the greeting",
{ timeout: 60000 },
(t) =>
scenario(t, {}, async (page) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await page.setViewportSize({ width: 1440, height: 900 });
await routeHomeAccount(page.context());
await login(page, "/");
const chromium = await userIdChipGeometry(page);
const browser = await firefox.launch();
try {
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
serviceWorkers: "block",
});
// 与共享场景相同:Firefox 也只能访问两个本地 origin。
const blocked = [];
await context.route("**/*", (route) => {
const url = new URL(route.request().url());
if (
!["http:", "https:"].includes(url.protocol) ||
[appOrigin, backendOrigin].includes(url.origin)
)
return route.fallback();
blocked.push(
`${route.request().method()} ${url.origin}${url.pathname}`,
);
return route.abort("blockedbyclient");
});
await routeHomeAccount(context);
const firefoxPage = await context.newPage();
await login(firefoxPage, "/");
const gecko = await userIdChipGeometry(firefoxPage);
assert.deepEqual(blocked, [], "Firefox must stay on local origins");
return { chromium, firefox: gecko };
} finally {
await browser.close();
}
}),
);