fix(e2e): build Electron only before Electron per-file E2E targets

The `e2e-ci--src/*.e2e.ts` target default also matched web-e2e, so
every browser-only per-file target built electron-backend first. Split
it into project-filtered entries: Electron targets keep `build-e2e`,
web targets get an empty dependency list (which also keeps the
inferred `web:serve` dependency, rejected by Nx on a non-parallel
target, out of them).

The mock dependency guard now also catches `^serve`-style
dependencies, which schedule the mocks through the E2E projects'
implicit dependencies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-30 22:44:06 +02:00
1 parent abb7e5fa41
commit 311f73ce7a
2 files changed
+65 -28

No files matched your search

@@ -89,22 +89,55 @@ describe('Playwright mock-server launch', () => {
});
describe('Nx E2E task dependencies', () => {
type Dependency = string | { projects?: string | string[] };
type TargetConfig = { dependsOn?: Dependency[] };
type Dependency =
| string
| {
projects?: string | string[];
dependencies?: boolean;
target?: string;
};
type TargetConfig = {
dependsOn?: Dependency[];
continuous?: boolean;
filter?: { projects?: string[] };
};
type ProjectJson = { targets: Record<string, TargetConfig> };
const readJson = <T>(path: string) =>
JSON.parse(readFileSync(join(process.cwd(), path), 'utf8')) as T;
const nxJson = readJson<{
targetDefaults: Record<string, TargetConfig | TargetConfig[]>;
}>('nx.json');
const e2eProjectFiles = readdirSync(join(process.cwd(), 'apps'))
const appNames = readdirSync(join(process.cwd(), 'apps'));
const e2eProjectFiles = appNames
.filter((name) => name.endsWith('-e2e'))
.map((name) => `apps/${name}/project.json`);
// The E2E projects list the mocks as implicit dependencies, so a `^serve`
// dependency schedules the mock serve tasks as well.
const mockContinuousTargets = new Set(
appNames
.filter((name) => name.endsWith('-mock-server'))
.flatMap((name) =>
Object.entries(
readJson<ProjectJson>(`apps/${name}/project.json`).targets
)
.filter(([, target]) => target.continuous)
.map(([targetName]) => targetName)
)
);
const dependsOnMock = (dependency: Dependency) =>
typeof dependency === 'string'
? dependency.includes('-mock-server:')
? dependency.includes('-mock-server:') ||
(dependency.startsWith('^') &&
mockContinuousTargets.has(dependency.slice(1)))
: [dependency.projects ?? []]
.flat()
.some((project) => project.includes('-mock-server'));
.some((project) => project.includes('-mock-server')) ||
(dependency.dependencies === true &&
mockContinuousTargets.has(dependency.target ?? ''));
const perFileDefaultFor = (project: string) =>
[nxJson.targetDefaults['e2e-ci--src/*.e2e.ts'] ?? []]
.flat()
.find((entry) => entry.filter?.projects?.includes(project));
// The Playwright configs start the mocks themselves, so @nx/playwright
// infers their E2E targets as non-parallel, and Nx refuses to run a
@@ -119,13 +152,12 @@ describe('Nx E2E task dependencies', () => {
}))
);
const e2eProjectTargets = e2eProjectFiles.flatMap((path) =>
Object.entries(
readJson<{ targets: Record<string, TargetConfig> }>(path)
.targets
).map(([targetName, config]) => ({
source: `${path} ${targetName}`,
config,
}))
Object.entries(readJson<ProjectJson>(path).targets).map(
([targetName, config]) => ({
source: `${path} ${targetName}`,
config,
})
)
);
const mockDependencies = [...e2eDefaults, ...e2eProjectTargets]
.map(({ source, config }) => ({
@@ -140,16 +172,14 @@ describe('Nx E2E task dependencies', () => {
'apps/web-e2e/project.json',
])
);
expect([...mockContinuousTargets]).toContain('serve');
expect(mockDependencies).toEqual([]);
});
it('still builds the Electron app before each per-file E2E target', () => {
expect(
(nxJson.targetDefaults['e2e-ci--src/*.e2e.ts'] as TargetConfig)
.dependsOn
).toContainEqual({
projects: ['electron-backend'],
target: 'build-e2e',
});
it('builds the Electron app only before Electron per-file E2E targets', () => {
expect(perFileDefaultFor('electron-backend-e2e')?.dependsOn).toEqual([
{ projects: ['electron-backend'], target: 'build-e2e' },
]);
expect(perFileDefaultFor('web-e2e')?.dependsOn).toEqual([]);
});
});
+15 -8
View File
@@ -68,14 +68,21 @@
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"e2e-ci--src/*.e2e.ts": {
"dependsOn": [
{
"projects": ["electron-backend"],
"target": "build-e2e"
}
]
}
"e2e-ci--src/*.e2e.ts": [
{
"filter": { "projects": ["electron-backend-e2e"] },
"dependsOn": [
{
"projects": ["electron-backend"],
"target": "build-e2e"
}
]
},
{
"filter": { "projects": ["web-e2e"] },
"dependsOn": []
}
]
},
"plugins": [
{