fix: port
option to run dev and e2e side-by-side (#10458)
This commit is contained in:
parent
ceb6fbb365
commit
a43b346bfc
@ -2,9 +2,10 @@
|
||||
|
||||
Adds Puppeteer-based e2e tests to your Angular project.
|
||||
|
||||
## Usage
|
||||
## Getting started
|
||||
|
||||
Run the command below in an Angular CLI app directory and follow the prompts.
|
||||
|
||||
_Note this will add the schematic as a dependency to your project._
|
||||
|
||||
```bash
|
||||
@ -15,10 +16,10 @@ Or you can use the same command followed by the [options](#options) below.
|
||||
|
||||
Currently, this schematic supports the following test frameworks:
|
||||
|
||||
- **Jasmine** [https://jasmine.github.io/]
|
||||
- **Jest** [https://jestjs.io/]
|
||||
- **Mocha** [https://mochajs.org/]
|
||||
- **Node Test Runner** _(Experimental)_ [https://nodejs.org/api/test.html]
|
||||
- [**Jasmine**](https://jasmine.github.io/)
|
||||
- [**Jest**](https://jestjs.io/)
|
||||
- [**Mocha**](https://mochajs.org/)
|
||||
- [**Node Test Runner** _(Experimental)_](https://nodejs.org/api/test.html)
|
||||
|
||||
With the schematics installed you can run E2E tests:
|
||||
|
||||
@ -26,9 +27,7 @@ With the schematics installed you can run E2E tests:
|
||||
ng e2e
|
||||
```
|
||||
|
||||
> Note: Command spawns it's own server on the same port `ng serve` does.
|
||||
|
||||
## Options
|
||||
### Options
|
||||
|
||||
When adding schematics to your project you can to provide following options:
|
||||
|
||||
@ -37,6 +36,41 @@ When adding schematics to your project you can to provide following options:
|
||||
| `--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` |
|
||||
| `--port` | The port to spawn server for E2E. If default is used `ng serve` and `ng e2e` will not run side-by-side. | `number` | `4200` |
|
||||
|
||||
## Creating a single test file
|
||||
|
||||
Puppeteer Angular Schematic exposes a method to create a single test file.
|
||||
|
||||
```bash
|
||||
ng generate @puppeteer/ng-schematics:test "<TestName>"
|
||||
```
|
||||
|
||||
### Running test server and dev server at the same time
|
||||
|
||||
By default the E2E test will run the app on the same port as `ng start`.
|
||||
To avoid this you can specify the port the an the `angular.json`
|
||||
Update either `e2e` or `puppeteer` (depending on the initial setup) to:
|
||||
|
||||
```json
|
||||
{
|
||||
"e2e": {
|
||||
"builder": "@puppeteer/ng-schematics:puppeteer",
|
||||
"options": {
|
||||
"commands": [...],
|
||||
"devServerTarget": "sandbox:serve",
|
||||
"testingFramework": "<TestingFramework>",
|
||||
"port": 8080
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Now update the E2E test file `utils.ts` baseUrl to:
|
||||
|
||||
```ts
|
||||
const baseUrl = 'http://localhost:8080';
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -58,6 +92,12 @@ npm run sandbox
|
||||
npm run sandbox -- --build
|
||||
```
|
||||
|
||||
To run the creating of single test schematic:
|
||||
|
||||
```bash
|
||||
npm run sandbox:test
|
||||
```
|
||||
|
||||
### Unit Testing
|
||||
|
||||
The schematics utilize `@angular-devkit/schematics/testing` for verifying correct file creation and `package.json` updates. To execute the test suit:
|
||||
|
@ -8,7 +8,8 @@
|
||||
"dev:test": "npm run test --watch",
|
||||
"dev": "npm run build --watch",
|
||||
"test": "wireit",
|
||||
"sandbox": "node tools/sandbox.js"
|
||||
"sandbox": "node tools/sandbox.js",
|
||||
"sandbox:test": "node tools/sandbox.js --test"
|
||||
},
|
||||
"wireit": {
|
||||
"build": {
|
||||
|
@ -105,7 +105,7 @@ async function startServer(
|
||||
const overrides = {
|
||||
watch: false,
|
||||
host: defaultServerOptions['host'],
|
||||
port: defaultServerOptions['port'],
|
||||
port: options.port ?? defaultServerOptions['port'],
|
||||
} as JsonObject;
|
||||
|
||||
message(' Spawning test server ⚙️ ... \n', context);
|
||||
@ -138,7 +138,7 @@ async function executeE2ETest(
|
||||
if (error instanceof Error) {
|
||||
return {success: false, error: error.message};
|
||||
}
|
||||
return {success: false, error: error as any};
|
||||
return {success: false, error: error as string};
|
||||
} finally {
|
||||
if (server) {
|
||||
await server.stop();
|
||||
@ -146,4 +146,4 @@ async function executeE2ETest(
|
||||
}
|
||||
}
|
||||
|
||||
export default createBuilder<PuppeteerBuilderOptions>(executeE2ETest) as any;
|
||||
export default createBuilder<PuppeteerBuilderOptions>(executeE2ETest);
|
||||
|
@ -16,6 +16,10 @@
|
||||
"devServerTarget": {
|
||||
"type": "string",
|
||||
"description": "Angular target that spawns the server."
|
||||
},
|
||||
"port": {
|
||||
"type": ["number", "null"],
|
||||
"description": "Port to run the test server on."
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
@ -21,4 +21,5 @@ type Command = [string, ...string[]];
|
||||
export interface PuppeteerBuilderOptions extends JsonObject {
|
||||
commands: Command[];
|
||||
devServerTarget: string;
|
||||
port: number | null;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ describe('App test', function () {
|
||||
setupBrowserHooks();
|
||||
it('is running', async function () {
|
||||
const {page} = getBrowserState();
|
||||
await page.goto('<%= baseUrl %>');
|
||||
const element = await page.waitForSelector('text/sandbox app is running!');
|
||||
|
||||
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
|
||||
|
@ -3,6 +3,7 @@ import {before, beforeEach, after, afterEach} from 'node:test';
|
||||
<% } %>
|
||||
import * as puppeteer from 'puppeteer';
|
||||
|
||||
const baseUrl = '<%= baseUrl %>';
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
|
||||
@ -21,6 +22,7 @@ export function setupBrowserHooks(): void {
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await browser.newPage();
|
||||
await page.goto(baseUrl);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@ -41,6 +43,7 @@ export function setupBrowserHooks(): void {
|
||||
export function getBrowserState(): {
|
||||
browser: puppeteer.Browser;
|
||||
page: puppeteer.Page;
|
||||
baseUrl: string;
|
||||
} {
|
||||
if (!browser) {
|
||||
throw new Error(
|
||||
@ -50,5 +53,6 @@ export function getBrowserState(): {
|
||||
return {
|
||||
browser,
|
||||
page,
|
||||
baseUrl,
|
||||
};
|
||||
}
|
||||
|
@ -43,6 +43,12 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"type": ["number"],
|
||||
"default": 4200,
|
||||
"alias": "p",
|
||||
"x-prompt": "On which port to spawn test server on?"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
@ -10,6 +10,5 @@ describe('<%= classify(name) %>', function () {
|
||||
setupBrowserHooks();
|
||||
it('', async function () {
|
||||
const {page} = getBrowserState();
|
||||
await page.goto('<%= baseUrl %>');
|
||||
});
|
||||
});
|
||||
|
@ -24,7 +24,12 @@ import {
|
||||
|
||||
import {addBaseFiles} from '../utils/files.js';
|
||||
import {getAngularConfig} from '../utils/json.js';
|
||||
import {TestingFramework, SchematicsSpec} from '../utils/types.js';
|
||||
import {
|
||||
TestingFramework,
|
||||
SchematicsSpec,
|
||||
SchematicsOptions,
|
||||
AngularProject,
|
||||
} from '../utils/types.js';
|
||||
|
||||
// You don't have to export the function as default. You can also have more than one rule
|
||||
// factory per file.
|
||||
@ -34,21 +39,25 @@ export function test(options: SchematicsSpec): Rule {
|
||||
};
|
||||
}
|
||||
|
||||
function findTestingFramework([name, project]: [
|
||||
string,
|
||||
any
|
||||
]): TestingFramework {
|
||||
function findTestingOption<Property extends keyof SchematicsOptions>(
|
||||
[name, project]: [string, AngularProject | undefined],
|
||||
property: Property
|
||||
): SchematicsOptions[Property] {
|
||||
if (!project) {
|
||||
throw new Error(`Project "${name}" not found.`);
|
||||
}
|
||||
|
||||
const e2e = project.architect?.e2e;
|
||||
const puppeteer = project.architect?.puppeteer;
|
||||
const builder = '@puppeteer/ng-schematics:puppeteer';
|
||||
|
||||
if (e2e?.builder === builder) {
|
||||
return e2e.options.testingFramework;
|
||||
return e2e.options[property];
|
||||
} else if (puppeteer?.builder === builder) {
|
||||
return puppeteer.options.testingFramework;
|
||||
return puppeteer.options[property];
|
||||
}
|
||||
|
||||
throw new Error(`Can't find TestingFramework info for project ${name}`);
|
||||
throw new Error(`Can't find property "${property}" for project "${name}".`);
|
||||
}
|
||||
|
||||
function addSpecFile(options: SchematicsSpec): Rule {
|
||||
@ -57,13 +66,13 @@ function addSpecFile(options: SchematicsSpec): Rule {
|
||||
|
||||
const {projects} = getAngularConfig(tree);
|
||||
const projectNames = Object.keys(projects) as [string, ...string[]];
|
||||
const foundProject: [string, unknown] | undefined =
|
||||
const foundProject: [string, AngularProject | undefined] | undefined =
|
||||
projectNames.length === 1
|
||||
? [projectNames[0], projects[projectNames[0]]]
|
||||
: Object.entries(projects).find(([name, project]) => {
|
||||
return options.project
|
||||
? options.project === name
|
||||
: (project as any).root === '';
|
||||
: project.root === '';
|
||||
});
|
||||
if (!foundProject) {
|
||||
throw new SchematicsException(
|
||||
@ -71,7 +80,11 @@ function addSpecFile(options: SchematicsSpec): Rule {
|
||||
);
|
||||
}
|
||||
|
||||
const testingFramework = findTestingFramework(foundProject);
|
||||
const testingFramework = findTestingOption(
|
||||
foundProject,
|
||||
'testingFramework'
|
||||
);
|
||||
const port = findTestingOption(foundProject, 'port');
|
||||
|
||||
context.logger.debug('Creating Spec file.');
|
||||
|
||||
@ -83,6 +96,7 @@ function addSpecFile(options: SchematicsSpec): Rule {
|
||||
// Node test runner does not support glob patterns
|
||||
// It looks for files `*.test.js`
|
||||
ext: testingFramework === TestingFramework.Node ? 'test' : 'e2e',
|
||||
port,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -35,6 +35,7 @@ export interface FilesOptions {
|
||||
projects: Record<string, any>;
|
||||
options: {
|
||||
testingFramework: TestingFramework;
|
||||
port: number;
|
||||
name?: string;
|
||||
exportConfig?: boolean;
|
||||
ext?: string;
|
||||
@ -70,7 +71,7 @@ export function addFiles(
|
||||
workspacePath
|
||||
);
|
||||
|
||||
const baseUrl = getProjectBaseUrl(project);
|
||||
const baseUrl = getProjectBaseUrl(project, options.port);
|
||||
|
||||
return mergeWith(
|
||||
apply(url(applyPath), [
|
||||
@ -95,13 +96,13 @@ export function addFiles(
|
||||
)(tree, context);
|
||||
}
|
||||
|
||||
function getProjectBaseUrl(project: any): string {
|
||||
let options = {protocol: 'http', port: 4200, host: 'localhost'};
|
||||
function getProjectBaseUrl(project: any, port: number): string {
|
||||
let options = {protocol: 'http', port, host: 'localhost'};
|
||||
|
||||
if (project.architect?.serve?.options) {
|
||||
const projectOptions = project.architect?.serve?.options;
|
||||
|
||||
options = {...options, ...projectOptions};
|
||||
const projectPort = port !== 4200 ? port : projectOptions?.port ?? port;
|
||||
options = {...options, ...projectOptions, port: projectPort};
|
||||
options.protocol = projectOptions.ssl ? 'https' : 'http';
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
import {SchematicsException, Tree} from '@angular-devkit/schematics';
|
||||
|
||||
import {AngularJson} from './types.js';
|
||||
|
||||
export function getJsonFileAsObject(
|
||||
tree: Tree,
|
||||
path: string
|
||||
@ -33,6 +35,6 @@ export function getObjectAsJson(object: Record<string, any>): string {
|
||||
return JSON.stringify(object, null, 2);
|
||||
}
|
||||
|
||||
export function getAngularConfig(tree: Tree): Record<string, any> {
|
||||
return getJsonFileAsObject(tree, './angular.json');
|
||||
export function getAngularConfig(tree: Tree): AngularJson {
|
||||
return getJsonFileAsObject(tree, './angular.json') as AngularJson;
|
||||
}
|
||||
|
@ -170,6 +170,7 @@ export function updateAngularJsonScripts(
|
||||
const angularJson = getAngularConfig(tree);
|
||||
const commands = getScriptFromOptions(options);
|
||||
const name = getNgCommandName(options);
|
||||
const port = options.port !== 4200 ? Number(options.port) : undefined;
|
||||
|
||||
Object.keys(angularJson['projects']).forEach(project => {
|
||||
const e2eScript = [
|
||||
@ -181,6 +182,7 @@ export function updateAngularJsonScripts(
|
||||
commands,
|
||||
devServerTarget: `${project}:serve`,
|
||||
testingFramework: options.testingFramework,
|
||||
port,
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
@ -192,7 +194,7 @@ export function updateAngularJsonScripts(
|
||||
];
|
||||
|
||||
updateJsonValues(
|
||||
angularJson['projects'][project],
|
||||
angularJson['projects'][project]!,
|
||||
'architect',
|
||||
e2eScript,
|
||||
overwrite
|
||||
|
@ -25,6 +25,22 @@ export interface SchematicsOptions {
|
||||
isDefaultTester: boolean;
|
||||
exportConfig: boolean;
|
||||
testingFramework: TestingFramework;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface PuppeteerSchematicsConfig {
|
||||
builder: string;
|
||||
options: SchematicsOptions;
|
||||
}
|
||||
export interface AngularProject {
|
||||
root: string;
|
||||
architect: {
|
||||
e2e?: PuppeteerSchematicsConfig;
|
||||
puppeteer?: PuppeteerSchematicsConfig;
|
||||
};
|
||||
}
|
||||
export interface AngularJson {
|
||||
projects: Record<string, AngularProject>;
|
||||
}
|
||||
|
||||
export interface SchematicsSpec {
|
||||
|
@ -121,4 +121,20 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
['node', '--test', 'e2e/build/'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create port option', async () => {
|
||||
const tree = await buildTestingTree('ng-add');
|
||||
|
||||
const {options} = getAngularJsonScripts(tree);
|
||||
expect(options['port']).toBeUndefined();
|
||||
});
|
||||
it('should create port option when specified', async () => {
|
||||
const port = 8080;
|
||||
const tree = await buildTestingTree('ng-add', {
|
||||
port,
|
||||
});
|
||||
|
||||
const {options} = getAngularJsonScripts(tree);
|
||||
expect(options['port']).toBe(port);
|
||||
});
|
||||
});
|
||||
|
@ -21,6 +21,7 @@ const {cwd} = require('process');
|
||||
|
||||
const isInit = process.argv.indexOf('--init') !== -1;
|
||||
const isBuild = process.argv.indexOf('--build') !== -1;
|
||||
const isTest = process.argv.indexOf('--test') !== -1;
|
||||
const commands = {
|
||||
build: ['npm run build'],
|
||||
createSandbox: ['npx ng new sandbox --defaults'],
|
||||
@ -32,6 +33,14 @@ const commands = {
|
||||
},
|
||||
},
|
||||
],
|
||||
runSchematicsTest: [
|
||||
{
|
||||
command: 'npm run schematics:test',
|
||||
options: {
|
||||
cwd: join(cwd(), '/sandbox/'),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const scripts = {
|
||||
// Builds the ng-schematics before running them
|
||||
@ -100,7 +109,9 @@ async function main() {
|
||||
if (isBuild) {
|
||||
await executeCommand(commands.build);
|
||||
}
|
||||
await executeCommand(commands.runSchematics);
|
||||
await executeCommand(
|
||||
isTest ? commands.runSchematicsTest : commands.runSchematics
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user