rebuild(stage-4): deliver shared shell and account interfaces

This commit is contained in:
leefer
2026-07-30 02:08:20 +08:00
parent d1c658d8ef
commit 3b75bf2d63
47 changed files with 3042 additions and 99 deletions
+132
View File
@@ -0,0 +1,132 @@
const fs = require("node:fs");
const path = require("node:path");
const { expect, test } = require("@playwright/test");
const evidence = path.resolve(__dirname, "../../docs/evidence/stage-4");
test.beforeAll(() => fs.mkdirSync(evidence, { recursive: true }));
async function authenticate(page, username, password) {
await page.goto("/");
await expect(page.getByRole("heading", { name: "小白复盘" })).toBeVisible();
await page.getByLabel("账号名").fill(username);
await page.getByLabel("密码").fill(password);
await page.getByRole("button", { name: "登录", exact: true }).click();
await expect(page.locator(".sidebar, .field-error")).toBeVisible();
if (!(await page.locator(".sidebar").isVisible())) {
await page.getByRole("tab", { name: "注册" }).click();
await page.getByRole("button", { name: "注册并登录" }).click();
}
await expect(page.getByRole("navigation", { name: "主导航", exact: true })).toBeVisible();
}
test("authentication, shell, dialogs, theme and responsive structure", async ({ page }) => {
const consoleErrors = [];
const unexpectedResponses = [];
page.on("console", (message) => {
if (message.type() === "error" && !message.text().includes("401 (Unauthorized)")) {
consoleErrors.push(message.text());
}
});
page.on("response", (response) => {
const expectedAnonymousProbe = response.status() === 401 && response.url().endsWith("/api/auth/session");
if (response.status() >= 400 && !expectedAnonymousProbe) {
unexpectedResponses.push(`${response.status()} ${response.url()}`);
}
});
await authenticate(page, "stage4admin", "Stage4-pass-123!");
await expect(page.getByText("管理员", { exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "系统管理" })).toBeVisible();
await expect(page.locator(".nav-item")).toHaveCount(16);
await page.getByRole("button", { name: /stage4admin/ }).click();
const accountMenu = page.locator(".account-menu");
await expect(accountMenu.getByRole("button")).toHaveCount(5);
await accountMenu.getByRole("button", { name: "个人资料" }).click();
const profileDialog = page.getByRole("dialog", { name: "个人资料" });
await expect(profileDialog).toBeVisible();
await profileDialog.getByLabel("出生日期").fill("1990-03-08");
await profileDialog.getByLabel("出生时间").fill("08:30");
await profileDialog.getByLabel("性别").selectOption("male");
await profileDialog.getByRole("button", { name: "保存", exact: true }).click();
await expect(page.getByRole("status")).toContainText("个人资料已保存");
await page.getByLabel("关闭").click();
await page.getByRole("button", { name: "普通用户" }).click();
await expect(page.getByRole("dialog", { name: "会员状态" })).toContainText("智能选股、问师、问天、复盘助手");
await page.getByLabel("关闭").click();
await page.getByRole("button", { name: /stage4admin/ }).click();
await page.locator(".account-menu").getByRole("button", { name: "修改密码" }).click();
const passwordDialog = page.getByRole("dialog", { name: "修改密码" });
await expect(passwordDialog.getByLabel("当前密码")).toBeVisible();
await expect(passwordDialog.getByLabel("确认新密码")).toBeVisible();
await page.getByLabel("关闭").click();
await page.keyboard.press("Control+K");
const searchDialog = page.getByRole("dialog", { name: "全局搜索" });
await expect(searchDialog).toBeVisible();
const dialogBox = await searchDialog.boundingBox();
expect(dialogBox).not.toBeNull();
expect(Math.abs(dialogBox.x + dialogBox.width / 2 - 960)).toBeLessThan(2);
await page.keyboard.press("Escape");
const desktopMetrics = await page.evaluate(() => ({
topbar: document.querySelector(".topbar")?.getBoundingClientRect().height,
sidebar: document.querySelector(".sidebar")?.getBoundingClientRect().width,
paddingTop: getComputedStyle(document.querySelector(".page-frame")).paddingTop,
paddingLeft: getComputedStyle(document.querySelector(".page-frame")).paddingLeft,
overflow: document.documentElement.scrollWidth - window.innerWidth,
}));
expect(desktopMetrics).toEqual({ topbar: 46, sidebar: 200, paddingTop: "14px", paddingLeft: "16px", overflow: 0 });
await page.screenshot({ path: path.join(evidence, "shell-light-1920x1080.jpg"), type: "jpeg", quality: 82 });
await page.getByRole("button", { name: "夜间" }).click();
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
await page.reload();
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
await expect(page.getByRole("button", { name: "日间" })).toBeVisible();
await page.screenshot({ path: path.join(evidence, "shell-dark-1920x1080.jpg"), type: "jpeg", quality: 82 });
await page.getByRole("button", { name: "系统管理" }).click();
await expect(page.getByRole("heading", { name: "系统管理" })).toBeVisible();
await expect(page.getByRole("tab")).toHaveCount(0);
await expect(page.getByRole("button", { name: "行情管理" })).toBeVisible();
await expect(page.getByRole("button", { name: "模型池" })).toBeVisible();
await expect(page.getByRole("button", { name: "会员管理" })).toBeVisible();
await page.getByRole("button", { name: "模型池" }).click();
await expect(page.getByRole("heading", { name: "添加模型" })).toBeVisible();
await page.getByRole("button", { name: "会员管理" }).click();
await expect(page.getByRole("heading", { name: "账号会员状态" })).toBeVisible();
await page.getByRole("button", { name: "行情管理" }).click();
await page.setViewportSize({ width: 3840, height: 2160 });
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBe(0);
await page.screenshot({ path: path.join(evidence, "system-dark-3840x2160.jpg"), type: "jpeg", quality: 72 });
await page.goto("/workspace/emotion");
await page.setViewportSize({ width: 390, height: 844 });
await expect(page.getByRole("navigation", { name: "移动端主导航" })).toBeVisible();
await expect(page.getByRole("navigation", { name: "主导航", exact: true })).toBeHidden();
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBe(0);
await page.screenshot({ path: path.join(evidence, "shell-dark-390x844.jpg"), type: "jpeg", quality: 82 });
expect(consoleErrors).toEqual([]);
expect(unexpectedResponses).toEqual([]);
});
test("regular users do not receive administrator controls and see intelligent locks", async ({ page }) => {
await authenticate(page, "stage4user", "Stage4-user-123!");
await expect(page.getByText("管理员", { exact: true })).toHaveCount(0);
await expect(page.getByRole("button", { name: "系统管理" })).toHaveCount(0);
await expect(page.getByRole("button", { name: "后台刷新" })).toHaveCount(0);
await page.goto("/system");
await expect(page).toHaveURL(/\/workspace\/emotion$/);
await page.goto("/workspace/screener");
await expect(page.getByText("智能选股仅对会员开放")).toBeVisible();
await expect(page.getByRole("button", { name: "查看会员状态" })).toBeVisible();
});