Skip to content

Commit

Permalink
chore(ng-schematics): Spawn server when running ng e2e (#9306)
Browse files Browse the repository at this point in the history
**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>
  • Loading branch information
Lightning00Blade and OrKoN committed Nov 23, 2022
1 parent 181b20f commit 689a084
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 39 deletions.
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;
}

0 comments on commit 689a084

Please sign in to comment.