R28-1 R28-4: 收口网页门禁与公网 smoke

这个提交包含在:
2026-03-23 09:59:54 +08:00
父节点 7e183c0cdc
当前提交 c17a72830c
共修改 10 个文件,包含 456 行新增和 63 行删除
+72
查看文件
@@ -0,0 +1,72 @@
import { expect, type Page } from "@playwright/test";
import { logout } from "./auth";
export type LiveSmokeRole = "admin" | "employee";
export type LiveSmokeUser = {
role: LiveSmokeRole;
identifier: string;
password: string;
name: string;
};
function requireEnv(key: string) {
const value = process.env[key]?.trim();
if (!value) {
throw new Error(`缺少 live smoke 环境变量:${key}`);
}
return value;
}
export function getExpectedWecomStatus() {
return process.env.LIVE_EXPECT_WECOM_STATUS?.trim() || "disabled";
}
export function getLiveSmokeUser(role: LiveSmokeRole) {
if (role === "admin") {
return {
role,
identifier: requireEnv("LIVE_SMOKE_ADMIN_IDENTIFIER"),
password: requireEnv("LIVE_SMOKE_ADMIN_PASSWORD"),
};
}
return {
role,
identifier: requireEnv("LIVE_SMOKE_EMPLOYEE_IDENTIFIER"),
password: requireEnv("LIVE_SMOKE_EMPLOYEE_PASSWORD"),
};
}
async function readCurrentUserName(page: Page): Promise<string> {
const profileTrigger = page.getByTestId("topbar-profile-trigger").first();
await expect(profileTrigger).toBeVisible();
const name = (await profileTrigger.textContent())?.trim();
if (!name) {
throw new Error("无法从顶栏读取当前登录用户名称");
}
return name;
}
export async function loginLiveUser(page: Page, role: LiveSmokeRole): Promise<LiveSmokeUser> {
const credentials = getLiveSmokeUser(role);
await page.goto("/login");
await expect(page.getByTestId("login-identifier")).toBeVisible();
await page.getByTestId("login-identifier").fill(credentials.identifier);
await page.getByTestId("login-password").fill(credentials.password);
await page.getByTestId("login-submit").click();
await expect(page).toHaveURL(/\/portal/);
const name = await readCurrentUserName(page);
return {
...credentials,
name,
};
}
export async function logoutLiveUser(page: Page, user?: Pick<LiveSmokeUser, "name">) {
await logout(page, user?.name);
}
@@ -0,0 +1,67 @@
import { expect, type Page } from "@playwright/test";
export type WecomEntryConfig = {
enabled: boolean;
entryEnabled: boolean;
oauthEnabled: boolean;
corpId: string;
agentId: string;
loginUrl: string | null;
bindEnabled: boolean;
defaultReturnTo: string;
status: string;
statusReasons: string[];
};
type Envelope<T> = {
data: T;
error?: {
code?: string;
message?: string;
};
};
export async function assertPortalLoaded(page: Page) {
await expect(page).toHaveURL(/\/portal/);
await expect(page.getByRole("heading", { name: "Portal" })).toBeVisible();
await expect(page.getByRole("link", { name: "Portal" }).first()).toBeVisible();
}
export async function assertMainNavVisible(page: Page) {
await expect(page.getByRole("link", { name: "Portal" }).first()).toBeVisible();
await expect(page.getByRole("link", { name: "团队" }).first()).toBeVisible();
await expect(page.getByRole("link", { name: "项目" }).first()).toBeVisible();
}
export async function assertBackupModulesCollapsed(page: Page) {
const summary = page.locator("summary").filter({ hasText: "备用模块" }).first();
await expect(summary).toBeVisible();
const details = summary.locator("xpath=ancestor::details[1]");
const isOpen = await details.evaluate((node) => (node as HTMLDetailsElement).open);
expect(isOpen).toBe(false);
await expect(page.getByRole("link", { name: "采购申请" })).not.toBeVisible();
}
export async function assertWecomBindingCardVisible(page: Page) {
await expect(page.getByRole("heading", { name: "企业微信绑定" })).toBeVisible();
await expect(page.getByText("接入状态", { exact: true })).toBeVisible();
}
export async function fetchWecomConfig(page: Page) {
const response = await page.request.get("/api/auth/wecom/config");
const text = await response.text();
let parsed: Envelope<WecomEntryConfig> | null = null;
try {
parsed = text ? JSON.parse(text) as Envelope<WecomEntryConfig> : null;
} catch {
parsed = null;
}
return {
status: response.status(),
ok: response.ok(),
body: text,
data: parsed?.data ?? null,
};
}
+145
查看文件
@@ -0,0 +1,145 @@
import { expect, test, type Page, type TestInfo } from "@playwright/test";
import { getExpectedWecomStatus, loginLiveUser, logoutLiveUser } from "./helpers/liveAuth";
import { openProfileSettings } from "./helpers/navigation";
import {
assertBackupModulesCollapsed,
assertMainNavVisible,
assertPortalLoaded,
assertWecomBindingCardVisible,
fetchWecomConfig,
} from "./helpers/smokeAssertions";
async function attachDebugArtifacts(page: Page, testInfo: TestInfo, consoleMessages: string[]) {
await testInfo.attach("current-url.txt", {
body: Buffer.from(page.url() || "about:blank"),
contentType: "text/plain",
});
await testInfo.attach("console.log", {
body: Buffer.from(consoleMessages.join("\n") || "(no console output)"),
contentType: "text/plain",
});
try {
const wecomConfig = await fetchWecomConfig(page);
await testInfo.attach("wecom-config.json", {
body: Buffer.from(JSON.stringify(wecomConfig, null, 2)),
contentType: "application/json",
});
} catch (error) {
await testInfo.attach("wecom-config-error.txt", {
body: Buffer.from(String(error)),
contentType: "text/plain",
});
}
}
test.describe("live smoke", () => {
test.skip(process.env.E2E_MODE !== "live", "仅在 live smoke 模式下执行");
test.describe.configure({ mode: "serial" });
test("公网基础探活与企微配置状态", async ({ request, page }, testInfo) => {
const consoleMessages: string[] = [];
page.on("console", (message) => {
consoleMessages.push(`[${message.type()}] ${message.text()}`);
});
try {
const readyResponse = await request.get("/ready");
expect(readyResponse.ok()).toBeTruthy();
await page.goto("/login");
await expect(page.getByTestId("login-identifier")).toBeVisible();
const wecomConfig = await fetchWecomConfig(page);
expect(wecomConfig.status).toBe(200);
expect(wecomConfig.data?.status).toBe(getExpectedWecomStatus());
} finally {
await attachDebugArtifacts(page, testInfo, consoleMessages);
}
});
test("管理员只读网页 smoke", async ({ page }, testInfo) => {
const consoleMessages: string[] = [];
page.on("console", (message) => {
consoleMessages.push(`[${message.type()}] ${message.text()}`);
});
let userName = "管理员";
try {
const admin = await loginLiveUser(page, "admin");
userName = admin.name;
await assertPortalLoaded(page);
await assertMainNavVisible(page);
await assertBackupModulesCollapsed(page);
await page.goto("/team");
await expect(page.getByRole("heading", { name: "团队状态", exact: true })).toBeVisible();
await page.goto("/projects");
await expect(page.getByRole("heading", { name: "项目", exact: true })).toBeVisible();
await page.goto("/announcements");
await expect(page.getByRole("heading", { name: "公告", exact: true })).toBeVisible();
await page.goto("/system/workspace");
await expect(page.getByRole("heading", { name: "工作区设置", exact: true })).toBeVisible();
await page.goto("/system/feature-flags");
await expect(page.getByRole("heading", { name: "功能开关", exact: true })).toBeVisible();
await page.goto("/audit-logs");
await expect(page.getByRole("heading", { name: "审计工作台", exact: true })).toBeVisible();
await openProfileSettings(page, userName);
await assertWecomBindingCardVisible(page);
await logoutLiveUser(page, { name: userName });
} finally {
await attachDebugArtifacts(page, testInfo, consoleMessages);
}
});
test("员工只读网页 smoke", async ({ page }, testInfo) => {
const consoleMessages: string[] = [];
page.on("console", (message) => {
consoleMessages.push(`[${message.type()}] ${message.text()}`);
});
let userName = "员工";
try {
const employee = await loginLiveUser(page, "employee");
userName = employee.name;
await assertPortalLoaded(page);
await assertMainNavVisible(page);
await page.goto("/team");
await expect(page.getByRole("heading", { name: "团队状态", exact: true })).toBeVisible();
await page.goto("/projects");
await expect(page.getByRole("heading", { name: "项目", exact: true })).toBeVisible();
await page.goto("/announcements");
await expect(page.getByRole("heading", { name: "公告", exact: true })).toBeVisible();
await page.goto("/system/workspace");
await expect(page.getByRole("heading", { name: "工作区设置", exact: true })).toBeVisible();
await page.goto("/system/feature-flags");
await expect(page.getByRole("heading", { name: "功能开关", exact: true })).toBeVisible();
await page.goto("/audit-logs");
await expect(page.getByRole("heading", { name: "审计工作台", exact: true })).toBeVisible();
await openProfileSettings(page, userName);
await assertWecomBindingCardVisible(page);
await logoutLiveUser(page, { name: userName });
} finally {
await attachDebugArtifacts(page, testInfo, consoleMessages);
}
});
});
+46
查看文件
@@ -0,0 +1,46 @@
import { expect, test, type Page, type TestInfo } from "@playwright/test";
import { EMPLOYEE, login, logout, type E2EUser } from "./helpers/auth";
const ADMIN: E2EUser = {
identifier: "[email protected]",
password: "AdminPassword1234",
name: "Admin",
};
async function attachScreenshot(page: Page, testInfo: TestInfo, name: string) {
await testInfo.attach(name, {
body: await page.screenshot({ fullPage: true }),
contentType: "image/png",
});
}
test.describe("visual portal", () => {
test("员工 Portal 视觉基线", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1440, height: 1200 });
await login(page, EMPLOYEE);
await expect(page.locator('[aria-label="portal-card-my_todos"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-today_presence"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-my_weekly_report"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-my_projects"]')).toBeVisible();
await expect(page.getByText("我的申请与资产")).toBeVisible();
await attachScreenshot(page, testInfo, "portal-employee.png");
});
test("管理员 Portal 视觉基线", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1440, height: 1200 });
await login(page, ADMIN);
await expect(page.locator('[aria-label="portal-card-pending_approvals"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-team_presence"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-weekly_missing"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-weekly_help"]')).toBeVisible();
await expect(page.getByText("今日管理重点")).toBeVisible();
await expect(page.getByText("试点观测摘要")).toBeVisible();
await attachScreenshot(page, testInfo, "portal-admin.png");
await logout(page, ADMIN.name);
});
});
+35
查看文件
@@ -0,0 +1,35 @@
import { expect, test, type Page, type TestInfo } from "@playwright/test";
import { EMPLOYEE, login } from "./helpers/auth";
async function attachScreenshot(page: Page, testInfo: TestInfo, name: string) {
await testInfo.attach(name, {
body: await page.screenshot({ fullPage: true }),
contentType: "image/png",
});
}
test.describe("visual shell", () => {
test("登录页入口布局基线", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1440, height: 1024 });
await page.goto("/login");
await expect(page.getByRole("heading", { name: "登录海音达工作台" })).toBeVisible();
await expect(page.getByText("统一入口").first()).toBeVisible();
await expect(page.getByTestId("login-password-primary-panel")).toBeVisible();
await attachScreenshot(page, testInfo, "login-shell.png");
});
test("登录后壳层布局基线", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1440, height: 1024 });
await login(page, EMPLOYEE);
await expect(page.locator("aside")).toBeVisible();
await expect(page.getByTestId("topbar-profile-trigger")).toBeVisible();
await expect(page.locator("aside").getByRole("link", { name: "Portal", exact: true })).toBeVisible();
await expect(page.locator("summary").filter({ hasText: "备用模块" })).toBeVisible();
await attachScreenshot(page, testInfo, "app-shell.png");
});
});