From 3c684f293eafaf1656b60641802317de360223e5 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Thu, 12 Dec 2024 14:44:30 -0300 Subject: [PATCH] cypress: add @cypress/schematic for angular --- .../app/__snapshots__/generator.spec.ts.snap | 8 ++ generators/base-application/types.d.ts | 1 + generators/client/resources/package.json | 1 + .../__snapshots__/generator.spec.ts.snap | 9 ++ generators/cypress/command.ts | 14 +- generators/cypress/generator.ts | 127 +++++++++++++++++- .../java/generators/server/generator.ts | 13 -- lib/types/application/yo-rc.d.ts | 1 + 8 files changed, 150 insertions(+), 24 deletions(-) diff --git a/generators/app/__snapshots__/generator.spec.ts.snap b/generators/app/__snapshots__/generator.spec.ts.snap index 313f3d0d58db..41c7c5c176c2 100644 --- a/generators/app/__snapshots__/generator.spec.ts.snap +++ b/generators/app/__snapshots__/generator.spec.ts.snap @@ -286,6 +286,8 @@ exports[`generator - app with default config should match snapshot 1`] = ` "communicationSpringWebsocket": false, "cucumberTests": false, "customizeTemplatePaths": [], + "cypressAudit": undefined, + "cypressCoverage": undefined, "cypressTests": false, "dasherizedBaseName": "jhipster", "databaseMigration": undefined, @@ -650,6 +652,7 @@ exports[`generator - app with default config should match snapshot 1`] = ` "@angular/cli": "ANGULAR_CLI_VERSION", "@angular/common": "ANGULAR_COMMON_VERSION", "@cypress/code-coverage": "CYPRESS_CODE_COVERAGE_VERSION", + "@cypress/schematic": "CYPRESS_SCHEMATIC_VERSION", "@eslint/js": "ESLINT_JS_VERSION", "@fortawesome/angular-fontawesome": "FORTAWESOME_ANGULAR_FONTAWESOME_VERSION", "@fortawesome/fontawesome-svg-core": "FORTAWESOME_FONTAWESOME_SVG_CORE_VERSION", @@ -952,6 +955,8 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "communicationSpringWebsocket": false, "cucumberTests": false, "customizeTemplatePaths": [], + "cypressAudit": undefined, + "cypressCoverage": undefined, "cypressTests": false, "dasherizedBaseName": "jhipster", "databaseMigration": undefined, @@ -1312,6 +1317,7 @@ exports[`generator - app with gateway should match snapshot 1`] = ` "@angular/cli": "ANGULAR_CLI_VERSION", "@angular/common": "ANGULAR_COMMON_VERSION", "@cypress/code-coverage": "CYPRESS_CODE_COVERAGE_VERSION", + "@cypress/schematic": "CYPRESS_SCHEMATIC_VERSION", "@eslint/js": "ESLINT_JS_VERSION", "@fortawesome/angular-fontawesome": "FORTAWESOME_ANGULAR_FONTAWESOME_VERSION", "@fortawesome/fontawesome-svg-core": "FORTAWESOME_FONTAWESOME_SVG_CORE_VERSION", @@ -1613,6 +1619,8 @@ exports[`generator - app with microservice should match snapshot 1`] = ` "communicationSpringWebsocket": false, "cucumberTests": false, "customizeTemplatePaths": [], + "cypressAudit": undefined, + "cypressCoverage": undefined, "cypressTests": false, "dasherizedBaseName": "jhipster", "databaseMigration": undefined, diff --git a/generators/base-application/types.d.ts b/generators/base-application/types.d.ts index c1b3279e0389..3b7da3466254 100644 --- a/generators/base-application/types.d.ts +++ b/generators/base-application/types.d.ts @@ -147,6 +147,7 @@ export type CommonClientServerApplication = BaseApplication & AuthenticationProperties & SpringBootApplication & ClientApplication & + ExportApplicationPropertiesFromCommand & ExportApplicationPropertiesFromCommand & ExportApplicationPropertiesFromCommand & ApplicationProperties & { diff --git a/generators/client/resources/package.json b/generators/client/resources/package.json index 76db5ac1d7c8..68c9ab524e43 100644 --- a/generators/client/resources/package.json +++ b/generators/client/resources/package.json @@ -4,6 +4,7 @@ }, "devDependencies": { "@cypress/code-coverage": "3.13.9", + "@cypress/schematic": "2.5.1", "babel-loader": "9.2.1", "babel-plugin-istanbul": "7.0.0", "cypress": "13.16.1", diff --git a/generators/cypress/__snapshots__/generator.spec.ts.snap b/generators/cypress/__snapshots__/generator.spec.ts.snap index 6ad260052d17..6bae1e264b61 100644 --- a/generators/cypress/__snapshots__/generator.spec.ts.snap +++ b/generators/cypress/__snapshots__/generator.spec.ts.snap @@ -8,6 +8,9 @@ exports[`generator - cypress jwt-cypressAudit(false)-angular-withAdminUi(false)- ".yo-rc.json": { "stateCleared": "modified", }, + "clientRoot/angular.json": { + "stateCleared": "modified", + }, "clientRoot/cypress.config.ts": { "stateCleared": "modified", }, @@ -156,6 +159,9 @@ exports[`generator - cypress oauth2-cypressAudit(false)-angular-withAdminUi(fals ".yo-rc.json": { "stateCleared": "modified", }, + "angular.json": { + "stateCleared": "modified", + }, "cypress.config.ts": { "stateCleared": "modified", }, @@ -342,6 +348,9 @@ exports[`generator - cypress session-cypressAudit(false)-angular-withAdminUi(fal ".yo-rc.json": { "stateCleared": "modified", }, + "angular.json": { + "stateCleared": "modified", + }, "cypress.config.ts": { "stateCleared": "modified", }, diff --git a/generators/cypress/command.ts b/generators/cypress/command.ts index 1786a5693d29..eb5ee8045120 100644 --- a/generators/cypress/command.ts +++ b/generators/cypress/command.ts @@ -18,19 +18,23 @@ */ import type { JHipsterCommandDefinition } from '../../lib/command/index.js'; -const command: JHipsterCommandDefinition = { - options: { +const command = { + configs: { cypressCoverage: { description: 'Enable Cypress code coverage report generation', - type: Boolean, + cli: { + type: Boolean, + }, scope: 'storage', }, cypressAudit: { description: 'Enable cypress-audit/lighthouse report generation', - type: Boolean, + cli: { + type: Boolean, + }, scope: 'storage', }, }, -}; +} as const satisfies JHipsterCommandDefinition; export default command; diff --git a/generators/cypress/generator.ts b/generators/cypress/generator.ts index e12cbbef710f..8fa430666945 100644 --- a/generators/cypress/generator.ts +++ b/generators/cypress/generator.ts @@ -27,6 +27,8 @@ import { cypressEntityFiles, cypressFiles } from './files.js'; const { ANGULAR } = clientFrameworkTypes; +const WAIT_TIMEOUT = 3 * 60000; + export default class CypressGenerator extends BaseApplicationGenerator { async beforeQueue() { if (!this.fromBlueprint) { @@ -67,6 +69,21 @@ export default class CypressGenerator extends BaseApplicationGenerator { return this.delegateTasksToBlueprint(() => this.prompting); } + get configuring() { + return this.asConfiguringTaskGroup({ + async configureCypressOptions() { + if (this.jhipsterConfigWithDefaults.cypressCoverage && this.jhipsterConfigWithDefaults.clientBundler === 'experimentalEsbuild') { + this.log.warn('Code coverage for Cypress tests is not supported with the experimental ESBuild bundler.'); + this.jhipsterConfig.cypressCoverage = false; + } + }, + }); + } + + get [BaseApplicationGenerator.CONFIGURING]() { + return this.delegateTasksToBlueprint(() => this.configuring); + } + get loading() { return this.asLoadingTaskGroup({ prepareForTemplates({ application }) { @@ -188,22 +205,99 @@ export default class CypressGenerator extends BaseApplicationGenerator { }, configure({ application }) { + const { + clientFrameworkAngular, + cypressCoverage, + dasherizedBaseName, + devServerPort, + devServerPortProxy: devServerPortE2e = devServerPort, + } = application; + const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); clientPackageJson.merge({ devDependencies: { 'eslint-plugin-cypress': application.nodeDependencies['eslint-plugin-cypress'], }, scripts: { - e2e: 'npm run e2e:cypress:headed --', - 'e2e:headless': 'npm run e2e:cypress --', - 'e2e:cypress:headed': 'npm run e2e:cypress -- --headed', + 'ci:e2e:run': 'concurrently -k -s first -n application,e2e -c red,blue npm:ci:e2e:server:start npm:e2e:headless', + 'ci:e2e:dev': `concurrently -k -s first -n application,e2e -c red,blue npm:app:start npm:e2e:headless`, + cypress: 'cypress open --e2e', 'e2e:cypress': 'cypress run --e2e --browser chrome', + 'e2e:cypress:headed': 'npm run e2e:cypress -- --headed', 'e2e:cypress:record': 'npm run e2e:cypress -- --record', - cypress: 'cypress open --e2e', + 'e2e:dev': `concurrently -k -s first -n application,e2e -c red,blue npm:app:start npm:e2e`, + 'pree2e:headless': 'npm run ci:server:await', + 'e2e:headless': 'npm run e2e:cypress --', + ...(clientFrameworkAngular + ? { + e2e: 'ng e2e', + 'e2e:devserver': `concurrently -k -s first -n backend,e2e -c red,blue npm:backend:start "npm run ci:server:await && ng run ${dasherizedBaseName}:cypress-headless${cypressCoverage ? ':instrumenter' : ''}"`, + } + : { + e2e: 'npm run e2e:cypress:headed --', + 'e2e:devserver': `concurrently -k -s first -n backend,frontend,e2e -c red,yellow,blue npm:backend:start npm:start "wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:${devServerPortE2e} && npm run e2e:headless -- -c baseUrl=http://localhost:${devServerPortE2e}"`, + }), }, }); }, + cypressSchematics({ application, source }) { + const { clientFrameworkAngular, dasherizedBaseName, clientRootDir } = application; + if (!clientFrameworkAngular) return; + source.mergeClientPackageJson?.({ + devDependencies: { + '@cypress/schematic': null, + }, + }); + this.mergeDestinationJson(`${clientRootDir}angular.json`, { + projects: { + [application.dasherizedBaseName]: { + architect: { + e2e: { + builder: '@cypress/schematic:cypress', + options: { + devServerTarget: `${dasherizedBaseName}:serve`, + watch: false, + headed: true, + browser: 'chrome', + }, + configurations: { + production: { + devServerTarget: `${dasherizedBaseName}:serve:production`, + }, + }, + }, + 'cypress-headless': { + builder: '@cypress/schematic:cypress', + options: { + devServerTarget: `${dasherizedBaseName}:serve`, + watch: false, + browser: 'chrome', + }, + configurations: { + production: { + devServerTarget: `${dasherizedBaseName}:serve:production`, + }, + }, + }, + 'cypress-open': { + builder: '@cypress/schematic:cypress', + options: { + devServerTarget: `${dasherizedBaseName}:serve`, + watch: true, + headless: false, + }, + configurations: { + production: { + devServerTarget: `${dasherizedBaseName}:serve:production`, + }, + }, + }, + }, + }, + }, + }); + }, configureAudits({ application }) { if (!application.cypressAudit) return; const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); @@ -220,7 +314,7 @@ export default class CypressGenerator extends BaseApplicationGenerator { }); }, configureCoverage({ application, source }) { - const { cypressCoverage, clientFrameworkAngular, dasherizedBaseName } = application; + const { cypressCoverage, clientFrameworkAngular, clientRootDir, dasherizedBaseName } = application; if (!cypressCoverage) return; const clientPackageJson = this.createStorage(this.destinationPath(application.clientRootDir!, 'package.json')); clientPackageJson.merge({ @@ -241,7 +335,28 @@ export default class CypressGenerator extends BaseApplicationGenerator { }); if (clientFrameworkAngular) { // Add 'ng build --configuration instrumenter' support - this.createStorage('angular.json').setPath(`projects.${dasherizedBaseName}.architect.build.configurations.instrumenter`, {}); + const e2eConfigurations = { + configurations: { + instrumenter: { + devServerTarget: `${dasherizedBaseName}:serve:instrumenter`, + }, + }, + }; + + this.mergeDestinationJson(`${clientRootDir}angular.json`, { + projects: { + [dasherizedBaseName]: { + architect: { + build: { configurations: { instrumenter: {} } }, + serve: { configurations: { instrumenter: { buildTarget: `${dasherizedBaseName}:build:instrumenter` } } }, + e2e: e2eConfigurations, + 'cypress-headless': e2eConfigurations, + 'cypress-open': e2eConfigurations, + }, + }, + }, + }); + source.addWebpackConfig?.({ config: `targetOptions.configuration === 'instrumenter' ? { diff --git a/generators/java/generators/server/generator.ts b/generators/java/generators/server/generator.ts index 2c2bff0a8792..2bce4fcb45b2 100644 --- a/generators/java/generators/server/generator.ts +++ b/generators/java/generators/server/generator.ts @@ -98,9 +98,7 @@ export default class ServerGenerator extends BaseApplicationGenerator { }); }, packageJsonE2eScripts({ application }) { - const { devServerPort, devServerPortProxy: devServerPortE2e = devServerPort } = application; const scriptsStorage = this.packageJson.createStorage('scripts'); - const buildCmd = application.buildToolGradle ? 'gradlew' : 'mvnw -ntp'; let applicationWaitTimeout = WAIT_TIMEOUT * (application.applicationTypeGateway ? 2 : 1); applicationWaitTimeout = application.authenticationTypeOauth2 ? applicationWaitTimeout * 2 : applicationWaitTimeout; @@ -110,17 +108,6 @@ export default class ServerGenerator extends BaseApplicationGenerator { scriptsStorage.set({ 'ci:server:await': `echo "Waiting for server at port $npm_package_config_backend_port to start" && wait-on -t ${applicationWaitTimeout} ${applicationEndpoint} && echo "Server at port $npm_package_config_backend_port started"`, }); - - // TODO add e2eTests property to application. - if (this.jhipsterConfig.testFrameworks?.includes('cypress')) { - scriptsStorage.set({ - 'pree2e:headless': 'npm run ci:server:await', - 'ci:e2e:run': 'concurrently -k -s first -n application,e2e -c red,blue npm:ci:e2e:server:start npm:e2e:headless', - 'ci:e2e:dev': `concurrently -k -s first -n application,e2e -c red,blue "./${buildCmd}" npm:e2e:headless`, - 'e2e:dev': `concurrently -k -s first -n application,e2e -c red,blue "./${buildCmd}" npm:e2e`, - 'e2e:devserver': `concurrently -k -s first -n backend,frontend,e2e -c red,yellow,blue npm:backend:start npm:start "wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:${devServerPortE2e} && npm run e2e:headless -- -c baseUrl=http://localhost:${devServerPortE2e}"`, - }); - } }, }); } diff --git a/lib/types/application/yo-rc.d.ts b/lib/types/application/yo-rc.d.ts index 899cfebec3e3..7fd5b93802eb 100644 --- a/lib/types/application/yo-rc.d.ts +++ b/lib/types/application/yo-rc.d.ts @@ -16,6 +16,7 @@ export type ApplicationConfiguration = Simplify< ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand & + ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand & ExportStoragePropertiesFromCommand &