feat: add Test command (#10443)
This commit is contained in:
parent
b6a733cdfe
commit
2d8993b45b
@ -12,8 +12,9 @@ import {JsonObject} from '@angular-devkit/core';
|
||||
import {PuppeteerBuilderOptions} from './types.js';
|
||||
|
||||
const terminalStyles = {
|
||||
blue: '\u001b[34m',
|
||||
cyan: '\u001b[36;1m',
|
||||
green: '\u001b[32m',
|
||||
red: '\u001b[31m',
|
||||
bold: '\u001b[1m',
|
||||
reverse: '\u001b[7m',
|
||||
clear: '\u001b[0m',
|
||||
@ -21,8 +22,6 @@ const terminalStyles = {
|
||||
|
||||
function getError(executable: string, args: string[]) {
|
||||
return (
|
||||
`Puppeteer E2E tests failed!` +
|
||||
'\n' +
|
||||
`Error running '${executable}' with arguments '${args.join(' ')}'.` +
|
||||
`\n` +
|
||||
'Please look at the output above to determine the issue!'
|
||||
@ -76,11 +75,22 @@ async function executeCommand(context: BuilderContext, command: string[]) {
|
||||
function message(
|
||||
message: string,
|
||||
context: BuilderContext,
|
||||
type: 'info' | 'success' = 'info'
|
||||
type: 'info' | 'success' | 'error' = 'info'
|
||||
): void {
|
||||
const color = type === 'info' ? terminalStyles.blue : terminalStyles.green;
|
||||
let style: string;
|
||||
switch (type) {
|
||||
case 'info':
|
||||
style = terminalStyles.reverse + terminalStyles.cyan;
|
||||
break;
|
||||
case 'success':
|
||||
style = terminalStyles.reverse + terminalStyles.green;
|
||||
break;
|
||||
case 'error':
|
||||
style = terminalStyles.red;
|
||||
break;
|
||||
}
|
||||
context.logger.info(
|
||||
`${terminalStyles.bold}${terminalStyles.reverse}${color}${message}${terminalStyles.clear}`
|
||||
`${terminalStyles.bold}${style}${message}${terminalStyles.clear}`
|
||||
);
|
||||
}
|
||||
|
||||
@ -98,7 +108,7 @@ async function startServer(
|
||||
port: defaultServerOptions['port'],
|
||||
} as JsonObject;
|
||||
|
||||
message('Spawning test server...\n', context);
|
||||
message(' Spawning test server ⚙️ ... \n', context);
|
||||
const server = await context.scheduleTarget(target, overrides);
|
||||
const result = await server.result;
|
||||
if (!result.success) {
|
||||
@ -116,14 +126,15 @@ async function executeE2ETest(
|
||||
try {
|
||||
server = await startServer(options, context);
|
||||
|
||||
message('\nRunning tests...\n', context);
|
||||
message('\n Running tests 🧪 ... \n', context);
|
||||
for (const command of options.commands) {
|
||||
await executeCommand(context, command);
|
||||
}
|
||||
|
||||
message('\nTest ran successfully!', context, 'success');
|
||||
message('\n 🚀 Test ran successfully! 🚀 ', context, 'success');
|
||||
return {success: true};
|
||||
} catch (error) {
|
||||
message('\n 🛑 Test failed! 🛑 ', context, 'error');
|
||||
if (error instanceof Error) {
|
||||
return {success: false, error: error.message};
|
||||
}
|
||||
|
@ -5,6 +5,11 @@
|
||||
"description": "Add Puppeteer to an Angular project",
|
||||
"factory": "./ng-add/index#ngAdd",
|
||||
"schema": "./ng-add/schema.json"
|
||||
},
|
||||
"test": {
|
||||
"description": "Create a single test file",
|
||||
"factory": "./test/index#test",
|
||||
"schema": "./test/schema.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ describe('App test', function () {
|
||||
setupBrowserHooks();
|
||||
it('is running', async function () {
|
||||
const {page} = getBrowserState();
|
||||
await page.goto('http://localhost:4200');
|
||||
await page.goto('<%= baseUrl %>');
|
||||
const element = await page.waitForSelector('text/sandbox app is running!');
|
||||
|
||||
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
|
||||
|
@ -9,7 +9,9 @@ let page: puppeteer.Page;
|
||||
export function setupBrowserHooks(): void {
|
||||
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
|
||||
beforeAll(async () => {
|
||||
browser = await puppeteer.launch();
|
||||
browser = await puppeteer.launch({
|
||||
headless: 'new'
|
||||
});
|
||||
});
|
||||
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
|
||||
before(async () => {
|
||||
|
@ -5,22 +5,22 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isDefaultTester": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"alias": "d",
|
||||
"x-prompt": "Use Puppeteer as default `ng e2e` command?"
|
||||
},
|
||||
"exportConfig": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"alias": "c",
|
||||
"x-prompt": "Export default Puppeteer config file?"
|
||||
},
|
||||
"testingFramework": {
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"enum": ["jasmine", "jest", "mocha", "node"],
|
||||
"default": "jasmine",
|
||||
"alias": "t",
|
||||
"x-prompt": {
|
||||
"message": "With what Testing Library do you wish to integrate?",
|
||||
"type": "list",
|
||||
|
@ -0,0 +1,15 @@
|
||||
<% if(testingFramework == 'node') { %>
|
||||
import * as assert from 'assert';
|
||||
import {describe, it} from 'node:test';
|
||||
<% } %><% if(testingFramework == 'mocha') { %>
|
||||
import * as assert from 'assert';
|
||||
<% } %>
|
||||
import {setupBrowserHooks, getBrowserState} from './utils';
|
||||
|
||||
describe('<%= classify(name) %>', function () {
|
||||
setupBrowserHooks();
|
||||
it('', async function () {
|
||||
const {page} = getBrowserState();
|
||||
await page.goto('<%= baseUrl %>');
|
||||
});
|
||||
});
|
89
packages/ng-schematics/src/schematics/test/index.ts
Normal file
89
packages/ng-schematics/src/schematics/test/index.ts
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright 2023 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
chain,
|
||||
Rule,
|
||||
SchematicContext,
|
||||
SchematicsException,
|
||||
Tree,
|
||||
} from '@angular-devkit/schematics';
|
||||
|
||||
import {addBaseFiles} from '../utils/files.js';
|
||||
import {getAngularConfig} from '../utils/json.js';
|
||||
import {TestingFramework, SchematicsSpec} 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.
|
||||
export function test(options: SchematicsSpec): Rule {
|
||||
return (tree: Tree, context: SchematicContext) => {
|
||||
return chain([addSpecFile(options)])(tree, context);
|
||||
};
|
||||
}
|
||||
|
||||
function findTestingFramework([name, project]: [
|
||||
string,
|
||||
any
|
||||
]): TestingFramework {
|
||||
const e2e = project.architect?.e2e;
|
||||
const puppeteer = project.architect?.puppeteer;
|
||||
const builder = '@puppeteer/ng-schematics:puppeteer';
|
||||
|
||||
if (e2e?.builder === builder) {
|
||||
return e2e.options.testingFramework;
|
||||
} else if (puppeteer?.builder === builder) {
|
||||
return puppeteer.options.testingFramework;
|
||||
}
|
||||
|
||||
throw new Error(`Can't find TestingFramework info for project ${name}`);
|
||||
}
|
||||
|
||||
function addSpecFile(options: SchematicsSpec): Rule {
|
||||
return async (tree: Tree, context: SchematicContext) => {
|
||||
context.logger.debug('Adding Spec file.');
|
||||
|
||||
const {projects} = getAngularConfig(tree);
|
||||
const projectNames = Object.keys(projects) as [string, ...string[]];
|
||||
const foundProject: [string, unknown] | 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 === '';
|
||||
});
|
||||
if (!foundProject) {
|
||||
throw new SchematicsException(
|
||||
`Project not found! Please use -p to specify in which project to run.`
|
||||
);
|
||||
}
|
||||
|
||||
const testingFramework = findTestingFramework(foundProject);
|
||||
|
||||
context.logger.debug('Creating Spec file.');
|
||||
|
||||
return addBaseFiles(tree, context, {
|
||||
projects: {[foundProject[0]]: foundProject[1]},
|
||||
options: {
|
||||
name: options.name,
|
||||
testingFramework,
|
||||
// Node test runner does not support glob patterns
|
||||
// It looks for files `*.test.js`
|
||||
ext: testingFramework === TestingFramework.Node ? 'test' : 'e2e',
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
22
packages/ng-schematics/src/schematics/test/schema.json
Normal file
22
packages/ng-schematics/src/schematics/test/schema.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Puppeteer",
|
||||
"title": "Puppeteer Spec Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"alias": "n",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "Name for spec to be created:"
|
||||
},
|
||||
"project": {
|
||||
"type": "string",
|
||||
"alias": "p"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
@ -32,9 +32,10 @@ import {
|
||||
import {SchematicsOptions, TestingFramework} from './types.js';
|
||||
|
||||
export interface FilesOptions {
|
||||
projects: any;
|
||||
projects: Record<string, any>;
|
||||
options: {
|
||||
testingFramework: TestingFramework;
|
||||
name?: string;
|
||||
exportConfig?: boolean;
|
||||
ext?: string;
|
||||
};
|
||||
@ -153,7 +154,7 @@ export function getScriptFromOptions(options: SchematicsOptions): string[][] {
|
||||
case TestingFramework.Node:
|
||||
return [
|
||||
[`tsc`, '-p', 'e2e/tsconfig.json'],
|
||||
['node', '--test', 'e2e/'],
|
||||
['node', '--test', 'e2e/build/'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -180,6 +180,7 @@ export function updateAngularJsonScripts(
|
||||
options: {
|
||||
commands,
|
||||
devServerTarget: `${project}:serve`,
|
||||
testingFramework: options.testingFramework,
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
|
@ -26,3 +26,8 @@ export interface SchematicsOptions {
|
||||
exportConfig: boolean;
|
||||
testingFramework: TestingFramework;
|
||||
}
|
||||
|
||||
export interface SchematicsSpec {
|
||||
name: string;
|
||||
project?: string;
|
||||
}
|
||||
|
@ -1,32 +1,18 @@
|
||||
import https from 'https';
|
||||
|
||||
import expect from 'expect';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import {
|
||||
buildTestingTree,
|
||||
getAngularJsonScripts,
|
||||
getPackageJson,
|
||||
getProjectFile,
|
||||
setupHttpHooks,
|
||||
} from './utils.js';
|
||||
|
||||
describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
// Stop outgoing Request for version fetching
|
||||
before(() => {
|
||||
const httpsGetStub = sinon.stub(https, 'get');
|
||||
httpsGetStub.returns({
|
||||
on: (_: any, callback: () => void) => {
|
||||
callback();
|
||||
},
|
||||
} as any);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
setupHttpHooks();
|
||||
|
||||
it('should create base files and update to "package.json"', async () => {
|
||||
const tree = await buildTestingTree();
|
||||
const tree = await buildTestingTree('ng-add');
|
||||
const {devDependencies, scripts} = getPackageJson(tree);
|
||||
const {builder, configurations} = getAngularJsonScripts(tree);
|
||||
|
||||
@ -44,7 +30,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
});
|
||||
|
||||
it('should update create proper "ng" command for non default tester', async () => {
|
||||
const tree = await buildTestingTree({
|
||||
const tree = await buildTestingTree('ng-add', {
|
||||
isDefaultTester: false,
|
||||
});
|
||||
const {scripts} = getPackageJson(tree);
|
||||
@ -55,7 +41,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
});
|
||||
|
||||
it('should create Puppeteer config', async () => {
|
||||
const {files} = await buildTestingTree({
|
||||
const {files} = await buildTestingTree('ng-add', {
|
||||
exportConfig: true,
|
||||
});
|
||||
|
||||
@ -63,7 +49,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
});
|
||||
|
||||
it('should not create Puppeteer config', async () => {
|
||||
const {files} = await buildTestingTree({
|
||||
const {files} = await buildTestingTree('ng-add', {
|
||||
exportConfig: false,
|
||||
});
|
||||
|
||||
@ -71,7 +57,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
});
|
||||
|
||||
it('should create Jasmine files and update "package.json"', async () => {
|
||||
const tree = await buildTestingTree({
|
||||
const tree = await buildTestingTree('ng-add', {
|
||||
testingFramework: 'jasmine',
|
||||
});
|
||||
const {devDependencies} = getPackageJson(tree);
|
||||
@ -89,7 +75,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
});
|
||||
|
||||
it('should create Jest files and update "package.json"', async () => {
|
||||
const tree = await buildTestingTree({
|
||||
const tree = await buildTestingTree('ng-add', {
|
||||
testingFramework: 'jest',
|
||||
});
|
||||
const {devDependencies} = getPackageJson(tree);
|
||||
@ -103,7 +89,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
});
|
||||
|
||||
it('should create Mocha files and update "package.json"', async () => {
|
||||
const tree = await buildTestingTree({
|
||||
const tree = await buildTestingTree('ng-add', {
|
||||
testingFramework: 'mocha',
|
||||
});
|
||||
const {devDependencies} = getPackageJson(tree);
|
||||
@ -121,8 +107,8 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create Node files"', async () => {
|
||||
const tree = await buildTestingTree({
|
||||
it('should create Node files', async () => {
|
||||
const tree = await buildTestingTree('ng-add', {
|
||||
testingFramework: 'node',
|
||||
});
|
||||
const {options} = getAngularJsonScripts(tree);
|
||||
@ -132,7 +118,7 @@ describe('@puppeteer/ng-schematics: ng-add', () => {
|
||||
expect(tree.files).toContain(getProjectFile('e2e/tests/app.test.ts'));
|
||||
expect(options['commands']).toEqual([
|
||||
[`tsc`, '-p', 'e2e/tsconfig.json'],
|
||||
['node', '--test', 'e2e/'],
|
||||
['node', '--test', 'e2e/build/'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
28
packages/ng-schematics/test/src/test.spec.ts
Normal file
28
packages/ng-schematics/test/src/test.spec.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import expect from 'expect';
|
||||
|
||||
import {buildTestingTree, getProjectFile, setupHttpHooks} from './utils.js';
|
||||
|
||||
describe('@puppeteer/ng-schematics: test', () => {
|
||||
setupHttpHooks();
|
||||
|
||||
it('should create default file', async () => {
|
||||
const tree = await buildTestingTree('test', {
|
||||
name: 'myTest',
|
||||
});
|
||||
expect(tree.files).toContain(getProjectFile('e2e/tests/my-test.e2e.ts'));
|
||||
expect(tree.files).not.toContain(
|
||||
getProjectFile('e2e/tests/my-test.test.ts')
|
||||
);
|
||||
});
|
||||
|
||||
it('should create Node file', async () => {
|
||||
const tree = await buildTestingTree('test', {
|
||||
name: 'myTest',
|
||||
testingFramework: 'node',
|
||||
});
|
||||
expect(tree.files).not.toContain(
|
||||
getProjectFile('e2e/tests/my-test.e2e.ts')
|
||||
);
|
||||
expect(tree.files).toContain(getProjectFile('e2e/tests/my-test.test.ts'));
|
||||
});
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
import https from 'https';
|
||||
import {join} from 'path';
|
||||
|
||||
import {JsonObject} from '@angular-devkit/core';
|
||||
@ -5,6 +6,7 @@ import {
|
||||
SchematicTestRunner,
|
||||
UnitTestTree,
|
||||
} from '@angular-devkit/schematics/testing';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const WORKSPACE_OPTIONS = {
|
||||
name: 'workspace',
|
||||
@ -16,6 +18,22 @@ const APPLICATION_OPTIONS = {
|
||||
name: 'sandbox',
|
||||
};
|
||||
|
||||
export function setupHttpHooks(): void {
|
||||
// Stop outgoing Request for version fetching
|
||||
before(() => {
|
||||
const httpsGetStub = sinon.stub(https, 'get');
|
||||
httpsGetStub.returns({
|
||||
on: (_: any, callback: () => void) => {
|
||||
callback();
|
||||
},
|
||||
} as any);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
}
|
||||
|
||||
export function getProjectFile(file: string): string {
|
||||
return `/${WORKSPACE_OPTIONS.newProjectRoot}/${APPLICATION_OPTIONS.name}/${file}`;
|
||||
}
|
||||
@ -49,6 +67,7 @@ export function getPackageJson(tree: UnitTestTree): {
|
||||
}
|
||||
|
||||
export async function buildTestingTree(
|
||||
command: 'ng-add' | 'test',
|
||||
userOptions?: Record<string, any>
|
||||
): Promise<UnitTestTree> {
|
||||
const runner = new SchematicTestRunner(
|
||||
@ -77,5 +96,11 @@ export async function buildTestingTree(
|
||||
workingTree
|
||||
);
|
||||
|
||||
return await runner.runSchematic('ng-add', options, workingTree);
|
||||
if (command !== 'ng-add') {
|
||||
// We want to create update the proper files with `ng-add`
|
||||
// First else the angular.json will have wrong data
|
||||
workingTree = await runner.runSchematic('ng-add', options, workingTree);
|
||||
}
|
||||
|
||||
return await runner.runSchematic(command, options, workingTree);
|
||||
}
|
||||
|
@ -34,11 +34,16 @@ const commands = {
|
||||
],
|
||||
};
|
||||
const scripts = {
|
||||
// Builds the ng-schematics before running them
|
||||
'build:schematics': 'npm run --prefix ../ build',
|
||||
// Deletes all files created by Puppeteer Ng-Schematics to avoid errors
|
||||
'delete:file':
|
||||
'rm -f .puppeteerrc.cjs && rm -f tsconfig.e2e.json && rm -R -f e2e/',
|
||||
// Runs the Puppeteer Ng-Schematics against the sandbox
|
||||
schematics: 'npm run delete:file && schematics ../:ng-add --dry-run=false',
|
||||
schematics:
|
||||
'npm run delete:file && npm run build:schematics && schematics ../:ng-add --dry-run=false',
|
||||
'schematics:spec':
|
||||
'npm run build:schematics && schematics ../:test --dry-run=false',
|
||||
};
|
||||
/**
|
||||
*
|
||||
@ -99,6 +104,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(() => {
|
||||
console.log('\n');
|
||||
main().catch(error => {
|
||||
console.log('Something went wrong');
|
||||
console.error(error);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user