Files
iptvnator/tools/nx/check-e2e-task-graphs.mjs
T
4grayandClaude Opus 5.5 3f3e9ffefb fix(e2e): let web-e2e:e2e run outside CI
Without CI, every web-e2e webServer has reuseExistingServer, so
@nx/playwright infers a continuous web:serve dependency for the
`pnpm nx run web:serve` entry. The mocks and the web backend launch as
plain node processes with env, which no Nx task covers, so the plugin
also marks the target non-parallel. Nx refuses a non-parallel task with
continuous dependencies, and `nx run web-e2e:e2e` failed before running
anything; CI passed only because CI=1 disables reuse and with it the
inferred dependency.

Give web-e2e:e2e an empty dependsOn so Playwright starts every server,
as it already does in CI. The Angular dev-server runs inside the Nx
process, so Playwright's process-group kill still stops it. Turn off
the plugin's readiness gate, which no target consumes any more.

Add `pnpm run e2e:task-graphs:validate`: it builds every Playwright
target's task graph with Nx's own validation, with CI unset and set,
and runs in the unit-and-typecheck job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 06:29:38 +02:00

121 lines
4.1 KiB
JavaScript

import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
/**
* Nx refuses to run a task with `parallelism: false` that depends on a
* continuous task. `@nx/playwright/plugin` sets `parallelism: false` whenever
* a config has a webServer no inferred Nx task covers (our mocks and the web
* backend launch as plain `node` processes), and it infers continuous serve
* dependencies only from servers with `reuseExistingServer`, which the
* configs enable only when `CI` is unset. A target graph can therefore pass
* in CI and still fail locally, so every Playwright target is validated with
* Nx's own task-graph checks under both environments.
*/
const RESULT_PREFIX = 'e2e-task-graphs-result:';
export const MODES = {
local: { CI: undefined },
ci: { CI: 'true' },
};
export function playwrightTargets(projectGraph) {
return Object.values(projectGraph.nodes)
.flatMap((node) =>
Object.entries(node.data.targets ?? {})
.filter(([, target]) =>
target.metadata?.technologies?.includes('playwright')
)
.map(([target]) => ({ project: node.name, target }))
)
.sort((a, b) =>
`${a.project}:${a.target}`.localeCompare(`${b.project}:${b.target}`)
);
}
export async function findInvalidTaskGraphs(projectGraph) {
const { createTaskGraph } =
await import('nx/src/tasks-runner/create-task-graph.js');
const { assertTaskGraphDoesNotContainInvalidTargets } =
await import('nx/src/tasks-runner/task-graph-utils.js');
return playwrightTargets(projectGraph).flatMap(({ project, target }) => {
try {
assertTaskGraphDoesNotContainInvalidTargets(
createTaskGraph(
projectGraph,
{},
[project],
[target],
undefined,
{}
)
);
return [];
} catch (error) {
return [{ task: `${project}:${target}`, message: error.message }];
}
});
}
function childEnv(mode) {
const env = { ...process.env, NX_DAEMON: 'false' };
for (const [name, value] of Object.entries(MODES[mode])) {
if (value === undefined) delete env[name];
else env[name] = value;
}
return env;
}
async function checkCurrentEnvironment() {
const { createProjectGraphAsync } = await import('@nx/devkit');
const projectGraph = await createProjectGraphAsync({ exitOnError: true });
const failures = await findInvalidTaskGraphs(projectGraph);
const checked = playwrightTargets(projectGraph).length;
console.log(`${RESULT_PREFIX}${JSON.stringify({ checked, failures })}`);
}
function main() {
let failed = false;
for (const mode of Object.keys(MODES)) {
const output = execFileSync(
process.execPath,
[fileURLToPath(import.meta.url), '--current-environment'],
{
env: childEnv(mode),
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'inherit'],
}
);
const result = output
.split('\n')
.find((line) => line.startsWith(RESULT_PREFIX));
const { checked, failures } = JSON.parse(
result.slice(RESULT_PREFIX.length)
);
if (checked === 0) {
failed = true;
console.error(`[${mode}] no Playwright targets were inferred`);
}
for (const { task, message } of failures) {
failed = true;
console.error(
`[${mode}] ${task}\n ${message.replace(/\n/g, '\n ')}`
);
}
if (failures.length === 0 && checked > 0) {
console.log(
`[${mode}] ${checked} Playwright task graphs are valid`
);
}
}
process.exitCode = failed ? 1 : 0;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
if (process.argv.includes('--current-environment')) {
await checkCurrentEnvironment();
} else {
main();
}
}