fix(testing): update the cypress plugin implementation (#20314)

This commit is contained in:
Jason Jean 2023-11-21 08:57:58 -05:00 committed by GitHub
parent 5d82a2aab2
commit a916794318
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 285 deletions

View File

@ -52,11 +52,6 @@
"version": "17.2.0-beta.0",
"description": "Add devServerTargets into cypress.config.ts files for @nx/cypress/plugin",
"implementation": "./src/migrations/update-17-2-0/add-dev-server-targets-to-cypress-configs"
},
"add-nx-cypress-plugin": {
"version": "17.2.0-beta.0",
"description": "Add the @nx/cypress/plugin to nx.json plugins",
"implementation": "./src/migrations/update-17-2-0/add-nx-cypress-plugin"
}
},
"packageJsonUpdates": {

View File

@ -1,6 +1,5 @@
import {
createProjectGraphAsync,
logger,
parseTargetString,
workspaceRoot,
} from '@nx/devkit';
@ -8,7 +7,6 @@ import { dirname, join, relative } from 'path';
import { lstatSync } from 'fs';
import vitePreprocessor from '../src/plugins/preprocessor-vite';
import { ChildProcess, fork } from 'node:child_process';
import { createExecutorContext } from '../src/utils/ct-helpers';
import { startDevServer } from '../src/utils/start-dev-server';

View File

@ -1,170 +0,0 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import {
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import update from './add-nx-cypress-plugin';
import { defineConfig } from 'cypress';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { join } from 'path';
describe('add-nx-cypress-plugin migration', () => {
let tree: Tree;
let tempFs: TempFs;
function mockCypressConfig(cypressConfig: Cypress.ConfigOptions) {
jest.mock(
join(tempFs.tempDir, 'e2e/cypress.config.ts'),
() => ({
default: cypressConfig,
}),
{
virtual: true,
}
);
}
beforeEach(async () => {
tempFs = new TempFs('test');
tree = createTreeWithEmptyWorkspace();
tree.root = tempFs.tempDir;
await tempFs.createFiles({
'e2e/cypress.config.ts': '',
'e2e/project.json': '{ "name": "e2e" }',
});
tree.write('e2e/cypress.config.ts', `console.log('hi');`);
});
afterEach(() => {
jest.resetModules();
tempFs.cleanup();
});
it('should remove the e2e target when there are no other options', async () => {
mockCypressConfig(
defineConfig({
env: {
devServerTargets: {
default: 'my-app:serve',
production: 'my-app:serve:production',
},
ciDevServerTarget: 'my-app:serve-static',
},
e2e: {
specPattern: '**/*.cy.ts',
},
})
);
updateProjectConfiguration(tree, 'e2e', {
name: 'e2e',
root: 'e2e',
targets: {
e2e: {
executor: '@nx/cypress:cypress',
options: {
devServerTarget: 'my-app:serve',
},
configurations: {
production: {
devServerTarget: 'my-app:serve:production',
},
ci: {
devServerTarget: 'my-app:serve-static',
},
},
},
},
});
await update(tree);
expect(readProjectConfiguration(tree, 'e2e').targets.e2e).toEqual({
configurations: {
ci: {
devServerTarget: 'my-app:serve-static',
},
},
});
});
it('should not the e2e target when it uses a different executor', async () => {
const e2eTarget = {
executor: '@nx/playwright:playwright',
options: {
devServerTarget: 'my-app:serve',
},
configurations: {
production: {
devServerTarget: 'my-app:serve:production',
},
ci: {
devServerTarget: 'my-app:serve-static',
},
},
};
updateProjectConfiguration(tree, 'e2e', {
root: 'e2e',
targets: {
e2e: e2eTarget,
},
});
await update(tree);
expect(readProjectConfiguration(tree, 'e2e').targets.e2e).toEqual(
e2eTarget
);
});
it('should leave the e2e target with other options', async () => {
mockCypressConfig(
defineConfig({
env: {
devServerTargets: {
default: 'my-app:serve',
production: 'my-app:serve:production',
},
ciDevServerTarget: 'my-app:serve-static',
},
e2e: {
specPattern: '**/*.cy.ts',
},
})
);
updateProjectConfiguration(tree, 'e2e', {
root: 'e2e',
targets: {
e2e: {
executor: '@nx/cypress:cypress',
options: {
devServerTarget: 'my-app:serve',
watch: false,
},
configurations: {
production: {
devServerTarget: 'my-app:serve:production',
},
ci: {
devServerTarget: 'my-app:serve-static',
},
},
},
},
});
await update(tree);
expect(readProjectConfiguration(tree, 'e2e').targets.e2e).toEqual({
options: {
watch: false,
},
configurations: {
ci: {
devServerTarget: 'my-app:serve-static',
},
},
});
});
});

View File

@ -1,24 +0,0 @@
import { formatFiles, getProjects, Tree } from '@nx/devkit';
import { createNodes } from '../../plugins/plugin';
import { createProjectRootMappingsFromProjectConfigurations } from 'nx/src/project-graph/utils/find-project-for-path';
import { replaceProjectConfigurationsWithPlugin } from '@nx/devkit/src/utils/replace-project-configuration-with-plugin';
export default async function update(tree: Tree) {
const proj = Object.fromEntries(getProjects(tree).entries());
const rootMappings = createProjectRootMappingsFromProjectConfigurations(proj);
await replaceProjectConfigurationsWithPlugin(
tree,
rootMappings,
'@nx/cypress/plugin',
createNodes,
{
targetName: 'e2e',
componentTestingTargetName: 'component-test',
}
);
await formatFiles(tree);
}

View File

@ -37,6 +37,12 @@ describe('@nx/cypress/plugin', () => {
mockCypressConfig(
defineConfig({
e2e: {
env: {
devServerTargets: {
default: 'my-app:serve',
production: 'my-app:serve:production',
},
},
videosFolder: './dist/videos',
screenshotsFolder: './dist/screenshots',
},
@ -58,18 +64,25 @@ describe('@nx/cypress/plugin', () => {
"targets": {
"e2e": {
"cache": true,
"executor": "@nx/cypress:cypress",
"command": "cypress run --config-file cypress.config.js --e2e",
"configurations": {
"production": {
"command": "cypress run --config-file cypress.config.js --e2e --env.devServerTarget my-app:serve:production",
},
},
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"cypress",
],
},
],
"options": {
"cypressConfig": "cypress.config.js",
"testingType": "e2e",
"cwd": ".",
},
"outputs": [
"{options.videosFolder}",
"{options.screenshotsFolder}",
"{projectRoot}/dist/videos",
"{projectRoot}/dist/screenshots",
],
@ -110,18 +123,20 @@ describe('@nx/cypress/plugin', () => {
"targets": {
"component-test": {
"cache": true,
"executor": "@nx/cypress:cypress",
"command": "cypress open --config-file cypress.config.js --component",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"cypress",
],
},
],
"options": {
"cypressConfig": "cypress.config.js",
"testingType": "component",
"cwd": ".",
},
"outputs": [
"{options.videosFolder}",
"{options.screenshotsFolder}",
"{projectRoot}/dist/videos",
"{projectRoot}/dist/screenshots",
],
@ -138,6 +153,8 @@ describe('@nx/cypress/plugin', () => {
defineConfig({
e2e: {
specPattern: '**/*.cy.ts',
videosFolder: './dist/videos',
screenshotsFolder: './dist/screenshots',
env: {
devServerTargets: {
default: 'my-app:serve',
@ -164,24 +181,27 @@ describe('@nx/cypress/plugin', () => {
"targets": {
"e2e": {
"cache": true,
"command": "cypress run --config-file cypress.config.js --e2e",
"configurations": {
"production": {
"devServerTarget": "my-app:serve:production",
"command": "cypress run --config-file cypress.config.js --e2e --env.devServerTarget my-app:serve:production",
},
},
"executor": "@nx/cypress:cypress",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"cypress",
],
},
],
"options": {
"cypressConfig": "cypress.config.js",
"devServerTarget": "my-app:serve",
"testingType": "e2e",
"cwd": ".",
},
"outputs": [
"{options.videosFolder}",
"{options.screenshotsFolder}",
"{projectRoot}/dist/videos",
"{projectRoot}/dist/screenshots",
],
},
"e2e-ci": {
@ -197,29 +217,32 @@ describe('@nx/cypress/plugin', () => {
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"cypress",
],
},
],
"outputs": [
"{options.videosFolder}",
"{options.screenshotsFolder}",
"{projectRoot}/dist/videos",
"{projectRoot}/dist/screenshots",
],
},
"e2e-ci--test.cy.ts": {
"cache": true,
"configurations": undefined,
"executor": "@nx/cypress:cypress",
"command": "cypress run --config-file cypress.config.js --e2e --env.devServerTarget my-app:serve-static --spec test.cy.ts",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"cypress",
],
},
],
"options": {
"cypressConfig": "cypress.config.js",
"devServerTarget": "my-app:serve-static",
"spec": "test.cy.ts",
"testingType": "e2e",
},
"outputs": [
"{options.videosFolder}",
"{options.screenshotsFolder}",
"{projectRoot}/dist/videos",
"{projectRoot}/dist/screenshots",
],
},
},

View File

@ -2,11 +2,12 @@ import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
NxJsonConfiguration,
readJsonFile,
TargetConfiguration,
writeJsonFile,
} from '@nx/devkit';
import { dirname, extname, join } from 'path';
import { dirname, extname, join, relative } from 'path';
import { registerTsProject } from '@nx/js/src/internal';
import { getRootTsConfigPath } from '@nx/js';
@ -102,7 +103,7 @@ function getOutputs(
}
const { screenshotsFolder, videosFolder, e2e, component } = cypressConfig;
const outputs = ['{options.videosFolder}', '{options.screenshotsFolder}'];
const outputs = [];
if (videosFolder) {
outputs.push(getOutput(videosFolder));
@ -143,44 +144,39 @@ function buildCypressTargets(
) {
const cypressConfig = getCypressConfig(configFilePath, context);
const namedInputs = getNamedInputs(projectRoot, context);
const baseTargetConfig: TargetConfiguration<CypressExecutorOptions> = {
executor: '@nx/cypress:cypress',
options: {
cypressConfig: configFilePath,
},
const cypressEnv = {
...cypressConfig.env,
...cypressConfig.e2e?.env,
};
const targets: Record<
string,
TargetConfiguration<CypressExecutorOptions>
> = {};
const devServerTargets: Record<string, string> = cypressEnv?.devServerTargets;
const relativeConfigPath = relative(projectRoot, configFilePath);
const namedInputs = getNamedInputs(projectRoot, context);
const targets: Record<string, TargetConfiguration> = {};
if ('e2e' in cypressConfig) {
targets[options.targetName] = {
command: `cypress run --config-file ${relativeConfigPath} --e2e`,
options: {
cwd: projectRoot,
},
};
const e2eTargetDefaults = readTargetDefaultsForTarget(
options.targetName,
context.nxJsonConfiguration.targetDefaults,
'@nx/cypress:cypress'
'run-commands'
);
targets[options.targetName] = {
...baseTargetConfig,
options: {
...baseTargetConfig.options,
testingType: 'e2e',
},
};
if (e2eTargetDefaults?.cache === undefined) {
targets[options.targetName].cache = true;
}
if (e2eTargetDefaults?.inputs === undefined) {
targets[options.targetName].inputs =
'production' in namedInputs
? ['default', '^production']
: ['default', '^default'];
targets[options.targetName].inputs = getInputs(namedInputs);
}
if (e2eTargetDefaults?.outputs === undefined) {
@ -191,17 +187,7 @@ function buildCypressTargets(
);
}
const cypressEnv = {
...cypressConfig.env,
...cypressConfig.e2e?.env,
};
const devServerTargets: Record<string, string> =
cypressEnv?.devServerTargets;
if (devServerTargets?.default) {
targets[options.targetName].options.devServerTarget =
devServerTargets.default;
delete devServerTargets.default;
}
@ -211,7 +197,7 @@ function buildCypressTargets(
devServerTargets ?? {}
)) {
targets[options.targetName].configurations[configuration] = {
devServerTarget,
command: `cypress run --config-file ${relativeConfigPath} --e2e --env.devServerTarget ${devServerTarget}`,
};
}
}
@ -236,23 +222,15 @@ function buildCypressTargets(
const dependsOn: TargetConfiguration['dependsOn'] = [];
const outputs = getOutputs(projectRoot, cypressConfig, 'e2e');
const inputs =
'production' in namedInputs
? ['default', '^production']
: ['default', '^default'];
const inputs = getInputs(namedInputs);
for (const file of specFiles) {
const targetName = options.ciTargetName + '--' + file;
const relativeSpecFilePath = relative(projectRoot, file);
const targetName = options.ciTargetName + '--' + relativeSpecFilePath;
targets[targetName] = {
...targets[options.targetName],
outputs,
inputs,
cache: true,
configurations: undefined,
options: {
...targets[options.targetName].options,
devServerTarget: ciDevServerTarget,
spec: file,
},
command: `cypress run --config-file ${relativeConfigPath} --e2e --env.devServerTarget ${ciDevServerTarget} --spec ${relativeSpecFilePath}`,
};
dependsOn.push({
target: targetName,
@ -281,10 +259,9 @@ function buildCypressTargets(
// This will not override the e2e target if it is the same
targets[options.componentTestingTargetName] ??= {
...baseTargetConfig,
command: `cypress open --config-file ${relativeConfigPath} --component`,
options: {
...baseTargetConfig.options,
testingType: 'component',
cwd: projectRoot,
},
};
@ -294,9 +271,7 @@ function buildCypressTargets(
if (componentTestingTargetDefaults?.inputs === undefined) {
targets[options.componentTestingTargetName].inputs =
'production' in namedInputs
? ['default', '^production']
: ['default', '^default'];
getInputs(namedInputs);
}
if (componentTestingTargetDefaults?.outputs === undefined) {
@ -344,3 +319,16 @@ function normalizeOptions(options: CypressPluginOptions): CypressPluginOptions {
options.ciTargetName ??= 'e2e-ci';
return options;
}
function getInputs(
namedInputs: NxJsonConfiguration['namedInputs']
): TargetConfiguration['inputs'] {
return [
...('production' in namedInputs
? ['default', '^production']
: ['default', '^default']),
{
externalDependencies: ['cypress'],
},
];
}