60 lines
2.7 KiB
JavaScript
60 lines
2.7 KiB
JavaScript
import { readdirSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { loadShardDurations, partitionGeneralServerSuites } from "./general-server-shard.mjs";
|
|
|
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
const REPO_ROOT = path.resolve(HERE, "..");
|
|
const E2E_DIR = path.join(REPO_ROOT, "tests", "e2e");
|
|
const DURATIONS_MANIFEST = path.join(HERE, "e2e-shard-durations.json");
|
|
|
|
// Specs the default local_trusted Playwright project deliberately skips. Keep
|
|
// this in sync with `testIgnore` in tests/e2e/playwright.config.ts — the unit
|
|
// test in scripts/__tests__/e2e-shard.test.mjs fails if the two ever drift.
|
|
export const IGNORED_SPECS = ["in-feed-native/**", "multi-user.spec.ts", "multi-user-authenticated.spec.ts"];
|
|
|
|
// Enumerates the specs the default e2e lane actually runs, as repo-relative
|
|
// paths so the output can be handed straight to `playwright test`.
|
|
export function listE2eSpecs(e2eDir = E2E_DIR, repoRoot = REPO_ROOT) {
|
|
return readdirSync(e2eDir)
|
|
.filter((entry) => entry.endsWith(".spec.ts") && !IGNORED_SPECS.includes(entry))
|
|
.map((entry) => path.relative(repoRoot, path.join(e2eDir, entry)).split(path.sep).join("/"))
|
|
.sort((a, b) => a.localeCompare(b));
|
|
}
|
|
|
|
// Playwright's own --shard balances by test count, which is useless here: one
|
|
// spec (smoke-lab) is ~40% of the lane's wall clock. Reuse the deterministic
|
|
// longest-processing-time partition already proven on the general-server lane
|
|
// so every runner computes the identical, non-overlapping split.
|
|
export function selectE2eShard(files, shardIndex, shardCount, durations = {}) {
|
|
return partitionGeneralServerSuites(files, shardCount, durations)[shardIndex].files;
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const args = { shardIndex: 0, shardCount: 1 };
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
if (argv[index] === "--shard-index") args.shardIndex = Number(argv[index + 1]);
|
|
if (argv[index] === "--shard-count") args.shardCount = Number(argv[index + 1]);
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function main(argv) {
|
|
const { shardIndex, shardCount } = parseArgs(argv);
|
|
if (!Number.isInteger(shardCount) || shardCount < 1) {
|
|
throw new Error(`--shard-count must be a positive integer, got ${shardCount}`);
|
|
}
|
|
if (!Number.isInteger(shardIndex) || shardIndex < 0 || shardIndex >= shardCount) {
|
|
throw new Error(`--shard-index must be in [0, ${shardCount}), got ${shardIndex}`);
|
|
}
|
|
|
|
const specs = listE2eSpecs();
|
|
const durations = loadShardDurations(DURATIONS_MANIFEST);
|
|
process.stdout.write(`${selectE2eShard(specs, shardIndex, shardCount, durations).join(" ")}\n`);
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
main(process.argv.slice(2));
|
|
}
|