mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore(ng-schematics): Spawn server when running ng e2e
(#9306)
**What kind of change does this PR introduce?** Spawn own server when running `ng e2e`. Give user option to not replace `ng e2e`. **Did you add tests for your changes?** Yes. **If relevant, did you update the documentation?** Yes, `ng-schematics` README.md updated. **Summary** When running `ng-schematics`'s `ng e2e` command spawns it's own server. This way we remove the need of developers to run `ng server` separately thus increasing ease of use in development and CI. We want to support Protractor migration so we give the user the option to opt out of replacing `ng e2e` so they can have a gradual migration. (Note: There may be issues with folder conflicts, to be address in a PR for adding better Migration support) **Does this PR introduce a breaking change?** Yes, as we don't check if required options are there before spawning the server. **Other information** Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
This commit is contained in:
parent
181b20fedf
commit
689a084251
@ -26,7 +26,7 @@ With the schematics installed you can run E2E tests:
|
||||
ng e2e
|
||||
```
|
||||
|
||||
> Note: Server must be running before executing the command.
|
||||
> Note: Command spawns it's own server on the same port `ng serve` does.
|
||||
|
||||
## Options
|
||||
|
||||
@ -34,9 +34,14 @@ When adding schematics to your project you can to provide following options:
|
||||
|
||||
| Option | Description | Value | Required |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------- |
|
||||
| `--isDefaultTester` | When true, replaces default `ng e2e` command. | `boolean` | `true` |
|
||||
| `--exportConfig` | When true, creates an empty [Puppeteer configuration](https://pptr.dev/guides/configuration) file. (`.puppeteerrc.cjs`) | `boolean` | `true` |
|
||||
| `--testingFramework` | The testing framework to install along side Puppeteer. | `"jasmine"`, `"jest"`, `"mocha"`, `"node"` | `true` |
|
||||
|
||||
## Contributing
|
||||
|
||||
Check out our [contributing guide](https://pptr.dev/contributing) to get an overview of what you need to develop in the Puppeteer repo.
|
||||
|
||||
### Unit Testing
|
||||
|
||||
The schematics utilize `@angular-devkit/schematics/testing` for verifying correct file creation and `package.json` updates. To execute the test suit:
|
||||
|
@ -2,11 +2,22 @@ import {
|
||||
createBuilder,
|
||||
BuilderContext,
|
||||
BuilderOutput,
|
||||
targetFromTargetString,
|
||||
BuilderRun,
|
||||
} from '@angular-devkit/architect';
|
||||
import {JsonObject} from '@angular-devkit/core';
|
||||
import {spawn} from 'child_process';
|
||||
|
||||
import {PuppeteerBuilderOptions} from './types.js';
|
||||
|
||||
const terminalStyles = {
|
||||
blue: '\u001b[34m',
|
||||
green: '\u001b[32m',
|
||||
bold: '\u001b[1m',
|
||||
reverse: '\u001b[7m',
|
||||
clear: '\u001b[0m',
|
||||
};
|
||||
|
||||
function getError(executable: string, args: string[]) {
|
||||
return (
|
||||
`Puppeteer E2E tests failed!` +
|
||||
@ -38,6 +49,7 @@ function getExecutable(command: string[]) {
|
||||
|
||||
async function executeCommand(context: BuilderContext, command: string[]) {
|
||||
await new Promise((resolve, reject) => {
|
||||
context.logger.debug(`Trying to execute command - ${command.join(' ')}.`);
|
||||
const {executable, args, error} = getExecutable(command);
|
||||
|
||||
const child = spawn(executable, args, {
|
||||
@ -60,22 +72,65 @@ async function executeCommand(context: BuilderContext, command: string[]) {
|
||||
});
|
||||
}
|
||||
|
||||
function message(
|
||||
message: string,
|
||||
context: BuilderContext,
|
||||
type: 'info' | 'success' = 'info'
|
||||
): void {
|
||||
const color = type === 'info' ? terminalStyles.blue : terminalStyles.green;
|
||||
context.logger.info(
|
||||
`${terminalStyles.bold}${terminalStyles.reverse}${color}${message}${terminalStyles.clear}`
|
||||
);
|
||||
}
|
||||
|
||||
async function startServer(
|
||||
options: PuppeteerBuilderOptions,
|
||||
context: BuilderContext
|
||||
): Promise<BuilderRun> {
|
||||
context.logger.debug('Trying to start server.');
|
||||
const target = targetFromTargetString(options.devServerTarget);
|
||||
const defaultServerOptions = await context.getTargetOptions(target);
|
||||
|
||||
const overrides = {
|
||||
watch: false,
|
||||
host: defaultServerOptions['host'],
|
||||
port: defaultServerOptions['port'],
|
||||
} as JsonObject;
|
||||
|
||||
message('Spawning test server...\n', context);
|
||||
const server = await context.scheduleTarget(target, overrides);
|
||||
const result = await server.result;
|
||||
if (!result.success) {
|
||||
throw new Error('Failed to spawn server! Stopping tests...');
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
async function executeE2ETest(
|
||||
options: PuppeteerBuilderOptions,
|
||||
context: BuilderContext
|
||||
): Promise<BuilderOutput> {
|
||||
context.logger.debug('Running commands for E2E test.');
|
||||
let server: BuilderRun | null = null;
|
||||
try {
|
||||
server = await startServer(options, context);
|
||||
|
||||
message('\nRunning tests...\n', context);
|
||||
for (const command of options.commands) {
|
||||
await executeCommand(context, command);
|
||||
}
|
||||
|
||||
message('\nTest ran successfully!', context, 'success');
|
||||
return {success: true};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return {success: false, error: error.message};
|
||||
}
|
||||
return {success: false, error: error as any};
|
||||
} finally {
|
||||
if (server) {
|
||||
await server.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,10 @@
|
||||
}
|
||||
},
|
||||
"description": "Commands to execute in the repo. Commands prefixed with `./node_modules/bin` (Exception: 'node')."
|
||||
},
|
||||
"devServerTarget": {
|
||||
"type": "string",
|
||||
"description": "Angular target that spawns the server."
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
@ -20,4 +20,5 @@ type Command = [string, ...string[]];
|
||||
|
||||
export interface PuppeteerBuilderOptions extends JsonObject {
|
||||
commands: Command[];
|
||||
devServerTarget: string;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import {of} from 'rxjs';
|
||||
import {
|
||||
addBaseFiles,
|
||||
addFrameworkFiles,
|
||||
getScriptFromOptions,
|
||||
getNgCommandName,
|
||||
} from '../utils/files.js';
|
||||
import {
|
||||
addPackageJsonDependencies,
|
||||
@ -75,16 +75,23 @@ function addDependencies(options: SchematicsOptions): Rule {
|
||||
};
|
||||
}
|
||||
|
||||
function updateScripts(_options: SchematicsOptions): Rule {
|
||||
function updateScripts(options: SchematicsOptions): Rule {
|
||||
return (tree: Tree, context: SchematicContext): Tree => {
|
||||
context.logger.debug('Updating "package.json" scripts');
|
||||
const angularJson = getAngularConfig(tree);
|
||||
const projects = Object.keys(angularJson['projects']);
|
||||
|
||||
return addPackageJsonScripts(tree, [
|
||||
{
|
||||
name: 'e2e',
|
||||
script: 'ng e2e',
|
||||
},
|
||||
]);
|
||||
if (projects.length === 1) {
|
||||
const name = getNgCommandName(options);
|
||||
const prefix = options.isDefaultTester ? '' : `run ${projects[0]}:`;
|
||||
return addPackageJsonScripts(tree, [
|
||||
{
|
||||
name,
|
||||
script: `ng ${prefix}${name}`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
|
||||
@ -115,8 +122,7 @@ function addOtherFiles(options: SchematicsOptions): Rule {
|
||||
function updateAngularConfig(options: SchematicsOptions): Rule {
|
||||
return (tree: Tree, context: SchematicContext): Tree => {
|
||||
context.logger.debug('Updating "angular.json".');
|
||||
const script = getScriptFromOptions(options);
|
||||
|
||||
return updateAngularJsonScripts(tree, script);
|
||||
return updateAngularJsonScripts(tree, options);
|
||||
};
|
||||
}
|
||||
|
@ -4,11 +4,17 @@
|
||||
"title": "Puppeteer Install Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isDefaultTester": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"x-prompt": "Use Puppeteer as default `ng e2e` command?"
|
||||
},
|
||||
"exportConfig": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-prompt": "Do you wish to export default Puppeteer config file?"
|
||||
"x-prompt": "Export default Puppeteer config file?"
|
||||
},
|
||||
"testingFramework": {
|
||||
"description": "",
|
||||
|
@ -151,3 +151,10 @@ export function getScriptFromOptions(options: SchematicsOptions): string[][] {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export function getNgCommandName(options: SchematicsOptions): string {
|
||||
if (options.isDefaultTester) {
|
||||
return 'e2e';
|
||||
}
|
||||
return 'puppeteer';
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
getJsonFileAsObject,
|
||||
getObjectAsJson,
|
||||
} from './json.js';
|
||||
import {getNgCommandName, getScriptFromOptions} from './files.js';
|
||||
export interface NodePackage {
|
||||
name: string;
|
||||
version: string;
|
||||
@ -161,24 +162,32 @@ export function addPackageJsonScripts(
|
||||
|
||||
export function updateAngularJsonScripts(
|
||||
tree: Tree,
|
||||
commands: string[][],
|
||||
options: SchematicsOptions,
|
||||
overwrite = true
|
||||
): Tree {
|
||||
const angularJson = getAngularConfig(tree);
|
||||
|
||||
const e2eScript = [
|
||||
{
|
||||
name: 'e2e',
|
||||
value: {
|
||||
builder: '@puppeteer/ng-schematics:puppeteer',
|
||||
options: {
|
||||
commands,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const commands = getScriptFromOptions(options);
|
||||
const name = getNgCommandName(options);
|
||||
|
||||
Object.keys(angularJson['projects']).forEach(project => {
|
||||
const e2eScript = [
|
||||
{
|
||||
name,
|
||||
value: {
|
||||
builder: '@puppeteer/ng-schematics:puppeteer',
|
||||
options: {
|
||||
commands,
|
||||
devServerTarget: `${project}:serve`,
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
devServerTarget: `${project}:serve:production`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
updateJsonValues(
|
||||
angularJson['projects'][project],
|
||||
'architect',
|
||||
|
@ -22,6 +22,7 @@ export enum TestingFramework {
|
||||
}
|
||||
|
||||
export interface SchematicsOptions {
|
||||
isDefaultTester: boolean;
|
||||
exportConfig: boolean;
|
||||
testingFramework: TestingFramework;
|
||||
exportConfig?: boolean;
|
||||
}
|
||||
|
@ -22,9 +22,19 @@ function getProjectFile(file: string): string {
|
||||
return `/${WORKSPACE_OPTIONS.newProjectRoot}/${APPLICATION_OPTIONS.name}/${file}`;
|
||||
}
|
||||
|
||||
function getAngularJsonScripts(tree: UnitTestTree): Record<string, any> {
|
||||
function getAngularJsonScripts(
|
||||
tree: UnitTestTree,
|
||||
isDefault = true
|
||||
): {
|
||||
builder: string;
|
||||
configurations: Record<string, any>;
|
||||
options: Record<string, any>;
|
||||
} {
|
||||
const angularJson = tree.readJson('angular.json') as any;
|
||||
return angularJson['projects']?.[APPLICATION_OPTIONS.name]?.['architect'];
|
||||
const e2eScript = isDefault ? 'e2e' : 'puppeteer';
|
||||
return angularJson['projects']?.[APPLICATION_OPTIONS.name]?.['architect'][
|
||||
e2eScript
|
||||
];
|
||||
}
|
||||
|
||||
function getPackageJson(tree: UnitTestTree): {
|
||||
@ -46,6 +56,7 @@ async function buildTestingTree(userOptions?: Record<string, any>) {
|
||||
join(__dirname, '../../lib/schematics/collection.json')
|
||||
);
|
||||
const options = {
|
||||
isDefaultTester: true,
|
||||
exportConfig: false,
|
||||
testingFramework: 'jasmine',
|
||||
...userOptions,
|
||||
@ -93,13 +104,29 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
it('should create base files and update to "package.json"', async () => {
|
||||
const tree = await buildTestingTree();
|
||||
const {devDependencies, scripts} = getPackageJson(tree);
|
||||
const {e2e} = getAngularJsonScripts(tree);
|
||||
const {builder, configurations} = getAngularJsonScripts(tree);
|
||||
|
||||
expect(tree.files).toContain(getProjectFile('e2e/tsconfig.json'));
|
||||
expect(tree.files).toContain(getProjectFile('e2e/tests/app.e2e.ts'));
|
||||
expect(devDependencies).toContain('puppeteer');
|
||||
expect(scripts['e2e']).toBe('ng e2e');
|
||||
expect(e2e.builder).toBe('@puppeteer/ng-schematics:puppeteer');
|
||||
expect(builder).toBe('@puppeteer/ng-schematics:puppeteer');
|
||||
expect(configurations).toEqual({
|
||||
production: {
|
||||
devServerTarget: 'sandbox:serve:production',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update create proper "ng" command for non default tester', async () => {
|
||||
const tree = await buildTestingTree({
|
||||
isDefaultTester: false,
|
||||
});
|
||||
const {scripts} = getPackageJson(tree);
|
||||
const {builder} = getAngularJsonScripts(tree, false);
|
||||
|
||||
expect(scripts['puppeteer']).toBe('ng run sandbox:puppeteer');
|
||||
expect(builder).toBe('@puppeteer/ng-schematics:puppeteer');
|
||||
});
|
||||
|
||||
it('should create Puppeteer config', async () => {
|
||||
@ -123,7 +150,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
testingFramework: 'jasmine',
|
||||
});
|
||||
const {devDependencies} = getPackageJson(tree);
|
||||
const {e2e} = getAngularJsonScripts(tree);
|
||||
const {options} = getAngularJsonScripts(tree);
|
||||
|
||||
expect(tree.files).toContain(getProjectFile('e2e/support/jasmine.json'));
|
||||
expect(tree.files).toContain(getProjectFile('e2e/helpers/babel.js'));
|
||||
@ -131,7 +158,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
expect(devDependencies).toContain('@babel/core');
|
||||
expect(devDependencies).toContain('@babel/register');
|
||||
expect(devDependencies).toContain('@babel/preset-typescript');
|
||||
expect(e2e.options.commands).toEqual([
|
||||
expect(options['commands']).toEqual([
|
||||
[`jasmine`, '--config=./e2e/support/jasmine.json'],
|
||||
]);
|
||||
});
|
||||
@ -141,15 +168,13 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
testingFramework: 'jest',
|
||||
});
|
||||
const {devDependencies} = getPackageJson(tree);
|
||||
const {e2e} = getAngularJsonScripts(tree);
|
||||
const {options} = getAngularJsonScripts(tree);
|
||||
|
||||
expect(tree.files).toContain(getProjectFile('e2e/jest.config.js'));
|
||||
expect(devDependencies).toContain('jest');
|
||||
expect(devDependencies).toContain('@types/jest');
|
||||
expect(devDependencies).toContain('ts-jest');
|
||||
expect(e2e.options.commands).toEqual([
|
||||
[`jest`, '-c', 'e2e/jest.config.js'],
|
||||
]);
|
||||
expect(options['commands']).toEqual([[`jest`, '-c', 'e2e/jest.config.js']]);
|
||||
});
|
||||
|
||||
it('should create Mocha files and update "package.json"', async () => {
|
||||
@ -157,7 +182,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
testingFramework: 'mocha',
|
||||
});
|
||||
const {devDependencies} = getPackageJson(tree);
|
||||
const {e2e} = getAngularJsonScripts(tree);
|
||||
const {options} = getAngularJsonScripts(tree);
|
||||
|
||||
expect(tree.files).toContain(getProjectFile('e2e/.mocharc.js'));
|
||||
expect(tree.files).toContain(getProjectFile('e2e/babel.js'));
|
||||
@ -166,7 +191,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
expect(devDependencies).toContain('@babel/core');
|
||||
expect(devDependencies).toContain('@babel/register');
|
||||
expect(devDependencies).toContain('@babel/preset-typescript');
|
||||
expect(e2e.options.commands).toEqual([
|
||||
expect(options['commands']).toEqual([
|
||||
[`mocha`, '--config=./e2e/.mocharc.js'],
|
||||
]);
|
||||
});
|
||||
@ -175,10 +200,10 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
const tree = await buildTestingTree({
|
||||
testingFramework: 'node',
|
||||
});
|
||||
const {e2e} = getAngularJsonScripts(tree);
|
||||
const {options} = getAngularJsonScripts(tree);
|
||||
|
||||
expect(tree.files).toContain(getProjectFile('e2e/.gitignore'));
|
||||
expect(e2e.options.commands).toEqual([
|
||||
expect(options['commands']).toEqual([
|
||||
[`tsc`, '-p', 'e2e/tsconfig.json'],
|
||||
['node', '--test', 'e2e/'],
|
||||
]);
|
||||
|
Loading…
Reference in New Issue
Block a user