mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
test(e2e): gate beta packaged runtime (#637)
* test(e2e): gate beta mac packaged runtime * test(e2e): separate ui automation layout * test(e2e): move localized content coverage * chore(release): prepare packaged 0.4.1 beta validation * test(e2e): keep ui lane playwright-only * fix(web): keep chat recoverable after conversation load failure * fix(desktop): honor native mac quit
This commit is contained in:
+137
-2
@@ -1,4 +1,4 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { readFile, readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
@@ -69,6 +69,10 @@ const residualAllowedPathPrefixes = [
|
||||
"e2e/reports/html/",
|
||||
"e2e/reports/playwright-html-report/",
|
||||
"e2e/reports/test-results/",
|
||||
"e2e/ui/.od-data/",
|
||||
"e2e/ui/reports/playwright-html-report/",
|
||||
"e2e/ui/reports/test-results/",
|
||||
"e2e/ui/test-results/",
|
||||
// Vendored upstream HyperFrames skill helper scripts.
|
||||
"skills/hyperframes/scripts/",
|
||||
// Vendored upstream html-ppt skill runtime assets (lewislulu/html-ppt-skill).
|
||||
@@ -136,7 +140,7 @@ async function checkResidualJavaScript(): Promise<boolean> {
|
||||
}
|
||||
|
||||
const testLayoutScopedDirectories = ["apps", "packages", "tools"];
|
||||
const testLayoutSkippedDirectories = new Set([".next", "dist", "node_modules", "out"]);
|
||||
const testLayoutSkippedDirectories = new Set([".next", ".od-data", "dist", "node_modules", "out", "reports", "test-results"]);
|
||||
|
||||
function isTestFile(fileName: string): boolean {
|
||||
return /\.test\.tsx?$/.test(fileName);
|
||||
@@ -205,9 +209,140 @@ async function checkTestLayout(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
const e2ePackageJsonPath = path.join(repoRoot, "e2e", "package.json");
|
||||
const e2eSkippedDirectories = new Set([".od-data", "node_modules", "reports", "test-results"]);
|
||||
const e2eAllowedScripts = ["test", "typecheck"];
|
||||
|
||||
async function collectRepositoryFiles(directory: string, skippedDirectoryNames = new Set<string>()): Promise<string[]> {
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
const files: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (skippedDirectoryNames.has(entry.name)) continue;
|
||||
files.push(...(await collectRepositoryFiles(fullPath, skippedDirectoryNames)));
|
||||
continue;
|
||||
}
|
||||
if (entry.isFile()) files.push(toRepositoryPath(fullPath));
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
async function checkE2eLayout(): Promise<boolean> {
|
||||
const violations: string[] = [];
|
||||
const packageJson = JSON.parse(await readFile(e2ePackageJsonPath, "utf8")) as {
|
||||
scripts?: Record<string, unknown>;
|
||||
};
|
||||
const scriptNames = Object.keys(packageJson.scripts ?? {}).sort();
|
||||
if (scriptNames.join("\0") !== e2eAllowedScripts.join("\0")) {
|
||||
violations.push(
|
||||
`e2e/package.json scripts must be exactly ${e2eAllowedScripts.join(", ")} (found: ${scriptNames.join(", ")})`,
|
||||
);
|
||||
}
|
||||
|
||||
const e2eRoot = path.join(repoRoot, "e2e");
|
||||
for (const repositoryPath of await collectRepositoryFiles(e2eRoot, e2eSkippedDirectories)) {
|
||||
if (
|
||||
repositoryPath === "e2e/package.json" ||
|
||||
repositoryPath === "e2e/tsconfig.json" ||
|
||||
repositoryPath === "e2e/vitest.config.ts" ||
|
||||
repositoryPath === "e2e/playwright.config.ts" ||
|
||||
repositoryPath === "e2e/AGENTS.md"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repositoryPath.startsWith("e2e/specs/")) {
|
||||
if (!/\.spec\.ts$/.test(repositoryPath)) {
|
||||
violations.push(`${repositoryPath} -> e2e specs must be *.spec.ts`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repositoryPath.startsWith("e2e/tests/")) {
|
||||
if (!/\.test\.ts$/.test(repositoryPath)) {
|
||||
violations.push(`${repositoryPath} -> e2e tests must be *.test.ts`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repositoryPath.startsWith("e2e/ui/")) {
|
||||
const relativePath = repositoryPath.slice("e2e/ui/".length);
|
||||
if (relativePath.includes("/") || !/\.test\.ts$/.test(repositoryPath)) {
|
||||
violations.push(`${repositoryPath} -> e2e UI files must be flat Playwright *.test.ts files under ui/`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repositoryPath.startsWith("e2e/resources/")) {
|
||||
const relativePath = repositoryPath.slice("e2e/resources/".length);
|
||||
if (relativePath.includes("/") || !/\.ts$/.test(repositoryPath)) {
|
||||
violations.push(`${repositoryPath} -> e2e resources must be flat TypeScript files under resources/`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repositoryPath.startsWith("e2e/lib/")) {
|
||||
if (!/\.ts$/.test(repositoryPath)) {
|
||||
violations.push(`${repositoryPath} -> e2e lib files must be TypeScript`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (repositoryPath.startsWith("e2e/scripts/")) {
|
||||
if (repositoryPath !== "e2e/scripts/playwright.ts") {
|
||||
violations.push(`${repositoryPath} -> e2e scripts currently allow only scripts/playwright.ts`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
violations.push(`${repositoryPath} -> e2e source files must live in specs/, tests/, ui/, resources/, lib/, or scripts/playwright.ts`);
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error("E2E package layout violations found:");
|
||||
for (const violation of violations) console.error(`- ${violation}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("E2E layout check passed: Vitest, Playwright UI, resources, lib, and scripts stay in their lanes.");
|
||||
return true;
|
||||
}
|
||||
|
||||
const webTestSkippedDirectories = new Set([".od-data", "reports", "test-results"]);
|
||||
|
||||
async function checkWebTestLayout(): Promise<boolean> {
|
||||
const violations: string[] = [];
|
||||
const webTestsRoot = path.join(repoRoot, "apps", "web", "tests");
|
||||
|
||||
for (const repositoryPath of await collectRepositoryFiles(webTestsRoot, webTestSkippedDirectories)) {
|
||||
if (repositoryPath.startsWith("apps/web/tests/vitest/") || repositoryPath.startsWith("apps/web/tests/playwright/")) {
|
||||
violations.push(`${repositoryPath} -> web tests should stay lightweight under apps/web/tests/ without vitest/playwright nesting`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/\.(spec|test)\.tsx?$/.test(repositoryPath) && !/\.test\.tsx?$/.test(repositoryPath)) {
|
||||
violations.push(`${repositoryPath} -> web Vitest test files must be *.test.ts or *.test.tsx`);
|
||||
}
|
||||
}
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error("Web test layout violations found:");
|
||||
for (const violation of violations) console.error(`- ${violation}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("Web test layout check passed: web tests stay lightweight and Vitest-only.");
|
||||
return true;
|
||||
}
|
||||
|
||||
const checks: GuardCheck[] = [
|
||||
{ name: "residual JavaScript", run: checkResidualJavaScript },
|
||||
{ name: "test layout", run: checkTestLayout },
|
||||
{ name: "e2e layout", run: checkE2eLayout },
|
||||
{ name: "web test layout", run: checkWebTestLayout },
|
||||
];
|
||||
|
||||
const results: boolean[] = [];
|
||||
|
||||
Reference in New Issue
Block a user