- 禁止用户 ID 在按钮内拆行,确保长用户名场景下按钮整体换行 - 添加 Chromium 和 Firefox 在不同视口宽度下的布局回归测试 - 更新 CI 浏览器安装配置及测试文档 - 忽略包含生产服务器结构信息的本地运维技能目录
This commit is contained in:
5 files changed
+101
-2
No files matched your search
+1
-1
@@ -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`,保留旧号码真实区号和地址国家选项。登录发码与登录协议由原有认证场景共同覆盖。
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}),
|
||||
);
|
||||
Reference in new issue
Block a user