From a916794318a31ddb4c54d690bbffa56146a5f94e Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Tue, 21 Nov 2023 08:57:58 -0500 Subject: [PATCH] fix(testing): update the cypress plugin implementation (#20314) --- packages/cypress/migrations.json | 5 - packages/cypress/plugins/cypress-preset.ts | 2 - .../add-nx-cypress-plugin.spec.ts | 170 ------------------ .../update-17-2-0/add-nx-cypress-plugin.ts | 24 --- packages/cypress/src/plugins/plugin.spec.ts | 81 ++++++--- packages/cypress/src/plugins/plugin.ts | 98 +++++----- 6 files changed, 95 insertions(+), 285 deletions(-) delete mode 100644 packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts delete mode 100644 packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.ts diff --git a/packages/cypress/migrations.json b/packages/cypress/migrations.json index e502df40c652b..18cdc7dfe619a 100644 --- a/packages/cypress/migrations.json +++ b/packages/cypress/migrations.json @@ -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": { diff --git a/packages/cypress/plugins/cypress-preset.ts b/packages/cypress/plugins/cypress-preset.ts index 95b7e1e461a14..f478cf5831c02 100644 --- a/packages/cypress/plugins/cypress-preset.ts +++ b/packages/cypress/plugins/cypress-preset.ts @@ -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'; diff --git a/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts b/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts deleted file mode 100644 index 26979c983990e..0000000000000 --- a/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts +++ /dev/null @@ -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', - }, - }, - }); - }); -}); diff --git a/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.ts b/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.ts deleted file mode 100644 index 3f3a4a57b36a2..0000000000000 --- a/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.ts +++ /dev/null @@ -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); -} diff --git a/packages/cypress/src/plugins/plugin.spec.ts b/packages/cypress/src/plugins/plugin.spec.ts index 9e161e17de8bf..edb2ba144a813 100644 --- a/packages/cypress/src/plugins/plugin.spec.ts +++ b/packages/cypress/src/plugins/plugin.spec.ts @@ -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", ], }, }, diff --git a/packages/cypress/src/plugins/plugin.ts b/packages/cypress/src/plugins/plugin.ts index 97cf61d401c46..35d3b208cbdda 100644 --- a/packages/cypress/src/plugins/plugin.ts +++ b/packages/cypress/src/plugins/plugin.ts @@ -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 = { - executor: '@nx/cypress:cypress', - options: { - cypressConfig: configFilePath, - }, + const cypressEnv = { + ...cypressConfig.env, + ...cypressConfig.e2e?.env, }; - const targets: Record< - string, - TargetConfiguration - > = {}; + const devServerTargets: Record = cypressEnv?.devServerTargets; - if ('e2e' in cypressConfig) { - const e2eTargetDefaults = readTargetDefaultsForTarget( - options.targetName, - context.nxJsonConfiguration.targetDefaults, - '@nx/cypress:cypress' - ); + const relativeConfigPath = relative(projectRoot, configFilePath); + + const namedInputs = getNamedInputs(projectRoot, context); + const targets: Record = {}; + + if ('e2e' in cypressConfig) { targets[options.targetName] = { - ...baseTargetConfig, + command: `cypress run --config-file ${relativeConfigPath} --e2e`, options: { - ...baseTargetConfig.options, - testingType: 'e2e', + cwd: projectRoot, }, }; + const e2eTargetDefaults = readTargetDefaultsForTarget( + options.targetName, + context.nxJsonConfiguration.targetDefaults, + 'run-commands' + ); + 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 = - 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'], + }, + ]; +}