From 6e325335a9cffecc0c95e9ba1a4971628e7a7c33 Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Wed, 19 Jul 2023 13:08:21 -0400 Subject: [PATCH] feat(react): add playwright to e2eTestRunner option --- .../react/generators/application.json | 3 +- .../packages/react/generators/init.json | 2 +- packages/react/.eslintrc.json | 1 + .../application/application.spec.ts | 60 ++++++++++++++++-- .../src/generators/application/application.ts | 8 +-- .../generators/application/lib/add-cypress.ts | 28 --------- .../src/generators/application/lib/add-e2e.ts | 63 +++++++++++++++++++ .../application/lib/normalize-options.ts | 5 ++ .../src/generators/application/schema.d.ts | 3 +- .../src/generators/application/schema.json | 3 +- .../react/src/generators/init/schema.d.ts | 2 +- .../react/src/generators/init/schema.json | 2 +- 12 files changed, 138 insertions(+), 42 deletions(-) delete mode 100644 packages/react/src/generators/application/lib/add-cypress.ts create mode 100644 packages/react/src/generators/application/lib/add-e2e.ts diff --git a/docs/generated/packages/react/generators/application.json b/docs/generated/packages/react/generators/application.json index 58aae1b3fe8a2..d705ea6308d9e 100644 --- a/docs/generated/packages/react/generators/application.json +++ b/docs/generated/packages/react/generators/application.json @@ -111,8 +111,9 @@ }, "e2eTestRunner": { "type": "string", - "enum": ["cypress", "none"], + "enum": ["cypress", "playwright", "none"], "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "tags": { diff --git a/docs/generated/packages/react/generators/init.json b/docs/generated/packages/react/generators/init.json index 9c4e5d9f3f698..63380111587a4 100644 --- a/docs/generated/packages/react/generators/init.json +++ b/docs/generated/packages/react/generators/init.json @@ -18,7 +18,7 @@ "e2eTestRunner": { "description": "Adds the specified E2E test runner.", "type": "string", - "enum": ["cypress", "none"], + "enum": ["cypress", "playwright", "none"], "default": "cypress" }, "skipFormat": { diff --git a/packages/react/.eslintrc.json b/packages/react/.eslintrc.json index 03611d9778d6c..8c2a56fa639fa 100644 --- a/packages/react/.eslintrc.json +++ b/packages/react/.eslintrc.json @@ -47,6 +47,7 @@ "react", // These are installed by ensurePackage so missing in package.json "@nx/cypress", + "@nx/playwright", "@nx/jest", "@nx/rollup", "@nx/storybook", diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index d51711c0a48ea..00d21866a5226 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -267,6 +267,24 @@ describe('app', () => { }, ].forEach(hasJsonValue); }); + + it('should setup playwright', async () => { + await applicationGenerator(appTree, { + ...schema, + directory: 'myDir', + e2eTestRunner: 'playwright', + }); + + expect( + appTree.exists('apps/my-dir/my-app-e2e/playwright.config.ts') + ).toBeTruthy(); + expect( + appTree.exists('apps/my-dir/my-app-e2e/src/example.spec.ts') + ).toBeTruthy(); + expect( + readProjectConfiguration(appTree, 'my-app-e2e')?.targets?.e2e?.executor + ).toEqual('@nx/playwright:playwright'); + }); }); it('should create Nx specific template', async () => { @@ -332,7 +350,7 @@ describe('app', () => { ); }); - it('should setup the nrwl web build builder', async () => { + it('should setup the nx web build builder', async () => { await applicationGenerator(appTree, { ...schema, name: 'my-app', @@ -372,7 +390,7 @@ describe('app', () => { }); }); - it('should setup the nrwl vite builder if bundler is vite', async () => { + it('should setup the nx vite builder if bundler is vite', async () => { await applicationGenerator(appTree, { ...schema, name: 'my-app', @@ -394,7 +412,7 @@ describe('app', () => { ).toBeFalsy(); }); - it('should setup the nrwl web dev server builder', async () => { + it('should setup the nx web dev server builder', async () => { await applicationGenerator(appTree, { ...schema, name: 'my-app', @@ -414,7 +432,7 @@ describe('app', () => { }); }); - it('should setup the nrwl vite dev server builder if bundler is vite', async () => { + it('should setup the nx vite dev server builder if bundler is vite', async () => { await applicationGenerator(appTree, { ...schema, name: 'my-app', @@ -486,6 +504,25 @@ describe('app', () => { }); }); + describe('--e2e-test-runner playwright', () => { + it('should setup playwright', async () => { + await applicationGenerator(appTree, { + ...schema, + e2eTestRunner: 'playwright', + }); + + expect( + appTree.exists('apps/my-app-e2e/playwright.config.ts') + ).toBeTruthy(); + expect( + appTree.exists('apps/my-app-e2e/src/example.spec.ts') + ).toBeTruthy(); + expect( + readProjectConfiguration(appTree, 'my-app-e2e')?.targets?.e2e?.executor + ).toEqual('@nx/playwright:playwright'); + }); + }); + describe('--pascalCaseFiles', () => { it('should use upper case app file', async () => { await applicationGenerator(appTree, { ...schema, pascalCaseFiles: true }); @@ -950,6 +987,21 @@ describe('app', () => { ] ).toEqual('dist/my-app2'); }); + + it('should setup playwright', async () => { + await applicationGenerator(appTree, { + ...schema, + name: 'my-app3', + rootProject: true, + e2eTestRunner: 'playwright', + }); + + expect(appTree.exists('e2e/playwright.config.ts')).toBeTruthy(); + expect(appTree.exists('e2e/src/example.spec.ts')).toBeTruthy(); + expect( + readProjectConfiguration(appTree, 'e2e')?.targets?.e2e?.executor + ).toEqual('@nx/playwright:playwright'); + }); }); describe('setup React app with --bundler=vite', () => { diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index 7086e4720084c..8192e55971e1f 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -7,7 +7,6 @@ import { createApplicationFiles } from './lib/create-application-files'; import { updateSpecConfig } from './lib/update-jest-config'; import { normalizeOptions } from './lib/normalize-options'; import { addProject, maybeJs } from './lib/add-project'; -import { addCypress } from './lib/add-cypress'; import { addJest } from './lib/add-jest'; import { addRouting } from './lib/add-routing'; import { setDefaults } from './lib/set-defaults'; @@ -39,6 +38,7 @@ import { extractTsConfigBase } from '../../utils/create-ts-config'; import { addSwcDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies'; import * as chalk from 'chalk'; import { showPossibleWarnings } from './lib/show-possible-warnings'; +import { addE2e } from './lib/add-e2e'; async function addLinting(host: Tree, options: NormalizedSchema) { const tasks: GeneratorCallback[] = []; @@ -70,7 +70,7 @@ async function addLinting(host: Tree, options: NormalizedSchema) { ); if (!options.skipPackageJson) { - const installTask = await addDependenciesToPackageJson( + const installTask = addDependenciesToPackageJson( host, extraEslintDependencies.dependencies, extraEslintDependencies.devDependencies @@ -189,8 +189,8 @@ export async function applicationGenerator( const lintTask = await addLinting(host, options); tasks.push(lintTask); - const cypressTask = await addCypress(host, options); - tasks.push(cypressTask); + const e2eTask = await addE2e(host, options); + tasks.push(e2eTask); if (options.unitTestRunner === 'jest') { const jestTask = await addJest(host, options); diff --git a/packages/react/src/generators/application/lib/add-cypress.ts b/packages/react/src/generators/application/lib/add-cypress.ts deleted file mode 100644 index cb65f18050c6e..0000000000000 --- a/packages/react/src/generators/application/lib/add-cypress.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ensurePackage, Tree } from '@nx/devkit'; -import { webStaticServeGenerator } from '@nx/web'; -import { nxVersion } from '../../../utils/versions'; -import { NormalizedSchema } from '../schema'; - -export async function addCypress(host: Tree, options: NormalizedSchema) { - if (options.e2eTestRunner !== 'cypress') { - return () => {}; - } - - await webStaticServeGenerator(host, { - buildTarget: `${options.projectName}:build`, - targetName: 'serve-static', - }); - - const { cypressProjectGenerator } = ensurePackage< - typeof import('@nx/cypress') - >('@nx/cypress', nxVersion); - - return await cypressProjectGenerator(host, { - ...options, - name: options.e2eProjectName, - directory: options.directory, - project: options.projectName, - bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler, - skipFormat: true, - }); -} diff --git a/packages/react/src/generators/application/lib/add-e2e.ts b/packages/react/src/generators/application/lib/add-e2e.ts new file mode 100644 index 0000000000000..bd551cb9d29e2 --- /dev/null +++ b/packages/react/src/generators/application/lib/add-e2e.ts @@ -0,0 +1,63 @@ +import type { GeneratorCallback, Tree } from '@nx/devkit'; +import { + addProjectConfiguration, + ensurePackage, + getPackageManagerCommand, + joinPathFragments, + readProjectConfiguration, +} from '@nx/devkit'; +import { webStaticServeGenerator } from '@nx/web'; + +import { nxVersion } from '../../../utils/versions'; +import { NormalizedSchema } from '../schema'; + +export async function addE2e( + tree: Tree, + options: NormalizedSchema +): Promise { + switch (options.e2eTestRunner) { + case 'cypress': + webStaticServeGenerator(tree, { + buildTarget: `${options.projectName}:build`, + targetName: 'serve-static', + }); + + const { cypressProjectGenerator } = ensurePackage< + typeof import('@nx/cypress') + >('@nx/cypress', nxVersion); + + return await cypressProjectGenerator(tree, { + ...options, + name: options.e2eProjectName, + directory: options.directory, + project: options.projectName, + bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler, + skipFormat: true, + }); + case 'playwright': + const { configurationGenerator } = ensurePackage< + typeof import('@nx/playwright') + >('@nx/playwright', nxVersion); + addProjectConfiguration(tree, options.e2eProjectName, { + root: options.e2eProjectRoot, + sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), + targets: {}, + implicitDependencies: [options.projectName], + }); + return configurationGenerator(tree, { + project: options.e2eProjectName, + skipFormat: true, + skipPackageJson: options.skipPackageJson, + directory: 'src', + js: false, + linter: options.linter, + setParserOptionsProject: options.setParserOptionsProject, + webServerCommand: `${getPackageManagerCommand().exec} nx serve ${ + options.name + }`, + }); + case 'none': + default: + return () => {}; + } +} diff --git a/packages/react/src/generators/application/lib/normalize-options.ts b/packages/react/src/generators/application/lib/normalize-options.ts index 461bf3a0b182b..7ab1ef94542b4 100644 --- a/packages/react/src/generators/application/lib/normalize-options.ts +++ b/packages/react/src/generators/application/lib/normalize-options.ts @@ -3,6 +3,7 @@ import { assertValidStyle } from '../../../utils/assertion'; import { extractLayoutDirectory, getWorkspaceLayout, + joinPathFragments, names, normalizePath, Tree, @@ -36,6 +37,9 @@ export function normalizeOptions( const appProjectRoot = options.rootProject ? '.' : normalizePath(`${appsDir}/${appDirectory}`); + const e2eProjectRoot = options.rootProject + ? 'e2e' + : joinPathFragments(appsDir, `${appDirectory}-e2e`); const parsedTags = options.tags ? options.tags.split(',').map((s) => s.trim()) @@ -59,6 +63,7 @@ export function normalizeOptions( projectName: appProjectName, appProjectRoot, e2eProjectName, + e2eProjectRoot, parsedTags, fileName, styledModule, diff --git a/packages/react/src/generators/application/schema.d.ts b/packages/react/src/generators/application/schema.d.ts index 7b8ae279ddbac..a742bcf57a190 100644 --- a/packages/react/src/generators/application/schema.d.ts +++ b/packages/react/src/generators/application/schema.d.ts @@ -9,7 +9,7 @@ export interface Schema { tags?: string; unitTestRunner?: 'jest' | 'vitest' | 'none'; inSourceTests?: boolean; - e2eTestRunner: 'cypress' | 'none'; + e2eTestRunner: 'cypress' | 'playwright' | 'none'; linter: Linter; pascalCaseFiles?: boolean; classComponent?: boolean; @@ -32,6 +32,7 @@ export interface NormalizedSchema extends T { projectName: string; appProjectRoot: string; e2eProjectName: string; + e2eProjectRoot: string; parsedTags: string[]; fileName: string; styledModule: null | SupportedStyles; diff --git a/packages/react/src/generators/application/schema.json b/packages/react/src/generators/application/schema.json index f0dc9d5d15219..78e8a732a5a75 100644 --- a/packages/react/src/generators/application/schema.json +++ b/packages/react/src/generators/application/schema.json @@ -117,8 +117,9 @@ }, "e2eTestRunner": { "type": "string", - "enum": ["cypress", "none"], + "enum": ["cypress", "playwright", "none"], "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "tags": { diff --git a/packages/react/src/generators/init/schema.d.ts b/packages/react/src/generators/init/schema.d.ts index 9892a322415a9..7146fd7a3610e 100644 --- a/packages/react/src/generators/init/schema.d.ts +++ b/packages/react/src/generators/init/schema.d.ts @@ -1,6 +1,6 @@ export interface InitSchema { unitTestRunner?: 'jest' | 'vitest' | 'none'; - e2eTestRunner?: 'cypress' | 'none'; + e2eTestRunner?: 'cypress' | 'playwright' | 'none'; skipFormat?: boolean; skipPackageJson?: boolean; skipHelperLibs?: boolean; diff --git a/packages/react/src/generators/init/schema.json b/packages/react/src/generators/init/schema.json index 78b86fa7791cd..56e9c902989ba 100644 --- a/packages/react/src/generators/init/schema.json +++ b/packages/react/src/generators/init/schema.json @@ -15,7 +15,7 @@ "e2eTestRunner": { "description": "Adds the specified E2E test runner.", "type": "string", - "enum": ["cypress", "none"], + "enum": ["cypress", "playwright", "none"], "default": "cypress" }, "skipFormat": {