feat(angular): add playwright to e2eTestRunner option (#18163)

This commit is contained in:
Caleb Ukle 2023-08-02 10:59:00 -05:00 committed by GitHub
parent b5305604cd
commit 1dcb80d447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 196 additions and 26 deletions

View File

@ -102,8 +102,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": {

View File

@ -109,8 +109,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": {

View File

@ -24,7 +24,8 @@
}, },
"e2eTestRunner": { "e2eTestRunner": {
"type": "string", "type": "string",
"enum": ["cypress", "none"], "enum": ["cypress", "playwright", "none"],
"x-prompt": "Which E2E test runner would you like to use?",
"description": "Test runner to use for end to end (e2e) tests.", "description": "Test runner to use for end to end (e2e) tests.",
"default": "cypress", "default": "cypress",
"x-priority": "important" "x-priority": "important"

View File

@ -103,8 +103,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": {

View File

@ -438,8 +438,8 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
// add other projects // add other projects
const app1 = uniq('app1'); const app1 = uniq('app1');
const lib1 = uniq('lib1'); const lib1 = uniq('lib1');
runCommand(`ng g @schematics/angular:application ${app1}`); runCommand(`ng g @schematics/angular:application ${app1} --no-interactive`);
runCommand(`ng g @schematics/angular:library ${lib1}`); runCommand(`ng g @schematics/angular:library ${lib1} --no-interactive`);
runNgAdd('@nx/angular', '--npm-scope projscope'); runNgAdd('@nx/angular', '--npm-scope projscope');

View File

@ -2,6 +2,7 @@ import { names } from '@nx/devkit';
import { import {
checkFilesExist, checkFilesExist,
cleanupProject, cleanupProject,
ensurePlaywrightBrowsersInstallation,
getSize, getSize,
killPorts, killPorts,
killProcessAndPorts, killProcessAndPorts,
@ -123,6 +124,23 @@ describe('Angular Projects', () => {
await killProcessAndPorts(esbProcess.pid, appPort); await killProcessAndPorts(esbProcess.pid, appPort);
}, 1000000); }, 1000000);
it('should successfully work with playwright for e2e tests', async () => {
const app = uniq('app');
runCLI(
`generate @nx/angular:app ${app} --e2eTestRunner=playwright --no-interactive`
);
if (runCypressTests()) {
ensurePlaywrightBrowsersInstallation();
const e2eResults = runCLI(`e2e ${app}-e2e`);
expect(e2eResults).toContain(
`Successfully ran target e2e for project ${app}-e2e`
);
expect(await killPorts()).toBeTruthy();
}
}, 1000000);
it('should lint correctly with eslint and handle external HTML files and inline templates', async () => { it('should lint correctly with eslint and handle external HTML files and inline templates', async () => {
// check apps and lib pass linting for initial generated code // check apps and lib pass linting for initial generated code
runCLI(`run-many --target lint --projects=${app1},${lib1} --parallel`); runCLI(`run-many --target lint --projects=${app1},${lib1} --parallel`);

View File

@ -49,6 +49,7 @@
"semver", "semver",
// These are installed by ensurePackage so missing in package.json // These are installed by ensurePackage so missing in package.json
"@nx/cypress", "@nx/cypress",
"@nx/playwright",
"@nx/jest", "@nx/jest",
"@nx/rollup", "@nx/rollup",
"@nx/storybook", "@nx/storybook",

View File

@ -57,8 +57,8 @@
"webpack": "^5.80.0", "webpack": "^5.80.0",
"webpack-merge": "^5.8.0", "webpack-merge": "^5.8.0",
"enquirer": "^2.3.6", "enquirer": "^2.3.6",
"@nx/cypress": "file:../cypress",
"@nx/devkit": "file:../devkit", "@nx/devkit": "file:../devkit",
"@nx/cypress": "file:../cypress",
"@nx/jest": "file:../jest", "@nx/jest": "file:../jest",
"@nx/js": "file:../js", "@nx/js": "file:../js",
"@nx/linter": "file:../linter", "@nx/linter": "file:../linter",

View File

@ -74,5 +74,5 @@
}, },
"lint": {} "lint": {}
}, },
"implicitDependencies": ["workspace", "cypress", "jest"] "implicitDependencies": ["workspace", "playwright", "cypress", "jest"]
} }

View File

@ -26,6 +26,13 @@ import type { Schema } from './schema';
// which is v9 while we are testing for the new v10 version // which is v9 while we are testing for the new v10 version
jest.mock('@nx/cypress/src/utils/cypress-version'); jest.mock('@nx/cypress/src/utils/cypress-version');
jest.mock('enquirer'); jest.mock('enquirer');
jest.mock('@nx/devkit', () => {
const original = jest.requireActual('@nx/devkit');
return {
...original,
ensurePackage: (pkg: string) => jest.requireActual(pkg),
};
});
describe('app', () => { describe('app', () => {
let appTree: Tree; let appTree: Tree;
@ -128,6 +135,23 @@ describe('app', () => {
expect(tsconfigE2E).toMatchSnapshot('e2e tsconfig.json'); expect(tsconfigE2E).toMatchSnapshot('e2e tsconfig.json');
}); });
it('should setup playwright', async () => {
await generateApp(appTree, 'playwright-app', {
e2eTestRunner: E2eTestRunner.Playwright,
});
expect(
appTree.exists('apps/playwright-app-e2e/playwright.config.ts')
).toBeTruthy();
expect(
appTree.exists('apps/playwright-app-e2e/src/example.spec.ts')
).toBeTruthy();
expect(
readProjectConfiguration(appTree, 'playwright-app-e2e')?.targets?.e2e
?.executor
).toEqual('@nx/playwright:playwright');
});
it('should setup jest with serializers', async () => { it('should setup jest with serializers', async () => {
await generateApp(appTree); await generateApp(appTree);
@ -869,6 +893,18 @@ describe('app', () => {
const project = readProjectConfiguration(appTree, 'my-app'); const project = readProjectConfiguration(appTree, 'my-app');
expect(project.targets.build.options['outputPath']).toBe('dist/my-app'); expect(project.targets.build.options['outputPath']).toBe('dist/my-app');
}); });
it('should generate playwright with root project', async () => {
await generateApp(appTree, 'root-app', {
e2eTestRunner: E2eTestRunner.Playwright,
rootProject: true,
});
expect(
readProjectConfiguration(appTree, 'e2e').targets.e2e.executor
).toEqual('@nx/playwright:playwright');
expect(appTree.exists('e2e/playwright.config.ts')).toBeTruthy();
expect(appTree.exists('e2e/src/example.spec.ts')).toBeTruthy();
});
}); });
it('should error correctly when Angular version does not support standalone', async () => { it('should error correctly when Angular version does not support standalone', async () => {

View File

@ -1,7 +1,10 @@
import { cypressProjectGenerator } from '@nx/cypress';
import type { Tree } from '@nx/devkit'; import type { Tree } from '@nx/devkit';
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
addProjectConfiguration,
ensurePackage,
getPackageManagerCommand,
joinPathFragments,
readProjectConfiguration, readProjectConfiguration,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nx/devkit'; } from '@nx/devkit';
@ -13,6 +16,9 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
removeScaffoldedE2e(tree, options, options.ngCliSchematicE2ERoot); removeScaffoldedE2e(tree, options, options.ngCliSchematicE2ERoot);
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { cypressProjectGenerator } = ensurePackage<
typeof import('@nx/cypress')
>('@nx/cypress', nxVersion);
// TODO: This can call `@nx/web:static-config` generator when ready // TODO: This can call `@nx/web:static-config` generator when ready
addFileServerTarget(tree, options, 'serve-static'); addFileServerTarget(tree, options, 'serve-static');
@ -25,6 +31,31 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
skipFormat: true, skipFormat: true,
}); });
} else if (options.e2eTestRunner === 'playwright') {
const { configurationGenerator: playwrightConfigurationGenerator } =
ensurePackage<typeof import('@nx/playwright')>(
'@nx/playwright',
nxVersion
);
addProjectConfiguration(tree, options.e2eProjectName, {
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {},
implicitDependencies: [options.name],
});
await playwrightConfigurationGenerator(tree, {
project: options.e2eProjectName,
skipFormat: true,
skipPackageJson: options.skipPackageJson,
directory: 'src',
js: false,
linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
options.name
}`,
webServerAddress: `http://localhost:${options.port ?? 4200}`,
});
} }
} }

View File

@ -105,8 +105,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": {

View File

@ -112,8 +112,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": {

View File

@ -2,9 +2,13 @@ jest.mock('@nx/devkit', () => ({
...jest.requireActual('@nx/devkit'), ...jest.requireActual('@nx/devkit'),
// need to mock so it doesn't resolve what the workspace has installed // need to mock so it doesn't resolve what the workspace has installed
// and be able to test with different versions // and be able to test with different versions
ensurePackage: jest.fn(), ensurePackage: jest.fn(() => ({
cypressInitGenerator: jest.fn(),
initGenerator: jest.fn(),
})),
})); }));
import { import {
ensurePackage,
NxJsonConfiguration, NxJsonConfiguration,
readJson, readJson,
readNxJson, readNxJson,
@ -171,8 +175,41 @@ describe('init', () => {
}); });
describe('--e2e-test-runner', () => { describe('--e2e-test-runner', () => {
describe('playwright', () => {
it('should call @nx/playwright:init', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.None,
e2eTestRunner: E2eTestRunner.Playwright,
linter: Linter.EsLint,
skipFormat: false,
});
expect(ensurePackage).toHaveBeenLastCalledWith(
'@nx/playwright',
'0.0.1'
);
});
it('should set defaults', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.None,
e2eTestRunner: E2eTestRunner.Playwright,
linter: Linter.EsLint,
skipFormat: false,
});
const { generators } = readJson<NxJsonConfiguration>(tree, 'nx.json');
// ASSERT
expect(generators['@nx/angular:application'].e2eTestRunner).toEqual(
'playwright'
);
});
});
describe('cypress', () => { describe('cypress', () => {
it('should add cypress dependencies', async () => { it('should call @nx/cypress:init', async () => {
// ACT // ACT
await init(tree, { await init(tree, {
unitTestRunner: UnitTestRunner.None, unitTestRunner: UnitTestRunner.None,
@ -181,11 +218,7 @@ describe('init', () => {
skipFormat: false, skipFormat: false,
}); });
const { devDependencies } = readJson(tree, 'package.json'); expect(ensurePackage).toHaveBeenLastCalledWith('@nx/cypress', '0.0.1');
// ASSERT
expect(devDependencies['@nx/cypress']).toBeDefined();
expect(devDependencies['cypress']).toBeDefined();
}); });
it('should set defaults', async () => { it('should set defaults', async () => {
@ -523,6 +556,38 @@ bar
}); });
describe('--e2e-test-runner', () => { describe('--e2e-test-runner', () => {
it('should call @nx/playwright:init', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.None,
e2eTestRunner: E2eTestRunner.Playwright,
linter: Linter.EsLint,
skipFormat: false,
});
expect(ensurePackage).toHaveBeenLastCalledWith(
'@nx/playwright',
'0.0.1'
);
});
it('should set defaults', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.None,
e2eTestRunner: E2eTestRunner.Playwright,
linter: Linter.EsLint,
skipFormat: false,
});
const { generators } = readJson<NxJsonConfiguration>(tree, 'nx.json');
// ASSERT
expect(generators['@nx/angular:application'].e2eTestRunner).toEqual(
'playwright'
);
});
describe('cypress', () => { describe('cypress', () => {
it('should add cypress dependencies', async () => { it('should add cypress dependencies', async () => {
// ACT // ACT
@ -532,12 +597,11 @@ bar
linter: Linter.EsLint, linter: Linter.EsLint,
skipFormat: false, skipFormat: false,
}); });
const { devDependencies } = readJson(tree, 'package.json');
// ASSERT // ASSERT
expect(devDependencies['@nx/cypress']).toBeDefined(); expect(ensurePackage).toHaveBeenLastCalledWith(
expect(devDependencies['cypress']).toBeDefined(); '@nx/cypress',
'0.0.1'
);
}); });
it('should set defaults', async () => { it('should set defaults', async () => {

View File

@ -1,4 +1,3 @@
import { cypressInitGenerator } from '@nx/cypress';
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
ensurePackage, ensurePackage,
@ -21,6 +20,7 @@ import {
} from '../utils/version-utils'; } from '../utils/version-utils';
import type { PackageVersions } from '../../utils/backward-compatible-versions'; import type { PackageVersions } from '../../utils/backward-compatible-versions';
import { Schema } from './schema'; import { Schema } from './schema';
import { nxVersion } from '../../utils/versions';
export async function angularInitGenerator( export async function angularInitGenerator(
tree: Tree, tree: Tree,
@ -198,9 +198,20 @@ async function addE2ETestRunner(
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
switch (options.e2eTestRunner) { switch (options.e2eTestRunner) {
case E2eTestRunner.Cypress: case E2eTestRunner.Cypress:
const { cypressInitGenerator } = ensurePackage<
typeof import('@nx/cypress')
>('@nx/cypress', nxVersion);
return cypressInitGenerator(tree, { return cypressInitGenerator(tree, {
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
}); });
case E2eTestRunner.Playwright:
const { initGenerator: playwrightInitGenerator } = ensurePackage<
typeof import('@nx/playwright')
>('@nx/playwright', nxVersion);
return playwrightInitGenerator(tree, {
skipFormat: true,
skipPackageJson: options.skipPackageJson,
});
default: default:
return () => {}; return () => {};
} }

View File

@ -21,7 +21,8 @@
}, },
"e2eTestRunner": { "e2eTestRunner": {
"type": "string", "type": "string",
"enum": ["cypress", "none"], "enum": ["cypress", "playwright", "none"],
"x-prompt": "Which E2E test runner would you like to use?",
"description": "Test runner to use for end to end (e2e) tests.", "description": "Test runner to use for end to end (e2e) tests.",
"default": "cypress", "default": "cypress",
"x-priority": "important" "x-priority": "important"

View File

@ -106,8 +106,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": {

View File

@ -5,5 +5,6 @@ export enum UnitTestRunner {
export enum E2eTestRunner { export enum E2eTestRunner {
Cypress = 'cypress', Cypress = 'cypress',
Playwright = 'playwright',
None = 'none', None = 'none',
} }