fix(e2e): let per-file E2E targets run without mock serve dependencies (#1772)

* fix(e2e): let per-file E2E targets run without mock serve dependencies

Since #1710 the Playwright configs start the Stalker and Xtream mocks
themselves (`node --import tsx …`), so @nx/playwright can no longer map
those webServers to Nx tasks and infers the atomized `e2e-ci--*` targets
as non-parallel. The `e2e-ci--src/*.e2e.ts` target default still made
them depend on the continuous `stalker-mock-server:serve` and
`xtream-mock-server:serve` targets, and Nx refuses to run a
non-parallel task with continuous dependencies, so every per-file
target failed before running anything.

Drop the redundant mock dependencies and keep the Electron build.
The mock-launch guard spec now also rejects any nx.json target default
that depends on a mock-server task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(e2e): scope the mock dependency guard to E2E targets

Check only the `e2e*` target defaults in nx.json, so an unrelated
default may still depend on a mock, and also check every target in the
`apps/*-e2e` project.json files, where a mock `serve` dependency would
break the same targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 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>

---------

Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-01 06:27:08 +02:00
1 parent 1dba959852
commit 5d50995c30
3 files changed
+116 -13

No files matched your search

@@ -87,3 +87,99 @@ describe('Playwright mock-server launch', () => {
}
);
});
describe('Nx E2E task dependencies', () => {
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 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.startsWith('^') &&
mockContinuousTargets.has(dependency.slice(1)))
: [dependency.projects ?? []]
.flat()
.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
// non-parallel task that depends on a continuous `serve` task.
it('never makes an E2E target depend on a mock-server task', () => {
const e2eDefaults = Object.entries(nxJson.targetDefaults)
.filter(([targetName]) => targetName.startsWith('e2e'))
.flatMap(([targetName, config]) =>
[config].flat().map((entry) => ({
source: `nx.json ${targetName}`,
config: entry,
}))
);
const e2eProjectTargets = e2eProjectFiles.flatMap((path) =>
Object.entries(readJson<ProjectJson>(path).targets).map(
([targetName, config]) => ({
source: `${path} ${targetName}`,
config,
})
)
);
const mockDependencies = [...e2eDefaults, ...e2eProjectTargets]
.map(({ source, config }) => ({
source,
dependencies: (config.dependsOn ?? []).filter(dependsOnMock),
}))
.filter(({ dependencies }) => dependencies.length > 0);
expect(e2eProjectFiles).toEqual(
expect.arrayContaining([
'apps/electron-backend-e2e/project.json',
'apps/web-e2e/project.json',
])
);
expect([...mockContinuousTargets]).toContain('serve');
expect(mockDependencies).toEqual([]);
});
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([]);
});
});
+5 -1
View File
@@ -579,7 +579,11 @@ for the serve target's `--tsconfig` flag and is required for the `@iptvnator/*`
path aliases. `project-config.spec.ts` pins which configs start which mock,
and fails if any of them launches a mock through Nx or a new config starts one
without being listed there. The `serve` targets remain the entry point
for starting a mock by hand. The web-e2e `web-backend` entry uses the same
for starting a mock by hand, and no E2E target may depend on them: with no
Nx-launched server, `@nx/playwright` infers the atomized `e2e-ci--*` targets
as non-parallel, and Nx refuses to run a non-parallel task that depends on a
continuous `serve` task. The same spec guards the `e2e*` entries of `nx.json`
`targetDefaults` and every target in the `apps/*-e2e` `project.json` files. The web-e2e `web-backend` entry uses the same
launch form; see [PWA web backend](pwa-self-hosted.md#web-backend).
### Request Interception
+15 -12
View File
@@ -68,18 +68,21 @@
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"e2e-ci--src/*.e2e.ts": {
"dependsOn": [
{
"projects": ["electron-backend"],
"target": "build-e2e"
},
{
"projects": ["stalker-mock-server", "xtream-mock-server"],
"target": "serve"
}
]
}
"e2e-ci--src/*.e2e.ts": [
{
"filter": { "projects": ["electron-backend-e2e"] },
"dependsOn": [
{
"projects": ["electron-backend"],
"target": "build-e2e"
}
]
},
{
"filter": { "projects": ["web-e2e"] },
"dependsOn": []
}
]
},
"plugins": [
{