R105/A2-A5 补强试运行自动化验收

这个提交包含在:
2026-05-07 12:52:18 +08:00
父节点 d99cbf8491
当前提交 86975dd2a4
共修改 9 个文件,包含 679 行新增和 4 行删除
@@ -197,7 +197,7 @@ export function NavLinks({ onNavigate }: { onNavigate?: () => void }) {
return (
<nav className="space-y-5">
{renderSection("主导航", "优先处理今日工作与高频协同。", sections.main, onNavigate)}
{renderSection("主导航", "今日工作与高频协同。", sections.main, onNavigate)}
{renderSection("管理入口", "仅管理员与负责人使用。", sections.admin, onNavigate)}
{renderBackupSection(sections.backup, onNavigate)}
</nav>
+1 -1
查看文件
@@ -11,7 +11,7 @@ export function Sidebar() {
<span className="size-2 rounded-full bg-primary" />
今日工作台
</div>
<div className="mt-2 leading-6">先处理待办和团队状态,再进入具体模块。</div>
<div className="mt-2 leading-6">待处理、团队状态和常用入口。</div>
</div>
</div>
+1 -1
查看文件
@@ -176,7 +176,7 @@ export function ProjectsPage() {
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<MetricCard title="当前项目" value={projectStats.total} note={mine ? "已切换到我的项目" : "当前筛选结果"} />
<MetricCard title="进行中" value={projectStats.active} note="优先跟进活跃项目" />
<MetricCard title="逾期里程碑" value={projectStats.overdueMilestones} note="建议先处理延期事项" tone={projectStats.overdueMilestones > 0 ? "warning" : "neutral"} />
<MetricCard title="逾期里程碑" value={projectStats.overdueMilestones} note="延期事项待处理" tone={projectStats.overdueMilestones > 0 ? "warning" : "neutral"} />
<MetricCard title="未关闭风险" value={projectStats.openRisks} note="问题留痕建议绑定责任人" tone={projectStats.openRisks > 0 ? "danger" : "neutral"} />
</section>
@@ -0,0 +1,135 @@
import { mkdirSync } from "node:fs";
import path from "node:path";
import { expect, test, type Locator, type Page } from "@playwright/test";
import { getExpectedWecomStatus, loginLiveUser } from "./helpers/liveAuth";
import { fetchWecomConfig } from "./helpers/smokeAssertions";
const screenshotDir = path.resolve(process.cwd(), "../output/playwright/r105");
function screenshotPath(fileName: string) {
mkdirSync(screenshotDir, { recursive: true });
return path.join(screenshotDir, fileName);
}
async function expectNoLegacyDesignCopy(page: Page) {
const bodyText = await page.locator("body").innerText();
expect(bodyText).not.toContain("先处理");
expect(bodyText).not.toContain("清爽");
expect(bodyText).not.toContain("低频模块放在这里");
expect(bodyText).not.toContain("/invoice-pool");
expect(bodyText).not.toContain("/purchase-requests");
}
async function expectNoDesktopTableOnMobile(page: Page) {
await expect(page.locator("table")).toHaveCount(0);
}
function parseRgb(value: string) {
const match = value.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?/i);
if (!match) {
return null;
}
return {
r: Number(match[1]),
g: Number(match[2]),
b: Number(match[3]),
a: match[4] == null ? 1 : Number(match[4]),
};
}
async function expectNotBlackSurface(locator: Locator) {
const backgroundColor = await locator.evaluate((element) => {
return window.getComputedStyle(element).backgroundColor;
});
const rgb = parseRgb(backgroundColor);
if (!rgb) {
return;
}
if (rgb.a === 0) {
return;
}
const average = (rgb.r + rgb.g + rgb.b) / 3;
expect(average, `backgroundColor=${backgroundColor}`).toBeGreaterThan(24);
}
test.describe("R105 pretrial visual acceptance", () => {
test.skip(process.env.E2E_MODE !== "live", "仅在公网 live 模式下执行");
test("公网登录与企业微信入口仍默认进入移动工作台", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/login");
await expect(page.getByRole("button", { name: /企业微信/ })).toBeVisible();
const wecomConfig = await fetchWecomConfig(page);
expect(wecomConfig.status).toBe(200);
expect(wecomConfig.data?.status).toBe(getExpectedWecomStatus());
expect(wecomConfig.data?.defaultReturnTo).toBe("/mobile");
expect(wecomConfig.data?.loginUrl).toContain("returnTo=%2Fmobile");
await expectNoLegacyDesignCopy(page);
await page.screenshot({ path: screenshotPath("mobile-login-wecom-entry.png"), fullPage: true });
});
test("员工移动首页、票据上传和我的页面保持手机体验", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await loginLiveUser(page, "employee");
await page.goto("/mobile");
await expect(page.getByTestId("mobile-nav-home")).toBeVisible();
await expect(page.getByTestId("mobile-nav-capture")).toBeVisible();
await expectNoDesktopTableOnMobile(page);
await expectNoLegacyDesignCopy(page);
await expectNotBlackSurface(page.getByTestId("mobile-nav-home"));
await page.screenshot({ path: screenshotPath("employee-mobile-home.png"), fullPage: true });
await page.goto("/mobile/capture");
await expect(page.getByRole("heading", { name: "票据上传", exact: true })).toBeVisible();
const fileInput = page.getByTestId("mobile-capture-file");
await expect(fileInput).toHaveAttribute("multiple", "");
await expect(page.getByTestId("mobile-capture-upload")).toBeVisible();
await expectNoDesktopTableOnMobile(page);
await expectNoLegacyDesignCopy(page);
await page.screenshot({ path: screenshotPath("employee-mobile-capture.png"), fullPage: true });
await page.goto("/mobile/me");
await expect(page.getByRole("heading", { name: "我的", exact: true })).toBeVisible();
await expect(page.getByTestId("mobile-more-links")).toBeVisible();
await expectNoDesktopTableOnMobile(page);
await expectNoLegacyDesignCopy(page);
await page.screenshot({ path: screenshotPath("employee-mobile-me.png"), fullPage: true });
});
test("管理员移动审批页不退回桌面表格体验", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await loginLiveUser(page, "admin");
await page.goto("/mobile/approvals");
await expect(page.getByRole("heading", { name: /审批/ })).toBeVisible();
await expect(page.getByText("待处理").first()).toBeVisible();
await expectNoDesktopTableOnMobile(page);
await expectNoLegacyDesignCopy(page);
await expectNotBlackSurface(page.getByTestId("mobile-nav-approvals"));
await page.screenshot({ path: screenshotPath("admin-mobile-approvals.png"), fullPage: true });
});
test("PC Portal、报销和票据中心保持试运行首页一致性", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 960 });
await loginLiveUser(page, "employee");
await page.goto("/portal");
await expect(page.locator('[aria-label^="portal-card-"]').first()).toBeVisible();
await expectNoLegacyDesignCopy(page);
await page.screenshot({ path: screenshotPath("desktop-portal.png"), fullPage: true });
await page.goto("/reimbursements");
await expect(page.getByRole("heading", { name: /报销/ })).toBeVisible();
await expectNoLegacyDesignCopy(page);
await page.screenshot({ path: screenshotPath("desktop-reimbursements.png"), fullPage: true });
await page.goto("/invoice-pool");
await expect(page.getByRole("heading", { name: "票据中心", exact: true })).toBeVisible();
await expectNoLegacyDesignCopy(page);
await page.screenshot({ path: screenshotPath("desktop-invoice-pool.png"), fullPage: true });
});
});