修复首次鉴权跳转白屏并补齐登录表单回归
前端验证 / verify (push) Failing after 5m15s

This commit is contained in:
cieanyau committed 2026-11-06 03:55:12 +08:00
1 parent 216354cef1
commit 6e2c114474
2 files changed
+117 -22

No files matched your search

+106 -16
View File
@@ -7,6 +7,7 @@ import { createDecipheriv } from "node:crypto";
import { test } from "node:test";
import {
appOrigin,
backendOrigin,
eventually,
fixtureRequest,
login,
@@ -24,29 +25,105 @@ async function visibleKeyIds(page) {
return rows.map((row) => Number(row.match(/#(\d+)/)[1]));
}
async function visibleLoginForm(page) {
// 地址跳转不代表登录页已挂载:必须能看到并操作真实表单,避免白屏被误报通过。
await page.getByLabel("账号", { exact: true }).waitFor();
const password = page.getByLabel("密码", { exact: true });
await password.waitFor();
assert.equal(await password.getAttribute("type"), "password");
const submit = page.getByRole("button", { name: "登录", exact: true });
await submit.waitFor();
assert.equal(await submit.isEnabled(), true);
}
for (const destination of [
"/",
"/security/ssh-keys?m=7#details",
"/servers/server?id=5",
"/products/123?edit_position=0",
]) {
test(
`auth guard renders the login form after opening ${destination}`,
{ timeout: 20000 },
(t) =>
scenario(t, {}, async (page) => {
// 覆盖首次打开根入口、列表与嵌套详情;不能只检查地址而漏掉登录表单的挂载死锁。
await page.goto(`${appOrigin}${destination}`);
await page.waitForURL((url) => url.pathname === "/login");
await visibleLoginForm(page);
assert.equal(
new URL(page.url()).searchParams.get("redirect"),
destination === "/" ? null : destination,
);
assert.equal(
await page.evaluate(() => localStorage.getItem("jwt")),
null,
);
const requests = await fixtureRequest("requests");
assert.equal(
requests.some((request) =>
["/ssh_key", "/host/5", "/menu", "/account"].includes(request.path),
),
false,
"The redirected login page must not mount the protected page or layout",
);
return {
loginFormVisible: true,
destinationPreserved: true,
protectedApiNotCalled: true,
};
}),
);
}
test(
"auth guard preserves the protected destination",
{ timeout: 20000 },
"public catalog switches from guest to member layout after real login",
{ timeout: 25000 },
(t) =>
scenario(t, {}, async (page) => {
// 访客导航保留查询串和锚点,但不能先发受保护的列表请求再跳转。
await page.goto(`${appOrigin}/security/ssh-keys?m=7#details`);
await page.waitForURL((url) => url.pathname === "/login");
const url = new URL(page.url());
// 分类是公开 HTTP 边界;空分类契约足以验证入口布局,不引入测试专用产品参数。
await page
.context()
.route(`${backendOrigin}/console/v1/product/group/*`, (route) =>
route.fulfill({
json: { status: 200, msg: "", data: { list: [] } },
headers: { "Access-Control-Allow-Origin": "*" },
}),
);
await page.goto(`${appOrigin}/products`);
await page
.getByRole("heading", { name: "订购产品", exact: true })
.waitFor();
await page
.getByRole("navigation", { name: "产品导航", exact: true })
.waitFor();
assert.equal(
url.searchParams.get("redirect"),
"/security/ssh-keys?m=7#details",
await page.getByRole("navigation", { name: "会员中心导航" }).count(),
0,
);
assert.equal(
await page.evaluate(() => localStorage.getItem("jwt")),
null,
);
const requests = await fixtureRequest("requests");
assert.equal(
requests.some((request) => request.path === "/ssh_key"),
(await fixtureRequest("requests")).some((request) =>
["/index", "/account", "/menu"].includes(request.path),
),
false,
);
return { destinationPreserved: true, protectedApiNotCalled: true };
await page.getByRole("link", { name: "登录", exact: true }).click();
await visibleLoginForm(page);
await page.getByLabel("账号", { exact: true }).fill(loginAccount);
await page.getByLabel("密码", { exact: true }).fill(loginPassword);
await page.getByRole("button", { name: "登录", exact: true }).click();
await page.waitForURL((url) => url.pathname === "/products");
await page.getByRole("navigation", { name: "会员中心导航" }).waitFor();
await page
.getByRole("heading", { name: "订购产品", exact: true })
.waitFor();
assert.equal(
await page
.getByRole("navigation", { name: "产品导航", exact: true })
.count(),
0,
);
return { guestCatalogVisible: true, memberLayoutAfterLogin: true };
}),
);
@@ -55,7 +132,13 @@ test(
{ timeout: 25000 },
(t) =>
scenario(t, {}, async (page) => {
await login(page, "/security/ssh-keys?m=1");
// 从受保护入口跳出的表单继续登录,不能用 helper 重新打开 /login 掩盖首次导航缺陷。
await page.goto(`${appOrigin}/security/ssh-keys?m=1`);
await page.waitForURL((url) => url.pathname === "/login");
await visibleLoginForm(page);
await page.getByLabel("账号", { exact: true }).fill(loginAccount);
await page.getByLabel("密码", { exact: true }).fill(loginPassword);
await page.getByRole("button", { name: "登录", exact: true }).click();
await page
.getByRole("heading", { name: "SSH 密钥", exact: true })
.waitFor();
@@ -581,6 +664,7 @@ test(
await page.waitForURL((url) => url.pathname === "/login", {
timeout: 1500,
});
await visibleLoginForm(page);
assert.equal(
await page.evaluate(() => localStorage.getItem("jwt")),
null,
@@ -596,6 +680,7 @@ test(
);
await page.goto(`${appOrigin}/security/ssh-keys`);
await page.waitForURL((url) => url.pathname === "/login");
await visibleLoginForm(page);
return {
localSessionClearedWithoutResponse: true,
nextProtectedNavigationBlocked: true,
@@ -620,6 +705,7 @@ test(
"Authenticated page must request its data",
);
await page.waitForURL((url) => url.pathname === "/login");
await visibleLoginForm(page);
await eventually(
() => page.evaluate(() => localStorage.getItem("jwt")),
(jwt) => jwt === null,
@@ -863,6 +949,7 @@ test(
await page.waitForURL((url) => url.pathname === "/login", {
timeout: 1500,
});
await visibleLoginForm(page);
assert.equal(
await page.evaluate(() => localStorage.getItem("jwt")),
null,
@@ -906,6 +993,7 @@ test(
await fixtureRequest("options", { expiredPayment: true });
await page.getByRole("button", { name: "确认支付", exact: true }).click();
await page.waitForURL((url) => url.pathname === "/login");
await visibleLoginForm(page);
assert.equal(
await page.evaluate(() => localStorage.getItem("jwt")),
null,
@@ -964,6 +1052,8 @@ test(
await page.waitForURL((url) => url.pathname === "/login", {
timeout: 1500,
});
await visibleLoginForm(page);
await visibleLoginForm(secondTab);
assert.equal(
await page.evaluate(() => localStorage.getItem("jwt")),
null,