feat(nextjs): add playwright as an option for e2e testing (#18281)
This commit is contained in:
parent
3313c7619a
commit
e78575badc
@ -81,8 +81,9 @@
|
|||||||
},
|
},
|
||||||
"e2eTestRunner": {
|
"e2eTestRunner": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["cypress", "none"],
|
"enum": ["cypress", "playwright", "none"],
|
||||||
"description": "Test runner to use for end to end (E2E) tests.",
|
"description": "Test runner to use for end to end (E2E) tests.",
|
||||||
|
"x-prompt": "Which E2E test runner would you like to use?",
|
||||||
"default": "cypress"
|
"default": "cypress"
|
||||||
},
|
},
|
||||||
"tags": {
|
"tags": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
|
isNotWindows,
|
||||||
newProject,
|
newProject,
|
||||||
runCLI,
|
runCLI,
|
||||||
uniq,
|
uniq,
|
||||||
@ -22,7 +23,7 @@ describe('Next.js App Router', () => {
|
|||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
const jsLib = uniq('tslib');
|
const jsLib = uniq('tslib');
|
||||||
|
|
||||||
runCLI(`generate @nx/next:app ${appName}`);
|
runCLI(`generate @nx/next:app ${appName} --e2eTestRunner=playwright`);
|
||||||
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`);
|
runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`);
|
||||||
|
|
||||||
updateFile(
|
updateFile(
|
||||||
@ -42,7 +43,7 @@ describe('Next.js App Router', () => {
|
|||||||
await checkApp(appName, {
|
await checkApp(appName, {
|
||||||
checkUnitTest: false,
|
checkUnitTest: false,
|
||||||
checkLint: true,
|
checkLint: true,
|
||||||
checkE2E: false,
|
checkE2E: isNotWindows(),
|
||||||
checkExport: false,
|
checkExport: false,
|
||||||
});
|
});
|
||||||
}, 300_000);
|
}, 300_000);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { execSync } from 'child_process';
|
|
||||||
import {
|
import {
|
||||||
checkFilesExist,
|
checkFilesExist,
|
||||||
killPort,
|
killPorts,
|
||||||
readJson,
|
readJson,
|
||||||
runCLI,
|
runCLI,
|
||||||
runCLIAsync,
|
runCLIAsync,
|
||||||
@ -43,10 +42,10 @@ export async function checkApp(
|
|||||||
|
|
||||||
if (opts.checkE2E && runCypressTests()) {
|
if (opts.checkE2E && runCypressTests()) {
|
||||||
const e2eResults = runCLI(
|
const e2eResults = runCLI(
|
||||||
`e2e ${appName}-e2e --no-watch --configuration=production --port=9000`
|
`e2e ${appName}-e2e --no-watch --configuration=production`
|
||||||
);
|
);
|
||||||
expect(e2eResults).toContain('All specs passed!');
|
expect(e2eResults).toContain('Successfully ran target e2e for project');
|
||||||
expect(await killPort(9000)).toBeTruthy();
|
expect(await killPorts()).toBeTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.checkExport) {
|
if (opts.checkExport) {
|
||||||
|
|||||||
@ -82,6 +82,7 @@
|
|||||||
"@nx/webpack",
|
"@nx/webpack",
|
||||||
"@nx/cypress",
|
"@nx/cypress",
|
||||||
"@nx/jest",
|
"@nx/jest",
|
||||||
|
"@nx/playwright",
|
||||||
"typescript",
|
"typescript",
|
||||||
"react",
|
"react",
|
||||||
"webpack",
|
"webpack",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
convertNxGenerator,
|
convertNxGenerator,
|
||||||
formatFiles,
|
formatFiles,
|
||||||
|
GeneratorCallback,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
runTasksInSerial,
|
runTasksInSerial,
|
||||||
Tree,
|
Tree,
|
||||||
@ -8,7 +9,7 @@ import {
|
|||||||
|
|
||||||
import { normalizeOptions } from './lib/normalize-options';
|
import { normalizeOptions } from './lib/normalize-options';
|
||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
import { addCypress } from './lib/add-cypress';
|
import { addE2e } from './lib/add-e2e';
|
||||||
import { addJest } from './lib/add-jest';
|
import { addJest } from './lib/add-jest';
|
||||||
import { addProject } from './lib/add-project';
|
import { addProject } from './lib/add-project';
|
||||||
import { createApplicationFiles } from './lib/create-application-files';
|
import { createApplicationFiles } from './lib/create-application-files';
|
||||||
@ -22,6 +23,7 @@ import { updateCypressTsConfig } from './lib/update-cypress-tsconfig';
|
|||||||
import { showPossibleWarnings } from './lib/show-possible-warnings';
|
import { showPossibleWarnings } from './lib/show-possible-warnings';
|
||||||
|
|
||||||
export async function applicationGenerator(host: Tree, schema: Schema) {
|
export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
const options = normalizeOptions(host, schema);
|
const options = normalizeOptions(host, schema);
|
||||||
|
|
||||||
showPossibleWarnings(host, options);
|
showPossibleWarnings(host, options);
|
||||||
@ -30,17 +32,28 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
|||||||
...options,
|
...options,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
});
|
});
|
||||||
|
tasks.push(nextTask);
|
||||||
|
|
||||||
createApplicationFiles(host, options);
|
createApplicationFiles(host, options);
|
||||||
addProject(host, options);
|
addProject(host, options);
|
||||||
const cypressTask = await addCypress(host, options);
|
|
||||||
|
const e2eTask = await addE2e(host, options);
|
||||||
|
tasks.push(e2eTask);
|
||||||
|
|
||||||
const jestTask = await addJest(host, options);
|
const jestTask = await addJest(host, options);
|
||||||
|
tasks.push(jestTask);
|
||||||
|
|
||||||
const lintTask = await addLinting(host, options);
|
const lintTask = await addLinting(host, options);
|
||||||
updateJestConfig(host, options);
|
tasks.push(lintTask);
|
||||||
updateCypressTsConfig(host, options);
|
|
||||||
const styledTask = addStyleDependencies(host, {
|
const styledTask = addStyleDependencies(host, {
|
||||||
style: options.style,
|
style: options.style,
|
||||||
swc: !host.exists(joinPathFragments(options.appProjectRoot, '.babelrc')),
|
swc: !host.exists(joinPathFragments(options.appProjectRoot, '.babelrc')),
|
||||||
});
|
});
|
||||||
|
tasks.push(styledTask);
|
||||||
|
|
||||||
|
updateJestConfig(host, options);
|
||||||
|
updateCypressTsConfig(host, options);
|
||||||
setDefaults(host, options);
|
setDefaults(host, options);
|
||||||
|
|
||||||
if (options.customServer) {
|
if (options.customServer) {
|
||||||
@ -54,13 +67,7 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
|||||||
await formatFiles(host);
|
await formatFiles(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
return runTasksInSerial(
|
return runTasksInSerial(...tasks);
|
||||||
nextTask,
|
|
||||||
cypressTask,
|
|
||||||
jestTask,
|
|
||||||
lintTask,
|
|
||||||
styledTask
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const applicationSchematic = convertNxGenerator(applicationGenerator);
|
export const applicationSchematic = convertNxGenerator(applicationGenerator);
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
import { ensurePackage, Tree } from '@nx/devkit';
|
|
||||||
import { Linter } from '@nx/linter';
|
|
||||||
|
|
||||||
import { nxVersion } from '../../../utils/versions';
|
|
||||||
import { NormalizedSchema } from './normalize-options';
|
|
||||||
|
|
||||||
export async function addCypress(host: Tree, options: NormalizedSchema) {
|
|
||||||
if (options.e2eTestRunner !== 'cypress') {
|
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { cypressProjectGenerator } = ensurePackage<
|
|
||||||
typeof import('@nx/cypress')
|
|
||||||
>('@nx/cypress', nxVersion);
|
|
||||||
return cypressProjectGenerator(host, {
|
|
||||||
...options,
|
|
||||||
linter: Linter.EsLint,
|
|
||||||
name: options.e2eProjectName,
|
|
||||||
directory: options.directory,
|
|
||||||
project: options.projectName,
|
|
||||||
skipFormat: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
51
packages/next/src/generators/application/lib/add-e2e.ts
Normal file
51
packages/next/src/generators/application/lib/add-e2e.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
ensurePackage,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
joinPathFragments,
|
||||||
|
Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { Linter } from '@nx/linter';
|
||||||
|
|
||||||
|
import { nxVersion } from '../../../utils/versions';
|
||||||
|
import { NormalizedSchema } from './normalize-options';
|
||||||
|
|
||||||
|
export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||||
|
if (options.e2eTestRunner === 'cypress') {
|
||||||
|
const { cypressProjectGenerator } = ensurePackage<
|
||||||
|
typeof import('@nx/cypress')
|
||||||
|
>('@nx/cypress', nxVersion);
|
||||||
|
return cypressProjectGenerator(host, {
|
||||||
|
...options,
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
name: options.e2eProjectName,
|
||||||
|
directory: options.directory,
|
||||||
|
project: options.projectName,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
} else if (options.e2eTestRunner === 'playwright') {
|
||||||
|
const { configurationGenerator } = ensurePackage<
|
||||||
|
typeof import('@nx/playwright')
|
||||||
|
>('@nx/playwright', nxVersion);
|
||||||
|
addProjectConfiguration(host, options.e2eProjectName, {
|
||||||
|
root: options.e2eProjectRoot,
|
||||||
|
sourceRoot: joinPathFragments(options.e2eProjectRoot, ''),
|
||||||
|
targets: {},
|
||||||
|
implicitDependencies: [options.projectName],
|
||||||
|
});
|
||||||
|
return configurationGenerator(host, {
|
||||||
|
project: options.e2eProjectName,
|
||||||
|
skipFormat: true,
|
||||||
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
directory: 'src',
|
||||||
|
js: false,
|
||||||
|
linter: options.linter,
|
||||||
|
setParserOptionsProject: options.setParserOptionsProject,
|
||||||
|
webServerAddress: 'http://127.0.0.1:4200',
|
||||||
|
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
|
||||||
|
options.name
|
||||||
|
}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@ export interface Schema {
|
|||||||
directory?: string;
|
directory?: string;
|
||||||
tags?: string;
|
tags?: string;
|
||||||
unitTestRunner?: 'jest' | 'none';
|
unitTestRunner?: 'jest' | 'none';
|
||||||
e2eTestRunner?: 'cypress' | 'none';
|
e2eTestRunner?: 'cypress' | 'playwright' | 'none';
|
||||||
linter?: Linter;
|
linter?: Linter;
|
||||||
js?: boolean;
|
js?: boolean;
|
||||||
setParserOptionsProject?: boolean;
|
setParserOptionsProject?: boolean;
|
||||||
|
|||||||
@ -84,8 +84,9 @@
|
|||||||
},
|
},
|
||||||
"e2eTestRunner": {
|
"e2eTestRunner": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["cypress", "none"],
|
"enum": ["cypress", "playwright", "none"],
|
||||||
"description": "Test runner to use for end to end (E2E) tests.",
|
"description": "Test runner to use for end to end (E2E) tests.",
|
||||||
|
"x-prompt": "Which E2E test runner would you like to use?",
|
||||||
"default": "cypress"
|
"default": "cypress"
|
||||||
},
|
},
|
||||||
"tags": {
|
"tags": {
|
||||||
|
|||||||
@ -54,14 +54,26 @@ export async function nextInitGenerator(host: Tree, schema: InitSchema) {
|
|||||||
const jestTask = await jestInitGenerator(host, schema);
|
const jestTask = await jestInitGenerator(host, schema);
|
||||||
tasks.push(jestTask);
|
tasks.push(jestTask);
|
||||||
}
|
}
|
||||||
if (!schema.e2eTestRunner || schema.e2eTestRunner === 'cypress') {
|
if (schema.e2eTestRunner === 'cypress') {
|
||||||
const { cypressInitGenerator } = ensurePackage<
|
const { cypressInitGenerator } = ensurePackage<
|
||||||
typeof import('@nx/cypress')
|
typeof import('@nx/cypress')
|
||||||
>('@nx/cypress', nxVersion);
|
>('@nx/cypress', nxVersion);
|
||||||
const cypressTask = await cypressInitGenerator(host, {});
|
const cypressTask = await cypressInitGenerator(host, {});
|
||||||
tasks.push(cypressTask);
|
tasks.push(cypressTask);
|
||||||
|
} else if (schema.e2eTestRunner === 'playwright') {
|
||||||
|
const { initGenerator } = ensurePackage<typeof import('@nx/playwright')>(
|
||||||
|
'@nx/playwright',
|
||||||
|
nxVersion
|
||||||
|
);
|
||||||
|
const playwrightTask = await initGenerator(host, {
|
||||||
|
skipFormat: true,
|
||||||
|
skipPackageJson: schema.skipPackageJson,
|
||||||
|
});
|
||||||
|
tasks.push(playwrightTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// TODO(jack): remove once the React Playwright PR lands first
|
||||||
const reactTask = await reactInitGenerator(host, {
|
const reactTask = await reactInitGenerator(host, {
|
||||||
...schema,
|
...schema,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export interface InitSchema {
|
export interface InitSchema {
|
||||||
unitTestRunner?: 'jest' | 'none';
|
unitTestRunner?: 'jest' | 'none';
|
||||||
e2eTestRunner?: 'cypress' | 'none';
|
e2eTestRunner?: 'cypress' | 'playwright' | 'none';
|
||||||
skipFormat?: boolean;
|
skipFormat?: boolean;
|
||||||
js?: boolean;
|
js?: boolean;
|
||||||
skipPackageJson?: boolean;
|
skipPackageJson?: boolean;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user