Colum Ferry 320d9f223f
fix(testing): application generators should accurately configure e2e projects (#27453)
- feat(devkit): add util for determining the e2e web server info
- feat(vite): add util for determining the e2e web server info
- feat(webpack): add util for determining the e2e web server info
- fix(webpack): allow port override
- fix(devkit): e2e web server info util should handle target defaults
- feat(webpack): export the e2e web server info utils
- fix(vite): rename util
- fix(devkit): util should determine the devTarget for cypress
- fix(react): improve accuracy of e2e project generation

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
The logic for finding the correct targets and web addresses to use when
setting up e2e projects is flawed and missing some key considerations.


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The logic is accurate and usage is simplified across plugins 

Projects:
- [x] Angular
- [x] Expo
- [x] Next
- [x] Nuxt
- [x] Vue
- [x] Web
- [x] Remix
- [x] React
- [x] React Native


## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2024-08-27 10:00:43 -04:00

221 lines
7.3 KiB
TypeScript

import type { GeneratorCallback, Tree } from '@nx/devkit';
import {
addProjectConfiguration,
ensurePackage,
getPackageManagerCommand,
joinPathFragments,
readNxJson,
} from '@nx/devkit';
import { webStaticServeGenerator } from '@nx/web';
import { nxVersion } from '../../../utils/versions';
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
import { hasVitePlugin } from '../../../utils/has-vite-plugin';
import { NormalizedSchema } from '../schema';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function addE2e(
tree: Tree,
options: NormalizedSchema
): Promise<GeneratorCallback> {
const hasNxBuildPlugin =
(options.bundler === 'webpack' && hasWebpackPlugin(tree)) ||
(options.bundler === 'vite' && hasVitePlugin(tree));
let e2eWebServerInfo: E2EWebServerDetails = {
e2eWebServerAddress: `http://localhost:${options.devServerPort ?? 4200}`,
e2eWebServerCommand: `${getPackageManagerCommand().exec} nx run ${
options.projectName
}:serve`,
e2eCiWebServerCommand: `${getPackageManagerCommand().exec} nx run ${
options.projectName
}:serve-static`,
e2eCiBaseUrl: `http://localhost:4200`,
e2eDevServerTarget: `${options.projectName}:serve`,
};
if (options.bundler === 'webpack') {
const { getWebpackE2EWebServerInfo } = ensurePackage<
typeof import('@nx/webpack')
>('@nx/webpack', nxVersion);
e2eWebServerInfo = await getWebpackE2EWebServerInfo(
tree,
options.projectName,
joinPathFragments(
options.appProjectRoot,
`webpack.config.${options.js ? 'js' : 'ts'}`
),
options.addPlugin,
options.devServerPort ?? 4200
);
} else if (options.bundler === 'vite') {
const { getViteE2EWebServerInfo } = ensurePackage<
typeof import('@nx/vite')
>('@nx/vite', nxVersion);
e2eWebServerInfo = await getViteE2EWebServerInfo(
tree,
options.projectName,
joinPathFragments(
options.appProjectRoot,
`vite.config.${options.js ? 'js' : 'ts'}`
),
options.addPlugin,
options.devServerPort ?? 4200
);
}
if (!hasNxBuildPlugin) {
await webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:build`,
targetName: 'serve-static',
spa: true,
});
}
switch (options.e2eTestRunner) {
case 'cypress': {
const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress')
>('@nx/cypress', nxVersion);
addProjectConfiguration(tree, options.e2eProjectName, {
projectType: 'application',
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {},
implicitDependencies: [options.projectName],
tags: [],
});
const e2eTask = await configurationGenerator(tree, {
...options,
project: options.e2eProjectName,
directory: 'src',
// the name and root are already normalized, instruct the generator to use them as is
bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler,
skipFormat: true,
devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
baseUrl: e2eWebServerInfo.e2eWebServerAddress,
jsx: true,
rootProject: options.rootProject,
webServerCommands: hasNxBuildPlugin
? {
default: e2eWebServerInfo.e2eWebServerCommand,
production: e2eWebServerInfo.e2eCiWebServerCommand,
}
: undefined,
ciWebServerCommand: hasNxBuildPlugin
? e2eWebServerInfo.e2eCiWebServerCommand
: undefined,
ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
});
if (
options.addPlugin ||
readNxJson(tree).plugins?.find((p) =>
typeof p === 'string'
? p === '@nx/cypress/plugin'
: p.plugin === '@nx/cypress/plugin'
)
) {
let buildTarget = '^build';
if (hasNxBuildPlugin) {
const configFile =
options.bundler === 'webpack'
? 'webpack.config.js'
: options.bundler === 'vite'
? `vite.config.${options.js ? 'js' : 'ts'}`
: 'webpack.config.js';
const matchingPlugin = await findPluginForConfigFile(
tree,
`@nx/${options.bundler}/plugin`,
joinPathFragments(options.appProjectRoot, configFile)
);
if (matchingPlugin && typeof matchingPlugin !== 'string') {
buildTarget = `^${
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
}`;
}
}
await addE2eCiTargetDefaults(
tree,
'@nx/cypress/plugin',
buildTarget,
joinPathFragments(
options.e2eProjectRoot,
`cypress.config.${options.js ? 'js' : 'ts'}`
)
);
}
return e2eTask;
}
case 'playwright': {
const { configurationGenerator } = ensurePackage<
typeof import('@nx/playwright')
>('@nx/playwright', nxVersion);
addProjectConfiguration(tree, options.e2eProjectName, {
projectType: 'application',
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {},
implicitDependencies: [options.projectName],
});
const e2eTask = await configurationGenerator(tree, {
project: options.e2eProjectName,
skipFormat: true,
skipPackageJson: options.skipPackageJson,
directory: 'src',
js: false,
linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
rootProject: options.rootProject,
addPlugin: options.addPlugin,
});
if (
options.addPlugin ||
readNxJson(tree).plugins?.find((p) =>
typeof p === 'string'
? p === '@nx/playwright/plugin'
: p.plugin === '@nx/playwright/plugin'
)
) {
let buildTarget = '^build';
if (hasNxBuildPlugin) {
const configFile =
options.bundler === 'webpack'
? 'webpack.config.js'
: options.bundler === 'vite'
? `vite.config.${options.js ? 'js' : 'ts'}`
: 'webpack.config.js';
const matchingPlugin = await findPluginForConfigFile(
tree,
`@nx/${options.bundler}/plugin`,
joinPathFragments(options.appProjectRoot, configFile)
);
if (matchingPlugin && typeof matchingPlugin !== 'string') {
buildTarget = `^${
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
}`;
}
}
await addE2eCiTargetDefaults(
tree,
'@nx/playwright/plugin',
buildTarget,
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
);
}
return e2eTask;
}
case 'none':
default:
return () => {};
}
}