diff --git a/docs/generated/packages/remix/generators/application.json b/docs/generated/packages/remix/generators/application.json index 2d2e45eec901d..67b65b0bcc4a8 100644 --- a/docs/generated/packages/remix/generators/application.json +++ b/docs/generated/packages/remix/generators/application.json @@ -45,7 +45,7 @@ }, "e2eTestRunner": { "type": "string", - "enum": ["cypress", "none"], + "enum": ["cypress", "playwright", "none"], "default": "cypress", "description": "Test runner to use for e2e tests" }, diff --git a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap index 559da7b1ef73a..f73d0613e55af 100644 --- a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap +++ b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap @@ -144,7 +144,7 @@ export default function Index() { " `; -exports[`Remix Application Integrated Repo --projectNameAndRootFormat=as-provided --e2eTestRunner should generate an e2e application for the app 1`] = ` +exports[`Remix Application Integrated Repo --projectNameAndRootFormat=as-provided --e2eTestRunner should generate a cypress e2e application for the app 1`] = ` "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; import { defineConfig } from 'cypress'; @@ -158,6 +158,79 @@ export default defineConfig({ " `; +exports[`Remix Application Integrated Repo --projectNameAndRootFormat=as-provided --e2eTestRunner should generate a playwright e2e application for the app 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: 'pnpm exec nx serve test', + 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[`Remix Application Integrated Repo --projectNameAndRootFormat=as-provided --js should create the application correctly 1`] = ` "import { createWatchPaths } from '@nx/remix'; import { dirname } from 'path'; @@ -549,7 +622,7 @@ export default function Index() { " `; -exports[`Remix Application Integrated Repo --projectNameAndRootFormat=derived --e2eTestRunner should generate an e2e application for the app 1`] = ` +exports[`Remix Application Integrated Repo --projectNameAndRootFormat=derived --e2eTestRunner should generate a cypress e2e application for the app 1`] = ` "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; import { defineConfig } from 'cypress'; @@ -563,6 +636,79 @@ export default defineConfig({ " `; +exports[`Remix Application Integrated Repo --projectNameAndRootFormat=derived --e2eTestRunner should generate a playwright e2e application for the app 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: 'pnpm exec nx serve test', + 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[`Remix Application Integrated Repo --projectNameAndRootFormat=derived --js should create the application correctly 1`] = ` "import { createWatchPaths } from '@nx/remix'; import { dirname } from 'path'; @@ -810,7 +956,7 @@ export default function Index() { " `; -exports[`Remix Application Standalone Project Repo --e2eTestRunner should generate an e2e application for the app 1`] = ` +exports[`Remix Application Standalone Project Repo --e2eTestRunner should generate a cypress e2e application for the app 1`] = ` "import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; import { defineConfig } from 'cypress'; @@ -1157,3 +1303,76 @@ exports[`Remix Application Standalone Project Repo should create the application } " `; + +exports[`Remix Application Standalone Project Repo should generate a playwright e2e application for the app 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: 'pnpm exec nx serve test', + 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' }, + } */ + ], +}); +" +`; diff --git a/packages/remix/src/generators/application/application.impl.spec.ts b/packages/remix/src/generators/application/application.impl.spec.ts index c1a4a60841302..6346c60a804e6 100644 --- a/packages/remix/src/generators/application/application.impl.spec.ts +++ b/packages/remix/src/generators/application/application.impl.spec.ts @@ -103,7 +103,7 @@ describe('Remix Application', () => { }); describe('--e2eTestRunner', () => { - it('should generate an e2e application for the app', async () => { + it('should generate a cypress e2e application for the app', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(); @@ -121,6 +121,24 @@ describe('Remix Application', () => { expect(tree.read('e2e/cypress.config.ts', 'utf-8')).toMatchSnapshot(); }); }); + + it('should generate a playwright e2e application for the app', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + + // ACT + await applicationGenerator(tree, { + name: 'test', + e2eTestRunner: 'playwright', + rootProject: true, + addPlugin: true, + }); + + // ASSERT + expectTargetsToBeCorrect(tree, '.'); + + expect(tree.read('e2e/playwright.config.ts', 'utf-8')).toMatchSnapshot(); + }); }); describe.each([ @@ -294,7 +312,7 @@ describe('Remix Application', () => { }); describe('--e2eTestRunner', () => { - it('should generate an e2e application for the app', async () => { + it('should generate a cypress e2e application for the app', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); @@ -313,6 +331,26 @@ describe('Remix Application', () => { tree.read(`${appDir}-e2e/cypress.config.ts`, 'utf-8') ).toMatchSnapshot(); }); + + it('should generate a playwright e2e application for the app', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + + // ACT + await applicationGenerator(tree, { + name: 'test', + e2eTestRunner: 'playwright', + projectNameAndRootFormat, + addPlugin: true, + }); + + // ASSERT + expectTargetsToBeCorrect(tree, appDir); + + expect( + tree.read(`${appDir}-e2e/playwright.config.ts`, 'utf-8') + ).toMatchSnapshot(); + }); }); } ); diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts index 629abdfd42306..b2563364e8fc5 100644 --- a/packages/remix/src/generators/application/application.impl.ts +++ b/packages/remix/src/generators/application/application.impl.ts @@ -28,11 +28,7 @@ import { typesReactDomVersion, typesReactVersion, } from '../../utils/versions'; -import { - NormalizedSchema, - normalizeOptions, - updateUnitTestConfig, -} from './lib'; +import { normalizeOptions, updateUnitTestConfig, addE2E } from './lib'; import { NxRemixGeneratorSchema } from './schema'; import { updateDependencies } from '../utils/update-dependencies'; import initGenerator from '../init/init'; @@ -270,31 +266,7 @@ export async function remixApplicationGeneratorInternal( extractTsConfigBase(tree); } - // TODO(@columferry): add support for playwright? - if (options.e2eTestRunner === 'cypress') { - const { configurationGenerator } = ensurePackage< - typeof import('@nx/cypress') - >('@nx/cypress', getPackageVersion(tree, 'nx')); - addFileServerTarget(tree, options, 'serve-static'); - addProjectConfiguration(tree, options.e2eProjectName, { - projectType: 'application', - root: options.e2eProjectRoot, - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), - targets: {}, - tags: [], - implicitDependencies: [options.projectName], - }); - tasks.push( - await configurationGenerator(tree, { - project: options.e2eProjectName, - directory: 'src', - skipFormat: true, - devServerTarget: `${options.projectName}:serve:development`, - baseUrl: 'http://localhost:4200', - addPlugin: options.addPlugin, - }) - ); - } + tasks.push(await addE2E(tree, options)); if (!options.skipFormat) { await formatFiles(tree); @@ -307,20 +279,4 @@ export async function remixApplicationGeneratorInternal( return runTasksInSerial(...tasks); } -function addFileServerTarget( - tree: Tree, - options: NormalizedSchema, - targetName: string -) { - const projectConfig = readProjectConfiguration(tree, options.projectName); - projectConfig.targets[targetName] = { - executor: '@nx/web:file-server', - options: { - buildTarget: `${options.projectName}:build`, - port: 4200, - }, - }; - updateProjectConfiguration(tree, options.projectName, projectConfig); -} - export default remixApplicationGenerator; diff --git a/packages/remix/src/generators/application/lib/add-e2e.ts b/packages/remix/src/generators/application/lib/add-e2e.ts new file mode 100644 index 0000000000000..de1d61bea9315 --- /dev/null +++ b/packages/remix/src/generators/application/lib/add-e2e.ts @@ -0,0 +1,86 @@ +import { + type Tree, + addProjectConfiguration, + joinPathFragments, + readProjectConfiguration, + updateProjectConfiguration, + ensurePackage, + getPackageManagerCommand, +} from '@nx/devkit'; +import { type NormalizedSchema } from './normalize-options'; +import { getPackageVersion } from '../../../utils/versions'; + +export async function addE2E(tree: Tree, options: NormalizedSchema) { + if (options.e2eTestRunner === 'cypress') { + const { configurationGenerator } = ensurePackage< + typeof import('@nx/cypress') + >('@nx/cypress', getPackageVersion(tree, 'nx')); + + addFileServerTarget(tree, options, 'serve-static'); + + addProjectConfiguration(tree, options.e2eProjectName, { + projectType: 'application', + root: options.e2eProjectRoot, + sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), + targets: {}, + tags: [], + implicitDependencies: [options.projectName], + }); + + return await configurationGenerator(tree, { + project: options.e2eProjectName, + directory: 'src', + skipFormat: true, + devServerTarget: `${options.projectName}:serve:development`, + baseUrl: 'http://localhost:4200', + addPlugin: options.addPlugin, + }); + } else if (options.e2eTestRunner === 'playwright') { + const { configurationGenerator } = ensurePackage< + typeof import('@nx/playwright') + >('@nx/playwright', getPackageVersion(tree, 'nx')); + + addProjectConfiguration(tree, options.e2eProjectName, { + projectType: 'application', + root: options.e2eProjectRoot, + sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), + targets: {}, + tags: [], + implicitDependencies: [options.projectName], + }); + + return configurationGenerator(tree, { + project: options.e2eProjectName, + skipFormat: true, + skipPackageJson: false, + directory: 'src', + js: false, + linter: options.linter, + setParserOptionsProject: false, + webServerCommand: `${getPackageManagerCommand().exec} nx serve ${ + options.name + }`, + webServerAddress: 'http://localhost:4200', + rootProject: options.rootProject, + addPlugin: options.addPlugin, + }); + } else { + return () => {}; + } +} + +function addFileServerTarget( + tree: Tree, + options: NormalizedSchema, + targetName: string +) { + const projectConfig = readProjectConfiguration(tree, options.projectName); + projectConfig.targets[targetName] = { + executor: '@nx/web:file-server', + options: { + buildTarget: `${options.projectName}:build`, + port: 4200, + }, + }; + updateProjectConfiguration(tree, options.projectName, projectConfig); +} diff --git a/packages/remix/src/generators/application/lib/index.ts b/packages/remix/src/generators/application/lib/index.ts index df3573ed82c11..713c924e664b3 100644 --- a/packages/remix/src/generators/application/lib/index.ts +++ b/packages/remix/src/generators/application/lib/index.ts @@ -1,2 +1,3 @@ export * from './normalize-options'; export * from './update-unit-test-config'; +export * from './add-e2e'; diff --git a/packages/remix/src/generators/application/schema.d.ts b/packages/remix/src/generators/application/schema.d.ts index 52fa508f09bd8..7b3da9aee6c43 100644 --- a/packages/remix/src/generators/application/schema.d.ts +++ b/packages/remix/src/generators/application/schema.d.ts @@ -9,7 +9,7 @@ export interface NxRemixGeneratorSchema { projectNameAndRootFormat?: ProjectNameAndRootFormat; linter?: Linter; unitTestRunner?: 'vitest' | 'jest' | 'none'; - e2eTestRunner?: 'cypress' | 'none'; + e2eTestRunner?: 'cypress' | 'playwright' | 'none'; skipFormat?: boolean; rootProject?: boolean; addPlugin?: boolean; diff --git a/packages/remix/src/generators/application/schema.json b/packages/remix/src/generators/application/schema.json index 8ead343cd4a33..3d5d894d05cbe 100644 --- a/packages/remix/src/generators/application/schema.json +++ b/packages/remix/src/generators/application/schema.json @@ -45,7 +45,7 @@ }, "e2eTestRunner": { "type": "string", - "enum": ["cypress", "none"], + "enum": ["cypress", "playwright", "none"], "default": "cypress", "description": "Test runner to use for e2e tests" },