Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ng-schematics): Spawn server when running ng e2e #9306

Merged
merged 3 commits into from Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/ng-schematics/README.md
Expand Up @@ -26,17 +26,22 @@ 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

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:
Expand Down
57 changes: 56 additions & 1 deletion packages/ng-schematics/src/builders/puppeteer/index.ts
Expand Up @@ -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!` +
Expand Down Expand Up @@ -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, {
Expand All @@ -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();
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/ng-schematics/src/builders/puppeteer/schema.json
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/ng-schematics/src/builders/puppeteer/types.ts
Expand Up @@ -20,4 +20,5 @@ type Command = [string, ...string[]];

export interface PuppeteerBuilderOptions extends JsonObject {
commands: Command[];
devServerTarget: string;
}
26 changes: 16 additions & 10 deletions packages/ng-schematics/src/schematics/ng-add/index.ts
Expand Up @@ -22,7 +22,7 @@ import {of} from 'rxjs';
import {
addBaseFiles,
addFrameworkFiles,
getScriptFromOptions,
getNgCommandName,
} from '../utils/files.js';
import {
addPackageJsonDependencies,
Expand Down Expand Up @@ -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;
};
}

Expand Down Expand Up @@ -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);
};
}
8 changes: 7 additions & 1 deletion packages/ng-schematics/src/schematics/ng-add/schema.json
Expand Up @@ -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": "",
Expand Down
7 changes: 7 additions & 0 deletions packages/ng-schematics/src/schematics/utils/files.ts
Expand Up @@ -151,3 +151,10 @@ export function getScriptFromOptions(options: SchematicsOptions): string[][] {
];
}
}

export function getNgCommandName(options: SchematicsOptions): string {
if (options.isDefaultTester) {
return 'e2e';
}
return 'puppeteer';
}
31 changes: 20 additions & 11 deletions packages/ng-schematics/src/schematics/utils/packages.ts
Expand Up @@ -22,6 +22,7 @@ import {
getJsonFileAsObject,
getObjectAsJson,
} from './json.js';
import {getNgCommandName, getScriptFromOptions} from './files.js';
export interface NodePackage {
name: string;
version: string;
Expand Down Expand Up @@ -161,24 +162,32 @@ export function addPackageJsonScripts(

export function updateAngularJsonScripts(
tree: Tree,
commands: string[][],
options: SchematicsOptions,
overwrite = true
): Tree {
const angularJson = getAngularConfig(tree);
const commands = getScriptFromOptions(options);
const name = getNgCommandName(options);

const e2eScript = [
{
name: 'e2e',
value: {
builder: '@puppeteer/ng-schematics:puppeteer',
options: {
commands,
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`,
},
},
},
},
},
];
];

Object.keys(angularJson['projects']).forEach(project => {
updateJsonValues(
angularJson['projects'][project],
'architect',
Expand Down
3 changes: 2 additions & 1 deletion packages/ng-schematics/src/schematics/utils/types.ts
Expand Up @@ -22,6 +22,7 @@ export enum TestingFramework {
}

export interface SchematicsOptions {
isDefaultTester: boolean;
exportConfig: boolean;
testingFramework: TestingFramework;
exportConfig?: boolean;
}