ci(frontend): 提高前端流水线及端到端测试稳定性
前端验证 / verify (push) Failing after 15m3s

- 使用内部 checkout 镜像,并校验运行环境中的 Node.js 22
- 检测预览服务端口冲突并重试,避免并行测试误连其他进程
- 调整布局测试的视口宽度和溢出断言,兼容经典滚动条
- 增加时钟暂停的时间余量,避免 CI 调度延迟导致测试失败
This commit is contained in:
cneicy committed 2026-11-06 20:24:56 +08:00
1 parent 6041d717f7
commit 5d080cacec
4 files changed
+69 -48

No files matched your search

+52 -36
View File
@@ -87,6 +87,57 @@ async function freePort() {
return port;
}
// node --test 并行运行各验收文件:freePort 释放端口到预览进程绑定之间,其他文件的进程可能占用同一端口。
// Nitro 绑定失败时只记录 EADDRINUSE 而不退出,HTTP 探测还会命中别的文件的预览服务,
// 因此只以本进程输出的监听地址为准,冲突时换端口重试。
async function startApp(attempts = 5) {
for (let attempt = 1; attempt <= attempts; attempt += 1) {
const port = await freePort();
const origin = `http://127.0.0.1:${port}`;
const app = startProcess(
[join(appRoot, ".output", "server", "index.mjs")],
{
cwd: appRoot,
env: {
// 使用生产已有的公开 API 配置接入外部边界,不读取或修改项目 .env。
...process.env,
NODE_ENV: "production",
HOST: "127.0.0.1",
PORT: String(port),
NITRO_HOST: "127.0.0.1",
NITRO_PORT: String(port),
NUXT_PUBLIC_API_BASE: backendOrigin,
},
},
);
const state = await eventually(
async () => {
if (app.exitCode !== null || app.signalCode !== null) return "exited";
if (app.log.includes("EADDRINUSE")) return "collision";
if (!app.log.includes(`Listening on ${origin}`)) return false;
try {
const response = await fetch(`${origin}/login`, {
signal: AbortSignal.timeout(1000),
});
return response.ok && "ready";
} catch {
return false;
}
},
Boolean,
"Built app did not start",
30000,
);
if (state === "ready") return origin;
if (state === "exited") throw new Error(`Preview exited: ${app.log}`);
children.splice(children.indexOf(app), 1);
const exited = new Promise((resolve_) => app.once("exit", resolve_));
app.kill("SIGKILL");
await exited;
}
throw new Error(`Built app could not bind a free port in ${attempts} tries`);
}
before(
async () => {
// 必须先构建;此处运行 .output,而非开发服务器,确保测试走实际交付入口。
@@ -121,42 +172,7 @@ before(
);
});
const port = await freePort();
appOrigin = `http://127.0.0.1:${port}`;
const app = startProcess(
[join(appRoot, ".output", "server", "index.mjs")],
{
cwd: appRoot,
env: {
// 使用生产已有的公开 API 配置接入外部边界,不读取或修改项目 .env。
...process.env,
NODE_ENV: "production",
HOST: "127.0.0.1",
PORT: String(port),
NITRO_HOST: "127.0.0.1",
NITRO_PORT: String(port),
NUXT_PUBLIC_API_BASE: backendOrigin,
},
},
);
await eventually(
async () => {
if (app.exitCode !== null)
throw new Error(`Preview exited: ${app.log}`);
try {
return (
await fetch(`${appOrigin}/login`, {
signal: AbortSignal.timeout(1000),
})
).ok;
} catch {
return false;
}
},
Boolean,
"Built app did not start",
30000,
);
appOrigin = await startApp();
browser = await chromium.launch({
executablePath: chromePath,