项目文件夹

文件
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

144 行
4.3 KiB
TypeScript

export type UiPlaywrightGroup = {
files: readonly string[];
grep: string;
workers?: number;
};
export type UiP0CiMatrixEntry = {
name: string;
shard: string;
};
export type VisualCiMatrixEntry = {
files: string;
name: string;
};
export const uiP0Groups = {
smoke: {
grep: String.raw`\[P0\]`,
files: ["ui/critical-smoke.test.ts"],
},
"workspace-restoration": {
grep: String.raw`\[P0\]`,
files: ["ui/app-restoration.test.ts"],
},
"entry-settings": {
grep: String.raw`\[P0\]`,
files: [
"ui/entry-chrome-flows.test.ts",
"ui/entry-configuration-flows.test.ts",
"ui/amr-onboarding.test.ts",
"ui/api-empty-response.test.ts",
"ui/settings-api-protocol.test.ts",
"ui/settings-connectors-auth-happy-path.test.ts",
"ui/settings-connectors-auth-recovery.test.ts",
],
},
"project-workspace": {
grep: String.raw`\[P0\]`,
workers: 1,
files: [
"ui/app.test.ts",
"ui/app-design-files.test.ts",
"ui/app-manual-edit.test.ts",
"ui/project-management-flows.test.ts",
"ui/workspace-keyboard-flows.test.ts",
],
},
"project-runtime": {
grep: String.raw`\[P0\]`,
workers: 1,
files: [
"ui/real-daemon-run.test.ts",
"ui/amr-run-failure-recovery.test.ts",
"ui/amr-logout-requires-relogin.test.ts",
"ui/settings-local-cli-codex-fallback.test.ts",
],
},
} as const satisfies Record<string, UiPlaywrightGroup>;
export type UiP0GroupName = keyof typeof uiP0Groups;
export const uiP0CiMatrix = [
{ name: "entry-settings", shard: "entry-settings" },
{ name: "project-workspace", shard: "project-workspace" },
{ name: "project-runtime", shard: "project-runtime" },
{ name: "workspace-restoration", shard: "workspace-restoration" },
] as const satisfies readonly UiP0CiMatrixEntry[];
export const visualCiMatrix = [
{ name: "entry-navigation", files: "ui/visual-entry.test.ts ui/visual-navigation.test.ts" },
{ name: "settings-workspace", files: "ui/visual-settings.test.ts ui/visual-workspace.test.ts" },
] as const satisfies readonly VisualCiMatrixEntry[];
const uiP0CoverageFiles = [
"ui/amr-logout-requires-relogin.test.ts",
"ui/amr-onboarding.test.ts",
"ui/amr-run-failure-recovery.test.ts",
"ui/api-empty-response.test.ts",
"ui/app-design-files.test.ts",
"ui/app-manual-edit.test.ts",
"ui/app-restoration.test.ts",
"ui/app.test.ts",
"ui/entry-chrome-flows.test.ts",
"ui/entry-configuration-flows.test.ts",
"ui/project-management-flows.test.ts",
"ui/real-daemon-run.test.ts",
"ui/settings-api-protocol.test.ts",
"ui/settings-connectors-auth-happy-path.test.ts",
"ui/settings-connectors-auth-recovery.test.ts",
"ui/settings-local-cli-codex-fallback.test.ts",
"ui/workspace-keyboard-flows.test.ts",
] as const;
export function getUiP0Group(name: string): UiPlaywrightGroup | undefined {
return uiP0Groups[name as UiP0GroupName];
}
export function listUiP0GroupNames(): string[] {
return Object.keys(uiP0Groups).sort();
}
export function validatePlaywrightSuiteTopology(): string[] {
const errors: string[] = [];
const knownGroups = new Set(Object.keys(uiP0Groups));
const coverageFiles = sortedUnique(uiP0CoverageFiles);
const ciFiles = filesForUiP0Groups(uiP0CiMatrix.map((entry) => entry.shard));
for (const entry of uiP0CiMatrix) {
if (!knownGroups.has(entry.shard)) {
errors.push(`UI P0 CI matrix references unknown group ${entry.shard}`);
}
}
for (const file of difference(coverageFiles, ciFiles)) {
errors.push(`UI P0 CI matrix does not cover ${file}`);
}
for (const file of difference(ciFiles, coverageFiles)) {
errors.push(`UI P0 CI matrix unexpectedly covers ${file}`);
}
for (const entry of visualCiMatrix) {
if (entry.files.trim().length === 0) {
errors.push(`Visual CI matrix entry ${entry.name} has no files`);
}
}
return errors;
}
function filesForUiP0Groups(names: readonly string[]): string[] {
return sortedUnique(names.flatMap((name) => uiP0Groups[name as UiP0GroupName]?.files ?? []));
}
function difference(left: readonly string[], right: readonly string[]): string[] {
const rightSet = new Set(right);
return left.filter((value) => !rightSet.has(value));
}
function sortedUnique(values: readonly string[]): string[] {
return [...new Set(values)].sort();
}