From 6fe85aafce83159a90267f41586e52bf2b087dca Mon Sep 17 00:00:00 2001 From: Fabio Martino Date: Tue, 30 Nov 2021 17:03:18 +0100 Subject: [PATCH] feat: add cypress for e2e test (#605) Co-authored-by: Fabio Martino --- generators/app/index.js | 7 ++- generators/app/prompts.js | 8 +++- generators/app/templates/_README.md | 15 ++++++- .../app/templates/__cypress._cypress.json | 9 ++++ .../__cypress/cypress/fixtures/example.json | 5 +++ .../__cypress/cypress/integration/about.ts | 6 +++ .../__cypress/cypress/integration/home.ts | 6 +++ .../__cypress/cypress/plugins/index.ts | 3 ++ .../__cypress/cypress/support/commands.ts | 43 +++++++++++++++++++ .../__cypress/cypress/support/index.ts | 17 ++++++++ .../templates/__cypress/cypress/tsconfig.json | 8 ++++ generators/app/templates/_angular.json | 33 ++++++++++++++ generators/app/templates/_package.json | 42 ++++++++++-------- 13 files changed, 181 insertions(+), 21 deletions(-) create mode 100644 generators/app/templates/__cypress._cypress.json create mode 100644 generators/app/templates/__cypress/cypress/fixtures/example.json create mode 100644 generators/app/templates/__cypress/cypress/integration/about.ts create mode 100644 generators/app/templates/__cypress/cypress/integration/home.ts create mode 100644 generators/app/templates/__cypress/cypress/plugins/index.ts create mode 100644 generators/app/templates/__cypress/cypress/support/commands.ts create mode 100644 generators/app/templates/__cypress/cypress/support/index.ts create mode 100644 generators/app/templates/__cypress/cypress/tsconfig.json diff --git a/generators/app/index.js b/generators/app/index.js index 30ac4daf..f3cf56b9 100755 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -152,6 +152,7 @@ class NgxGenerator extends Generator { this.props.auth = this.props.features.includes('auth'); this.props.lazy = this.props.features.includes('lazy'); this.props.e2e = this.props.features.includes('e2e'); + this.props.cypress = this.props.features.includes('cypress'); this.props.angulartics = this.props.features.includes('angulartics'); this.shareProps(this.props); } @@ -277,8 +278,9 @@ class NgxGenerator extends Generator { this.log(`- $ ${chalk.green(`${this.packageManager} test`)}: run unit tests in watch mode for TDD`); this.log(`- $ ${chalk.green(`${this.packageManager} run test:ci`)}: lint code and run units tests with coverage`); - this.log(`- $ ${chalk.green(`${this.packageManager} run e2e`)}: launch e2e tests`); - + if (this.props.e2e || this.props.cypress) { + this.log(`- $ ${chalk.green(`${this.packageManager} run e2e`)}: launch e2e tests`); + } if (this.props.tools.includes('hads')) { this.log(`- $ ${chalk.green(`${this.packageManager} run docs`)}: show docs and coding guides`); } @@ -311,6 +313,7 @@ module.exports = Generator.make({ 'tools-jest': (props) => props.tools && props.tools.includes('jest'), 'tools-karma': (props) => props.tools && !props.tools.includes('jest'), e2e: (props) => !props.features || props.features.includes('e2e'), + cypress: (props) => !props.features || props.features.includes('cypress'), husky: (props) => props.initGit && props.tools.includes('prettier') }) }); diff --git a/generators/app/prompts.js b/generators/app/prompts.js index 1a9968a7..03dbadcb 100755 --- a/generators/app/prompts.js +++ b/generators/app/prompts.js @@ -139,7 +139,13 @@ module.exports = [ }, { value: 'e2e', - name: 'End-to-end tests', + name: 'End-to-end tests with Protractor (Wrapper over WebDrivers JS deprecated end 2022)', + checked: false, + when: true + }, + { + value: 'cypress', + name: 'End-to-end tests with Cypress (Automated testing framework designed specifically for the modern Web)', checked: true, when: true }, diff --git a/generators/app/templates/_README.md b/generators/app/templates/_README.md index 014ff447..a0f3d9ff 100644 --- a/generators/app/templates/_README.md +++ b/generators/app/templates/_README.md @@ -28,7 +28,10 @@ dist/ web app production build <% } -%> docs/ project docs and coding guides <% if (props.e2e) { -%> -e2e/ end-to-end tests +e2e/ end-to-end tests (Protractor) +<% } -%> +<% if (props.cypress) { -%> +cypress/ end-to-end tests (Cypress) <% } -%> src/ project source code |- app/ app components @@ -89,7 +92,12 @@ Task | Description <% } -%> `npm test` | Run unit tests via [Karma](https://karma-runner.github.io) in watch mode `npm run test:ci` | Lint code and run unit tests once for continuous integration +<% if (props.e2e) { -%> `npm run e2e` | Run e2e tests using [Protractor](http://www.protractortest.org) +<% } -%> +<% if (props.cypress) { -%> +`npm run e2e` | Run e2e tests using [Cypress](https://www.cypress.io/) +<% } -%> `npm run lint` | Lint code `npm run translations:extract` | Extract strings from code and templates to `src/app/translations/template.json` <% if (props.tools.includes('hads')) { -%> @@ -165,7 +173,12 @@ Development, build and quality processes are based on [angular-cli](https://gith [browserslist](https://github.com/ai/browserslist) - Asset revisioning for [better cache management](https://webpack.github.io/docs/long-term-caching.html) - Unit tests using [Jasmine](http://jasmine.github.io) and [Karma](https://karma-runner.github.io) +<% if (props.e2e) { -%> - End-to-end tests using [Protractor](https://github.com/angular/protractor) +<% } -%> +<% if (props.cypress) { -%> +- End-to-end tests using [Cypress](https://www.cypress.io/) +<% } -%> - Static code analysis: [TSLint](https://github.com/palantir/tslint), [Codelyzer](https://github.com/mgechev/codelyzer), [Stylelint](http://stylelint.io) and [HTMLHint](http://htmlhint.com/) <% if (props.tools.includes('hads')) { -%> diff --git a/generators/app/templates/__cypress._cypress.json b/generators/app/templates/__cypress._cypress.json new file mode 100644 index 00000000..bd321dcb --- /dev/null +++ b/generators/app/templates/__cypress._cypress.json @@ -0,0 +1,9 @@ +{ + "integrationFolder": "cypress/integration", + "supportFile": "cypress/support/index.ts", + "videosFolder": "cypress/videos", + "screenshotsFolder": "cypress/screenshots", + "pluginsFile": "cypress/plugins/index.ts", + "fixturesFolder": "cypress/fixtures", + "baseUrl": "http://localhost:4200" +} \ No newline at end of file diff --git a/generators/app/templates/__cypress/cypress/fixtures/example.json b/generators/app/templates/__cypress/cypress/fixtures/example.json new file mode 100644 index 00000000..02e42543 --- /dev/null +++ b/generators/app/templates/__cypress/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/generators/app/templates/__cypress/cypress/integration/about.ts b/generators/app/templates/__cypress/cypress/integration/about.ts new file mode 100644 index 00000000..ea40c092 --- /dev/null +++ b/generators/app/templates/__cypress/cypress/integration/about.ts @@ -0,0 +1,6 @@ +describe('About page E2E test', () => { + it('Visits the about page', () => { + cy.visit('/about') + cy.contains('Version 1.0.0-dev') + }); +}) diff --git a/generators/app/templates/__cypress/cypress/integration/home.ts b/generators/app/templates/__cypress/cypress/integration/home.ts new file mode 100644 index 00000000..0d4cf21a --- /dev/null +++ b/generators/app/templates/__cypress/cypress/integration/home.ts @@ -0,0 +1,6 @@ +describe('Home page E2E test', () => { + it('Visits the home page', () => { + cy.visit('/home') + cy.contains('Hello world !') + }); +}) diff --git a/generators/app/templates/__cypress/cypress/plugins/index.ts b/generators/app/templates/__cypress/cypress/plugins/index.ts new file mode 100644 index 00000000..bfc9d3b6 --- /dev/null +++ b/generators/app/templates/__cypress/cypress/plugins/index.ts @@ -0,0 +1,3 @@ +// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress +// For more info, visit https://on.cypress.io/plugins-api +module.exports = (on, config) => {} diff --git a/generators/app/templates/__cypress/cypress/support/commands.ts b/generators/app/templates/__cypress/cypress/support/commands.ts new file mode 100644 index 00000000..af1f44a0 --- /dev/null +++ b/generators/app/templates/__cypress/cypress/support/commands.ts @@ -0,0 +1,43 @@ +// *********************************************** +// This example namespace declaration will help +// with Intellisense and code completion in your +// IDE or Text Editor. +// *********************************************** +// declare namespace Cypress { +// interface Chainable { +// customCommand(param: any): typeof customCommand; +// } +// } +// +// function customCommand(param: any): void { +// console.warn(param); +// } +// +// NOTE: You can use it like so: +// Cypress.Commands.add('customCommand', customCommand); +// +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/generators/app/templates/__cypress/cypress/support/index.ts b/generators/app/templates/__cypress/cypress/support/index.ts new file mode 100644 index 00000000..ac293b61 --- /dev/null +++ b/generators/app/templates/__cypress/cypress/support/index.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// When a command from ./commands is ready to use, import with `import './commands'` syntax +// import './commands'; diff --git a/generators/app/templates/__cypress/cypress/tsconfig.json b/generators/app/templates/__cypress/cypress/tsconfig.json new file mode 100644 index 00000000..79d78d7e --- /dev/null +++ b/generators/app/templates/__cypress/cypress/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "include": ["**/*.ts"], + "compilerOptions": { + "sourceMap": false, + "types": ["cypress"] + } +} diff --git a/generators/app/templates/_angular.json b/generators/app/templates/_angular.json index 829b6a34..0c2dc478 100644 --- a/generators/app/templates/_angular.json +++ b/generators/app/templates/_angular.json @@ -179,6 +179,39 @@ "devServerTarget": "<%= props.projectName %>:serve:production" } } +<% } -%> +<% if (props.cypress) { -%> + }, + "cypress-run": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "<%= props.projectName %>:serve" + }, + "configurations": { + "production": { + "devServerTarget": "<%= props.projectName %>:serve:production" + } + } + }, + "cypress-open": { + "builder": "@cypress/schematic:cypress", + "options": { + "watch": true, + "headless": false + } + }, + "e2e": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "<%= props.projectName %>:serve", + "watch": true, + "headless": false + }, + "configurations": { + "production": { + "devServerTarget": "<%= props.projectName %>:serve:production" + } + } <% } -%> } } diff --git a/generators/app/templates/_package.json b/generators/app/templates/_package.json index 7fc67585..ddfa1968 100644 --- a/generators/app/templates/_package.json +++ b/generators/app/templates/_package.json @@ -19,8 +19,12 @@ "lint": "ng lint && stylelint \"src/**/*.scss\" --syntax scss", "test": "<%= run('write:env') %> && ng test", "test:ci": "<%= run('write:env') %> && <%= run('lint') %> && ng test --configuration=ci", -<% if (props.e2e) { -%> +<% if (props.e2e || props.cypress) { -%> "e2e": "<%= run('write:env') %> && ng e2e", +<% } -%> +<% if (props.cypress) { -%> + "cypress:open": "cypress open", + "cypress:run": "cypress run", <% } -%> "translations:extract": "ngx-translate-extract --input ./src --output ./src/translations/template.json --format=json --clean --sort", <% if (props.tools.includes('hads')) { -%> @@ -65,17 +69,17 @@ "generate": "ng generate" }, "dependencies": { - "@angular/animations": "^12.1.1", - "@angular/common": "^12.1.1", - "@angular/compiler": "^12.1.1", - "@angular/core": "^12.1.1", - "@angular/forms": "^12.1.1", + "@angular/animations": "^12.1.3", + "@angular/common": "^12.1.3", + "@angular/compiler": "^12.1.3", + "@angular/core": "^12.1.3", + "@angular/forms": "^12.1.3", <% if (props.ui === 'bootstrap' || props.ui === 'material') { -%> - "@angular/localize": "^12.1.1", + "@angular/localize": "^12.1.3", <% } -%> - "@angular/platform-browser": "^12.1.1", - "@angular/platform-browser-dynamic": "^12.1.1", - "@angular/router": "^12.1.1", + "@angular/platform-browser": "^12.1.3", + "@angular/platform-browser-dynamic": "^12.1.3", + "@angular/router": "^12.1.3", "@ngx-translate/core": "^13.0.0", <% if (props.target.includes('cordova')) { -%> "@ionic-native/core": "^5.30.0", @@ -93,7 +97,7 @@ "cordova-plugin-whitelist": "^1.3.4", <% } -%> <% if (props.pwa) { -%> - "@angular/service-worker": "^12.1.1", + "@angular/service-worker": "^12.1.3", <% } -%> <% if (props.ui === 'ionic') { -%> "@ionic/angular": "^5.6.11", @@ -102,8 +106,8 @@ "bootstrap": "^5.0.2", "@fortawesome/fontawesome-free": "^5.15.1", <% } else if (props.ui === 'material') { -%> - "@angular/cdk": "^12.1.1", - "@angular/material": "^12.1.1", + "@angular/cdk": "^12.1.3", + "@angular/material": "^12.1.3", "@angular/flex-layout": "^12.0.0-beta.34", "material-design-icons-iconfont": "^6.1.0", <% } -%> @@ -133,18 +137,22 @@ <% if (props.tools.includes('jest')) { -%> "@angular-builders/jest": "^12.1.0", <% } -%> - "@angular-devkit/build-angular": "~12.1.1", + "@angular-devkit/build-angular": "~12.1.3", "@angular-eslint/builder": "~12.3.1", "@angular-eslint/eslint-plugin": "~12.3.1", "@angular-eslint/eslint-plugin-template": "~12.3.1", "@angular-eslint/schematics": "~12.3.1", "@angular-eslint/template-parser": "~12.3.1", - "@angular/cli": "~12.1.1", - "@angular/compiler-cli": "~12.1.1", - "@angular/language-service": "~12.1.1", + "@angular/cli": "~12.1.3", + "@angular/compiler-cli": "~12.1.3", + "@angular/language-service": "~12.1.3", "@biesbjerg/ngx-translate-extract": "^7.0.3", "@biesbjerg/ngx-translate-extract-marker": "^1.0.0", "@ngx-rocket/scripts": "^5.1.0", +<% if (props.cypress) { -%> + "@cypress/schematic": "^1.5.0", + "cypress": "8.0.0", +<% } -%> "@ngneat/until-destroy": "^8.0.4", "@typescript-eslint/eslint-plugin": "4.28.2", "@typescript-eslint/parser": "4.28.2",