From 48210eb60f76cd8418c38152c91767a6d9580953 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 31 Jul 2024 16:13:48 +0100 Subject: [PATCH 01/19] fix(vite): preview should dependOn build --- .../vite/src/plugins/__snapshots__/plugin.spec.ts.snap | 6 ++++++ packages/vite/src/plugins/plugin.ts | 8 ++++++-- packages/vite/src/utils/generator-utils.ts | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/plugins/__snapshots__/plugin.spec.ts.snap b/packages/vite/src/plugins/__snapshots__/plugin.spec.ts.snap index 1b75426da5b23..1a419719c9407 100644 --- a/packages/vite/src/plugins/__snapshots__/plugin.spec.ts.snap +++ b/packages/vite/src/plugins/__snapshots__/plugin.spec.ts.snap @@ -126,6 +126,9 @@ exports[`@nx/vite/plugin not root project should create nodes 1`] = ` }, "preview-site": { "command": "vite preview", + "dependsOn": [ + "build-something", + ], "metadata": { "description": "Locally preview Vite production build", "help": { @@ -209,6 +212,9 @@ exports[`@nx/vite/plugin root project should create nodes 1`] = ` }, "preview": { "command": "vite preview", + "dependsOn": [ + "build", + ], "metadata": { "description": "Locally preview Vite production build", "help": { diff --git a/packages/vite/src/plugins/plugin.ts b/packages/vite/src/plugins/plugin.ts index b2b4a888c72eb..2575e392c1625 100644 --- a/packages/vite/src/plugins/plugin.ts +++ b/packages/vite/src/plugins/plugin.ts @@ -193,7 +193,10 @@ async function buildViteTargets( // If running in library mode, then there is nothing to serve. if (!viteConfig.build?.lib) { targets[options.serveTargetName] = serveTarget(projectRoot); - targets[options.previewTargetName] = previewTarget(projectRoot); + targets[options.previewTargetName] = previewTarget( + projectRoot, + options.buildTargetName + ); targets[options.serveStaticTargetName] = serveStaticTarget(options) as {}; } } @@ -272,9 +275,10 @@ function serveTarget(projectRoot: string) { return targetConfig; } -function previewTarget(projectRoot: string) { +function previewTarget(projectRoot: string, buildTargetName) { const targetConfig: TargetConfiguration = { command: `vite preview`, + dependsOn: [buildTargetName], options: { cwd: joinPathFragments(projectRoot), }, diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index 0bf85413acb3c..0b7c2790f155c 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -204,6 +204,7 @@ export function addPreviewTarget( // Adds a preview target. project.targets.preview = { + dependsOn: ['build'], executor: '@nx/vite:preview-server', defaultConfiguration: 'development', options: previewOptions, From 3a5041e45662aea98b84d2a6f0e36d6e87db3148 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 31 Jul 2024 16:25:44 +0100 Subject: [PATCH 02/19] fix(react): playwright should use vite preview --- .../__snapshots__/application.spec.ts.snap | 181 ++++++++++++++++++ .../application/application.spec.ts | 72 +++++++ .../src/generators/application/lib/add-e2e.ts | 6 +- .../application/lib/normalize-options.ts | 20 +- 4 files changed, 271 insertions(+), 8 deletions(-) diff --git a/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap index f90484aed48f0..b1b917fc9a9e8 100644 --- a/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/react/src/generators/application/__snapshots__/application.spec.ts.snap @@ -210,6 +210,60 @@ nxViteTsPaths()], });" `; +exports[`app not nested should add vite types to tsconfigs 1`] = ` +" + /// + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default defineConfig({ + root: __dirname, + cacheDir: '../node_modules/.vite/my-app', + + server:{ + port: 4200, + host: 'localhost', + }, + + preview:{ + port: 4300, + host: 'localhost', + }, + + plugins: [react(), +nxViteTsPaths()], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + + build: { + outDir: '../dist/my-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + + + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + reporters: ['default'], + coverage: { + reportsDirectory: '../coverage/my-app', + provider: 'v8', + } + }, + });" +`; + exports[`app not nested should generate files 1`] = ` "// eslint-disable-next-line @typescript-eslint/no-unused-vars import styles from './app.module.css'; @@ -228,6 +282,133 @@ export default App; " `; +exports[`app not nested should setup playwright correctly for vite 1`] = ` +"import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; + +import { workspaceRoot } from '@nx/devkit'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4300'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npx nx run my-app:preview', + url: 'http://localhost:4300', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); +" +`; + +exports[`app not nested should use preview vite types to tsconfigs 1`] = ` +" + /// + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + + export default defineConfig({ + root: __dirname, + cacheDir: '../node_modules/.vite/my-app', + + server:{ + port: 4200, + host: 'localhost', + }, + + preview:{ + port: 4300, + host: 'localhost', + }, + + plugins: [react(), +nxViteTsPaths()], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + + build: { + outDir: '../dist/my-app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + + + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + reporters: ['default'], + coverage: { + reportsDirectory: '../coverage/my-app', + provider: 'v8', + } + }, + });" +`; + exports[`app setup React app with --bundler=vite should setup targets with vite configuration 1`] = `null`; exports[`app should add custom webpack config 1`] = ` diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index bd72ff56d2994..479f1272e7190 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -69,6 +69,78 @@ describe('app', () => { '@nx/react/typings/cssmodule.d.ts', '@nx/react/typings/image.d.ts', ]); + expect(appTree.read('my-app/vite.config.ts', 'utf-8')).toMatchSnapshot(); + }); + + it('should setup cypress correctly for vite', async () => { + await applicationGenerator(appTree, { + ...schema, + bundler: 'vite', + unitTestRunner: 'vitest', + addPlugin: true, + }); + expect(appTree.read('my-app-e2e/cypress.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; + + import { defineConfig } from 'cypress'; + + export default defineConfig({ + e2e: { ...nxE2EPreset(__filename, {"cypressDir":"src","bundler":"vite","webServerCommands":{"default":"nx run my-app:serve","production":"nx run my-app:preview"},"ciWebServerCommand":"nx run my-app:serve-static"}), + baseUrl: 'http://localhost:4200' } + }); + " + `); + }); + + it('should setup playwright correctly for vite', async () => { + const nxJson = readNxJson(appTree); + nxJson.plugins ??= []; + nxJson.plugins.push({ + plugin: '@nx/vite/plugin', + options: { + buildTargetName: 'build', + previewTargetName: 'preview', + }, + }); + updateNxJson(appTree, nxJson); + + await applicationGenerator(appTree, { + ...schema, + bundler: 'vite', + unitTestRunner: 'vitest', + e2eTestRunner: 'playwright', + addPlugin: true, + }); + expect( + appTree.read('my-app-e2e/playwright.config.ts', 'utf-8') + ).toMatchSnapshot(); + }); + + it('should use preview vite types to tsconfigs', async () => { + await applicationGenerator(appTree, { + ...schema, + bundler: 'vite', + unitTestRunner: 'vitest', + }); + const tsconfigApp = readJson(appTree, 'my-app/tsconfig.app.json'); + expect(tsconfigApp.compilerOptions.types).toEqual([ + 'node', + '@nx/react/typings/cssmodule.d.ts', + '@nx/react/typings/image.d.ts', + 'vite/client', + ]); + const tsconfigSpec = readJson(appTree, 'my-app/tsconfig.spec.json'); + expect(tsconfigSpec.compilerOptions.types).toEqual([ + 'vitest/globals', + 'vitest/importMeta', + 'vite/client', + 'node', + 'vitest', + '@nx/react/typings/cssmodule.d.ts', + '@nx/react/typings/image.d.ts', + ]); + expect(appTree.read('my-app/vite.config.ts', 'utf-8')).toMatchSnapshot(); }); it('should not overwrite default project if already set', async () => { diff --git a/packages/react/src/generators/application/lib/add-e2e.ts b/packages/react/src/generators/application/lib/add-e2e.ts index 2602b12ff1e4f..447da4e206780 100644 --- a/packages/react/src/generators/application/lib/add-e2e.ts +++ b/packages/react/src/generators/application/lib/add-e2e.ts @@ -83,9 +83,9 @@ export async function addE2e( js: false, linter: options.linter, setParserOptionsProject: options.setParserOptionsProject, - webServerCommand: `${getPackageManagerCommand().exec} nx ${ - options.e2eWebServerTarget - } ${options.name}`, + webServerCommand: `${getPackageManagerCommand().exec} nx run ${ + options.projectName + }:${options.e2eWebServerTarget}`, webServerAddress: options.e2eWebServerAddress, rootProject: options.rootProject, addPlugin: options.addPlugin, diff --git a/packages/react/src/generators/application/lib/normalize-options.ts b/packages/react/src/generators/application/lib/normalize-options.ts index 136f50b820dd5..6f7badebcbce4 100644 --- a/packages/react/src/generators/application/lib/normalize-options.ts +++ b/packages/react/src/generators/application/lib/normalize-options.ts @@ -46,6 +46,8 @@ export async function normalizeOptions( options.rootProject = appProjectRoot === '.'; options.projectNameAndRootFormat = projectNameAndRootFormat; + let e2ePort = options.devServerPort ?? 4200; + let e2eWebServerTarget = 'serve'; if (options.addPlugin) { if (nxJson.plugins) { @@ -53,11 +55,20 @@ export async function normalizeOptions( if ( options.bundler === 'vite' && typeof plugin === 'object' && - plugin.plugin === '@nx/vite/plugin' && - (plugin.options as VitePluginOptions).serveTargetName + plugin.plugin === '@nx/vite/plugin' ) { - e2eWebServerTarget = (plugin.options as VitePluginOptions) - .serveTargetName; + e2eWebServerTarget = + options.e2eTestRunner === 'playwright' + ? (plugin.options as VitePluginOptions)?.previewTargetName ?? + 'preview' + : (plugin.options as VitePluginOptions)?.serveTargetName ?? + 'serve'; + e2ePort = + e2eWebServerTarget === + (plugin.options as VitePluginOptions)?.previewTargetName || + e2eWebServerTarget === 'preview' + ? 4300 + : e2ePort; } else if ( options.bundler === 'webpack' && typeof plugin === 'object' && @@ -71,7 +82,6 @@ export async function normalizeOptions( } } - let e2ePort = options.devServerPort ?? 4200; if ( nxJson.targetDefaults?.[e2eWebServerTarget] && nxJson.targetDefaults?.[e2eWebServerTarget].options?.port From 62b9d9fa3cc441f16fb35b288c305c7ca6d86e54 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 31 Jul 2024 16:52:58 +0100 Subject: [PATCH 03/19] fix(vue): playwright should use vite preview --- .../__snapshots__/application.spec.ts.snap | 73 +++++++++++++++++++ .../application/application.spec.ts | 26 ++++++- .../src/generators/application/lib/add-e2e.ts | 8 +- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap index a5f26bbb1e3a0..21aa11e4acc4e 100644 --- a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap @@ -290,6 +290,79 @@ describe('App', () => { `; exports[`application generator should set up project correctly with given options 5`] = ` +"import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; + +import { workspaceRoot } from '@nx/devkit'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4300'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npx nx run test:preview', + url: 'http://localhost:4300', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); +" +`; + +exports[`application generator should set up project correctly with given options 6`] = ` [ ".eslintignore", ".eslintrc.json", diff --git a/packages/vue/src/generators/application/application.spec.ts b/packages/vue/src/generators/application/application.spec.ts index 3fbbccfaaaab1..2cea477717649 100644 --- a/packages/vue/src/generators/application/application.spec.ts +++ b/packages/vue/src/generators/application/application.spec.ts @@ -1,7 +1,12 @@ import 'nx/src/internal-testing-utils/mock-project-graph'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { Tree, readProjectConfiguration } from '@nx/devkit'; +import { + Tree, + readProjectConfiguration, + readNxJson, + updateNxJson, +} from '@nx/devkit'; import { applicationGenerator } from './application'; import { Schema } from './schema'; @@ -21,11 +26,28 @@ describe('application generator', () => { }); it('should set up project correctly with given options', async () => { - await applicationGenerator(tree, { ...options, unitTestRunner: 'vitest' }); + const nxJson = readNxJson(tree); + nxJson.plugins ??= []; + nxJson.plugins.push({ + plugin: '@nx/vite/plugin', + options: { + buildTargetName: 'build', + previewTargetName: 'preview', + }, + }); + updateNxJson(tree, nxJson); + await applicationGenerator(tree, { + ...options, + unitTestRunner: 'vitest', + e2eTestRunner: 'playwright', + }); expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot(); expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchSnapshot(); expect(tree.read('test/.eslintrc.json', 'utf-8')).toMatchSnapshot(); expect(tree.read('test/src/app/App.spec.ts', 'utf-8')).toMatchSnapshot(); + expect( + tree.read('test-e2e/playwright.config.ts', 'utf-8') + ).toMatchSnapshot(); expect(listFiles(tree)).toMatchSnapshot(); }); diff --git a/packages/vue/src/generators/application/lib/add-e2e.ts b/packages/vue/src/generators/application/lib/add-e2e.ts index d71160c15c380..d38a1ca77c089 100644 --- a/packages/vue/src/generators/application/lib/add-e2e.ts +++ b/packages/vue/src/generators/application/lib/add-e2e.ts @@ -73,10 +73,10 @@ export async function addE2e( js: false, linter: options.linter, setParserOptionsProject: options.setParserOptionsProject, - webServerCommand: `${getPackageManagerCommand().exec} nx serve ${ - options.name - }`, - webServerAddress: 'http://localhost:4200', + webServerCommand: `${getPackageManagerCommand().exec} nx run ${ + options.projectName + }:preview`, + webServerAddress: 'http://localhost:4300', }); } case 'none': From cc9c2ad4cc0eeb8ad3f7cfe47b227ce166e66acc Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 31 Jul 2024 16:53:11 +0100 Subject: [PATCH 04/19] fix(web): playwright should use vite preview --- .../__snapshots__/application.spec.ts.snap | 146 ++++++++++++++++++ .../application/application.legacy.spec.ts | 3 + .../application/application.spec.ts | 35 ++++- .../src/generators/application/application.ts | 23 ++- 4 files changed, 198 insertions(+), 9 deletions(-) diff --git a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap index b21d49dd87823..95ca429da2c02 100644 --- a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap @@ -1,5 +1,151 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`app not nested should generate files if bundler is vite 1`] = ` +"import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; + +import { workspaceRoot } from '@nx/devkit'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4300'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npx nx my-app:preview', + url: 'http://localhost:4300', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); +" +`; + +exports[`app not nested should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false 1`] = ` +"import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; + +import { workspaceRoot } from '@nx/devkit'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4200'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npx nx my-app:serve', + url: 'http://localhost:4200', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); +" +`; + exports[`app setup web app with --bundler=vite should setup vite configuration 1`] = `null`; exports[`app should setup eslint 1`] = ` diff --git a/packages/web/src/generators/application/application.legacy.spec.ts b/packages/web/src/generators/application/application.legacy.spec.ts index 7da4e436afc3c..a541524603e59 100644 --- a/packages/web/src/generators/application/application.legacy.spec.ts +++ b/packages/web/src/generators/application/application.legacy.spec.ts @@ -179,6 +179,9 @@ describe('web app generator (legacy)', () => { }, }, "defaultConfiguration": "development", + "dependsOn": [ + "build", + ], "executor": "@nx/vite:preview-server", "options": { "buildTarget": "my-vite-app:build", diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 7c46f607846b0..52c4eff79fa99 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -1,7 +1,12 @@ import 'nx/src/internal-testing-utils/mock-project-graph'; import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version'; -import { readProjectConfiguration, Tree } from '@nx/devkit'; +import { + readNxJson, + readProjectConfiguration, + Tree, + updateNxJson, +} from '@nx/devkit'; import { getProjects, readJson } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; @@ -159,10 +164,22 @@ describe('app', () => { }); it('should generate files if bundler is vite', async () => { + const nxJson = readNxJson(tree); + nxJson.plugins ??= []; + nxJson.plugins.push({ + plugin: '@nx/vite/plugin', + options: { + buildTargetName: 'build', + previewTargetName: 'preview', + }, + }); + updateNxJson(tree, nxJson); await applicationGenerator(tree, { name: 'my-app', bundler: 'vite', projectNameAndRootFormat: 'as-provided', + e2eTestRunner: 'playwright', + addPlugin: true, }); expect(tree.exists('my-app/src/main.ts')).toBeTruthy(); expect(tree.exists('my-app/src/app/app.element.ts')).toBeTruthy(); @@ -179,7 +196,9 @@ describe('app', () => { path: './tsconfig.spec.json', }, ]); - expect(tree.exists('my-app-e2e/playwright.config.ts')).toBeTruthy(); + expect( + tree.read('my-app-e2e/playwright.config.ts', 'utf-8') + ).toMatchSnapshot(); expect(tree.exists('my-app/index.html')).toBeTruthy(); expect(tree.exists('my-app/vite.config.ts')).toBeTruthy(); expect(tree.exists(`my-app/environments/environment.ts`)).toBeFalsy(); @@ -188,6 +207,18 @@ describe('app', () => { ).toBeFalsy(); }); + it('should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false', async () => { + await applicationGenerator(tree, { + name: 'my-app', + bundler: 'vite', + projectNameAndRootFormat: 'as-provided', + e2eTestRunner: 'playwright', + }); + expect( + tree.read('my-app-e2e/playwright.config.ts', 'utf-8') + ).toMatchSnapshot(); + }); + it('should extend from root tsconfig.json when no tsconfig.base.json', async () => { tree.rename('tsconfig.base.json', 'tsconfig.json'); diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 8547f4c899d64..fbc361454a9da 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -406,8 +406,8 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { linter: options.linter, setParserOptionsProject: options.setParserOptionsProject, webServerCommand: `${getPackageManagerCommand().exec} nx ${ - options.e2eWebServerTarget - } ${options.name}`, + options.projectName + }:${options.e2eWebServerTarget}`, webServerAddress: options.e2eWebServerAddress, addPlugin: options.addPlugin, }); @@ -493,6 +493,7 @@ async function normalizeOptions( nxJson.useInferencePlugins !== false; options.addPlugin ??= addPluginDefault; + let e2ePort = 4200; let e2eWebServerTarget = 'serve'; if (options.addPlugin) { if (nxJson.plugins) { @@ -500,11 +501,20 @@ async function normalizeOptions( if ( options.bundler === 'vite' && typeof plugin === 'object' && - plugin.plugin === '@nx/vite/plugin' && - (plugin.options as VitePluginOptions).serveTargetName + plugin.plugin === '@nx/vite/plugin' ) { - e2eWebServerTarget = (plugin.options as VitePluginOptions) - .serveTargetName; + e2eWebServerTarget = + options.e2eTestRunner === 'playwright' + ? (plugin.options as VitePluginOptions).previewTargetName ?? + 'preview' + : (plugin.options as VitePluginOptions).serveTargetName ?? + 'serve'; + e2ePort = + e2eWebServerTarget === + (plugin.options as VitePluginOptions)?.previewTargetName || + e2eWebServerTarget === 'preview' + ? 4300 + : e2ePort; } else if ( options.bundler === 'webpack' && typeof plugin === 'object' && @@ -518,7 +528,6 @@ async function normalizeOptions( } } - let e2ePort = 4200; if ( nxJson.targetDefaults?.[e2eWebServerTarget] && nxJson.targetDefaults?.[e2eWebServerTarget].options?.port From 4e0a785f039196a85b0ab212dafba1c9fc2bf4fb Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Wed, 31 Jul 2024 16:59:30 +0100 Subject: [PATCH 05/19] chore(testing): add e2e test --- e2e/react/src/playwright.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/react/src/playwright.test.ts b/e2e/react/src/playwright.test.ts index 4b15d606213d0..9bd7894c96664 100644 --- a/e2e/react/src/playwright.test.ts +++ b/e2e/react/src/playwright.test.ts @@ -18,7 +18,7 @@ describe('React Playwright e2e tests', () => { packages: ['@nx/react'], }); runCLI( - `generate @nx/react:app ${appName} --e2eTestRunner=playwright --projectNameAndRootFormat=as-provided --no-interactive` + `generate @nx/react:app ${appName} --e2eTestRunner=playwright --bundler=vite --projectNameAndRootFormat=as-provided --no-interactive` ); }); From d5387643c9d1bd89668f4672a2d6b769b9ef378e Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 10:51:34 +0100 Subject: [PATCH 06/19] fix(web): static-serve configuration should add dependsOn --- .../static-serve-configuration.spec.ts | 15 +++++++++++++++ .../static-serve/static-serve-configuration.ts | 3 +++ 2 files changed, 18 insertions(+) diff --git a/packages/web/src/generators/static-serve/static-serve-configuration.spec.ts b/packages/web/src/generators/static-serve/static-serve-configuration.spec.ts index f703a50e48f23..469796aec1027 100644 --- a/packages/web/src/generators/static-serve/static-serve-configuration.spec.ts +++ b/packages/web/src/generators/static-serve/static-serve-configuration.spec.ts @@ -25,6 +25,9 @@ describe('Static serve configuration generator', () => { expect(readProjectConfiguration(tree, 'react-app').targets['serve-static']) .toMatchInlineSnapshot(` { + "dependsOn": [ + "build", + ], "executor": "@nx/web:file-server", "options": { "buildTarget": "react-app:build", @@ -40,6 +43,9 @@ describe('Static serve configuration generator', () => { readProjectConfiguration(tree, 'angular-app').targets['serve-static'] ).toMatchInlineSnapshot(` { + "dependsOn": [ + "build", + ], "executor": "@nx/web:file-server", "options": { "buildTarget": "angular-app:build", @@ -54,6 +60,9 @@ describe('Static serve configuration generator', () => { expect(readProjectConfiguration(tree, 'storybook').targets['serve-static']) .toMatchInlineSnapshot(` { + "dependsOn": [ + "build-storybook", + ], "executor": "@nx/web:file-server", "options": { "buildTarget": "storybook:build-storybook", @@ -75,6 +84,9 @@ describe('Static serve configuration generator', () => { readProjectConfiguration(tree, 'react-app').targets['serve-static-custom'] ).toMatchInlineSnapshot(` { + "dependsOn": [ + "build", + ], "executor": "@nx/web:file-server", "options": { "buildTarget": "react-app:build", @@ -101,6 +113,9 @@ describe('Static serve configuration generator', () => { readProjectConfiguration(tree, 'angular-app').targets['serve-static'] ).toMatchInlineSnapshot(` { + "dependsOn": [ + "build", + ], "executor": "@nx/web:file-server", "options": { "buildTarget": "angular-app:build", diff --git a/packages/web/src/generators/static-serve/static-serve-configuration.ts b/packages/web/src/generators/static-serve/static-serve-configuration.ts index 1fbf247a174ca..233d59cc8bc9a 100644 --- a/packages/web/src/generators/static-serve/static-serve-configuration.ts +++ b/packages/web/src/generators/static-serve/static-serve-configuration.ts @@ -23,6 +23,7 @@ interface NormalizedWebStaticServeSchema extends WebStaticServeSchema { projectName: string; targetName: string; spa: boolean; + parsedBuildTarget: string; } export async function webStaticServeGenerator( @@ -49,6 +50,7 @@ async function normalizeOptions( targetName: options.targetName || 'serve-static', projectName: target.project, spa: options.spa ?? true, + parsedBuildTarget: target.target, }; const projectConfig = readProjectConfiguration(tree, target.project); @@ -109,6 +111,7 @@ function addStaticConfig(tree: Tree, opts: NormalizedWebStaticServeSchema) { Partial > = { executor: '@nx/web:file-server', + dependsOn: [opts.parsedBuildTarget], options: { buildTarget: opts.buildTarget, staticFilePath: opts.outputPath, From 735bdc33cf11168aceb81c5e9ab07c688495817e Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 11:22:38 +0100 Subject: [PATCH 07/19] feat(testing): add ciBaseUrl for configuring a different url for ciWebServerCommand --- packages/cypress/plugins/cypress-preset.ts | 6 ++++++ .../cypress/src/generators/configuration/configuration.ts | 4 ++++ packages/cypress/src/plugins/plugin.ts | 6 +++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/cypress/plugins/cypress-preset.ts b/packages/cypress/plugins/cypress-preset.ts index 95879c1730571..ea763eaa48152 100644 --- a/packages/cypress/plugins/cypress-preset.ts +++ b/packages/cypress/plugins/cypress-preset.ts @@ -131,6 +131,7 @@ export function nxE2EPreset( webServerCommand: options?.webServerCommands?.default, webServerCommands: options?.webServerCommands, ciWebServerCommand: options?.ciWebServerCommand, + ciBaseUrl: options?.ciBaseUrl, }, async setupNodeEvents(on, config) { @@ -268,6 +269,11 @@ export type NxCypressE2EPresetOptions = { */ ciWebServerCommand?: string; + /** + * The url of the web server for ciWebServerCommand + */ + ciBaseUrl?: string; + /** * Configures how the web server command is started and monitored. */ diff --git a/packages/cypress/src/generators/configuration/configuration.ts b/packages/cypress/src/generators/configuration/configuration.ts index 8635ee5f9664c..d569a3f94941f 100644 --- a/packages/cypress/src/generators/configuration/configuration.ts +++ b/packages/cypress/src/generators/configuration/configuration.ts @@ -47,6 +47,7 @@ export interface CypressE2EConfigSchema { webServerCommands?: Record; ciWebServerCommand?: string; + ciBaseUrl?: string; addPlugin?: boolean; } @@ -218,10 +219,12 @@ async function addFiles( let webServerCommands: Record; let ciWebServerCommand: string; + let ciBaseUrl: string; if (hasPlugin && options.webServerCommands && options.ciWebServerCommand) { webServerCommands = options.webServerCommands; ciWebServerCommand = options.ciWebServerCommand; + ciBaseUrl = options.ciBaseUrl; } else if (hasPlugin && options.devServerTarget) { webServerCommands = {}; @@ -253,6 +256,7 @@ async function addFiles( bundler: options.bundler === 'vite' ? 'vite' : undefined, webServerCommands, ciWebServerCommand: ciWebServerCommand, + ciBaseUrl, }, options.baseUrl ); diff --git a/packages/cypress/src/plugins/plugin.ts b/packages/cypress/src/plugins/plugin.ts index 15a094bbaf3b5..77cee8ef6cd4c 100644 --- a/packages/cypress/src/plugins/plugin.ts +++ b/packages/cypress/src/plugins/plugin.ts @@ -257,6 +257,8 @@ async function buildCypressTargets( excludeSpecPatterns ); + const ciBaseUrl = pluginPresetOptions?.ciBaseUrl; + const dependsOn: TargetConfiguration['dependsOn'] = []; const outputs = getOutputs(projectRoot, cypressConfig, 'e2e'); const inputs = getInputs(namedInputs); @@ -273,7 +275,9 @@ async function buildCypressTargets( outputs, inputs, cache: true, - command: `cypress run --env webServerCommand="${ciWebServerCommand}" --spec ${relativeSpecFilePath}`, + command: `cypress run --env webServerCommand="${ciWebServerCommand}" --spec ${relativeSpecFilePath}${ + ciBaseUrl ? ` --config='{"baseUrl": "${ciBaseUrl}"}'` : '' + }`, options: { cwd: projectRoot, }, From 453ae0e720c40b8449b7a26134a5f0778b8bd488 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 12:01:47 +0100 Subject: [PATCH 08/19] fix(testing): cypress should use preview and serve static with correct ciBaseUrl --- .../application/application.spec.ts | 2 +- .../src/generators/application/lib/add-e2e.ts | 8 +- .../application/lib/normalize-options.ts | 41 +++-- .../src/generators/application/schema.d.ts | 2 + .../__snapshots__/application.spec.ts.snap | 152 ++++++++++++++++++ .../application/application.spec.ts | 24 +++ .../src/generators/application/lib/add-e2e.ts | 34 +++- .../__snapshots__/application.spec.ts.snap | 77 ++++++++- .../application/application.spec.ts | 80 +++++++++ .../src/generators/application/application.ts | 62 +++++-- 10 files changed, 436 insertions(+), 46 deletions(-) diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 479f1272e7190..e223343c079f6 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -86,7 +86,7 @@ describe('app', () => { import { defineConfig } from 'cypress'; export default defineConfig({ - e2e: { ...nxE2EPreset(__filename, {"cypressDir":"src","bundler":"vite","webServerCommands":{"default":"nx run my-app:serve","production":"nx run my-app:preview"},"ciWebServerCommand":"nx run my-app:serve-static"}), + e2e: { ...nxE2EPreset(__filename, {"cypressDir":"src","bundler":"vite","webServerCommands":{"default":"nx run my-app:serve","production":"nx run my-app:preview"},"ciWebServerCommand":"nx run my-app:preview","ciBaseUrl":"http://localhost:4300"}), baseUrl: 'http://localhost:4200' } }); " diff --git a/packages/react/src/generators/application/lib/add-e2e.ts b/packages/react/src/generators/application/lib/add-e2e.ts index 447da4e206780..a6f4617d0d2e1 100644 --- a/packages/react/src/generators/application/lib/add-e2e.ts +++ b/packages/react/src/generators/application/lib/add-e2e.ts @@ -60,8 +60,10 @@ export async function addE2e( } : undefined, ciWebServerCommand: hasNxBuildPlugin - ? `nx run ${options.projectName}:serve-static` + ? `nx run ${options.projectName}:${options.e2eCiWebServerTarget}` : undefined, + ciBaseUrl: + options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined, }); } case 'playwright': { @@ -85,8 +87,8 @@ export async function addE2e( setParserOptionsProject: options.setParserOptionsProject, webServerCommand: `${getPackageManagerCommand().exec} nx run ${ options.projectName - }:${options.e2eWebServerTarget}`, - webServerAddress: options.e2eWebServerAddress, + }:${options.e2eCiWebServerTarget}`, + webServerAddress: options.e2eCiBaseUrl, rootProject: options.rootProject, addPlugin: options.addPlugin, }); diff --git a/packages/react/src/generators/application/lib/normalize-options.ts b/packages/react/src/generators/application/lib/normalize-options.ts index 6f7badebcbce4..2de98bf6560e7 100644 --- a/packages/react/src/generators/application/lib/normalize-options.ts +++ b/packages/react/src/generators/application/lib/normalize-options.ts @@ -49,6 +49,8 @@ export async function normalizeOptions( let e2ePort = options.devServerPort ?? 4200; let e2eWebServerTarget = 'serve'; + let e2eCiWebServerTarget = + options.bundler === 'vite' ? 'preview' : 'serve-static'; if (options.addPlugin) { if (nxJson.plugins) { for (const plugin of nxJson.plugins) { @@ -57,26 +59,29 @@ export async function normalizeOptions( typeof plugin === 'object' && plugin.plugin === '@nx/vite/plugin' ) { + e2eCiWebServerTarget = + (plugin.options as VitePluginOptions)?.previewTargetName ?? + e2eCiWebServerTarget; + e2eWebServerTarget = - options.e2eTestRunner === 'playwright' - ? (plugin.options as VitePluginOptions)?.previewTargetName ?? - 'preview' - : (plugin.options as VitePluginOptions)?.serveTargetName ?? - 'serve'; - e2ePort = - e2eWebServerTarget === - (plugin.options as VitePluginOptions)?.previewTargetName || - e2eWebServerTarget === 'preview' - ? 4300 - : e2ePort; + options.e2eTestRunner === 'cypress' + ? (plugin.options as VitePluginOptions)?.serveTargetName ?? + 'serve' + : e2eWebServerTarget; } else if ( options.bundler === 'webpack' && typeof plugin === 'object' && - plugin.plugin === '@nx/webpack/plugin' && - (plugin.options as WebpackPluginOptions).serveTargetName + plugin.plugin === '@nx/webpack/plugin' ) { - e2eWebServerTarget = (plugin.options as WebpackPluginOptions) - .serveTargetName; + e2eCiWebServerTarget = + (plugin.options as WebpackPluginOptions)?.serveStaticTargetName ?? + e2eCiWebServerTarget; + + e2eWebServerTarget = + options.e2eTestRunner === 'cypress' + ? (plugin.options as WebpackPluginOptions)?.serveTargetName ?? + 'serve' + : e2eWebServerTarget; } } } @@ -92,6 +97,10 @@ export async function normalizeOptions( const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; const e2eWebServerAddress = `http://localhost:${e2ePort}`; + const e2eCiBaseUrl = + options.bundler === 'vite' + ? 'http://localhost:4300' + : `http://localhost:${e2ePort}`; const parsedTags = options.tags ? options.tags.split(',').map((s) => s.trim()) @@ -114,6 +123,8 @@ export async function normalizeOptions( e2eProjectRoot, e2eWebServerAddress, e2eWebServerTarget, + e2eCiWebServerTarget, + e2eCiBaseUrl, e2ePort, parsedTags, fileName, diff --git a/packages/react/src/generators/application/schema.d.ts b/packages/react/src/generators/application/schema.d.ts index f0e26f3e9cc58..76f2426ba4720 100644 --- a/packages/react/src/generators/application/schema.d.ts +++ b/packages/react/src/generators/application/schema.d.ts @@ -38,6 +38,8 @@ export interface NormalizedSchema extends T { e2eProjectRoot: string; e2eWebServerAddress: string; e2eWebServerTarget: string; + e2eCiWebServerTarget: string; + e2eCiBaseUrl: string; e2ePort: number; parsedTags: string[]; fileName: string; diff --git a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap index 21aa11e4acc4e..f318cac011b93 100644 --- a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap @@ -1,5 +1,157 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`application generator should set up project correctly for cypress 1`] = ` +"{ + "root": true, + "ignorePatterns": ["**/*"], + "plugins": ["@nx"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"], + "rules": { + "@nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allow": [], + "depConstraints": [ + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ] + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nx/typescript"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nx/javascript"], + "rules": {} + } + ] +} +" +`; + +exports[`application generator should set up project correctly for cypress 2`] = ` +"/// +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + +export default defineConfig({ + root: __dirname, + cacheDir: '../node_modules/.vite/test', + + server: { + port: 4200, + host: 'localhost', + }, + + preview: { + port: 4300, + host: 'localhost', + }, + + plugins: [vue(), nxViteTsPaths()], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + + build: { + outDir: '../dist/test', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + reporters: ['default'], + coverage: { + reportsDirectory: '../coverage/test', + provider: 'v8', + }, + }, +}); +" +`; + +exports[`application generator should set up project correctly for cypress 3`] = ` +"{ + "extends": [ + "plugin:vue/vue3-essential", + "eslint:recommended", + "@vue/eslint-config-typescript", + "@vue/eslint-config-prettier/skip-formatting", + "../.eslintrc.json" + ], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"], + "rules": { + "vue/multi-word-component-names": "off" + } + } + ] +} +" +`; + +exports[`application generator should set up project correctly for cypress 4`] = ` +"import { describe, it, expect } from 'vitest'; + +import { mount } from '@vue/test-utils'; +import App from './App.vue'; + +describe('App', () => { + it('renders properly', async () => { + const wrapper = mount(App, {}); + + expect(wrapper.text()).toContain('Welcome test 👋'); + }); +}); +" +`; + +exports[`application generator should set up project correctly for cypress 5`] = ` +"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; + +import { defineConfig } from 'cypress'; + +export default defineConfig({ + e2e: { + ...nxE2EPreset(__filename, { + cypressDir: 'src', + bundler: 'vite', + webServerCommands: { + default: 'nx run test:serve', + production: 'nx run test:preview', + }, + ciWebServerCommand: 'nx run test:preview', + ciBaseUrl: 'http://localhost:4300', + }), + baseUrl: 'http://localhost:4200', + }, +}); +" +`; + exports[`application generator should set up project correctly with PascalCase name 1`] = ` "{ "root": true, diff --git a/packages/vue/src/generators/application/application.spec.ts b/packages/vue/src/generators/application/application.spec.ts index 2cea477717649..aab1a17a2300c 100644 --- a/packages/vue/src/generators/application/application.spec.ts +++ b/packages/vue/src/generators/application/application.spec.ts @@ -51,6 +51,30 @@ describe('application generator', () => { expect(listFiles(tree)).toMatchSnapshot(); }); + it('should set up project correctly for cypress', async () => { + const nxJson = readNxJson(tree); + nxJson.plugins ??= []; + nxJson.plugins.push({ + plugin: '@nx/vite/plugin', + options: { + buildTargetName: 'build', + previewTargetName: 'preview', + }, + }); + updateNxJson(tree, nxJson); + await applicationGenerator(tree, { + ...options, + addPlugin: true, + unitTestRunner: 'vitest', + e2eTestRunner: 'cypress', + }); + expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot(); + expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchSnapshot(); + expect(tree.read('test/.eslintrc.json', 'utf-8')).toMatchSnapshot(); + expect(tree.read('test/src/app/App.spec.ts', 'utf-8')).toMatchSnapshot(); + expect(tree.read('test-e2e/cypress.config.ts', 'utf-8')).toMatchSnapshot(); + }); + it('should set up project correctly with PascalCase name', async () => { await applicationGenerator(tree, { ...options, diff --git a/packages/vue/src/generators/application/lib/add-e2e.ts b/packages/vue/src/generators/application/lib/add-e2e.ts index d38a1ca77c089..9268da0e2bd16 100644 --- a/packages/vue/src/generators/application/lib/add-e2e.ts +++ b/packages/vue/src/generators/application/lib/add-e2e.ts @@ -15,14 +15,24 @@ export async function addE2e( tree: Tree, options: NormalizedSchema ): Promise { + const nxJson = readNxJson(tree); + const hasPlugin = nxJson.plugins?.find((p) => + typeof p === 'string' + ? p === '@nx/vite/plugin' + : p.plugin === '@nx/vite/plugin' + ); + const e2eWebServerTarget = hasPlugin + ? typeof hasPlugin === 'string' + ? 'serve' + : (hasPlugin.options as any)?.serveTargetName ?? 'serve' + : 'serve'; + const e2eCiWebServerTarget = hasPlugin + ? typeof hasPlugin === 'string' + ? 'preview' + : (hasPlugin.options as any)?.previewTargetName ?? 'preview' + : 'preview'; switch (options.e2eTestRunner) { case 'cypress': { - const nxJson = readNxJson(tree); - const hasPlugin = nxJson.plugins?.some((p) => - typeof p === 'string' - ? p === '@nx/vite/plugin' - : p.plugin === '@nx/vite/plugin' - ); if (!hasPlugin) { await webStaticServeGenerator(tree, { buildTarget: `${options.projectName}:build`, @@ -48,9 +58,17 @@ export async function addE2e( directory: 'src', bundler: 'vite', skipFormat: true, - devServerTarget: `${options.projectName}:serve`, + devServerTarget: `${options.projectName}:${e2eWebServerTarget}`, baseUrl: 'http://localhost:4200', jsx: true, + webServerCommands: hasPlugin + ? { + default: `nx run ${options.projectName}:${e2eWebServerTarget}`, + production: `nx run ${options.projectName}:preview`, + } + : undefined, + ciWebServerCommand: `nx run ${options.projectName}:${e2eCiWebServerTarget}`, + ciBaseUrl: 'http://localhost:4300', }); } case 'playwright': { @@ -75,7 +93,7 @@ export async function addE2e( setParserOptionsProject: options.setParserOptionsProject, webServerCommand: `${getPackageManagerCommand().exec} nx run ${ options.projectName - }:preview`, + }:${e2eCiWebServerTarget}`, webServerAddress: 'http://localhost:4300', }); } diff --git a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap index 95ca429da2c02..1716ad1c71c5e 100644 --- a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap @@ -73,7 +73,7 @@ export default defineConfig({ " `; -exports[`app not nested should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false 1`] = ` +exports[`app not nested should setup playwright e2e project correctly for webpack 1`] = ` "import { defineConfig, devices } from '@playwright/test'; import { nxE2EPreset } from '@nx/playwright/preset'; @@ -101,7 +101,7 @@ export default defineConfig({ }, /* Run your local dev server before starting the tests */ webServer: { - command: 'npx nx my-app:serve', + command: 'npx nx cool-app:serve-static', url: 'http://localhost:4200', reuseExistingServer: !process.env.CI, cwd: workspaceRoot, @@ -146,6 +146,79 @@ export default defineConfig({ " `; +exports[`app not nested should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false 1`] = ` +"import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; + +import { workspaceRoot } from '@nx/devkit'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4300'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npx nx my-app:preview', + url: 'http://localhost:4300', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); +" +`; + exports[`app setup web app with --bundler=vite should setup vite configuration 1`] = `null`; exports[`app should setup eslint 1`] = ` diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 52c4eff79fa99..6aec78f324b93 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -163,6 +163,86 @@ describe('app', () => { expect(tree.exists('cool-app-e2e/playwright.config.ts')).toBeTruthy(); }); + it('should setup cypress e2e project correctly for vite', async () => { + await applicationGenerator(tree, { + name: 'cool-app', + e2eTestRunner: 'cypress', + unitTestRunner: 'none', + projectNameAndRootFormat: 'as-provided', + bundler: 'vite', + addPlugin: true, + }); + expect(tree.read('cool-app-e2e/cypress.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; + + import { defineConfig } from 'cypress'; + + export default defineConfig({ + e2e: { + ...nxE2EPreset(__filename, { + cypressDir: 'src', + bundler: 'vite', + webServerCommands: { + default: 'nx run cool-app:serve', + production: 'nx run cool-app:preview', + }, + ciWebServerCommand: 'nx run cool-app:preview', + ciBaseUrl: 'http://localhost:4300', + }), + baseUrl: 'http://localhost:4200', + }, + }); + " + `); + }); + + it('should setup cypress e2e project correctly for webpack', async () => { + await applicationGenerator(tree, { + name: 'cool-app', + e2eTestRunner: 'cypress', + unitTestRunner: 'none', + projectNameAndRootFormat: 'as-provided', + bundler: 'webpack', + addPlugin: true, + }); + expect(tree.read('cool-app-e2e/cypress.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; + + import { defineConfig } from 'cypress'; + + export default defineConfig({ + e2e: { + ...nxE2EPreset(__filename, { + cypressDir: 'src', + webServerCommands: { + default: 'nx run cool-app:serve', + production: 'nx run cool-app:preview', + }, + ciWebServerCommand: 'nx run cool-app:serve-static', + }), + baseUrl: 'http://localhost:4200', + }, + }); + " + `); + }); + + it('should setup playwright e2e project correctly for webpack', async () => { + await applicationGenerator(tree, { + name: 'cool-app', + e2eTestRunner: 'playwright', + unitTestRunner: 'none', + projectNameAndRootFormat: 'as-provided', + bundler: 'webpack', + addPlugin: true, + }); + expect( + tree.read('cool-app-e2e/playwright.config.ts', 'utf-8') + ).toMatchSnapshot(); + }); + it('should generate files if bundler is vite', async () => { const nxJson = readNxJson(tree); nxJson.plugins ??= []; diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index fbc361454a9da..47053df0b4a60 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -41,6 +41,7 @@ import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-targ import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { VitePluginOptions } from '@nx/vite/src/plugins/plugin'; import { WebpackPluginOptions } from '@nx/webpack/src/plugins/plugin'; +import { hasVitePlugin } from '@nx/react/src/utils/has-vite-plugin'; interface NormalizedSchema extends Schema { projectName: string; @@ -49,6 +50,8 @@ interface NormalizedSchema extends Schema { e2eProjectRoot: string; e2eWebServerAddress: string; e2eWebServerTarget: string; + e2eCiWebServerTarget: string; + e2eCiBaseUrl: string; e2ePort: number; parsedTags: string[]; } @@ -365,6 +368,9 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { } if (options.e2eTestRunner === 'cypress') { + const hasNxBuildPlugin = + (options.bundler === 'webpack' && hasWebpackPlugin(host)) || + (options.bundler === 'vite' && hasVitePlugin(host)); const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') >('@nx/cypress', nxVersion); @@ -383,6 +389,16 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { baseUrl: options.e2eWebServerAddress, directory: 'src', skipFormat: true, + webServerCommands: hasNxBuildPlugin + ? { + default: `nx run ${options.projectName}:${options.e2eWebServerTarget}`, + production: `nx run ${options.projectName}:preview`, + } + : undefined, + ciWebServerCommand: hasNxBuildPlugin + ? `nx run ${options.projectName}:${options.e2eCiWebServerTarget}` + : undefined, + ciBaseUrl: options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined, }); tasks.push(cypressTask); } else if (options.e2eTestRunner === 'playwright') { @@ -407,8 +423,8 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { setParserOptionsProject: options.setParserOptionsProject, webServerCommand: `${getPackageManagerCommand().exec} nx ${ options.projectName - }:${options.e2eWebServerTarget}`, - webServerAddress: options.e2eWebServerAddress, + }:${options.e2eCiWebServerTarget}`, + webServerAddress: options.e2eCiBaseUrl, addPlugin: options.addPlugin, }); tasks.push(playwrightTask); @@ -494,7 +510,10 @@ async function normalizeOptions( options.addPlugin ??= addPluginDefault; let e2ePort = 4200; + let e2eWebServerTarget = 'serve'; + let e2eCiWebServerTarget = + options.bundler === 'vite' ? 'preview' : 'serve-static'; if (options.addPlugin) { if (nxJson.plugins) { for (const plugin of nxJson.plugins) { @@ -503,26 +522,29 @@ async function normalizeOptions( typeof plugin === 'object' && plugin.plugin === '@nx/vite/plugin' ) { + e2eCiWebServerTarget = + (plugin.options as VitePluginOptions)?.previewTargetName ?? + e2eCiWebServerTarget; + e2eWebServerTarget = - options.e2eTestRunner === 'playwright' - ? (plugin.options as VitePluginOptions).previewTargetName ?? - 'preview' - : (plugin.options as VitePluginOptions).serveTargetName ?? - 'serve'; - e2ePort = - e2eWebServerTarget === - (plugin.options as VitePluginOptions)?.previewTargetName || - e2eWebServerTarget === 'preview' - ? 4300 - : e2ePort; + options.e2eTestRunner === 'cypress' + ? (plugin.options as VitePluginOptions)?.serveTargetName ?? + 'serve' + : e2eWebServerTarget; } else if ( options.bundler === 'webpack' && typeof plugin === 'object' && - plugin.plugin === '@nx/webpack/plugin' && - (plugin.options as WebpackPluginOptions).serveTargetName + plugin.plugin === '@nx/webpack/plugin' ) { - e2eWebServerTarget = (plugin.options as WebpackPluginOptions) - .serveTargetName; + e2eCiWebServerTarget = + (plugin.options as WebpackPluginOptions)?.serveStaticTargetName ?? + e2eCiWebServerTarget; + + e2eWebServerTarget = + options.e2eTestRunner === 'cypress' + ? (plugin.options as WebpackPluginOptions)?.serveTargetName ?? + 'serve' + : e2eWebServerTarget; } } } @@ -538,6 +560,10 @@ async function normalizeOptions( const e2eProjectName = `${appProjectName}-e2e`; const e2eProjectRoot = `${appProjectRoot}-e2e`; const e2eWebServerAddress = `http://localhost:${e2ePort}`; + const e2eCiBaseUrl = + options.bundler === 'vite' + ? 'http://localhost:4300' + : `http://localhost:${e2ePort}`; const npmScope = getNpmScope(host); @@ -563,6 +589,8 @@ async function normalizeOptions( e2eProjectName, e2eWebServerAddress, e2eWebServerTarget, + e2eCiWebServerTarget, + e2eCiBaseUrl, e2ePort, parsedTags, }; From 4e00ea94628f2b3108fccc2d2ac9909fe68839ff Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 12:09:31 +0100 Subject: [PATCH 09/19] fix(webpack): inferred serve-static target should dependOn buildTarget --- packages/webpack/src/plugins/plugin.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webpack/src/plugins/plugin.ts b/packages/webpack/src/plugins/plugin.ts index be92a147cb5ad..6db7bac14f2d2 100644 --- a/packages/webpack/src/plugins/plugin.ts +++ b/packages/webpack/src/plugins/plugin.ts @@ -235,6 +235,7 @@ async function createWebpackTargets( }; targets[options.serveStaticTargetName] = { + dependsOn: [options.buildTargetName], executor: '@nx/web:file-server', options: { buildTarget: options.buildTargetName, From bc79053b0ae140149d26b573d19634ef4aca5332 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 15:29:26 +0100 Subject: [PATCH 10/19] fix(vite): add migration to add dependsOn for preview --- packages/vite/migrations.json | 5 ++ .../add-depends-on-for-preview.spec.ts | 74 +++++++++++++++++++ .../add-depends-on-for-preview.ts | 26 +++++++ 3 files changed, 105 insertions(+) create mode 100644 packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.spec.ts create mode 100644 packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.ts diff --git a/packages/vite/migrations.json b/packages/vite/migrations.json index 800eb7be3fa05..3cf4523f77945 100644 --- a/packages/vite/migrations.json +++ b/packages/vite/migrations.json @@ -32,6 +32,11 @@ "version": "17.3.0-beta.0", "description": "Move the vitest coverage thresholds in their own object if exists and add reporters.", "implementation": "./src/migrations/update-17-3-0/vitest-coverage-and-reporters" + }, + "update-19-5-5-add-depends-on-for-preview-server": { + "version": "19.5.5-beta.0", + "description": "Add dependsOn: [build] to preview targets using preview-server", + "implementation": "./src/migrations/update-19-5-5/add-depends-on-for-preview" } }, "packageJsonUpdates": { diff --git a/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.spec.ts b/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.spec.ts new file mode 100644 index 0000000000000..e3882e44b4bdd --- /dev/null +++ b/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.spec.ts @@ -0,0 +1,74 @@ +import addDependsOnForPreview from './add-depends-on-for-preview'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { readJson } from '@nx/devkit'; + +describe('addDependsOnForPreview', () => { + it('should update when preview target exists in project.json', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'apps/app/project.json', + JSON.stringify({ + name: 'app', + root: 'apps/app', + projectType: 'application', + targets: { + preview: { + executor: '@nx/vite:preview-server', + }, + }, + }) + ); + + // ACT + await addDependsOnForPreview(tree); + + // ASSERT + expect(readJson(tree, 'apps/app/project.json').targets) + .toMatchInlineSnapshot(` + { + "preview": { + "dependsOn": [ + "build", + ], + "executor": "@nx/vite:preview-server", + }, + } + `); + }); + + it('should not update when preview target exists in project.json and has a dependsOn already', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + tree.write( + 'apps/app/project.json', + JSON.stringify({ + name: 'app', + root: 'apps/app', + projectType: 'application', + targets: { + preview: { + dependsOn: ['build'], + executor: '@nx/vite:preview-server', + }, + }, + }) + ); + + // ACT + await addDependsOnForPreview(tree); + + // ASSERT + expect(readJson(tree, 'apps/app/project.json').targets) + .toMatchInlineSnapshot(` + { + "preview": { + "dependsOn": [ + "build", + ], + "executor": "@nx/vite:preview-server", + }, + } + `); + }); +}); diff --git a/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.ts b/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.ts new file mode 100644 index 0000000000000..f78f0a360bc51 --- /dev/null +++ b/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.ts @@ -0,0 +1,26 @@ +import { + type Tree, + formatFiles, + readProjectConfiguration, + updateProjectConfiguration, +} from '@nx/devkit'; +import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils'; +import { VitePreviewServerExecutorOptions } from '../../executors/preview-server/schema'; + +export default async function (tree: Tree) { + forEachExecutorOptions( + tree, + '@nx/vite:preview-server', + (_, projectName, targetName) => { + const project = readProjectConfiguration(tree, projectName); + project.targets[targetName].dependsOn ??= []; + if (project.targets[targetName].dependsOn.includes('build')) { + return; + } + project.targets[targetName].dependsOn.push('build'); + updateProjectConfiguration(tree, projectName, project); + } + ); + + await formatFiles(tree); +} From 25a07c9896dfdd1e4d15bfb0e7edc999967122ba Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 16:06:13 +0100 Subject: [PATCH 11/19] fix(testing): add migration for cypress should use preview for vite applications --- packages/cypress/migrations.json | 6 + .../update-ci-webserver-for-vite.spec.ts | 262 ++++++++++++++++++ .../update-ci-webserver-for-vite.ts | 165 +++++++++++ packages/nx/src/devkit-internals.ts | 5 +- .../utils/project-configuration-utils.ts | 4 +- 5 files changed, 439 insertions(+), 3 deletions(-) create mode 100644 packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.spec.ts create mode 100644 packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.ts diff --git a/packages/cypress/migrations.json b/packages/cypress/migrations.json index 5bcd7d1698559..39b5b3ef71877 100644 --- a/packages/cypress/migrations.json +++ b/packages/cypress/migrations.json @@ -29,6 +29,12 @@ "version": "18.1.0-beta.3", "description": "Update to Cypress ^13.6.6 if the workspace is using Cypress v13 to ensure workspaces don't use v13.6.5 which has an issue when verifying Cypress.", "implementation": "./src/migrations/update-18-1-0/update-cypress-version-13-6-6" + }, + "update-19-5-5-update-ci-webserver-for-vite": { + "cli": "nx", + "version": "19-5-5-beta.0", + "description": "Update ciWebServerCommand to use previewTargetName if Vite is detected for the application.", + "implementation": "./src/migrations/update-19-5-5/update-ci-webserver-for-vite" } }, "packageJsonUpdates": { diff --git a/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.spec.ts b/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.spec.ts new file mode 100644 index 0000000000000..05dd4aaa0d817 --- /dev/null +++ b/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.spec.ts @@ -0,0 +1,262 @@ +import updateCiWebserverForVite from './update-ci-webserver-for-vite'; +import { + type Tree, + type ProjectGraph, + readNxJson, + updateNxJson, +} from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { TempFs } from 'nx/src/internal-testing-utils/temp-fs'; + +let projectGraph: ProjectGraph; +jest.mock('@nx/devkit', () => ({ + ...jest.requireActual('@nx/devkit'), + createProjectGraphAsync: jest.fn().mockImplementation(async () => { + return projectGraph; + }), +})); + +describe('updateCiWebserverForVite', () => { + let tree: Tree; + let tempFs: TempFs; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + tempFs = new TempFs('add-e2e-ci'); + tree.root = tempFs.tempDir; + projectGraph = { + nodes: {}, + dependencies: {}, + externalNodes: {}, + }; + }); + + afterEach(() => { + tempFs.reset(); + }); + + it('should do nothing if vite is not found for application', async () => { + // ARRANGE + const nxJson = readNxJson(tree); + nxJson.plugins = [ + { + plugin: '@nx/cypress/plugin', + options: { + targetName: 'e2e', + ciTargetName: 'e2e-ci', + }, + }, + ]; + updateNxJson(tree, nxJson); + + addProject(tree, tempFs, { + buildTargetName: 'build', + ciTargetName: 'e2e-ci', + appName: 'app', + noVite: true, + }); + + // ACT + await updateCiWebserverForVite(tree); + + // ASSERT + expect(tree.read('app-e2e/cypress.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; + import { defineConfig } from 'cypress'; + export default defineConfig({ + e2e: { + ...nxE2EPreset(__filename, { + cypressDir: 'src', + bundler: 'vite', + webServerCommands: { + default: 'nx run app:serve', + production: 'nx run app:preview', + }, + ciWebServerCommand: 'nx run app:serve-static', + }), + baseUrl: 'http://localhost:4200', + }, + }); + " + `); + }); + + it('should update ciWebServerCommand to preview for vite app', async () => { + // ARRANGE + const nxJson = readNxJson(tree); + nxJson.plugins = [ + { + plugin: '@nx/cypress/plugin', + options: { + targetName: 'e2e', + ciTargetName: 'e2e-ci', + }, + }, + { + plugin: '@nx/vite/plugin', + options: { + buildTargetName: 'build', + previewTargetName: 'preview', + }, + }, + ]; + updateNxJson(tree, nxJson); + + addProject(tree, tempFs); + + // ACT + await updateCiWebserverForVite(tree); + + // ASSERT + expect(tree.read('app-e2e/cypress.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; + import { defineConfig } from 'cypress'; + export default defineConfig({ + e2e: { + ...nxE2EPreset(__filename, { + cypressDir: 'src', + bundler: 'vite', + webServerCommands: { + default: 'nx run app:serve', + production: 'nx run app:preview', + }, + ciWebServerCommand: 'nx run app:preview', + ciBaseUrl: 'http://localhost:4300', + }), + baseUrl: 'http://localhost:4200', + }, + }); + " + `); + }); +}); + +function addProject( + tree: Tree, + tempFs: TempFs, + overrides: { + ciTargetName: string; + buildTargetName: string; + appName: string; + noCi?: boolean; + noVite?: boolean; + } = { ciTargetName: 'e2e-ci', buildTargetName: 'build', appName: 'app' } +) { + const appProjectConfig = { + name: overrides.appName, + root: overrides.appName, + sourceRoot: `${overrides.appName}/src`, + projectType: 'application', + }; + const viteConfig = `/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +export default defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/${overrides.appName}', + server: { + port: 4200, + host: 'localhost', + }, + preview: { + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths()], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../../dist/${overrides.appName}', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, +});`; + + const e2eProjectConfig = { + name: `${overrides.appName}-e2e`, + root: `${overrides.appName}-e2e`, + sourceRoot: `${overrides.appName}-e2e/src`, + projectType: 'application', + }; + + const cypressConfig = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; +import { defineConfig } from 'cypress'; +export default defineConfig({ + e2e: { + ...nxE2EPreset(__filename, { + cypressDir: 'src', + bundler: 'vite', + webServerCommands: { + default: 'nx run ${overrides.appName}:serve', + production: 'nx run ${overrides.appName}:preview', + }, + ${ + !overrides.noCi + ? `ciWebServerCommand: 'nx run ${overrides.appName}:serve-static',` + : '' + } + }), + baseUrl: 'http://localhost:4200', + }, +}); +`; + + if (!overrides.noVite) { + tree.write(`${overrides.appName}/vite.config.ts`, viteConfig); + } + tree.write( + `${overrides.appName}/project.json`, + JSON.stringify(appProjectConfig) + ); + tree.write(`${overrides.appName}-e2e/cypress.config.ts`, cypressConfig); + tree.write( + `${overrides.appName}-e2e/project.json`, + JSON.stringify(e2eProjectConfig) + ); + if (!overrides.noVite) { + tempFs.createFile(`${overrides.appName}/vite.config.ts`, viteConfig); + } + tempFs.createFilesSync({ + [`${overrides.appName}/project.json`]: JSON.stringify(appProjectConfig), + [`${overrides.appName}-e2e/cypress.config.ts`]: cypressConfig, + [`${overrides.appName}-e2e/project.json`]: JSON.stringify(e2eProjectConfig), + }); + + projectGraph.nodes[overrides.appName] = { + name: overrides.appName, + type: 'app', + data: { + projectType: 'application', + root: overrides.appName, + targets: { + [overrides.buildTargetName]: {}, + 'serve-static': { + options: { + buildTarget: overrides.buildTargetName, + }, + }, + }, + }, + }; + + projectGraph.nodes[`${overrides.appName}-e2e`] = { + name: `${overrides.appName}-e2e`, + type: 'app', + data: { + projectType: 'application', + root: `${overrides.appName}-e2e`, + targets: { + e2e: {}, + [overrides.ciTargetName]: {}, + }, + }, + }; +} diff --git a/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.ts b/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.ts new file mode 100644 index 0000000000000..86ace3649e6f9 --- /dev/null +++ b/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.ts @@ -0,0 +1,165 @@ +import { + type Tree, + CreateNodesV2, + createProjectGraphAsync, + readNxJson, + parseTargetString, + joinPathFragments, + PluginConfiguration, + CreateNodes, + formatFiles, +} from '@nx/devkit'; +import { + retrieveProjectConfigurations, + LoadedNxPlugin, + ProjectConfigurationsError, + findMatchingConfigFiles, +} from 'nx/src/devkit-internals'; +import { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils'; +import { tsquery } from '@phenomnomnominal/tsquery'; +import { CypressPluginOptions } from '../../plugins/plugin'; + +export default async function (tree: Tree) { + const pluginName = '@nx/cypress/plugin'; + const graph = await createProjectGraphAsync(); + const nxJson = readNxJson(tree); + const matchingPluginRegistrations = nxJson.plugins?.filter((p) => + typeof p === 'string' ? p === pluginName : p.plugin === pluginName + ); + + const { + createNodesV2, + }: { createNodesV2: CreateNodesV2 } = await import( + pluginName + ); + + for (const plugin of matchingPluginRegistrations) { + let projectConfigs: ConfigurationResult; + try { + const loadedPlugin = new LoadedNxPlugin( + { createNodesV2, name: pluginName }, + plugin + ); + projectConfigs = await retrieveProjectConfigurations( + [loadedPlugin], + tree.root, + nxJson + ); + } catch (e) { + if (e instanceof ProjectConfigurationsError) { + projectConfigs = e.partialProjectConfigurationsResult; + } else { + throw e; + } + } + + for (const configFile of projectConfigs.matchingProjectFiles) { + const configFileContents = tree.read(configFile, 'utf-8'); + if (!configFileContents.includes('ciWebServerCommand')) { + continue; + } + + const ast = tsquery.ast(configFileContents); + const CI_WEBSERVER_COMMAND_SELECTOR = + 'ObjectLiteralExpression PropertyAssignment:has(Identifier[name=ciWebServerCommand]) > StringLiteral'; + const nodes = tsquery(ast, CI_WEBSERVER_COMMAND_SELECTOR, { + visitAllChildren: true, + }); + if (!nodes.length) { + continue; + } + const ciWebServerCommand = nodes[0].getText(); + const NX_TARGET_REGEX = "(?<=nx run )[^']+"; + const matches = ciWebServerCommand.match(NX_TARGET_REGEX); + if (!matches) { + continue; + } + const targetString = matches[0]; + const { project, target, configuration } = parseTargetString( + targetString, + graph + ); + + const pathToViteConfig = [ + joinPathFragments(graph.nodes[project].data.root, 'vite.config.ts'), + joinPathFragments(graph.nodes[project].data.root, 'vite.config.js'), + ].find((p) => tree.exists(p)); + + if (!pathToViteConfig) { + continue; + } + + const viteConfigContents = tree.read(pathToViteConfig, 'utf-8'); + if (!viteConfigContents.includes('preview:')) { + continue; + } + + const matchingVitePlugin = await findPluginForConfigFile( + tree, + '@nx/vite/plugin', + pathToViteConfig + ); + const previewTargetName = matchingVitePlugin + ? typeof matchingVitePlugin === 'string' + ? 'preview' + : (matchingVitePlugin.options as any)?.previewTargetName ?? 'preview' + : 'preview'; + + tree.write( + configFile, + `${configFileContents.slice( + 0, + nodes[0].getStart() + )}'nx run ${project}:${previewTargetName}', + ciBaseUrl: "http://localhost:4300"${configFileContents.slice( + nodes[0].getEnd() + )}` + ); + } + } + + await formatFiles(tree); +} + +async function findPluginForConfigFile( + tree: Tree, + pluginName: string, + pathToConfigFile: string +): Promise { + const nxJson = readNxJson(tree); + if (!nxJson.plugins) { + return; + } + + const pluginRegistrations: PluginConfiguration[] = nxJson.plugins.filter( + (p) => (typeof p === 'string' ? p === pluginName : p.plugin === pluginName) + ); + + for (const plugin of pluginRegistrations) { + if (typeof plugin === 'string') { + return plugin; + } + + if (!plugin.include && !plugin.exclude) { + return plugin; + } + + if (plugin.include || plugin.exclude) { + const resolvedPlugin: { + createNodes?: CreateNodes; + createNodesV2?: CreateNodesV2; + } = await import(pluginName); + const pluginGlob = + resolvedPlugin.createNodesV2?.[0] ?? resolvedPlugin.createNodes?.[0]; + const matchingConfigFile = findMatchingConfigFiles( + [pathToConfigFile], + pluginGlob, + plugin.include, + plugin.exclude + ); + if (matchingConfigFile.length) { + return plugin; + } + } + } +} diff --git a/packages/nx/src/devkit-internals.ts b/packages/nx/src/devkit-internals.ts index 340f0373b8e08..72be433b4c674 100644 --- a/packages/nx/src/devkit-internals.ts +++ b/packages/nx/src/devkit-internals.ts @@ -8,7 +8,10 @@ export { getExecutorInformation } from './command-line/run/executor-utils'; export { readNxJson as readNxJsonFromDisk } from './config/nx-json'; export { calculateDefaultProjectName } from './config/calculate-default-project-name'; export { retrieveProjectConfigurationsWithAngularProjects } from './project-graph/utils/retrieve-workspace-files'; -export { mergeTargetConfigurations } from './project-graph/utils/project-configuration-utils'; +export { + mergeTargetConfigurations, + findMatchingConfigFiles, +} from './project-graph/utils/project-configuration-utils'; export { readProjectConfigurationsFromRootMap } from './project-graph/utils/project-configuration-utils'; export { splitTarget } from './utils/split-target'; export { combineOptionsForExecutor } from './utils/params'; diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.ts index db54257ac33e6..4696220e5057e 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -505,12 +505,12 @@ function mergeCreateNodesResults( return { projectRootMap, externalNodes, rootMap, configurationSourceMaps }; } -function findMatchingConfigFiles( +export function findMatchingConfigFiles( projectFiles: string[], pattern: string, include: string[], exclude: string[] -) { +): string[] { const matchingConfigFiles: string[] = []; for (const file of projectFiles) { From 60637a202eb7afb01d7683128cc278c47b0e5830 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 17:01:27 +0100 Subject: [PATCH 12/19] fix(testing): add migration for playwright to use serve-static or preview for command --- packages/playwright/migrations.json | 6 + ...e-serve-static-preview-for-command.spec.ts | 234 ++++++++++++++++++ .../use-serve-static-preview-for-command.ts | 142 +++++++++++ 3 files changed, 382 insertions(+) create mode 100644 packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.spec.ts create mode 100644 packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.ts diff --git a/packages/playwright/migrations.json b/packages/playwright/migrations.json index 936cecd33b5ce..d31b001e985ad 100644 --- a/packages/playwright/migrations.json +++ b/packages/playwright/migrations.json @@ -11,6 +11,12 @@ "version": "18.1.0-beta.3", "description": "Remove invalid baseUrl option from @nx/playwright:playwright targets in project.json.", "implementation": "./src/migrations/update-18-1-0/remove-baseUrl-from-project-json" + }, + "19-5-5-use-serve-static-preview-for-command": { + "cli": "nx", + "version": "19.5.5-beta.0", + "description": "Use serve-static or preview for webServerCommand.", + "implementation": "./src/migrations/update-19-5-5/use-serve-static-preview-for-command" } } } diff --git a/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.spec.ts b/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.spec.ts new file mode 100644 index 0000000000000..0122a55e20c47 --- /dev/null +++ b/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.spec.ts @@ -0,0 +1,234 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { ProjectGraph, type Tree } from '@nx/devkit'; +import useServeStaticPreviewForCommand from './use-serve-static-preview-for-command'; +import { TempFs } from 'nx/src/internal-testing-utils/temp-fs'; + +let projectGraph: ProjectGraph; +jest.mock('@nx/devkit', () => ({ + ...jest.requireActual('@nx/devkit'), + createProjectGraphAsync: jest.fn().mockImplementation(async () => { + return projectGraph; + }), +})); + +describe('useServeStaticPreviewForCommand', () => { + let tree: Tree; + let tempFs: TempFs; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + tempFs = new TempFs('add-e2e-ci'); + tree.root = tempFs.tempDir; + projectGraph = { + nodes: {}, + dependencies: {}, + externalNodes: {}, + }; + }); + + afterEach(() => { + tempFs.reset(); + }); + + it('should update when it does not use serve-static for non-vite', async () => { + // ARRANGE + addProject(tree, tempFs, { noVite: true }); + + // ACT + await useServeStaticPreviewForCommand(tree); + + // ASSERT + expect(tree.read('app-e2e/playwright.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { defineConfig, devices } from '@playwright/test'; + import { nxE2EPreset } from '@nx/playwright/preset'; + + import { workspaceRoot } from '@nx/devkit'; + + const baseURL = process.env['BASE_URL'] || 'http://localhost:4200'; + + export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + use: { + baseURL, + trace: 'on-first-retry', + }, + webServer: { + command: 'npx nx run app:serve-static', + url: 'http://localhost:4200', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + }); + " + `); + }); + it('should update when it does not use preview for vite', async () => { + // ARRANGE + addProject(tree, tempFs); + + // ACT + await useServeStaticPreviewForCommand(tree); + + // ASSERT + expect(tree.read('app-e2e/playwright.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { defineConfig, devices } from '@playwright/test'; + import { nxE2EPreset } from '@nx/playwright/preset'; + + import { workspaceRoot } from '@nx/devkit'; + + const baseURL = process.env['BASE_URL'] || 'http://localhost:4300'; + + export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + use: { + baseURL, + trace: 'on-first-retry', + }, + webServer: { + command: 'npx nx run app:preview', + url: 'http://localhost:4300', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + }); + " + `); + }); +}); + +const basePlaywrightConfig = ( + appName: string +) => `import { defineConfig, devices } from '@playwright/test'; +import { nxE2EPreset } from '@nx/playwright/preset'; + +import { workspaceRoot } from '@nx/devkit'; + +const baseURL = process.env['BASE_URL'] || 'http://localhost:4200'; + +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './src' }), + use: { + baseURL, + trace: 'on-first-retry', + }, + webServer: { + command: 'npx nx run ${appName}:serve', + url: 'http://localhost:4200', + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +});`; + +const viteConfig = `/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +export default defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/app', + server: { + port: 4200, + host: 'localhost', + }, + preview: { + port: 4300, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths()], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../../dist/app', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, +});`; + +function addProject( + tree: Tree, + tempFs: TempFs, + overrides: { + noVite?: boolean; + } = {} +) { + const appProjectConfig = { + name: 'app', + root: 'app', + sourceRoot: `${'app'}/src`, + projectType: 'application', + }; + + const e2eProjectConfig = { + name: `app-e2e`, + root: `app-e2e`, + sourceRoot: `app-e2e/src`, + projectType: 'application', + }; + + if (!overrides.noVite) { + tree.write(`app/vite.config.ts`, viteConfig); + } else { + tree.write(`app/webpack.config.ts`, ``); + } + + tree.write(`app/project.json`, JSON.stringify(appProjectConfig)); + tree.write(`app-e2e/playwright.config.ts`, basePlaywrightConfig('app')); + tree.write(`app-e2e/project.json`, JSON.stringify(e2eProjectConfig)); + if (!overrides.noVite) { + tempFs.createFile(`app/vite.config.ts`, viteConfig); + } else { + tempFs.createFile(`app/webpack.config.ts`, ``); + } + tempFs.createFilesSync({ + [`app/project.json`]: JSON.stringify(appProjectConfig), + [`app-e2e/playwright.config.ts`]: basePlaywrightConfig('app'), + [`app-e2e/project.json`]: JSON.stringify(e2eProjectConfig), + }); + + projectGraph.nodes['app'] = { + name: 'app', + type: 'app', + data: { + projectType: 'application', + root: 'app', + targets: {}, + }, + }; + + projectGraph.nodes[`app-e2e`] = { + name: `app-e2e`, + type: 'app', + data: { + projectType: 'application', + root: `app-e2e`, + targets: { + e2e: {}, + }, + }, + }; +} diff --git a/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.ts b/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.ts new file mode 100644 index 0000000000000..c98bfff41a031 --- /dev/null +++ b/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.ts @@ -0,0 +1,142 @@ +import { + createProjectGraphAsync, + formatFiles, + getPackageManagerCommand, + joinPathFragments, + parseTargetString, + type Tree, + visitNotIgnoredFiles, +} from '@nx/devkit'; +import { tsquery } from '@phenomnomnominal/tsquery'; + +export default async function (tree: Tree) { + const graph = await createProjectGraphAsync(); + visitNotIgnoredFiles(tree, '', (path) => { + if (!path.endsWith('playwright.config.ts')) { + return; + } + + let playwrightConfigFileContents = tree.read(path, 'utf-8'); + + const WEBSERVER_COMMAND_SELECTOR = + 'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=command]) > StringLiteral'; + let ast = tsquery.ast(playwrightConfigFileContents); + const nodes = tsquery(ast, WEBSERVER_COMMAND_SELECTOR, { + visitAllChildren: true, + }); + if (!nodes.length) { + return; + } + + const commandValueNode = nodes[0]; + const command = commandValueNode.getText(); + let project: string; + if (command.includes('nx run')) { + const NX_TARGET_REGEX = "(?<=nx run )[^']+"; + const matches = command.match(NX_TARGET_REGEX); + if (!matches) { + return; + } + const targetString = matches[0]; + const parsedTargetString = parseTargetString(targetString, graph); + + if ( + parsedTargetString.target === 'serve-static' || + parsedTargetString.target === 'preview' + ) { + return; + } + + project = parsedTargetString.project; + } else { + const NX_PROJECT_REGEX = "(?<=nx [^ ]+ )[^']+"; + const matches = command.match(NX_PROJECT_REGEX); + if (!matches) { + return; + } + project = matches[0]; + } + + const pathToViteConfig = [ + joinPathFragments(graph.nodes[project].data.root, 'vite.config.ts'), + joinPathFragments(graph.nodes[project].data.root, 'vite.config.js'), + ].find((p) => tree.exists(p)); + + if (!pathToViteConfig) { + const newCommand = `${ + getPackageManagerCommand().exec + } nx run ${project}:serve-static`; + tree.write( + path, + `${playwrightConfigFileContents.slice( + 0, + commandValueNode.getStart() + )}"${newCommand}"${playwrightConfigFileContents.slice( + commandValueNode.getEnd() + )}` + ); + } else { + const newCommand = `${ + getPackageManagerCommand().exec + } nx run ${project}:preview`; + tree.write( + path, + `${playwrightConfigFileContents.slice( + 0, + commandValueNode.getStart() + )}"${newCommand}"${playwrightConfigFileContents.slice( + commandValueNode.getEnd() + )}` + ); + playwrightConfigFileContents = tree.read(path, 'utf-8'); + ast = tsquery.ast(playwrightConfigFileContents); + + const BASE_URL_SELECTOR = + 'VariableDeclaration:has(Identifier[name=baseURL])'; + const baseUrlNodes = tsquery(ast, BASE_URL_SELECTOR, { + visitAllChildren: true, + }); + if (!baseUrlNodes.length) { + return; + } + + const baseUrlNode = baseUrlNodes[0]; + const newBaseUrlVariableDeclaration = + "baseURL = process.env['BASE_URL'] || 'http://localhost:4300';"; + tree.write( + path, + `${playwrightConfigFileContents.slice( + 0, + baseUrlNode.getStart() + )}${newBaseUrlVariableDeclaration}${playwrightConfigFileContents.slice( + baseUrlNode.getEnd() + )}` + ); + + playwrightConfigFileContents = tree.read(path, 'utf-8'); + ast = tsquery.ast(playwrightConfigFileContents); + const WEB_SERVER_URL_SELECTOR = + 'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=url]) > StringLiteral'; + const webServerUrlNodes = tsquery(ast, WEB_SERVER_URL_SELECTOR, { + visitAllChildren: true, + }); + if (!webServerUrlNodes.length) { + return; + } + + const webServerUrlNode = webServerUrlNodes[0]; + const newWebServerUrl = "'http://localhost:4300'"; + tree.write( + path, + `${playwrightConfigFileContents.slice( + 0, + webServerUrlNode.getStart() + )}${newWebServerUrl}${playwrightConfigFileContents.slice( + webServerUrlNode.getEnd() + )}` + ); + } + }); + + await formatFiles(tree); +} From 80fb3cea62619b1e44a8dee3c9e140cad04c2785 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 17:09:43 +0100 Subject: [PATCH 13/19] fix(react-native): call addE2eReact correctly --- .../react-native/src/generators/application/lib/add-e2e.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native/src/generators/application/lib/add-e2e.ts b/packages/react-native/src/generators/application/lib/add-e2e.ts index 6aff1fc81dd2f..c3e21a0f1b628 100644 --- a/packages/react-native/src/generators/application/lib/add-e2e.ts +++ b/packages/react-native/src/generators/application/lib/add-e2e.ts @@ -18,6 +18,8 @@ export async function addE2e( styledModule: null, hasStyles: false, unitTestRunner: 'none', + e2eCiWebServerTarget: options.e2eWebServerTarget, + e2eCiBaseUrl: options.e2eWebServerAddress, }); case 'playwright': return addE2eReact(host, { @@ -27,6 +29,8 @@ export async function addE2e( styledModule: null, hasStyles: false, unitTestRunner: 'none', + e2eCiWebServerTarget: options.e2eWebServerTarget, + e2eCiBaseUrl: options.e2eWebServerAddress, }); case 'detox': const { detoxApplicationGenerator } = ensurePackage< From 1a072b9ec9fb77793e26795c91ba192ac54536d9 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 17:17:04 +0100 Subject: [PATCH 14/19] fix(web): remove circular-dep --- packages/web/src/generators/application/application.ts | 2 +- packages/web/src/utils/has-vite-plugin.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/utils/has-vite-plugin.ts diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 47053df0b4a60..5196085ffd05f 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -41,7 +41,7 @@ import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-targ import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { VitePluginOptions } from '@nx/vite/src/plugins/plugin'; import { WebpackPluginOptions } from '@nx/webpack/src/plugins/plugin'; -import { hasVitePlugin } from '@nx/react/src/utils/has-vite-plugin'; +import { hasVitePlugin } from '../../utils/has-vite-plugin'; interface NormalizedSchema extends Schema { projectName: string; diff --git a/packages/web/src/utils/has-vite-plugin.ts b/packages/web/src/utils/has-vite-plugin.ts new file mode 100644 index 0000000000000..9430f5030ea07 --- /dev/null +++ b/packages/web/src/utils/has-vite-plugin.ts @@ -0,0 +1,10 @@ +import { readNxJson, Tree } from '@nx/devkit'; + +export function hasVitePlugin(tree: Tree) { + const nxJson = readNxJson(tree); + return !!nxJson.plugins?.some((p) => + typeof p === 'string' + ? p === '@nx/vite/plugin' + : p.plugin === '@nx/vite/plugin' + ); +} From 512b14e2c9e31aba66199205e18cedeb56e54ad1 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 18:49:04 +0100 Subject: [PATCH 15/19] fix(web): fix webservercommand --- packages/cypress/migrations.json | 2 +- packages/web/src/generators/application/application.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cypress/migrations.json b/packages/cypress/migrations.json index 39b5b3ef71877..ce612732021fc 100644 --- a/packages/cypress/migrations.json +++ b/packages/cypress/migrations.json @@ -32,7 +32,7 @@ }, "update-19-5-5-update-ci-webserver-for-vite": { "cli": "nx", - "version": "19-5-5-beta.0", + "version": "19.5.5-beta.0", "description": "Update ciWebServerCommand to use previewTargetName if Vite is detected for the application.", "implementation": "./src/migrations/update-19-5-5/update-ci-webserver-for-vite" } diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 5196085ffd05f..c54cda863a492 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -421,7 +421,7 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { js: false, linter: options.linter, setParserOptionsProject: options.setParserOptionsProject, - webServerCommand: `${getPackageManagerCommand().exec} nx ${ + webServerCommand: `${getPackageManagerCommand().exec} nx run ${ options.projectName }:${options.e2eCiWebServerTarget}`, webServerAddress: options.e2eCiBaseUrl, From bd7efe0ce3173922863ae73330e70f26bbdfdfaf Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 19:03:09 +0100 Subject: [PATCH 16/19] chore(repo): update snapshot --- .../application/__snapshots__/application.spec.ts.snap | 6 +++--- .../webpack/src/plugins/__snapshots__/plugin.spec.ts.snap | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap index 1716ad1c71c5e..0a8df963bc495 100644 --- a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap @@ -28,7 +28,7 @@ export default defineConfig({ }, /* Run your local dev server before starting the tests */ webServer: { - command: 'npx nx my-app:preview', + command: 'npx nx run my-app:preview', url: 'http://localhost:4300', reuseExistingServer: !process.env.CI, cwd: workspaceRoot, @@ -101,7 +101,7 @@ export default defineConfig({ }, /* Run your local dev server before starting the tests */ webServer: { - command: 'npx nx cool-app:serve-static', + command: 'npx nx run cool-app:serve-static', url: 'http://localhost:4200', reuseExistingServer: !process.env.CI, cwd: workspaceRoot, @@ -174,7 +174,7 @@ export default defineConfig({ }, /* Run your local dev server before starting the tests */ webServer: { - command: 'npx nx my-app:preview', + command: 'npx nx run my-app:preview', url: 'http://localhost:4300', reuseExistingServer: !process.env.CI, cwd: workspaceRoot, diff --git a/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap b/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap index a61d76d9e5f9c..ac4cb429b7f37 100644 --- a/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap +++ b/packages/webpack/src/plugins/__snapshots__/plugin.spec.ts.snap @@ -105,6 +105,9 @@ exports[`@nx/webpack/plugin should create nodes 1`] = ` }, }, "serve-static": { + "dependsOn": [ + "build-something", + ], "executor": "@nx/web:file-server", "options": { "buildTarget": "build-something", From 5fcfd758c8362dd1e75c2649623d511d9faed6ac Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 1 Aug 2024 19:39:51 +0100 Subject: [PATCH 17/19] fix(web): remove cypress conditional for normalise option --- .../generators/application/lib/normalize-options.ts | 12 ++++-------- .../web/src/generators/application/application.ts | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/react/src/generators/application/lib/normalize-options.ts b/packages/react/src/generators/application/lib/normalize-options.ts index 2de98bf6560e7..5031c90b9f303 100644 --- a/packages/react/src/generators/application/lib/normalize-options.ts +++ b/packages/react/src/generators/application/lib/normalize-options.ts @@ -64,10 +64,8 @@ export async function normalizeOptions( e2eCiWebServerTarget; e2eWebServerTarget = - options.e2eTestRunner === 'cypress' - ? (plugin.options as VitePluginOptions)?.serveTargetName ?? - 'serve' - : e2eWebServerTarget; + (plugin.options as VitePluginOptions)?.serveTargetName ?? + e2eWebServerTarget; } else if ( options.bundler === 'webpack' && typeof plugin === 'object' && @@ -78,10 +76,8 @@ export async function normalizeOptions( e2eCiWebServerTarget; e2eWebServerTarget = - options.e2eTestRunner === 'cypress' - ? (plugin.options as WebpackPluginOptions)?.serveTargetName ?? - 'serve' - : e2eWebServerTarget; + (plugin.options as WebpackPluginOptions)?.serveTargetName ?? + e2eWebServerTarget; } } } diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index c54cda863a492..0d161a2a559a6 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -527,10 +527,8 @@ async function normalizeOptions( e2eCiWebServerTarget; e2eWebServerTarget = - options.e2eTestRunner === 'cypress' - ? (plugin.options as VitePluginOptions)?.serveTargetName ?? - 'serve' - : e2eWebServerTarget; + (plugin.options as VitePluginOptions)?.serveTargetName ?? + e2eWebServerTarget; } else if ( options.bundler === 'webpack' && typeof plugin === 'object' && @@ -541,10 +539,8 @@ async function normalizeOptions( e2eCiWebServerTarget; e2eWebServerTarget = - options.e2eTestRunner === 'cypress' - ? (plugin.options as WebpackPluginOptions)?.serveTargetName ?? - 'serve' - : e2eWebServerTarget; + (plugin.options as WebpackPluginOptions)?.serveTargetName ?? + e2eWebServerTarget; } } } From 3d66565349e9c9d2c0d603527343be1e2c0c6857 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Fri, 2 Aug 2024 08:54:47 +0100 Subject: [PATCH 18/19] chore(testing): point migrations to 19.6 release --- packages/cypress/migrations.json | 6 +++--- .../update-ci-webserver-for-vite.spec.ts | 0 .../update-ci-webserver-for-vite.ts | 0 packages/playwright/migrations.json | 6 +++--- .../use-serve-static-preview-for-command.spec.ts | 0 .../use-serve-static-preview-for-command.ts | 0 packages/vite/migrations.json | 6 +++--- .../add-depends-on-for-preview.spec.ts | 0 .../add-depends-on-for-preview.ts | 0 9 files changed, 9 insertions(+), 9 deletions(-) rename packages/cypress/src/migrations/{update-19-5-5 => update-19-6-0}/update-ci-webserver-for-vite.spec.ts (100%) rename packages/cypress/src/migrations/{update-19-5-5 => update-19-6-0}/update-ci-webserver-for-vite.ts (100%) rename packages/playwright/src/migrations/{update-19-5-5 => update-19-6-0}/use-serve-static-preview-for-command.spec.ts (100%) rename packages/playwright/src/migrations/{update-19-5-5 => update-19-6-0}/use-serve-static-preview-for-command.ts (100%) rename packages/vite/src/migrations/{update-19-5-5 => update-19-6-0}/add-depends-on-for-preview.spec.ts (100%) rename packages/vite/src/migrations/{update-19-5-5 => update-19-6-0}/add-depends-on-for-preview.ts (100%) diff --git a/packages/cypress/migrations.json b/packages/cypress/migrations.json index ce612732021fc..b1df577eb07dc 100644 --- a/packages/cypress/migrations.json +++ b/packages/cypress/migrations.json @@ -30,11 +30,11 @@ "description": "Update to Cypress ^13.6.6 if the workspace is using Cypress v13 to ensure workspaces don't use v13.6.5 which has an issue when verifying Cypress.", "implementation": "./src/migrations/update-18-1-0/update-cypress-version-13-6-6" }, - "update-19-5-5-update-ci-webserver-for-vite": { + "update-19-6-0-update-ci-webserver-for-vite": { "cli": "nx", - "version": "19.5.5-beta.0", + "version": "19.6.0-beta.0", "description": "Update ciWebServerCommand to use previewTargetName if Vite is detected for the application.", - "implementation": "./src/migrations/update-19-5-5/update-ci-webserver-for-vite" + "implementation": "./src/migrations/update-19-6-0/update-ci-webserver-for-vite" } }, "packageJsonUpdates": { diff --git a/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.spec.ts b/packages/cypress/src/migrations/update-19-6-0/update-ci-webserver-for-vite.spec.ts similarity index 100% rename from packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.spec.ts rename to packages/cypress/src/migrations/update-19-6-0/update-ci-webserver-for-vite.spec.ts diff --git a/packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.ts b/packages/cypress/src/migrations/update-19-6-0/update-ci-webserver-for-vite.ts similarity index 100% rename from packages/cypress/src/migrations/update-19-5-5/update-ci-webserver-for-vite.ts rename to packages/cypress/src/migrations/update-19-6-0/update-ci-webserver-for-vite.ts diff --git a/packages/playwright/migrations.json b/packages/playwright/migrations.json index d31b001e985ad..e53a5f3c69189 100644 --- a/packages/playwright/migrations.json +++ b/packages/playwright/migrations.json @@ -12,11 +12,11 @@ "description": "Remove invalid baseUrl option from @nx/playwright:playwright targets in project.json.", "implementation": "./src/migrations/update-18-1-0/remove-baseUrl-from-project-json" }, - "19-5-5-use-serve-static-preview-for-command": { + "19-6-0-use-serve-static-preview-for-command": { "cli": "nx", - "version": "19.5.5-beta.0", + "version": "19.6.0-beta.0", "description": "Use serve-static or preview for webServerCommand.", - "implementation": "./src/migrations/update-19-5-5/use-serve-static-preview-for-command" + "implementation": "./src/migrations/update-19-6-0/use-serve-static-preview-for-command" } } } diff --git a/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.spec.ts b/packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.spec.ts similarity index 100% rename from packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.spec.ts rename to packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.spec.ts diff --git a/packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.ts b/packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.ts similarity index 100% rename from packages/playwright/src/migrations/update-19-5-5/use-serve-static-preview-for-command.ts rename to packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.ts diff --git a/packages/vite/migrations.json b/packages/vite/migrations.json index 3cf4523f77945..4c54200bab427 100644 --- a/packages/vite/migrations.json +++ b/packages/vite/migrations.json @@ -33,10 +33,10 @@ "description": "Move the vitest coverage thresholds in their own object if exists and add reporters.", "implementation": "./src/migrations/update-17-3-0/vitest-coverage-and-reporters" }, - "update-19-5-5-add-depends-on-for-preview-server": { - "version": "19.5.5-beta.0", + "update-19-6-0-add-depends-on-for-preview-server": { + "version": "19.6.0-beta.0", "description": "Add dependsOn: [build] to preview targets using preview-server", - "implementation": "./src/migrations/update-19-5-5/add-depends-on-for-preview" + "implementation": "./src/migrations/update-19-6-0/add-depends-on-for-preview" } }, "packageJsonUpdates": { diff --git a/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.spec.ts b/packages/vite/src/migrations/update-19-6-0/add-depends-on-for-preview.spec.ts similarity index 100% rename from packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.spec.ts rename to packages/vite/src/migrations/update-19-6-0/add-depends-on-for-preview.spec.ts diff --git a/packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.ts b/packages/vite/src/migrations/update-19-6-0/add-depends-on-for-preview.ts similarity index 100% rename from packages/vite/src/migrations/update-19-5-5/add-depends-on-for-preview.ts rename to packages/vite/src/migrations/update-19-6-0/add-depends-on-for-preview.ts From 09621614f63f64ffb4a41cbbcd566f3b395f0945 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Fri, 2 Aug 2024 10:43:58 +0100 Subject: [PATCH 19/19] fix(web): generate serve-static targets --- .../__snapshots__/webpack.legacy.test.ts.snap | 8 +++++++ e2e/webpack/src/webpack.legacy.test.ts | 6 +++--- .../src/generators/application/lib/add-e2e.ts | 21 +++++++++---------- .../application/application.legacy.spec.ts | 20 ++++++++++++++++++ .../src/generators/application/application.ts | 13 +++++++++--- 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/e2e/webpack/src/__snapshots__/webpack.legacy.test.ts.snap b/e2e/webpack/src/__snapshots__/webpack.legacy.test.ts.snap index 9852a63a9406b..31219273146df 100644 --- a/e2e/webpack/src/__snapshots__/webpack.legacy.test.ts.snap +++ b/e2e/webpack/src/__snapshots__/webpack.legacy.test.ts.snap @@ -88,6 +88,14 @@ exports[`Webpack Plugin (legacy) ConvertConfigToWebpackPlugin, should convert wi "lint": { "executor": "@nx/eslint:lint" }, + "serve-static": { + "executor": "@nx/web:file-server", + "dependsOn": ["build"], + "options": { + "buildTarget": "app3224373:build", + "spa": true + } + }, "test": { "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], diff --git a/e2e/webpack/src/webpack.legacy.test.ts b/e2e/webpack/src/webpack.legacy.test.ts index 080ea696740c4..1c9a8c020a861 100644 --- a/e2e/webpack/src/webpack.legacy.test.ts +++ b/e2e/webpack/src/webpack.legacy.test.ts @@ -113,17 +113,17 @@ describe('Webpack Plugin (legacy)', () => { updateFile( `${appName}/src/main.ts`, ` - document.querySelector('proj-root').innerHTML = '

Welcome

'; + document.querySelector('proj-root')!.innerHTML = '

Welcome

'; ` ); updateFile( `${appName}/webpack.config.js`, ` const { join } = require('path'); - const {NxWebpackPlugin} = require('@nx/webpack'); + const {NxAppWebpackPlugin} = require('@nx/webpack/app-plugin'); module.exports = { output: { - path: join(__dirname, '../dist/app9524918'), + path: join(__dirname, '../dist/${appName}'), }, plugins: [ new NxAppWebpackPlugin({ diff --git a/packages/react/src/generators/application/lib/add-e2e.ts b/packages/react/src/generators/application/lib/add-e2e.ts index a6f4617d0d2e1..5da77e12ce49a 100644 --- a/packages/react/src/generators/application/lib/add-e2e.ts +++ b/packages/react/src/generators/application/lib/add-e2e.ts @@ -16,19 +16,18 @@ export async function addE2e( tree: Tree, options: NormalizedSchema ): Promise { + const hasNxBuildPlugin = + (options.bundler === 'webpack' && hasWebpackPlugin(tree)) || + (options.bundler === 'vite' && hasVitePlugin(tree)); + if (!hasNxBuildPlugin) { + await webStaticServeGenerator(tree, { + buildTarget: `${options.projectName}:build`, + targetName: 'serve-static', + spa: true, + }); + } switch (options.e2eTestRunner) { case 'cypress': { - const hasNxBuildPlugin = - (options.bundler === 'webpack' && hasWebpackPlugin(tree)) || - (options.bundler === 'vite' && hasVitePlugin(tree)); - if (!hasNxBuildPlugin) { - await webStaticServeGenerator(tree, { - buildTarget: `${options.projectName}:build`, - targetName: 'serve-static', - spa: true, - }); - } - const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') >('@nx/cypress', nxVersion); diff --git a/packages/web/src/generators/application/application.legacy.spec.ts b/packages/web/src/generators/application/application.legacy.spec.ts index a541524603e59..55616211d8b51 100644 --- a/packages/web/src/generators/application/application.legacy.spec.ts +++ b/packages/web/src/generators/application/application.legacy.spec.ts @@ -106,6 +106,16 @@ describe('web app generator (legacy)', () => { "buildTarget": "my-app:build", }, }, + "serve-static": { + "dependsOn": [ + "build", + ], + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "my-app:build", + "spa": true, + }, + }, "test": { "executor": "@nx/jest:jest", "options": { @@ -204,6 +214,16 @@ describe('web app generator (legacy)', () => { "buildTarget": "my-vite-app:build", }, }, + "serve-static": { + "dependsOn": [ + "build", + ], + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "my-vite-app:build", + "spa": true, + }, + }, "test": { "executor": "@nx/jest:jest", "options": { diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 0d161a2a559a6..5b0df944b12fc 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -42,6 +42,7 @@ import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-com import { VitePluginOptions } from '@nx/vite/src/plugins/plugin'; import { WebpackPluginOptions } from '@nx/webpack/src/plugins/plugin'; import { hasVitePlugin } from '../../utils/has-vite-plugin'; +import staticServeConfiguration from '../static-serve/static-serve-configuration'; interface NormalizedSchema extends Schema { projectName: string; @@ -367,10 +368,16 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { tasks.push(lintTask); } + const hasNxBuildPlugin = + (options.bundler === 'webpack' && hasWebpackPlugin(host)) || + (options.bundler === 'vite' && hasVitePlugin(host)); + if (!hasNxBuildPlugin) { + await staticServeConfiguration(host, { + buildTarget: `${options.projectName}:build`, + spa: true, + }); + } if (options.e2eTestRunner === 'cypress') { - const hasNxBuildPlugin = - (options.bundler === 'webpack' && hasWebpackPlugin(host)) || - (options.bundler === 'vite' && hasVitePlugin(host)); const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') >('@nx/cypress', nxVersion);