QG6 完成QA治理矩阵与报告规范
这个提交包含在:
@@ -230,6 +230,7 @@ export function WorkflowTaskDetailPage() {
|
||||
const detail = detailQuery.data;
|
||||
const canTransfer = allowedActions.has("transfer");
|
||||
const allowedActionLabels = detail.allowedActions.map(workflowActionLabel).join(" / ");
|
||||
const businessStatusLabel = workflowInstanceStatusLabel(detail.workflowInstance?.currentStatus);
|
||||
|
||||
return (
|
||||
<PageScaffold>
|
||||
@@ -239,9 +240,8 @@ export function WorkflowTaskDetailPage() {
|
||||
meta={(
|
||||
<>
|
||||
<StatusBadge status={detail.task.status} />
|
||||
<span className="info-chip">任务ID:{detail.task.id}</span>
|
||||
<span className="info-chip">对象类型:{entityTitle(detail.workflowInstance?.entityType)}</span>
|
||||
<span className="info-chip">实例:{detail.workflowInstance?.id ?? "-"}</span>
|
||||
<span className="info-chip">业务状态:{businessStatusLabel}</span>
|
||||
</>
|
||||
)}
|
||||
actions={(
|
||||
@@ -276,7 +276,7 @@ export function WorkflowTaskDetailPage() {
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 p-4 text-sm" data-testid="workflow-task-summary">
|
||||
<div>状态:{workflowStatusLabel(detail.task.status)}</div>
|
||||
<div>实例:{detail.workflowInstance?.id ?? "-"}</div>
|
||||
<div>业务状态:{businessStatusLabel}</div>
|
||||
<div>对象类型:{entityTitle(detail.workflowInstance?.entityType)}</div>
|
||||
{detail.reimbursement ? (
|
||||
<>
|
||||
@@ -317,7 +317,7 @@ export function WorkflowTaskDetailPage() {
|
||||
) : null}
|
||||
{detail.travelRequest ? (
|
||||
<>
|
||||
<div>差旅单号:{detail.travelRequest.requestNo ?? detail.travelRequest.id}</div>
|
||||
<div>差旅单号:{detail.travelRequest.requestNo ?? "暂未生成"}</div>
|
||||
<div>申请人:{detail.travelRequest.applicant?.name ?? "-"}</div>
|
||||
<div>出差类型:{detail.travelRequest.tripType === "international" ? "国际" : "国内"}</div>
|
||||
<div>法体:{detail.travelRequest.legalEntity?.name ?? "-"}</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test, type Browser, type Page } from "@playwright/test";
|
||||
@@ -30,14 +31,41 @@ type PreparedRoutes = {
|
||||
|
||||
const ROOT_DIR = path.resolve(process.cwd(), "..");
|
||||
const MATRIX_PATH = path.join(ROOT_DIR, "docs/trialrun/project-wide-flow-matrix.csv");
|
||||
const SCREENSHOT_DIR = path.join(ROOT_DIR, "output/playwright/project-wide-visual");
|
||||
const ARTIFACT_DIR = path.join(ROOT_DIR, "artifacts/qa-flow/project-wide-visual");
|
||||
const ISSUE_ID = process.env.QA_ISSUE_ID?.trim() || "project-wide-visual";
|
||||
const BASE_URL = process.env.E2E_BASE_URL?.trim() || null;
|
||||
const SCREENSHOT_DIR = resolveRootPath(
|
||||
process.env.QA_VISUAL_OUTPUT_DIR,
|
||||
path.join(ROOT_DIR, "output/playwright/project-wide-visual"),
|
||||
);
|
||||
const ARTIFACT_DIR = resolveRootPath(
|
||||
process.env.QA_VISUAL_ARTIFACT_DIR,
|
||||
path.join(ROOT_DIR, "artifacts/qa-flow/project-wide-visual"),
|
||||
);
|
||||
const WECOM_UA =
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 wxwork/4.1.20 MicroMessenger/8.0.1 Language/zh_CN";
|
||||
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi;
|
||||
|
||||
const findings: VisualFinding[] = [];
|
||||
|
||||
function resolveRootPath(value: string | undefined, fallback: string) {
|
||||
if (!value?.trim()) {
|
||||
return fallback;
|
||||
}
|
||||
return path.isAbsolute(value) ? value : path.join(ROOT_DIR, value);
|
||||
}
|
||||
|
||||
function currentCommit() {
|
||||
try {
|
||||
return execSync("git rev-parse --short HEAD", {
|
||||
cwd: ROOT_DIR,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
}).trim();
|
||||
} catch {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
function outputPath(dir: string, fileName: string) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
return path.join(dir, fileName);
|
||||
@@ -357,7 +385,7 @@ async function auditPlans(browser: Browser, plans: Array<{ route: string; domain
|
||||
for (const { user, plans: userPlans } of plansByUser.values()) {
|
||||
const context = viewport === "mobile-wecom"
|
||||
? await newWecomContext(browser)
|
||||
: await browser.newContext({ viewport: { width: 1366, height: 900 }, serviceWorkers: "block" });
|
||||
: await browser.newContext({ viewport: { width: 1440, height: 960 }, serviceWorkers: "block" });
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
await loginForVisual(page, user);
|
||||
@@ -382,7 +410,15 @@ test.describe("项目级全页面视觉巡检", () => {
|
||||
|
||||
const reportPath = outputPath(ARTIFACT_DIR, "report.json");
|
||||
writeFileSync(reportPath, JSON.stringify({
|
||||
issueId: ISSUE_ID,
|
||||
baseURL: BASE_URL,
|
||||
commit: currentCommit(),
|
||||
checkedAt: new Date().toISOString(),
|
||||
status: findings.some((finding) => finding.severity === "P1") ? "FAIL" : "PASS",
|
||||
matrix: {
|
||||
pc: "1440x960",
|
||||
mobileWeCom: "390x844",
|
||||
},
|
||||
pcRoutes: pcPlans.map((item) => item.route),
|
||||
mobileRoutes: mobilePlans.map((item) => item.route),
|
||||
findings,
|
||||
|
||||
在新工单中引用
屏蔽一个用户