feat(HEL-240): 按确认样图还原桌面登录页左右骨架
把登录门户改回确认稿的品牌行、中下部主标题、底部行情标签和 K 线背景,并让超宽屏按比例展开左栏。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
541fb48c1c
commit
f905b44675
@@ -1,4 +1,9 @@
|
||||
const { test, expect } = require("@playwright/test");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const SHOT_DIR = path.resolve(__dirname, "../../../verify-shots");
|
||||
fs.mkdirSync(SHOT_DIR, { recursive: true });
|
||||
|
||||
function loginPayload(user) {
|
||||
return {
|
||||
@@ -54,6 +59,15 @@ async function mockLoginPortal(page, options = {}) {
|
||||
return;
|
||||
}
|
||||
if ((url.pathname === "/api/auth/login" || url.pathname === "/api/auth/register") && method === "POST") {
|
||||
if (options.loginDelay) await new Promise((resolve) => setTimeout(resolve, options.loginDelay));
|
||||
if (options.loginFails) {
|
||||
await route.fulfill({
|
||||
status: 401,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ error: "账号名或密码不正确,请重新输入。" }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
@@ -149,3 +163,128 @@ test("managing accounts removes a local record after inline confirmation", async
|
||||
await expect(page.locator(".login-account-row")).toHaveCount(1);
|
||||
await expect(page.locator(".login-account-row")).toContainText("alpha_user");
|
||||
});
|
||||
|
||||
async function assertConfirmedSkeleton(page, { width, height }) {
|
||||
await expect(page.locator(".login-brand-title")).toHaveText("看懂情绪周期,把复盘变成下一次的先手。");
|
||||
await expect(page.locator(".login-brand-header")).toBeVisible();
|
||||
await expect(page.locator(".login-brand-chart")).toBeVisible();
|
||||
await expect(page.locator(".login-brand-stats")).toBeVisible();
|
||||
await expect(page.locator(".login-brand-kicker")).toHaveText("收盘之后 · 复盘开始");
|
||||
const brand = await page.locator(".login-brand").boundingBox();
|
||||
const header = await page.locator(".login-brand-header").boundingBox();
|
||||
const mark = await page.locator(".login-brand-mark").boundingBox();
|
||||
const name = await page.locator(".login-brand-name").boundingBox();
|
||||
const title = await page.locator(".login-brand-title").boundingBox();
|
||||
const stats = await page.locator(".login-brand-stats").boundingBox();
|
||||
const chart = await page.locator(".login-brand-chart").boundingBox();
|
||||
const card = await page.locator(".login-card").boundingBox();
|
||||
expect(brand).toBeTruthy();
|
||||
expect(header.y - brand.y).toBeLessThan(48);
|
||||
expect(Math.abs(mark.y - name.y)).toBeLessThan(16);
|
||||
expect(title.y).toBeGreaterThan(height * 0.28);
|
||||
expect(title.y).toBeLessThan(height * 0.72);
|
||||
expect(stats.y).toBeGreaterThan(height * 0.55);
|
||||
expect(chart.height).toBeGreaterThan(80);
|
||||
expect(card.width).toBeGreaterThan(380);
|
||||
expect(card.width).toBeLessThan(450);
|
||||
if (width === 1440) {
|
||||
expect(brand.width).toBeGreaterThan(470);
|
||||
expect(brand.width).toBeLessThan(520);
|
||||
} else if (width === 1920) {
|
||||
expect(brand.width).toBeGreaterThan(540);
|
||||
expect(brand.width).toBeLessThan(580);
|
||||
} else {
|
||||
expect(brand.width).toBeGreaterThan(560);
|
||||
}
|
||||
}
|
||||
|
||||
async function openPortal(page, { theme, width, height, accounts, currentUserId, loginFails, loginDelay }) {
|
||||
await page.addInitScript((nextTheme) => {
|
||||
localStorage.setItem("xiaobaiTheme", nextTheme);
|
||||
}, theme);
|
||||
await page.setViewportSize({ width, height });
|
||||
await mockLoginPortal(page, { accounts, currentUserId, loginFails, loginDelay });
|
||||
await page.goto("/login/");
|
||||
}
|
||||
|
||||
for (const theme of ["light", "dark"]) {
|
||||
for (const [width, height] of [[1440, 900], [1920, 1080]]) {
|
||||
test(`confirmed skeleton ${theme} ${width}x${height}`, async ({ page }) => {
|
||||
await openPortal(page, { theme, width, height, accounts: [] });
|
||||
await assertConfirmedSkeleton(page, { width, height });
|
||||
await expect(page.locator("#loginThemeToggle")).toHaveText(theme === "dark" ? "☀ 日间" : "🌙 夜间");
|
||||
await page.screenshot({ path: path.join(SHOT_DIR, `first-${theme}-${width}.png`), fullPage: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test("ultrawide keeps the left brand from collapsing into a strip", async ({ page }) => {
|
||||
await openPortal(page, { theme: "dark", width: 2560, height: 1080, accounts: [] });
|
||||
await assertConfirmedSkeleton(page, { width: 2560, height: 1080 });
|
||||
});
|
||||
|
||||
test("picker add remove error and loading share the same desktop skeleton", async ({ page }) => {
|
||||
const accounts = SAVED_ACCOUNTS.map((item) => ({ ...item }));
|
||||
await openPortal(page, {
|
||||
theme: "dark",
|
||||
width: 1440,
|
||||
height: 900,
|
||||
accounts,
|
||||
currentUserId: 1,
|
||||
});
|
||||
await assertConfirmedSkeleton(page, { width: 1440, height: 900 });
|
||||
await expect(page.locator(".login-card-title")).toHaveText("选择账号");
|
||||
await expect(page.locator(".login-avatar")).toHaveCount(2);
|
||||
await expect(page.locator(".login-add")).toBeVisible();
|
||||
await page.screenshot({ path: path.join(SHOT_DIR, "picker-dark-1440.png"), fullPage: true });
|
||||
|
||||
await page.locator('[data-login-action="add"]').click();
|
||||
await expect(page.locator(".login-card-title")).toHaveText("添加账号");
|
||||
await assertConfirmedSkeleton(page, { width: 1440, height: 900 });
|
||||
await page.screenshot({ path: path.join(SHOT_DIR, "add-dark-1440.png"), fullPage: true });
|
||||
|
||||
await page.locator('[data-login-action="picker"]').click();
|
||||
await page.locator('[data-login-action="manage"]').click();
|
||||
await expect(page.locator(".login-card-title")).toHaveText("管理账号记录");
|
||||
await page.locator('[data-confirm-id="2"]').click();
|
||||
await expect(page.locator(".login-confirm-copy")).toContainText("beta_user");
|
||||
await page.screenshot({ path: path.join(SHOT_DIR, "remove-dark-1440.png"), fullPage: true });
|
||||
});
|
||||
|
||||
test("login failure and loading keep the confirmed first-login skeleton", async ({ page }) => {
|
||||
await openPortal(page, {
|
||||
theme: "light",
|
||||
width: 1440,
|
||||
height: 900,
|
||||
accounts: [],
|
||||
loginFails: true,
|
||||
});
|
||||
await page.locator("#loginUsername").fill("baiqizhi");
|
||||
await page.locator("#loginPassword").fill("wrong-password");
|
||||
await page.locator(".login-submit").click();
|
||||
await expect(page.locator(".login-error")).toContainText("账号名或密码不正确");
|
||||
await expect(page.locator("#loginPassword")).toHaveClass(/is-invalid/);
|
||||
await assertConfirmedSkeleton(page, { width: 1440, height: 900 });
|
||||
await page.screenshot({ path: path.join(SHOT_DIR, "error-light-1440.png"), fullPage: true });
|
||||
});
|
||||
|
||||
test("loading button appears on the confirmed first-login skeleton", async ({ page }) => {
|
||||
await openPortal(page, {
|
||||
theme: "dark",
|
||||
width: 1440,
|
||||
height: 900,
|
||||
accounts: [],
|
||||
loginDelay: 2500,
|
||||
});
|
||||
await page.evaluate(() => {
|
||||
window.location.replace = () => {};
|
||||
});
|
||||
await page.locator("#loginUsername").fill("baiqizhi");
|
||||
await page.locator("#loginPassword").fill("password12");
|
||||
const submit = page.locator(".login-submit").click();
|
||||
await expect(page.locator(".login-submit")).toContainText("正在登录...");
|
||||
await expect(page.locator(".login-spinner")).toBeVisible();
|
||||
await assertConfirmedSkeleton(page, { width: 1440, height: 900 });
|
||||
await page.screenshot({ path: path.join(SHOT_DIR, "loading-dark-1440.png"), fullPage: true });
|
||||
await submit;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user