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", "version": "17.2.0-beta.0",
"description": "Add devServerTargets into cypress.config.ts files for @nx/cypress/plugin", "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" "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": { "packageJsonUpdates": {

View File

@ -1,6 +1,5 @@
import { import {
createProjectGraphAsync, createProjectGraphAsync,
logger,
parseTargetString, parseTargetString,
workspaceRoot, workspaceRoot,
} from '@nx/devkit'; } from '@nx/devkit';
@ -8,7 +7,6 @@ import { dirname, join, relative } from 'path';
import { lstatSync } from 'fs'; import { lstatSync } from 'fs';
import vitePreprocessor from '../src/plugins/preprocessor-vite'; import vitePreprocessor from '../src/plugins/preprocessor-vite';
import { ChildProcess, fork } from 'node:child_process';
import { createExecutorContext } from '../src/utils/ct-helpers'; import { createExecutorContext } from '../src/utils/ct-helpers';
import { startDevServer } from '../src/utils/start-dev-server'; 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( mockCypressConfig(
defineConfig({ defineConfig({
e2e: { e2e: {
env: {
devServerTargets: {
default: 'my-app:serve',
production: 'my-app:serve:production',
},
},
videosFolder: './dist/videos', videosFolder: './dist/videos',
screenshotsFolder: './dist/screenshots', screenshotsFolder: './dist/screenshots',
}, },
@ -58,18 +64,25 @@ describe('@nx/cypress/plugin', () => {
"targets": { "targets": {
"e2e": { "e2e": {
"cache": true, "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": [ "inputs": [
"default", "default",
"^production", "^production",
{
"externalDependencies": [
"cypress",
],
},
], ],
"options": { "options": {
"cypressConfig": "cypress.config.js", "cwd": ".",
"testingType": "e2e",
}, },
"outputs": [ "outputs": [
"{options.videosFolder}",
"{options.screenshotsFolder}",
"{projectRoot}/dist/videos", "{projectRoot}/dist/videos",
"{projectRoot}/dist/screenshots", "{projectRoot}/dist/screenshots",
], ],
@ -110,18 +123,20 @@ describe('@nx/cypress/plugin', () => {
"targets": { "targets": {
"component-test": { "component-test": {
"cache": true, "cache": true,
"executor": "@nx/cypress:cypress", "command": "cypress open --config-file cypress.config.js --component",
"inputs": [ "inputs": [
"default", "default",
"^production", "^production",
{
"externalDependencies": [
"cypress",
],
},
], ],
"options": { "options": {
"cypressConfig": "cypress.config.js", "cwd": ".",
"testingType": "component",
}, },
"outputs": [ "outputs": [
"{options.videosFolder}",
"{options.screenshotsFolder}",
"{projectRoot}/dist/videos", "{projectRoot}/dist/videos",
"{projectRoot}/dist/screenshots", "{projectRoot}/dist/screenshots",
], ],
@ -138,6 +153,8 @@ describe('@nx/cypress/plugin', () => {
defineConfig({ defineConfig({
e2e: { e2e: {
specPattern: '**/*.cy.ts', specPattern: '**/*.cy.ts',
videosFolder: './dist/videos',
screenshotsFolder: './dist/screenshots',
env: { env: {
devServerTargets: { devServerTargets: {
default: 'my-app:serve', default: 'my-app:serve',
@ -164,24 +181,27 @@ describe('@nx/cypress/plugin', () => {
"targets": { "targets": {
"e2e": { "e2e": {
"cache": true, "cache": true,
"command": "cypress run --config-file cypress.config.js --e2e",
"configurations": { "configurations": {
"production": { "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": [ "inputs": [
"default", "default",
"^production", "^production",
{
"externalDependencies": [
"cypress",
],
},
], ],
"options": { "options": {
"cypressConfig": "cypress.config.js", "cwd": ".",
"devServerTarget": "my-app:serve",
"testingType": "e2e",
}, },
"outputs": [ "outputs": [
"{options.videosFolder}", "{projectRoot}/dist/videos",
"{options.screenshotsFolder}", "{projectRoot}/dist/screenshots",
], ],
}, },
"e2e-ci": { "e2e-ci": {
@ -197,29 +217,32 @@ describe('@nx/cypress/plugin', () => {
"inputs": [ "inputs": [
"default", "default",
"^production", "^production",
{
"externalDependencies": [
"cypress",
],
},
], ],
"outputs": [ "outputs": [
"{options.videosFolder}", "{projectRoot}/dist/videos",
"{options.screenshotsFolder}", "{projectRoot}/dist/screenshots",
], ],
}, },
"e2e-ci--test.cy.ts": { "e2e-ci--test.cy.ts": {
"cache": true, "cache": true,
"configurations": undefined, "command": "cypress run --config-file cypress.config.js --e2e --env.devServerTarget my-app:serve-static --spec test.cy.ts",
"executor": "@nx/cypress:cypress",
"inputs": [ "inputs": [
"default", "default",
"^production", "^production",
{
"externalDependencies": [
"cypress",
], ],
"options": {
"cypressConfig": "cypress.config.js",
"devServerTarget": "my-app:serve-static",
"spec": "test.cy.ts",
"testingType": "e2e",
}, },
],
"outputs": [ "outputs": [
"{options.videosFolder}", "{projectRoot}/dist/videos",
"{options.screenshotsFolder}", "{projectRoot}/dist/screenshots",
], ],
}, },
}, },

View File

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