From 96a9db4204f50a7605a5e51d51584d27aa9df164 Mon Sep 17 00:00:00 2001 From: Adam Murray Date: Fri, 25 Jun 2021 11:07:03 -0500 Subject: [PATCH] feat: add ng generate to @cypress/schematic to generate e2e spec files (#16962) --- circle.yml | 4 +- npm/cypress-schematic/.releaserc.js | 1 - npm/cypress-schematic/README.md | 38 +++++++++++ npm/cypress-schematic/package.json | 6 +- ...er-options.ts => cypressBuilderOptions.ts} | 0 .../src/builders/cypress/index.ts | 2 +- .../src/schematics/collection.json | 12 +++- .../cypress/files/cypress/plugins/index.js | 1 - .../{cypress => ng-add}/files/cypress.json | 0 .../files/cypress/integration/spec.ts | 0 .../ng-add/files/cypress/plugins/index.ts | 2 + .../files/cypress/support/commands.ts | 0 .../files/cypress/support/index.ts | 0 .../files/cypress/tsconfig.json | 0 .../index_spec.ts => ng-add/index.spec.ts} | 2 +- .../schematics/{cypress => ng-add}/index.ts | 2 +- .../{cypress => ng-add}/schema.json | 4 +- .../schematics/ng-generate/e2e/index.spec.ts | 43 ++++++++++++ .../src/schematics/ng-generate/e2e/index.ts | 68 +++++++++++++++++++ .../schematics/ng-generate/e2e/schema.json | 33 +++++++++ .../src/schematics/ng-generate/e2e/schema.ts | 10 +++ .../__name@dasherize__.spec.ts.template | 4 ++ .../src/schematics/utility/dependencies.ts | 2 +- .../src/schematics/utility/index.ts | 19 +----- .../utility/{json-file.ts => jsonFile.ts} | 0 npm/cypress-schematic/tsconfig.json | 2 +- npm/cypress-schematic/tsconfig.spec.json | 2 +- 27 files changed, 223 insertions(+), 34 deletions(-) rename npm/cypress-schematic/src/builders/cypress/{cypress-builder-options.ts => cypressBuilderOptions.ts} (100%) delete mode 100644 npm/cypress-schematic/src/schematics/cypress/files/cypress/plugins/index.js rename npm/cypress-schematic/src/schematics/{cypress => ng-add}/files/cypress.json (100%) rename npm/cypress-schematic/src/schematics/{cypress => ng-add}/files/cypress/integration/spec.ts (100%) create mode 100644 npm/cypress-schematic/src/schematics/ng-add/files/cypress/plugins/index.ts rename npm/cypress-schematic/src/schematics/{cypress => ng-add}/files/cypress/support/commands.ts (100%) rename npm/cypress-schematic/src/schematics/{cypress => ng-add}/files/cypress/support/index.ts (100%) rename npm/cypress-schematic/src/schematics/{cypress => ng-add}/files/cypress/tsconfig.json (100%) rename npm/cypress-schematic/src/schematics/{cypress/index_spec.ts => ng-add/index.spec.ts} (95%) rename npm/cypress-schematic/src/schematics/{cypress => ng-add}/index.ts (99%) rename npm/cypress-schematic/src/schematics/{cypress => ng-add}/schema.json (83%) create mode 100644 npm/cypress-schematic/src/schematics/ng-generate/e2e/index.spec.ts create mode 100644 npm/cypress-schematic/src/schematics/ng-generate/e2e/index.ts create mode 100644 npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.json create mode 100644 npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.ts create mode 100644 npm/cypress-schematic/src/schematics/ng-generate/files/__path__/__name@dasherize__.spec.ts.template rename npm/cypress-schematic/src/schematics/utility/{json-file.ts => jsonFile.ts} (100%) diff --git a/circle.yml b/circle.yml index 6bbe30f82b9b..e4afe8b1b067 100644 --- a/circle.yml +++ b/circle.yml @@ -1449,8 +1449,8 @@ jobs: command: yarn launch:test working_directory: npm/cypress-schematic - run: - name: Test Schematics - command: yarn test:schematics + name: Run unit tests + command: yarn test working_directory: npm/cypress-schematic - store-npm-logs diff --git a/npm/cypress-schematic/.releaserc.js b/npm/cypress-schematic/.releaserc.js index ef4e9210a534..7b15992ed704 100644 --- a/npm/cypress-schematic/.releaserc.js +++ b/npm/cypress-schematic/.releaserc.js @@ -2,6 +2,5 @@ module.exports = { ...require('../../.releaserc.base'), branches: [ { name: 'master', channel: 'latest' }, - { name: 'next/npm/cypress-schematic', channel: 'next', prerelease: 'alpha' }, ], } diff --git a/npm/cypress-schematic/README.md b/npm/cypress-schematic/README.md index 8a3390c711f6..4c5f075e7fca 100644 --- a/npm/cypress-schematic/README.md +++ b/npm/cypress-schematic/README.md @@ -23,6 +23,8 @@ ✅ Scaffold base Cypress files and directories +✅ Provide the ability to add new e2e files easily using `ng-generate` + ✅ Optional: prompt you to add or update the default `ng e2e` command to use Cypress. ## Usage ⏯ @@ -50,6 +52,12 @@ If you have chosen to add or update the `ng e2e` command, you can also run Cypre ng e2e ``` +To generate new e2e spec files: + +```shell script +ng generate @cypress/schematic:e2e +``` + ## Builder Options 🛠 ### Running the builder with a specific browser @@ -138,6 +146,36 @@ Read our docs to learn more about all the [configuration options](https://on.cyp Read our docs to learn more about speeding up test execution in CI via [Cypress parallelization](https://on.cypress.io/parallelization) +## Generator Options + +### Specify Filename (bypassing CLI prompt) + +In order to bypass the prompt asking for your e2e spec name, simply add a `--name=` flag like this: + +```shell script +ng generate @cypress/schematic:e2e --name=login +``` + +This will create a new spec file named `login.spec.ts` in the default Cypress folder location. + + +### Specify Project + +Add a `--project=` flag to specify the project: + +```shell script +ng generate @cypress/schematic:e2e --name=login --project=sandbox +``` +### Specify Path + +Add a `--path=` flag to specify the project: + +```shell script +ng generate @cypress/schematic:e2e --name=login --path=src/app/tests +``` + +This will create the e2e spec file in your specific location, creating folders as needed. + ## Migrating from Protractor to Cypress? Read our [migration guide](https://on.cypress.io/protractor-to-cypress) to help you make the transition from Protractor to Cypress. diff --git a/npm/cypress-schematic/package.json b/npm/cypress-schematic/package.json index 0d24027ef4c7..bba634724544 100644 --- a/npm/cypress-schematic/package.json +++ b/npm/cypress-schematic/package.json @@ -11,11 +11,9 @@ "build:watch": "tsc -p tsconfig.json --watch", "clean": "git checkout HEAD -- sandbox && git clean -f -d sandbox", "launch": "yarn link:sandbox && cd sandbox && ng add @cypress/schematic && cd ..", - "launch:test": "yarn link:sandbox && cd sandbox && ng add @cypress/schematic && cd ..", + "launch:test": "yarn link:sandbox && cd sandbox && ng add @cypress/schematic --e2eUpdate=true && cd ..", "link:sandbox": "yarn link && cd sandbox && yarn link @cypress/schematic", - "test:all": "yarn build:all && yarn test:schematics && yarn launch:test && yarn test:builders", - "test:builders": "mocha -r @packages/ts/register --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json src/builders/cypress/index_spec.ts", - "test:schematics": "mocha -r @packages/ts/register --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json src/schematics/cypress/index_spec.ts", + "test": "mocha -r @packages/ts/register --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json src/**/*.spec.ts", "test:e2e:sandbox": "cd sandbox && ng run sandbox:cypress-run", "unlink:sandbox": "cd sandbox && yarn unlink @cypress/schematic && cd .. && yarn unlink" }, diff --git a/npm/cypress-schematic/src/builders/cypress/cypress-builder-options.ts b/npm/cypress-schematic/src/builders/cypress/cypressBuilderOptions.ts similarity index 100% rename from npm/cypress-schematic/src/builders/cypress/cypress-builder-options.ts rename to npm/cypress-schematic/src/builders/cypress/cypressBuilderOptions.ts diff --git a/npm/cypress-schematic/src/builders/cypress/index.ts b/npm/cypress-schematic/src/builders/cypress/index.ts index b1fb27185d9a..d44b8ad99e9d 100644 --- a/npm/cypress-schematic/src/builders/cypress/index.ts +++ b/npm/cypress-schematic/src/builders/cypress/index.ts @@ -11,7 +11,7 @@ import { dirname, join } from 'path' import { open, run } from 'cypress' import { from, noop, Observable, of } from 'rxjs' import { catchError, concatMap, first, map, switchMap, tap } from 'rxjs/operators' -import { CypressBuilderOptions } from './cypress-builder-options' +import { CypressBuilderOptions } from './cypressBuilderOptions' type CypressOptions = Partial & Partial; diff --git a/npm/cypress-schematic/src/schematics/collection.json b/npm/cypress-schematic/src/schematics/collection.json index bd6fb1ff8fe5..4c27b6e1d4e5 100644 --- a/npm/cypress-schematic/src/schematics/collection.json +++ b/npm/cypress-schematic/src/schematics/collection.json @@ -3,8 +3,16 @@ "schematics": { "ng-add": { "description": "Adds Cypress to an Angular project.", - "factory": "./cypress/index", - "schema": "./cypress/schema.json" + "factory": "./ng-add/index", + "schema": "./ng-add/schema.json" + }, + "e2e": { + "description": "Create an e2e spec file", + "factory": "./ng-generate/e2e/index", + "schema": "./ng-generate/e2e/schema.json", + "aliases": [ + "e2e-spec" + ] } } } diff --git a/npm/cypress-schematic/src/schematics/cypress/files/cypress/plugins/index.js b/npm/cypress-schematic/src/schematics/cypress/files/cypress/plugins/index.js deleted file mode 100644 index 1ee7c778d63f..000000000000 --- a/npm/cypress-schematic/src/schematics/cypress/files/cypress/plugins/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = (on, config) => {} diff --git a/npm/cypress-schematic/src/schematics/cypress/files/cypress.json b/npm/cypress-schematic/src/schematics/ng-add/files/cypress.json similarity index 100% rename from npm/cypress-schematic/src/schematics/cypress/files/cypress.json rename to npm/cypress-schematic/src/schematics/ng-add/files/cypress.json diff --git a/npm/cypress-schematic/src/schematics/cypress/files/cypress/integration/spec.ts b/npm/cypress-schematic/src/schematics/ng-add/files/cypress/integration/spec.ts similarity index 100% rename from npm/cypress-schematic/src/schematics/cypress/files/cypress/integration/spec.ts rename to npm/cypress-schematic/src/schematics/ng-add/files/cypress/integration/spec.ts diff --git a/npm/cypress-schematic/src/schematics/ng-add/files/cypress/plugins/index.ts b/npm/cypress-schematic/src/schematics/ng-add/files/cypress/plugins/index.ts new file mode 100644 index 000000000000..84cb5fcd050d --- /dev/null +++ b/npm/cypress-schematic/src/schematics/ng-add/files/cypress/plugins/index.ts @@ -0,0 +1,2 @@ +// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress +// For more info, visit https://on.cypress.io/plugins-api diff --git a/npm/cypress-schematic/src/schematics/cypress/files/cypress/support/commands.ts b/npm/cypress-schematic/src/schematics/ng-add/files/cypress/support/commands.ts similarity index 100% rename from npm/cypress-schematic/src/schematics/cypress/files/cypress/support/commands.ts rename to npm/cypress-schematic/src/schematics/ng-add/files/cypress/support/commands.ts diff --git a/npm/cypress-schematic/src/schematics/cypress/files/cypress/support/index.ts b/npm/cypress-schematic/src/schematics/ng-add/files/cypress/support/index.ts similarity index 100% rename from npm/cypress-schematic/src/schematics/cypress/files/cypress/support/index.ts rename to npm/cypress-schematic/src/schematics/ng-add/files/cypress/support/index.ts diff --git a/npm/cypress-schematic/src/schematics/cypress/files/cypress/tsconfig.json b/npm/cypress-schematic/src/schematics/ng-add/files/cypress/tsconfig.json similarity index 100% rename from npm/cypress-schematic/src/schematics/cypress/files/cypress/tsconfig.json rename to npm/cypress-schematic/src/schematics/ng-add/files/cypress/tsconfig.json diff --git a/npm/cypress-schematic/src/schematics/cypress/index_spec.ts b/npm/cypress-schematic/src/schematics/ng-add/index.spec.ts similarity index 95% rename from npm/cypress-schematic/src/schematics/cypress/index_spec.ts rename to npm/cypress-schematic/src/schematics/ng-add/index.spec.ts index 2dbbb69b96b0..5adcc84af195 100644 --- a/npm/cypress-schematic/src/schematics/cypress/index_spec.ts +++ b/npm/cypress-schematic/src/schematics/ng-add/index.spec.ts @@ -30,7 +30,7 @@ describe('@cypress/schematic: ng-add', () => { }) it('should create cypress files', async () => { - const files = ['cypress/integration/spec.ts', 'cypress/plugins/index.js', 'cypress/support/commands.ts', 'cypress/support/index.ts', 'cypress/tsconfig.json', 'cypress.json'] + const files = ['cypress/integration/spec.ts', 'cypress/plugins/index.ts', 'cypress/support/commands.ts', 'cypress/support/index.ts', 'cypress/tsconfig.json', 'cypress.json'] const homePath = '/projects/sandbox/' return schematicRunner.runSchematicAsync('ng-add', {}, appTree).toPromise().then((tree) => { diff --git a/npm/cypress-schematic/src/schematics/cypress/index.ts b/npm/cypress-schematic/src/schematics/ng-add/index.ts similarity index 99% rename from npm/cypress-schematic/src/schematics/cypress/index.ts rename to npm/cypress-schematic/src/schematics/ng-add/index.ts index e8031ca7582f..6d56862523fd 100644 --- a/npm/cypress-schematic/src/schematics/cypress/index.ts +++ b/npm/cypress-schematic/src/schematics/ng-add/index.ts @@ -22,7 +22,7 @@ import { NodePackage, } from '../utility' import { relative, resolve } from 'path' -import { JSONFile, JSONPath } from '../utility/json-file' +import { JSONFile, JSONPath } from '../utility/jsonFile' export default function (_options: any): Rule { return (tree: Tree, _context: SchematicContext) => { diff --git a/npm/cypress-schematic/src/schematics/cypress/schema.json b/npm/cypress-schematic/src/schematics/ng-add/schema.json similarity index 83% rename from npm/cypress-schematic/src/schematics/cypress/schema.json rename to npm/cypress-schematic/src/schematics/ng-add/schema.json index 0992184fa4ff..131721b4a661 100644 --- a/npm/cypress-schematic/src/schematics/cypress/schema.json +++ b/npm/cypress-schematic/src/schematics/ng-add/schema.json @@ -1,14 +1,14 @@ { "$schema": "http://json-schema.org/draft-07/schema", "$id": "cypress-schematic", - "title": "Cypress ng-add schematic", + "title": "Cypress Install Schema", "type": "object", "properties": { "e2eUpdate": { "type": "boolean", "default": true, "description": "When true, `ng e2e` will be added or updated to use Cypress", - "x-prompt": "Would you like the default `ng e2e` command to use Cypress? [ Protractor to Cypress Migration Guide: https://on.cypress.io/protractor-to-cypress ]" + "x-prompt": "Would you like the default `ng e2e` command to use Cypress? [ Protractor to Cypress Migration Guide: https://on.cypress.io/protractor-to-cypress?cli=true ]" } }, "required": [] diff --git a/npm/cypress-schematic/src/schematics/ng-generate/e2e/index.spec.ts b/npm/cypress-schematic/src/schematics/ng-generate/e2e/index.spec.ts new file mode 100644 index 000000000000..28bc77f9550c --- /dev/null +++ b/npm/cypress-schematic/src/schematics/ng-generate/e2e/index.spec.ts @@ -0,0 +1,43 @@ +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing' +import { join, resolve } from 'path' +import { expect } from 'chai' + +describe('@cypress/schematic:e2e ng-generate', () => { + const schematicRunner = new SchematicTestRunner( + 'schematics', + join(__dirname, '../../collection.json'), + ) + let appTree: UnitTestTree + + const workspaceOptions = { + name: 'workspace', + newProjectRoot: 'projects', + version: '12.0.0', + } + + const appOptions = { + name: 'sandbox', + inlineTemplate: false, + routing: false, + skipTests: false, + skipPackageJson: false, + } + + beforeEach(async () => { + appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise() + appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise() + }) + + it('should create cypress files', async () => { + const files = ['cypress/integration/foo.spec.ts'] + const homePath = '/projects/sandbox/' + + return schematicRunner.runSchematicAsync('e2e', { name: 'foo' }, appTree).toPromise().then((tree) => { + files.forEach((f) => { + const pathToFile = resolve(homePath, f) + + expect(tree.exists(pathToFile), pathToFile).equal(true) + }) + }) + }) +}) diff --git a/npm/cypress-schematic/src/schematics/ng-generate/e2e/index.ts b/npm/cypress-schematic/src/schematics/ng-generate/e2e/index.ts new file mode 100644 index 000000000000..cc499f34bfa0 --- /dev/null +++ b/npm/cypress-schematic/src/schematics/ng-generate/e2e/index.ts @@ -0,0 +1,68 @@ +import { + Rule, Tree, SchematicsException, + apply, url, applyTemplates, move, + chain, mergeWith, +} from '@angular-devkit/schematics' + +import { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core' + +import { Schema } from './schema' + +function createSpec (tree: Tree): workspaces.WorkspaceHost { + return { + async readFile (path: string): Promise { + const data = tree.read(path) + + if (!data) { + throw new SchematicsException('File not found.') + } + + return virtualFs.fileBufferToString(data) + }, + async writeFile (path: string, data: string): Promise { + return tree.overwrite(path, data) + }, + async isDirectory (path: string): Promise { + return !tree.exists(path) && tree.getDir(path).subfiles.length > 0 + }, + async isFile (path: string): Promise { + return tree.exists(path) + }, + } +} + +export default function (options: Schema): Rule { + return async (tree: Tree) => { + const host = createSpec(tree) + const { workspace } = await workspaces.readWorkspace('/', host) + + if (!options.project) { + // @ts-ignore + options.project = workspace.extensions.defaultProject + } + + //@ts-ignore + const project = workspace.projects.get(options.project) + + if (!project) { + throw new SchematicsException(`Invalid project name: ${options.project}`) + } + + if (options.path === undefined) { + options.path = `${project.root}/cypress/integration` + } + + const templateSource = apply(url('../files/__path__'), [ + applyTemplates({ + classify: strings.classify, + dasherize: strings.dasherize, + name: options.name, + }), + move(normalize(options.path as string)), + ]) + + return chain([ + mergeWith(templateSource), + ]) + } +} diff --git a/npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.json b/npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.json new file mode 100644 index 000000000000..4087f806ba6a --- /dev/null +++ b/npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "cypress-schematics-e2e-spec", + "title": "Cypress E2E Spec Options Schema", + "type": "object", + "properties": { + "path": { + "type": "string", + "format": "path", + "description": "The path to create the component.", + "visible": false + }, + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + }, + "name": { + "type": "string", + "description": "The name of the e2e test.", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What is the name of the e2e test?" + } + }, + "required": [ + "name" + ] +} \ No newline at end of file diff --git a/npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.ts b/npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.ts new file mode 100644 index 000000000000..165f6641edab --- /dev/null +++ b/npm/cypress-schematic/src/schematics/ng-generate/e2e/schema.ts @@ -0,0 +1,10 @@ +export interface Schema { +// The name of the spec. +name: string + +// The path to create the spec. +path?: string + +// The name of the project. +project?: string +} diff --git a/npm/cypress-schematic/src/schematics/ng-generate/files/__path__/__name@dasherize__.spec.ts.template b/npm/cypress-schematic/src/schematics/ng-generate/files/__path__/__name@dasherize__.spec.ts.template new file mode 100644 index 000000000000..eb1de4106054 --- /dev/null +++ b/npm/cypress-schematic/src/schematics/ng-generate/files/__path__/__name@dasherize__.spec.ts.template @@ -0,0 +1,4 @@ +describe('<%= classify(name) %>', () => { + it('', () => { + }); +}); \ No newline at end of file diff --git a/npm/cypress-schematic/src/schematics/utility/dependencies.ts b/npm/cypress-schematic/src/schematics/utility/dependencies.ts index 011570304062..cf3d36f25e34 100644 --- a/npm/cypress-schematic/src/schematics/utility/dependencies.ts +++ b/npm/cypress-schematic/src/schematics/utility/dependencies.ts @@ -8,7 +8,7 @@ */ import { Tree } from '@angular-devkit/schematics' -import { JSONFile } from './json-file' +import { JSONFile } from './jsonFile' const PKG_JSON_PATH = '/package.json' diff --git a/npm/cypress-schematic/src/schematics/utility/index.ts b/npm/cypress-schematic/src/schematics/utility/index.ts index 314a0245adbe..a1ddc2baec32 100644 --- a/npm/cypress-schematic/src/schematics/utility/index.ts +++ b/npm/cypress-schematic/src/schematics/utility/index.ts @@ -4,22 +4,9 @@ import { get } from 'http' import { getPackageJsonDependency } from './dependencies' export interface NodePackage { - name: string - version: string - } - -export enum Paths { - AngularJson = './angular.json', - } - -export enum Configs { - JsonIndentLevel = 4, - } - -export interface CypressOptions { - project?: string - __version__: number - } + name: string + version: string +} export function getAngularVersion (tree: Tree): number { const packageNode = getPackageJsonDependency(tree, '@angular/core') diff --git a/npm/cypress-schematic/src/schematics/utility/json-file.ts b/npm/cypress-schematic/src/schematics/utility/jsonFile.ts similarity index 100% rename from npm/cypress-schematic/src/schematics/utility/json-file.ts rename to npm/cypress-schematic/src/schematics/utility/jsonFile.ts diff --git a/npm/cypress-schematic/tsconfig.json b/npm/cypress-schematic/tsconfig.json index a8b629f81c9e..e9f13bb37a57 100644 --- a/npm/cypress-schematic/tsconfig.json +++ b/npm/cypress-schematic/tsconfig.json @@ -27,5 +27,5 @@ "include": [ "src/**/*" ], - "exclude": ["src/**/files/**/*", "src/**/*_spec.ts"] + "exclude": ["src/**/files/**/*", "src/**/*.spec.ts"] } diff --git a/npm/cypress-schematic/tsconfig.spec.json b/npm/cypress-schematic/tsconfig.spec.json index 25e86e29d255..39bec09bcbf4 100644 --- a/npm/cypress-schematic/tsconfig.spec.json +++ b/npm/cypress-schematic/tsconfig.spec.json @@ -6,7 +6,7 @@ ] }, "include": [ - "src/**/*_spec.ts" + "src/**/*.spec.ts" ], "exclude": [ "src/**/files/**/*"