├── .eslintrc.cjs ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── lib ├── command │ ├── package_install.command.d.ts │ ├── package_install.command.js │ ├── pogen.command.d.ts │ ├── pogen.command.js │ ├── prettier.command.d.ts │ └── prettier.command.js ├── component │ ├── configs.component.d.ts │ ├── configs.component.js │ ├── data.component.d.ts │ ├── data.component.js │ ├── helper.component.d.ts │ ├── helper.component.js │ ├── pages.component.d.ts │ ├── pages.component.js │ ├── runner.component.d.ts │ ├── runner.component.js │ ├── schema.component.d.ts │ ├── schema.component.js │ ├── tests.component.d.ts │ ├── tests.component.js │ ├── utils.component.d.ts │ └── utils.component.js ├── generator │ ├── generate.d.ts │ ├── generate.js │ ├── generate_env.d.ts │ └── generate_env.js ├── index.d.ts ├── index.js ├── interface │ ├── package.interface.d.ts │ ├── package.interface.js │ ├── question.interface.d.ts │ └── question.interface.js ├── template │ ├── commonjs │ │ ├── config.dot │ │ ├── custom_file_transport.dot │ │ ├── logger.dot │ │ ├── pages.dot │ │ ├── pages_attach.dot │ │ ├── request_helper.dot │ │ ├── schema.dot │ │ └── spec.dot │ ├── config.d.ts │ ├── config.js │ └── jsimport │ │ ├── config.dot │ │ ├── custom_file_transport.dot │ │ ├── logger.dot │ │ ├── pages.dot │ │ ├── pages_attach.dot │ │ ├── request_helper.dot │ │ ├── schema.dot │ │ └── spec.dot └── utils │ ├── foreach.d.ts │ ├── foreach.js │ ├── logs.d.ts │ ├── logs.js │ ├── modul.d.ts │ ├── modul.js │ ├── path.d.ts │ ├── path.js │ ├── question.d.ts │ ├── question.js │ ├── string.d.ts │ ├── string.js │ ├── validation.d.ts │ ├── validation.js │ ├── wait.d.ts │ └── wait.js ├── package.json ├── src ├── command │ ├── package_install.command.ts │ ├── pogen.command.ts │ └── prettier.command.ts ├── component │ ├── configs.component.ts │ ├── data.component.ts │ ├── helper.component.ts │ ├── pages.component.ts │ ├── runner.component.ts │ ├── schema.component.ts │ ├── tests.component.ts │ └── utils.component.ts ├── generator │ ├── generate.ts │ └── generate_env.ts ├── index.ts ├── interface │ ├── package.interface.ts │ └── question.interface.ts ├── template │ ├── commonjs │ │ ├── config.dot │ │ ├── custom_file_transport.dot │ │ ├── logger.dot │ │ ├── pages.dot │ │ ├── pages_attach.dot │ │ ├── request_helper.dot │ │ ├── schema.dot │ │ └── spec.dot │ ├── config.ts │ └── jsimport │ │ ├── config.dot │ │ ├── custom_file_transport.dot │ │ ├── logger.dot │ │ ├── pages.dot │ │ ├── pages_attach.dot │ │ ├── request_helper.dot │ │ ├── schema.dot │ │ └── spec.dot └── utils │ ├── foreach.ts │ ├── logs.ts │ ├── modul.ts │ ├── path.ts │ ├── question.ts │ ├── string.ts │ ├── validation.ts │ └── wait.ts ├── test ├── .babelrc ├── .example.env.dev ├── .mocharc.js ├── .prettierignore ├── jsconfig.json ├── package.json └── tests │ ├── data │ └── Auth │ │ └── auth.data.js │ ├── helpers │ └── request.helper.js │ ├── pages │ └── Auth │ │ ├── POST_login.pages.js │ │ └── POST_logout.pages.js │ ├── scenarios │ └── Auth │ │ ├── POST_login.spec.js │ │ └── POST_logout.spec.js │ ├── schemas │ └── Auth │ │ ├── POST_login.schema.js │ │ └── POST_logout.schema.js │ └── utils │ ├── config.js │ ├── custom_file_transport.js │ └── logger.js ├── tsconfig.build.json └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "overrides": [ 11 | { 12 | "env": { 13 | "node": true 14 | }, 15 | "files": [ 16 | ".eslintrc.{js,cjs}" 17 | ], 18 | "parserOptions": { 19 | "sourceType": "script" 20 | } 21 | } 22 | ], 23 | "parser": "@typescript-eslint/parser", 24 | "parserOptions": { 25 | "ecmaVersion": "latest", 26 | "sourceType": "module" 27 | }, 28 | "plugins": [ 29 | "@typescript-eslint" 30 | ], 31 | "rules": { 32 | 'no-tabs': 0, 33 | 'max-len': ['error', { 'code': 200 }], 34 | 'require-jsdoc': 0, 35 | 'no-invalid-this': 'off', 36 | 'space-before-function-paren': 'off', 37 | }, 38 | "ignorePatterns": ['node_modules/**/*'], 39 | } 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/prod-test 3 | package-lock.json 4 | .env.* 5 | runner 6 | mochawesome-report 7 | notes.txt 8 | *json 9 | !package.json 10 | !tsconfig.json 11 | !jsconfig.json 12 | !tsconfig.build.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023-present Hadi Indrawan & Collaborators 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/command/package_install.command.d.ts: -------------------------------------------------------------------------------- 1 | import { devPackageInstallInterface, packageInstallInterface } from '../interface/package.interface.js'; 2 | /** 3 | * @description package execution 4 | * @param {packageInstallInterface} packageInterface included packacge, json file, and module type 5 | * @returns {Promise} 6 | */ 7 | export declare const installPackage: (packageInterface: packageInstallInterface) => Promise; 8 | /** 9 | * @description dev package execution 10 | * @param {devPackageInstallInterface} devPackageInterface included packacge, json file, and module type 11 | * @returns {Promise} 12 | */ 13 | export declare const installDevPackge: (devPackageInterface: devPackageInstallInterface) => Promise; 14 | -------------------------------------------------------------------------------- /lib/command/package_install.command.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import { exec } from 'child_process'; 11 | import { runPrettier } from './prettier.command.js'; 12 | import { rebuildPackagejson } from '../utils/modul.js'; 13 | import { generate } from '../generator/generate.js'; 14 | /** 15 | * @description package execution 16 | * @param {packageInstallInterface} packageInterface included packacge, json file, and module type 17 | * @returns {Promise} 18 | */ 19 | export const installPackage = (packageInterface) => __awaiter(void 0, void 0, void 0, function* () { 20 | const { stringPackage, stringDevPackage, jsonfile, moduleType, prettierExist, } = packageInterface; 21 | const installProcess = exec('npm install ' + stringPackage, (err, stdout) => { 22 | if (err) 23 | console.log(err); 24 | }); 25 | //This code is registering a listener to the exit event of installProcess 26 | installProcess.on('exit', (code) => __awaiter(void 0, void 0, void 0, function* () { 27 | //checking if npm install failed or succeeded by checking exit code 28 | if (code !== 0) { 29 | //if the exit code is not 0, it means the installation has failed. So, print error message and return. 30 | console.error(`${'\x1b[31m'}npm install failed with code ${code}${'\x1b[0m'}`); 31 | return; 32 | } 33 | if (stringDevPackage != '') { 34 | yield installDevPackge({ 35 | stringDevPackage, 36 | jsonfile, 37 | moduleType, 38 | prettierExist 39 | }); 40 | } 41 | else { 42 | //If the program reaches here, it means the install process was successful. Print a success message. 43 | console.log(`${'\x1b[32m'}Installation completed successfully!${'\x1b[0m'}`); 44 | //Print message indicating automation test generation has started.. 45 | console.log(`${'\x1b[34m'}Generating automation test..${'\x1b[0m'}`); 46 | //Call the generate function to generate automation tests. 47 | yield generate(jsonfile, moduleType); 48 | yield runPrettier(prettierExist); 49 | // write test script for run the regression test 50 | yield rebuildPackagejson(); 51 | } 52 | })); 53 | }); 54 | /** 55 | * @description dev package execution 56 | * @param {devPackageInstallInterface} devPackageInterface included packacge, json file, and module type 57 | * @returns {Promise} 58 | */ 59 | export const installDevPackge = (devPackageInterface) => __awaiter(void 0, void 0, void 0, function* () { 60 | const { stringDevPackage, jsonfile, moduleType, prettierExist, } = devPackageInterface; 61 | const installOption = exec('npm install' + stringDevPackage + ' --save-dev', (err, stdout) => { 62 | if (err) 63 | console.log(err); 64 | }); 65 | installOption.on('exit', (res) => __awaiter(void 0, void 0, void 0, function* () { 66 | //checking if npm install failed or succeeded by checking exit code 67 | if (res !== 0) { 68 | //if the exit code is not 0, it means the installation has failed. So, print error message and return. 69 | console.error(`${'\x1b[31m'}npm install failed with code ${res}${'\x1b[0m'}`); 70 | return; 71 | } 72 | //If the program reaches here, it means the install process was successful. Print a success message. 73 | console.log(`${'\x1b[32m'}Installation completed successfully!${'\x1b[0m'}`); 74 | //Print message indicating automation test generation has started.. 75 | console.log(`${'\x1b[34m'}Generating automation test..${'\x1b[0m'}`); 76 | //Call the generate function to generate automation tests. 77 | yield generate(jsonfile, moduleType); 78 | yield runPrettier(prettierExist); 79 | // write test script for run the regression test 80 | yield rebuildPackagejson(); 81 | })); 82 | }); 83 | -------------------------------------------------------------------------------- /lib/command/pogen.command.d.ts: -------------------------------------------------------------------------------- 1 | export declare class PogenCommand { 2 | private scriptArg?; 3 | constructor(scriptArg?: string); 4 | initiation: () => Promise<{ 5 | [key: string]: string | boolean | string[]; 6 | }>; 7 | automation: () => Promise; 8 | environment: () => Promise; 9 | } 10 | -------------------------------------------------------------------------------- /lib/command/pogen.command.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { promisify } from 'util'; 12 | import { exec } from 'child_process'; 13 | import inquirer from 'inquirer'; 14 | import { CLIAutomationQuestion, CLIEnvironmentQuestion, CLIJSONQuestion } from '../utils/question.js'; 15 | import { existModuleType } from '../utils/modul.js'; 16 | import { runPrettier } from './prettier.command.js'; 17 | import { rebuildPackagejson } from '../utils/modul.js'; 18 | import { installDevPackge, installPackage } from './package_install.command.js'; 19 | import { log } from '../utils/logs.js'; 20 | import { generateEnv } from '../generator/generate_env.js'; 21 | import { defaultEslintConfig } from '../template/config.js'; 22 | import { generate } from '../generator/generate.js'; 23 | export class PogenCommand { 24 | constructor(scriptArg = "") { 25 | this.initiation = () => __awaiter(this, void 0, void 0, function* () { 26 | const execAsync = promisify(exec); 27 | try { 28 | const { stdout } = yield execAsync('npm list --json'); 29 | const packageList = JSON.parse(stdout).dependencies || {}; 30 | const packagesExist = Object.keys(packageList); 31 | // Define required packages 32 | const needPackage = [ 33 | '@babel/preset-env', 34 | '@babel/register', 35 | 'babel-plugin-module-resolver', 36 | 'chai', 37 | 'mocha', 38 | 'chai-http', 39 | 'chai-json-schema', 40 | 'dotenv', 41 | 'to-json-schema', 42 | 'cross-env', 43 | 'winston', 44 | ]; 45 | // Filter packages that are not installed 46 | const matchedPack = needPackage.filter(key => !packagesExist.includes(key)); 47 | // Join packages into a string 48 | const stringPackage = matchedPack.join(' '); 49 | return { 50 | packagesExist, 51 | stringPackage, 52 | mochaExist: !packagesExist.includes('mocha'), 53 | eslintExist: !packagesExist.includes('eslint'), 54 | prettierExist: !packagesExist.includes('prettier'), 55 | }; 56 | } 57 | catch (error) { 58 | console.error(`exec error: ${error}`); 59 | throw new Error('Failed to check packages.'); 60 | } 61 | }); 62 | this.automation = () => __awaiter(this, void 0, void 0, function* () { 63 | const { packagesExist, stringPackage, mochaExist, eslintExist, prettierExist, } = yield this.initiation(); 64 | let allAnswer = {}; 65 | inquirer 66 | .prompt(yield CLIAutomationQuestion({ 67 | argument: this.scriptArg, 68 | packagesList: packagesExist, 69 | mochaExist, 70 | eslintExist 71 | })) 72 | .then((answers) => __awaiter(this, void 0, void 0, function* () { 73 | Object.assign(allAnswer, answers); 74 | return yield CLIJSONQuestion(answers); 75 | })) 76 | .then((answers) => __awaiter(this, void 0, void 0, function* () { 77 | Object.assign(allAnswer, answers); 78 | const moduleType = allAnswer.moduleQ || (yield existModuleType()); 79 | let stringDevPackage = ''; 80 | if (allAnswer.eslintQ == 'Yes') { 81 | stringDevPackage += ' eslint'; 82 | // Write eslint configuration 83 | let newEslintConfig = defaultEslintConfig; 84 | if (moduleType == "Javascript modules (import/export)") { 85 | const jsonConfig = JSON.parse(defaultEslintConfig); 86 | jsonConfig.parserOptions = { ecmaVersion: 'latest', sourceType: 'module' }; 87 | newEslintConfig = JSON.stringify(jsonConfig, null, 2); 88 | } 89 | fs.writeFile('.eslintrc.json', newEslintConfig, function (err) { if (err) 90 | throw err; }); 91 | } 92 | if (allAnswer.mochaweQ == 'Yes') 93 | stringDevPackage += ' mochawesome'; 94 | switch (true) { 95 | case stringPackage !== '' && stringDevPackage !== '': 96 | console.log("Installing dependencies..."); 97 | yield installPackage({ 98 | stringPackage, 99 | stringDevPackage, 100 | jsonfile: allAnswer, 101 | moduleType, 102 | prettierExist 103 | }); 104 | break; 105 | case stringPackage !== '' && stringDevPackage === '': 106 | console.log("Installing dependencies..."); 107 | yield installPackage({ 108 | stringPackage, 109 | stringDevPackage, 110 | jsonfile: allAnswer, 111 | moduleType, 112 | prettierExist 113 | }); 114 | break; 115 | case stringPackage === '' && stringDevPackage !== '': 116 | console.log("Installing dependencies..."); 117 | yield installDevPackge({ 118 | stringDevPackage, 119 | jsonfile: allAnswer, 120 | moduleType, 121 | prettierExist 122 | }); 123 | break; 124 | case stringPackage === '' && stringDevPackage === '': 125 | console.log(`${'\x1b[32m'}Dependencies already installed${'\x1b[0m'}`); 126 | yield generate(allAnswer, moduleType); 127 | yield runPrettier(prettierExist); 128 | yield rebuildPackagejson(); 129 | break; 130 | default: 131 | break; 132 | } 133 | })) 134 | .catch((err) => __awaiter(this, void 0, void 0, function* () { 135 | log('Please type correct answer!', 'yellow'); 136 | yield this.automation(); 137 | })); 138 | }); 139 | this.environment = () => __awaiter(this, void 0, void 0, function* () { 140 | inquirer 141 | .prompt(yield CLIEnvironmentQuestion()) 142 | .then((answers) => __awaiter(this, void 0, void 0, function* () { 143 | //Print message indicating environment test generation has started.. 144 | log(`Generating environment test..`, 'blue'); 145 | //Call the generate function to generate environment tests. 146 | yield generateEnv(answers.jsonFileQ.includes('"') ? answers.jsonFileQ.replace(/"/g, '') : answers.jsonFileQ, answers.envQ); 147 | })) 148 | .catch((err) => __awaiter(this, void 0, void 0, function* () { 149 | log('Please type correct answer!', 'yellow'); 150 | yield this.environment(); 151 | })); 152 | }); 153 | this.scriptArg = scriptArg; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/command/prettier.command.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description prettier plugin execution 3 | * @param {boolean | string[] | string} prettierExist prettier exist condition 4 | * @returns {Promise} 5 | */ 6 | export declare const runPrettier: (prettierExist: boolean | string[] | string) => Promise; 7 | -------------------------------------------------------------------------------- /lib/command/prettier.command.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import { exec } from 'child_process'; 11 | /** 12 | * @description prettier plugin execution 13 | * @param {boolean | string[] | string} prettierExist prettier exist condition 14 | * @returns {Promise} 15 | */ 16 | export const runPrettier = (prettierExist) => __awaiter(void 0, void 0, void 0, function* () { 17 | const command = 'npx prettier . --write --trailing-comma none'; 18 | if (!prettierExist) { 19 | const installProcess = exec('npm install --save-dev --save-exact prettier', (err, stdout) => { 20 | if (err) 21 | console.log(err); 22 | }); 23 | //This code is registering a listener to the exit event of installProcess 24 | installProcess.on('exit', (code) => __awaiter(void 0, void 0, void 0, function* () { 25 | exec(command, (err, stdout) => { 26 | if (err) 27 | console.log(err); 28 | }); 29 | })); 30 | } 31 | else { 32 | exec(command, (err, stdout) => { 33 | if (err) 34 | console.log(err); 35 | }); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /lib/component/configs.component.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description asynchronous function to write config into directory 3 | * @param {any[]} configList config list will be write 4 | * @returns {Promise} 5 | */ 6 | export declare const writeConfigs: (configList: any[]) => Promise; 7 | -------------------------------------------------------------------------------- /lib/component/configs.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { asyncForEach } from '../utils/foreach.js'; 12 | import { isFileExisted } from '../utils/modul.js'; 13 | import { basedir } from '../utils/path.js'; 14 | import { waitFor } from '../utils/wait.js'; 15 | /** 16 | * @description asynchronous function to write config into directory 17 | * @param {any[]} configList config list will be write 18 | * @returns {Promise} 19 | */ 20 | export const writeConfigs = (configList) => __awaiter(void 0, void 0, void 0, function* () { 21 | yield asyncForEach(configList, (item) => __awaiter(void 0, void 0, void 0, function* () { 22 | try { 23 | const [fileExists] = yield isFileExisted(basedir(), `${item.filename}`); 24 | if (!fileExists) { 25 | // create file test 26 | fs.writeFileSync(basedir() + `/${item.filename}`, item.template, "utf8"); 27 | yield waitFor(500); 28 | } 29 | } 30 | catch (err) { 31 | console.log(err); 32 | } 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /lib/component/data.component.d.ts: -------------------------------------------------------------------------------- 1 | interface dataComponentInterface { 2 | element: any; 3 | path: string; 4 | moduleType?: string; 5 | } 6 | /** 7 | * @description asynchronous function to write data into directory 8 | * @param {dataComponentInterface} writeDataParams included element json, path and module type 9 | * @returns {Promise} 10 | */ 11 | export declare const writeData: (writeDataParams: dataComponentInterface) => Promise; 12 | export {}; 13 | -------------------------------------------------------------------------------- /lib/component/data.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from "fs"; 11 | import { asyncForEach } from "../utils/foreach.js"; 12 | import { isFileExisted } from "../utils/modul.js"; 13 | import { basedir } from "../utils/path.js"; 14 | import { toLowerCase } from "../utils/string.js"; 15 | import { waitFor } from "../utils/wait.js"; 16 | /** 17 | * @description asynchronous function to write data into directory 18 | * @param {dataComponentInterface} writeDataParams included element json, path and module type 19 | * @returns {Promise} 20 | */ 21 | export const writeData = (writeDataParams) => __awaiter(void 0, void 0, void 0, function* () { 22 | var _a, _b; 23 | const { element, path, moduleType } = writeDataParams; 24 | // Here, a constant variable dataDir is declared and assigned to the string 'tests/data/file'. 25 | // console.log({ element, path }); 26 | let template = `export const {{var_name}} = [{{ddt}}]`; 27 | const ddt = { 28 | case: { 29 | name: "Successful login", 30 | schema: "success", 31 | status: 200, 32 | default: true 33 | }, 34 | driven: {}, 35 | attachment: {} 36 | }; 37 | if (element.request.hasOwnProperty('body')) { 38 | try { 39 | fs.mkdirSync(path, { recursive: true }); 40 | } 41 | catch (err) { 42 | console.log(err); 43 | } 44 | } 45 | // write body 46 | let bodyRaw = ''; 47 | let attKey = ''; 48 | if (element.request.hasOwnProperty('body')) { 49 | if (((_a = element.request.body) === null || _a === void 0 ? void 0 : _a.mode) == 'raw') { 50 | bodyRaw = yield element.request.body.raw; 51 | } 52 | else if (((_b = element.request.body) === null || _b === void 0 ? void 0 : _b.mode) == 'formdata') { 53 | let data = yield element.request.body.formdata; 54 | let first = true; 55 | let first1 = true; 56 | bodyRaw += '{' + '\r\n' + '\t\t\t'; 57 | attKey += '{' + '\r\n' + '\t\t\t'; 58 | if (data.some((datas) => datas['type'] === 'file')) { 59 | yield asyncForEach(data, (body) => __awaiter(void 0, void 0, void 0, function* () { 60 | // console.log(body); 61 | if (body.disabled !== true) { 62 | let value = yield body.value; 63 | if (body.type === 'text') { 64 | bodyRaw += `"${body.key}": "${value}"`; 65 | } 66 | else if (body.type === 'text' && (value.includes('{') || value.includes('['))) { 67 | bodyRaw += `"${body.key}": ${value}`; 68 | } 69 | else { 70 | let src = (typeof body.src !== 'object') ? body.src : JSON.stringify(body.src); 71 | attKey += `${first1 === false ? ',' : ''}\r\n\t\t\t"${body.key}": "${src}"`; 72 | first1 = false; 73 | } 74 | } 75 | })); 76 | } 77 | else { 78 | yield asyncForEach(data.filter((body) => body.disabled !== true), (body) => __awaiter(void 0, void 0, void 0, function* () { 79 | if (first === false) { 80 | bodyRaw += ',\r\n\t\t\t'; 81 | } 82 | bodyRaw += `"${body.key}": "${body.value}"`; 83 | first = false; 84 | })); 85 | } 86 | bodyRaw += '\r\n' + '\t\t' + '}'; 87 | attKey += '\r\n' + '\t\t' + '}'; 88 | yield waitFor(50); 89 | } 90 | } 91 | let name = toLowerCase(element.name); 92 | if (element.request.hasOwnProperty('body')) { 93 | ddt.driven = JSON.parse(bodyRaw); 94 | if (attKey != '') 95 | ddt.attachment = JSON.parse(attKey); 96 | template = template.replace('{{var_name}}', `${name.replace('-', '_').replace('(', '').replace(')', '')}_data`); 97 | template = template.replace('{{ddt}}', JSON.stringify(ddt)); 98 | yield waitFor(50); 99 | const path_split = path.split('/'); 100 | if (path_split.length > 2) { 101 | const suite_name = path_split[2].toLowerCase().replace(/\s/g, '') + '.data.js'; 102 | try { 103 | const [fileExists] = yield isFileExisted(path, suite_name); 104 | if (!fileExists) { 105 | // create file test 106 | fs.writeFileSync(`${path}/${suite_name}`, template, 'utf8'); 107 | yield waitFor(200); 108 | } 109 | else { 110 | let current_contents = fs.readFileSync(basedir() + `/${path}/${suite_name}`, 'utf8'); 111 | const regex = /(?<=export const\s)\w+(?=\s=)/g; 112 | const var_name_list = current_contents.match(regex) || ''; 113 | if (!var_name_list.includes(`${name.replace('-', '_')}_data`)) { 114 | current_contents += '\n\n' + template; 115 | } 116 | fs.writeFileSync(`${path}/${suite_name}`, current_contents, 'utf8'); 117 | yield waitFor(200); 118 | } 119 | } 120 | catch (err) { 121 | console.log(err); 122 | } 123 | } 124 | else { 125 | const fileName = `${name}.data.js`; 126 | try { 127 | const [fileExists] = yield isFileExisted(path, fileName); 128 | if (!fileExists) { 129 | // create file test 130 | fs.writeFileSync(`${path}/${fileName}`, template, 'utf8'); 131 | yield waitFor(500); 132 | } 133 | } 134 | catch (err) { 135 | console.log(err); 136 | } 137 | } 138 | } 139 | }); 140 | -------------------------------------------------------------------------------- /lib/component/helper.component.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description asynchronous function to write helper into directory 3 | * @param {string} moduleType module type will be used 4 | * @returns {Promise} 5 | */ 6 | export declare const writeHelper: (moduleType: string) => Promise; 7 | -------------------------------------------------------------------------------- /lib/component/helper.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { promisify } from 'util'; 12 | import { isFileExisted } from '../utils/modul.js'; 13 | import basePath from '../utils/path.js'; 14 | import { waitFor } from '../utils/wait.js'; 15 | /** 16 | * @description asynchronous function to write helper into directory 17 | * @param {string} moduleType module type will be used 18 | * @returns {Promise} 19 | */ 20 | export const writeHelper = (moduleType) => __awaiter(void 0, void 0, void 0, function* () { 21 | try { 22 | const writeFile = promisify(fs.writeFile); 23 | // template dir name 24 | const templateDirRequest = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/request_helper.dot" : "lib/template/commonjs/request_helper.dot"; 25 | // create helper directory if it doesn't exists 26 | const helperDir = "tests/helpers"; 27 | fs.mkdirSync(helperDir, { recursive: true }); 28 | // Check if a file named 'request.helper.js' exists in the tests/helper dir 29 | // If it does not exist then create a new file based on the template file 'requestHelper.dot' 30 | const [fileExists] = yield isFileExisted(helperDir, "request.helper.js"); 31 | if (!fileExists) { 32 | // create file test 33 | writeFile("tests/helpers/request.helper.js", fs.readFileSync(basePath() + templateDirRequest, "utf8")); 34 | yield waitFor(500); 35 | } 36 | } 37 | catch (err) { 38 | console.log(err); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /lib/component/pages.component.d.ts: -------------------------------------------------------------------------------- 1 | interface pagesComponentInterface { 2 | element: any; 3 | path: string; 4 | schemaPath: string; 5 | dataPath: string; 6 | helperPath: string; 7 | moduleType: string; 8 | configPath: string; 9 | loggerPath: string; 10 | } 11 | /** 12 | * @description asynchronous function to write pages into directory 13 | * @param {pagesComponentInterface} writePagesParams included element json and all needed path 14 | * @returns {Promise} 15 | */ 16 | export declare const writePages: (writePagesParams: pagesComponentInterface) => Promise; 17 | export {}; 18 | -------------------------------------------------------------------------------- /lib/component/pages.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { asyncForEach } from '../utils/foreach.js'; 12 | import { isFileExisted } from '../utils/modul.js'; 13 | import basePath from '../utils/path.js'; 14 | import { toLowerCase } from '../utils/string.js'; 15 | import { waitFor } from '../utils/wait.js'; 16 | /** 17 | * @description asynchronous function to write pages into directory 18 | * @param {pagesComponentInterface} writePagesParams included element json and all needed path 19 | * @returns {Promise} 20 | */ 21 | export const writePages = (writePagesParams) => __awaiter(void 0, void 0, void 0, function* () { 22 | const { element, path, schemaPath, dataPath, helperPath, moduleType, configPath, loggerPath } = writePagesParams; 23 | // template dir name 24 | const templateDir = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/pages.dot" : "lib/template/commonjs/pages.dot"; 25 | const templateDirAttach = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/pages_attach.dot" : "lib/template/commonjs/pages_attach.dot"; 26 | // read template file 27 | let contents = fs.readFileSync(basePath() + templateDir, 'utf8'); 28 | let name = toLowerCase(element.name); 29 | // write method 30 | let method; 31 | if (element.request.hasOwnProperty('method')) { 32 | method = yield element.request.method; 33 | } 34 | // write body 35 | let payload = ''; 36 | const request = element.request; 37 | const requestBody = request.body; 38 | if (element.request.hasOwnProperty('body')) { 39 | if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.mode) === 'raw') { 40 | payload = '\r\n\t\t.send(await this.getMappedBody(await new requestHelper().getPayload(args)))'; 41 | } 42 | else if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.mode) === 'formdata') { 43 | const formData = requestBody.formdata; 44 | if (formData.some((data) => data.type === 'file')) { 45 | contents = fs.readFileSync(basePath() + templateDirAttach, 'utf8'); 46 | } 47 | else { 48 | contents = fs.readFileSync(basePath() + templateDir, 'utf8'); 49 | payload = '\r\n\t\t.send(await this.getMappedBody(await new requestHelper().getPayload(args)))'; 50 | } 51 | } 52 | } 53 | // write headers 54 | let headers = ''; 55 | if (element.request.hasOwnProperty('header')) { 56 | yield asyncForEach(element.request.header, (header) => __awaiter(void 0, void 0, void 0, function* () { 57 | if (!header.disabled) { 58 | headers += '\r\n' + '\t\t' + '.set("' + header.key + '", "' + header.value + '")'; 59 | } 60 | })); 61 | } 62 | // if any auth 63 | if (element.request.hasOwnProperty('auth')) { 64 | let auth = yield element.request.auth; 65 | if (auth.hasOwnProperty('bearer')) { 66 | headers += '\r\n' + '\t\t' + '.set("Authorization", "' + (auth.type).replace(/\w\S*/g, (auth.type).charAt(0).toUpperCase() + (auth.type).substr(1).toLowerCase()) + ' ' + auth.bearer[0].value + '")'; 67 | } 68 | } 69 | // write endpoint 70 | let queries = ''; 71 | if (element.request.hasOwnProperty('url')) { 72 | if (element.request.url.hasOwnProperty('query')) { 73 | let firstData = true; 74 | queries += '\r\n' + '\t\t' + '.query({ '; 75 | yield asyncForEach(element.request.url.query, (query) => __awaiter(void 0, void 0, void 0, function* () { 76 | if (query.disabled != true) { 77 | if (firstData === false) 78 | queries += ', '; 79 | queries += query.key + ': "' + query.value + '"'; 80 | firstData = false; 81 | } 82 | })); 83 | queries += ' })'; 84 | } 85 | } 86 | // write url 87 | let url = ''; 88 | if (element.request.hasOwnProperty('url')) { 89 | let raw_url = yield element.request.url.raw; 90 | if (raw_url.includes('http')) { 91 | url = new URL(raw_url).pathname; 92 | } 93 | else { 94 | const fakeBase = 'https://fake.dot'; 95 | let base = raw_url.split('/'); 96 | url = new URL((raw_url).replace(base[0], fakeBase)).pathname; 97 | } 98 | } 99 | let code = contents.replace("{{method}}", (method).toLowerCase()); 100 | code = code.replace("{{url}}", url); 101 | code = code.replace("{{query}}", queries); 102 | code = code.replace("{{header}}", headers); 103 | code = code.replace("{{payload}}", payload); 104 | if (element.request.hasOwnProperty('body')) { 105 | code = code.replace("{{body_section}}", ` 106 | // This method used for provide body or payload of the request and return object 107 | async getMappedBody(...args) { 108 | const defaultData = new requestHelper().getDefaultData(data.${name.replace('-', '_').replace('(', '').replace(')', '')}_data) 109 | const dataMapped = await new requestHelper().mapObject(defaultData.driven, args); 110 | 111 | return dataMapped 112 | } 113 | `); 114 | } 115 | else { 116 | code = code.replace("{{body_section}}", ''); 117 | } 118 | if (payload == '') 119 | code = code.replace("{{data_attach_name}}", `${name.replace('-', '_')}_data`); 120 | code = code.replace("{{path_schema}}", schemaPath.replace(/\\/g, "/") + '/' + method + '_' + name + '.schema.js'); 121 | if (element.request.hasOwnProperty('body')) { 122 | let importStatement = moduleType === "Javascript modules (import/export)" ? "\n import * as data from '" : "\n const data = require('"; 123 | let endPath = moduleType === "Javascript modules (import/export)" ? ".data.js'" : ".data.js')"; 124 | if (dataPath.replace(/\\/g, "/").split('/').length >= 2) { 125 | code = code.replace("{{path_data}}", importStatement + dataPath.replace(/\\/g, "/").split('/').slice(0, 2).join('/') + `/${toLowerCase(dataPath.replace(/\\/g, "/").split('/')[1])}` + endPath); 126 | } 127 | else { 128 | code = code.replace("{{path_data}}", importStatement + dataPath.replace(/\\/g, "/") + `/${name}` + endPath); 129 | } 130 | } 131 | else { 132 | code = code.replace("{{path_data}}", ''); 133 | } 134 | code = code.replace("{{path_helper}}", helperPath); 135 | code = code.replace("{{path_config}}", configPath); 136 | code = code.replace("{{path_logger}}", loggerPath); 137 | // check if pages file exists 138 | if (element.request.hasOwnProperty('url')) { 139 | try { 140 | const [fileExists] = yield isFileExisted(path, method + '_' + name + '.pages.js'); 141 | if (!fileExists) { 142 | // create file test 143 | fs.writeFileSync(path + '/' + method + '_' + name + '.pages.js', code, 'utf8'); 144 | yield waitFor(500); 145 | } 146 | } 147 | catch (err) { 148 | console.log(err); 149 | } 150 | } 151 | }); 152 | -------------------------------------------------------------------------------- /lib/component/runner.component.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description asynchronous function to write runner into directory 3 | * @param {any[]} testsPath all generated test path 4 | * @returns {Promise} 5 | */ 6 | export declare const writeRunner: (testsPath: any[]) => Promise; 7 | -------------------------------------------------------------------------------- /lib/component/runner.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { defaultMochaConfig } from '../template/config.js'; 12 | import { isFileExisted } from '../utils/modul.js'; 13 | import { basedir } from '../utils/path.js'; 14 | import { waitFor } from '../utils/wait.js'; 15 | /** 16 | * @description asynchronous function to write runner into directory 17 | * @param {any[]} testsPath all generated test path 18 | * @returns {Promise} 19 | */ 20 | export const writeRunner = (testsPath) => __awaiter(void 0, void 0, void 0, function* () { 21 | const mapTestbySuite = yield testsPath.reduce((result, element) => { 22 | const parts = element.split('/'); 23 | const key = parts[2].includes('.js') ? 'Base' : parts[2].replace(/\s/g, ''); 24 | if (!result[key]) { 25 | result[key] = []; 26 | } 27 | result[key].push(element); 28 | return result; 29 | }, {}); 30 | try { 31 | const [fileExists] = yield isFileExisted(basedir(), ".mocharc.js"); 32 | if (!fileExists) { 33 | mapTestbySuite['Regression'] = 'tests/scenarios/**/*.spec.js'; 34 | let contents = defaultMochaConfig.replace('{{runner}}', JSON.stringify(mapTestbySuite)); 35 | contents = contents.replace('{{ignorelist}}', '[\n// write your ignore tests here \n]'); 36 | // create file test 37 | fs.writeFileSync(basedir() + '/.mocharc.js', contents, 'utf8'); 38 | yield waitFor(500); 39 | } 40 | else { 41 | let current_contents = fs.readFileSync(basedir() + `/.mocharc.js`, 'utf8'); 42 | let contents = defaultMochaConfig; 43 | const getTestListVariable = /const\s+runTestsList\s+=\s+(\{[\s\S]*?\});/; 44 | const match = getTestListVariable.exec(current_contents); 45 | const getIgnoreListVariable = /const\s+ignoreTestsList\s+=\s+(\[[\s\S]*?\]);/; 46 | const matchIgnore = getIgnoreListVariable.exec(current_contents); 47 | if (match && match[1]) { 48 | const convertObjectKey = /(\w+)\s*:/g; 49 | const finalString = match[1].replace(convertObjectKey, '"$1":'); 50 | const runTestsList = JSON.parse(finalString); 51 | contents = contents.replace('{{runner}}', JSON.stringify(Object.assign(runTestsList, mapTestbySuite))); 52 | } 53 | else { 54 | console.log("Variable ignoreTestsList not found in the input string."); 55 | } 56 | if (matchIgnore && matchIgnore[1]) { 57 | contents = contents.replace('{{ignorelist}}', matchIgnore[1]); 58 | } 59 | else { 60 | console.log("Variable runTestsList not found in the input string."); 61 | } 62 | // create file test 63 | fs.writeFileSync(basedir() + '/.mocharc.js', contents, 'utf8'); 64 | yield waitFor(500); 65 | } 66 | } 67 | catch (err) { 68 | console.log(err); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /lib/component/schema.component.d.ts: -------------------------------------------------------------------------------- 1 | interface schemaComponentInterface { 2 | element: any; 3 | path: string; 4 | moduleType: string; 5 | } 6 | /** 7 | * @description asynchronous function to write schema into directory 8 | * @param {schemaComponentInterface} writeSchemaParams included element json, path, and module type 9 | * @returns {Promise} 10 | */ 11 | export declare const writeSchema: (writeSchemaParams: schemaComponentInterface) => Promise; 12 | export {}; 13 | -------------------------------------------------------------------------------- /lib/component/schema.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from "fs"; 11 | import { isFileExisted } from "../utils/modul.js"; 12 | import basePath from "../utils/path.js"; 13 | import { waitFor } from "../utils/wait.js"; 14 | /** 15 | * @description asynchronous function to write schema into directory 16 | * @param {schemaComponentInterface} writeSchemaParams included element json, path, and module type 17 | * @returns {Promise} 18 | */ 19 | export const writeSchema = (writeSchemaParams) => __awaiter(void 0, void 0, void 0, function* () { 20 | const { element, path, moduleType } = writeSchemaParams; 21 | // template dir name 22 | const templateDir = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/schema.dot" : "lib/template/commonjs/schema.dot"; 23 | // The following code creates a variable called 'name' and assigns it the value obtained from the 'name' property of the 'element' object, which is then converted to lowercase and all spaces in it are removed. 24 | let name = (element.name).toLowerCase().replace(/\s/g, ''); 25 | name = name.replace(/\//g, ''); 26 | // A variable called 'method' is created and assigned the value obtained from the 'method' property of the 'element.request' object. 27 | let method; 28 | if (element.request.hasOwnProperty('method')) { 29 | method = element.request.method; 30 | } 31 | // check if file exists 32 | if (element.request.hasOwnProperty('url')) { 33 | try { 34 | const [fileExists] = yield isFileExisted(path, method + '_' + name + '.schema.js'); 35 | if (!fileExists) { 36 | // create file test 37 | fs.writeFileSync(path + '/' + method + '_' + name + '.schema.js', fs.readFileSync(basePath() + templateDir, 'utf8'), 'utf8'); 38 | yield waitFor(500); 39 | } 40 | } 41 | catch (err) { 42 | console.log(err); 43 | } 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /lib/component/tests.component.d.ts: -------------------------------------------------------------------------------- 1 | interface testComponentInterface { 2 | element: any; 3 | path: string; 4 | pagesPath: string; 5 | dataPath: string; 6 | moduleType: string; 7 | configPath: string; 8 | } 9 | /** 10 | * @description asynchronous function to write tests into directory 11 | * @param {testComponentInterface} writeTestParams included element json and all needed path 12 | * @returns {Promise} 13 | */ 14 | export declare const writeTest: (writeTestParams: testComponentInterface) => Promise; 15 | export {}; 16 | -------------------------------------------------------------------------------- /lib/component/tests.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from "fs"; 11 | import { log } from "../utils/logs.js"; 12 | import { isFileExisted } from "../utils/modul.js"; 13 | import basePath from "../utils/path.js"; 14 | import { toLowerCase } from "../utils/string.js"; 15 | import { waitFor } from "../utils/wait.js"; 16 | /** 17 | * @description asynchronous function to write tests into directory 18 | * @param {testComponentInterface} writeTestParams included element json and all needed path 19 | * @returns {Promise} 20 | */ 21 | export const writeTest = (writeTestParams) => __awaiter(void 0, void 0, void 0, function* () { 22 | var _a, _b; 23 | const { element, path, pagesPath, dataPath, moduleType, configPath } = writeTestParams; 24 | // template dir name 25 | const templateDir = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/spec.dot" : "lib/template/commonjs/spec.dot"; 26 | // read template fileimport { log } from './../utils/logs.js'; 27 | let contents = fs.readFileSync(basePath() + templateDir, 'utf8'); 28 | let name = toLowerCase(element.name); 29 | const method = (_b = (_a = element.request) === null || _a === void 0 ? void 0 : _a.method) !== null && _b !== void 0 ? _b : ''; 30 | let testFunc = ''; 31 | if (element.request.hasOwnProperty('body')) { 32 | testFunc = ` 33 | data.${toLowerCase(element.name).replace('(', '').replace(')', '')}_data.forEach(async (data) => { 34 | it(data.case.name, async () => { 35 | const response = await new Request().api(data.driven) 36 | 37 | expect(response.status).to.equals(data.case.status) 38 | expect(response.body).to.be.jsonSchema(new Request().expectedSchema(data.case.schema)) 39 | }) 40 | }) 41 | `; 42 | } 43 | else { 44 | testFunc = ` 45 | it("Successful case", async () => { 46 | const response = await new Request().api() 47 | 48 | expect(response.status).to.equals(200) 49 | expect(response.body).to.be.jsonSchema(new Request().expectedSchema("success")) 50 | }) 51 | `; 52 | } 53 | let suiteDataPath = toLowerCase(element.name); 54 | let code = contents.replace("{{describe}}", 'Test ' + element.name); 55 | code = code.replace("{{page_path}}", pagesPath.replace(/\\/g, "/") + '/' + method + '_' + name + '.pages.js'); 56 | if (element.request.hasOwnProperty('body')) { 57 | let importStatement = moduleType === "Javascript modules (import/export)" ? "\n import * as data from '" : "\n const data = require('"; 58 | let endPath = moduleType === "Javascript modules (import/export)" ? ".data.js'" : ".data.js')"; 59 | if (dataPath.replace(/\\/g, "/").split('/').length >= 2) { 60 | code = code.replace("{{data_path}}", importStatement + dataPath.replace(/\\/g, "/").split('/').slice(0, 2).join('/') + `/${toLowerCase(dataPath.replace(/\\/g, "/").split('/')[1])}` + endPath); 61 | } 62 | else { 63 | code = code.replace("{{data_path}}", importStatement + dataPath.replace(/\\/g, "/") + `/${suiteDataPath}` + endPath); 64 | } 65 | } 66 | else { 67 | code = code.replace("{{data_path}}", ''); 68 | } 69 | code = code.replace("{{config_path}}", configPath); 70 | code = code.replace("{{test_section}}", testFunc); 71 | // check if file exists 72 | if (element.request.hasOwnProperty('url')) { 73 | try { 74 | const [fileExists] = yield isFileExisted(path, method + '_' + name + '.spec.js'); 75 | if (!fileExists) { 76 | // create file test 77 | fs.writeFileSync(path + '/' + method + '_' + name + '.spec.js', code, 'utf8'); 78 | yield waitFor(500); 79 | log(`ø Generate Test ${path.replace(/\\/g, "/") + '/' + method + '_' + name + '.spec.js'} completed successfully`, 'green'); 80 | } 81 | else { 82 | // file was existed 83 | log(`ø The request of ${element.name} has already created`, 'yellow'); 84 | } 85 | } 86 | catch (err) { 87 | console.log(err); 88 | } 89 | } 90 | else { 91 | // invalid request 92 | log(`ø ${element.name} was invalid request!`, 'red'); 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /lib/component/utils.component.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description asynchronous function to write utils into directory 3 | * @param {string} moduleType module type will be used 4 | * @returns {Promise} 5 | */ 6 | export declare const writeUtils: (moduleType: string) => Promise; 7 | -------------------------------------------------------------------------------- /lib/component/utils.component.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { isFileExisted } from '../utils/modul.js'; 12 | import basePath from '../utils/path.js'; 13 | import { waitFor } from '../utils/wait.js'; 14 | /** 15 | * @description asynchronous function to write utils into directory 16 | * @param {string} moduleType module type will be used 17 | * @returns {Promise} 18 | */ 19 | export const writeUtils = (moduleType) => __awaiter(void 0, void 0, void 0, function* () { 20 | // create helper directory if it doesn't exists 21 | const utilsDir = "tests/utils"; 22 | fs.mkdirSync(utilsDir, { recursive: true }); 23 | const utilsList = [ 24 | 'config', 25 | 'logger', 26 | 'custom_file_transport' 27 | ]; 28 | for (const util of utilsList) { 29 | // template dir name 30 | const templateDir = moduleType == "Javascript modules (import/export)" ? `lib/template/jsimport/${util}.dot` : `lib/template/commonjs/${util}.dot`; 31 | // Check if a file named 'request.helper.js' exists in the tests/helper dir 32 | // If it does not exist then create a new file based on the template file 'requestHelper.dot' 33 | try { 34 | const [fileExists] = yield isFileExisted(utilsDir, `${util}.js`); 35 | if (!fileExists) { 36 | // create file test 37 | fs.writeFileSync(`tests/utils/${util}.js`, fs.readFileSync(basePath() + templateDir, "utf8"), 'utf8'); 38 | yield waitFor(500); 39 | } 40 | } 41 | catch (err) { 42 | console.log(err); 43 | } 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /lib/generator/generate.d.ts: -------------------------------------------------------------------------------- 1 | interface optionInterface { 2 | customKey: string[]; 3 | jsonFileQ: string; 4 | } 5 | /** 6 | * @description main automation generator 7 | * @param {optionInterface} option included custom key of collection and file (.json) 8 | * @param {any} moduleType module type selected 9 | * @returns {Promise} 10 | */ 11 | export declare const generate: (option: optionInterface, moduleType: any) => Promise; 12 | export {}; 13 | -------------------------------------------------------------------------------- /lib/generator/generate_env.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description main environment generator 3 | * @param {any} file json file (.json) 4 | * @param {any} envName environment name 5 | * @returns {Promise} 6 | */ 7 | export declare const generateEnv: (file: any, envName: any) => Promise; 8 | -------------------------------------------------------------------------------- /lib/generator/generate_env.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { promisify } from 'util'; 12 | import { asyncForEach } from '../utils/foreach.js'; 13 | import { isFileExisted } from '../utils/modul.js'; 14 | import { waitFor } from '../utils/wait.js'; 15 | const readFile = promisify(fs.readFile); 16 | /** 17 | * @description main environment generator 18 | * @param {any} file json file (.json) 19 | * @param {any} envName environment name 20 | * @returns {Promise} 21 | */ 22 | export const generateEnv = (file, envName) => __awaiter(void 0, void 0, void 0, function* () { 23 | let envStr = ''; 24 | const data = yield readFile(file, 'utf8'); 25 | const { item: items } = JSON.parse(data); 26 | let first; 27 | asyncForEach(items, (item) => __awaiter(void 0, void 0, void 0, function* () { 28 | if (item.enabled) { 29 | if (first === false) 30 | envStr += '\r\n'; 31 | envStr += item.key + '=' + item.value; 32 | first = false; 33 | } 34 | })); 35 | yield waitFor(100); 36 | // check if pages file exists 37 | isFileExisted(process.cwd(), '.env.' + envName) 38 | .then((data) => { 39 | if (!data[0]) { 40 | // create file test 41 | fs.writeFile('.env.' + envName, envStr, function (err) { if (err) 42 | throw err; }); 43 | // _postman_isSubFolder 44 | console.log(`${'\x1b[32m'}ø Generate environment file completed successfully${'\x1b[0m'}`); 45 | } 46 | else { 47 | console.log(`${'\x1b[33m'}ø The environment file has already created${'\x1b[0m'}`); 48 | } 49 | }) 50 | .catch((err) => console.log(err)); 51 | }); 52 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | export {}; 3 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { PogenCommand } from "./command/pogen.command.js"; 3 | const argument = process.argv[process.argv.length - 1]; 4 | const pogenCommand = new PogenCommand(argument); 5 | if (argument === 'generate' || argument === '' || argument.includes('/.bin/generate') || argument.includes('\\po-gen\\lib')) { 6 | console.log('Initiating automation generation'); 7 | pogenCommand.automation(); 8 | } 9 | else if (argument === 'env-generate') { 10 | console.log('Initiating environment generation'); 11 | pogenCommand.environment(); 12 | } 13 | else { 14 | console.log(`Unknown argument: ${argument}`); 15 | } 16 | -------------------------------------------------------------------------------- /lib/interface/package.interface.d.ts: -------------------------------------------------------------------------------- 1 | export interface packageInstallInterface { 2 | stringPackage: boolean | string[] | string; 3 | stringDevPackage: string; 4 | jsonfile: any; 5 | moduleType: string; 6 | prettierExist: boolean | string[] | string; 7 | } 8 | export interface devPackageInstallInterface { 9 | stringDevPackage: boolean | string[] | string; 10 | jsonfile: any; 11 | moduleType: string; 12 | prettierExist: boolean | string[] | string; 13 | } 14 | -------------------------------------------------------------------------------- /lib/interface/package.interface.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/interface/question.interface.d.ts: -------------------------------------------------------------------------------- 1 | export interface CLIAutomationQuestionInterface { 2 | argument: string | undefined; 3 | packagesList: boolean | string[] | string; 4 | mochaExist: boolean | string[] | string; 5 | eslintExist: boolean | string[] | string; 6 | } 7 | -------------------------------------------------------------------------------- /lib/interface/question.interface.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/template/commonjs/config.dot: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv"); 2 | 3 | export class Config { 4 | constructor() { 5 | // Write your constructor here, if you need 6 | } 7 | 8 | env() { 9 | // change according to your need 10 | dotenv.config({ path: __dirname + `/../../.env.${process.env.NODE_ENV}` }); 11 | // Defining an object named 'env', contained your variables needed 12 | return { 13 | host: process.env.MAIN 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/template/commonjs/custom_file_transport.dot: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const winston = require("winston"); 3 | 4 | const logDir = './logs'; 5 | if (!fs.existsSync(logDir)) { 6 | fs.mkdirSync(logDir); 7 | } 8 | 9 | export class CustomFileTransport extends winston.transports.File { 10 | constructor(options) { 11 | // Pass the options to the parent class constructor 12 | super({ ...options, dirname: logDir }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/template/commonjs/logger.dot: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const { createLogger, format, transports } = require("winston"); 3 | const { combine, timestamp, printf } = format; 4 | const { CustomFileTransport } = require('./custom_file_transport') 5 | const colorizer = format.colorize(); 6 | 7 | const logFormat = combine( 8 | timestamp({ 9 | format: "YYYY-MM-DD HH:mm:ss" 10 | }), 11 | printf((output) => 12 | colorizer.colorize( 13 | output.level, 14 | `[${output.timestamp}] ${output.level.toUpperCase()}: ${output.message}` 15 | ) 16 | ) 17 | ); 18 | 19 | export class Logger { 20 | constructor() { 21 | // Write your constructor here, if you need 22 | } 23 | 24 | console = ({ service = "example", level = "debug" }) => 25 | createLogger({ 26 | level, 27 | defaultMeta: { service }, 28 | format: logFormat, 29 | transports: [ 30 | // Output logs to console in simple message format 31 | new transports.Console() 32 | ] 33 | }); 34 | 35 | file = ({ service = "example", type = "response", level = "debug" }) => 36 | createLogger({ 37 | level, 38 | defaultMeta: { service }, 39 | format: logFormat, 40 | transports: [ 41 | // Output logs to console in simple message format 42 | new CustomFileTransport({ 43 | format: winston.format.json(), 44 | filename: `./logs/${type}.json` 45 | }) 46 | ] 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /lib/template/commonjs/pages.dot: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | chai.use(require('chai-http')) 3 | const requestHelper = require('{{path_helper}}') 4 | const { Config } = require('{{path_config}}'){{path_data}} 5 | const { schema } = require('{{path_schema}}') 6 | const { Logger } = require('{{path_logger}}') 7 | const logger = new Logger(); 8 | 9 | class Request { 10 | constructor() { 11 | // Write your constructor here, if you nee 12 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 13 | this.host = new Config().env().host; 14 | this.url = "{{url}}" // Set up the API path to the route endpoint 15 | } 16 | 17 | get request() { 18 | return chai.request(this.host) 19 | } 20 | 21 | // This method handles making the HTTP request based on given arguments. 22 | async api(...args) { 23 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 24 | try { 25 | const response = await this.request.{{method}}(this.url){{query}}{{header}}{{payload}} 26 | 27 | await this.createLogging(response); 28 | return response; 29 | } catch (err) { 30 | await this.createLogging(0, err); 31 | } 32 | } 33 | {{body_section}} 34 | 35 | async createLogging(response, error) { 36 | const isError = error !== undefined; 37 | const logLevel = isError ? 'error' : 'debug'; 38 | const statusMessage = isError ? 'Failed' : 'Successful'; 39 | const endpoint = `${this.host}${this.url}`; 40 | const requestPayload = isError ? error.response.request : response.request; 41 | const responsePayload = isError ? error.response.body : response.body; 42 | 43 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 44 | 45 | logger.file({ type: 'request', level: logLevel }).http({ 46 | endpoint: endpoint, 47 | data: requestPayload 48 | }); 49 | 50 | logger.file({ type: 'response', level: logLevel }).http({ 51 | endpoint: endpoint, 52 | data: responsePayload 53 | }); 54 | } 55 | 56 | // This method used for provide expectation and return json schema 57 | expectedSchema(cases="success") { 58 | return new requestHelper().getSchema(schema, cases) 59 | } 60 | } 61 | 62 | module.exports = Request -------------------------------------------------------------------------------- /lib/template/commonjs/pages_attach.dot: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | chai.use(require('chai-http')) 3 | const requestHelper = require('{{path_helper}}') 4 | const { Config } = require('{{path_config}}'){{path_data}} 5 | const { schema } = require('{{path_schema}}') 6 | const { Logger } = require('{{path_logger}}') 7 | const logger = new Logger(); 8 | 9 | class Request { 10 | constructor() { 11 | // Write your constructor here, if you nee 12 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 13 | this.host = new Config().env().host; 14 | this.url = "{{url}}" // Set up the API path to the route endpoint 15 | } 16 | 17 | get request() { 18 | return chai.request(this.host) 19 | } 20 | 21 | // This method handles making the HTTP request based on given arguments. 22 | async api(...args) { 23 | const payload = new requestHelper().getPayload(args) 24 | const attachment = new requestHelper().getAttachment(args) 25 | 26 | try { 27 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 28 | let response = await this.request.{{method}}(this.url){{query}} 29 | .set("Content-Type", "multipart/form-data"){{header}} 30 | 31 | Object.keys(await this.getMappedBody(payload)).forEach( async (key) => { 32 | response = await response.field(key, JSON.stringify(await this.getMappedBody(payload)[key])) 33 | }) 34 | 35 | Object.keys(await this.getMappedAttachment(attachment)).forEach(async (key) => { 36 | if( typeof await this.getMappedAttachment(attachment)[key] != 'object') { 37 | const raw = await new requestHelper().getFile(await this.getMappedAttachment(attachment)[key]) 38 | response = await response.attach(key, raw.file, raw.name) 39 | } else { 40 | await this.getMappedAttachment(attachment)[key].forEach(async (val) => { 41 | const raw = await new requestHelper().getFile(val) 42 | response = await response.attach(key, raw.file, raw.name) 43 | }) 44 | } 45 | }) 46 | 47 | await this.createLogging(response); 48 | return response 49 | } catch (err) { 50 | await this.createLogging(0, err); 51 | } 52 | } 53 | {{body_section}} 54 | 55 | // This method used for provide attachment file and return object 56 | async getMappedAttachment(...args) { 57 | const defaultData = new requestHelper().getDefaultData(data.{{data_attach_name}}) 58 | const dataMapped = await new requestHelper().mapObject(defaultData.driven.attachment, args); 59 | 60 | return dataMapped 61 | } 62 | 63 | async createLogging(response, error) { 64 | const isError = error !== undefined; 65 | const logLevel = isError ? 'error' : 'debug'; 66 | const statusMessage = isError ? 'Failed' : 'Successful'; 67 | const endpoint = `${this.host}${this.url}`; 68 | const requestPayload = isError ? error.response.request : response.request; 69 | const responsePayload = isError ? error.response.body : response.body; 70 | 71 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 72 | 73 | logger.file({ type: 'request', level: logLevel }).http({ 74 | endpoint: endpoint, 75 | data: requestPayload 76 | }); 77 | 78 | logger.file({ type: 'response', level: logLevel }).http({ 79 | endpoint: endpoint, 80 | data: responsePayload 81 | }); 82 | } 83 | 84 | // This method used for provide expectation and return json schema 85 | expectedSchema(cases="success") { 86 | return new requestHelper().getSchema(schema, cases) 87 | } 88 | } 89 | 90 | module.exports = Request -------------------------------------------------------------------------------- /lib/template/commonjs/request_helper.dot: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const toJsonSchema = require('to-json-schema'); 3 | 4 | class requestHelper { 5 | constructor() { 6 | // Write your constructor here, if you need 7 | this.waitFor = (ms) => new Promise(r => setTimeout(r, ms)) 8 | } 9 | 10 | // This method used for get parameter which send from test file 11 | async getPayload(arg) { 12 | let data = {} 13 | arg.forEach((datas) => { 14 | if(typeof datas == 'object') { 15 | data = Object.assign({}, datas) 16 | } 17 | }) 18 | return data 19 | } 20 | 21 | // If your test using attachment, this method used for get parameter of attachment file like path and keys 22 | async getAttachment(arg) { 23 | let data = {} 24 | arg.forEach((datas) => { 25 | if(typeof datas == 'object') { 26 | if(datas.hasOwnProperty('attachment')) { 27 | data = Object.assign({}, datas.attachment) 28 | } 29 | } 30 | }) 31 | return data 32 | } 33 | 34 | getDefaultData(data_array) { 35 | return data_array.find(item => item.case.default); 36 | } 37 | 38 | 39 | // If your test using attachment, this method used for get binary file from your computer 40 | async getFile(data) { 41 | const path = data.split('/') 42 | const name = path[path.length - 1] 43 | const file = await fs.readFile(data) 44 | 45 | return { 46 | file, 47 | name, 48 | } 49 | } 50 | 51 | // This method used for convert json file to json schema 52 | getSchema(json_responses, cases) { 53 | const options = { 54 | objects: { 55 | postProcessFnc: (schema, obj, defaultFnc) => ({...defaultFnc(schema, obj), required: Object.getOwnPropertyNames(obj)}) 56 | } 57 | } 58 | 59 | if (json_responses.hasOwnProperty(cases)) { 60 | return toJsonSchema(json_responses[cases], options) 61 | } else { 62 | throw new Error('JSON Schema: '+cases+', does not exist!') 63 | } 64 | 65 | } 66 | 67 | // This method user for mapping keys object which you want to be change/replace the object 68 | async mapObject(obj, arg) { 69 | let newObj = {}; 70 | let map = arg[0]; 71 | 72 | if (typeof obj === "object") { 73 | newObj = { ...obj }; 74 | } 75 | 76 | Object.keys(map).forEach((key) => { 77 | if (newObj[key] !== undefined) { 78 | newObj[key] = map[key]; 79 | } 80 | 81 | Object.entries(newObj).forEach(([nestedKey, nestedVal]) => { 82 | if (Array.isArray(nestedVal)) { 83 | nestedVal.forEach((innerObj) => { 84 | if (innerObj[key] !== undefined) { 85 | innerObj[key] = map[key]; 86 | } 87 | 88 | Object.values(innerObj).forEach((dObjArr) => { 89 | if (Array.isArray(dObjArr)) { 90 | dObjArr.forEach((dObj) => { 91 | if (dObj[key] !== undefined) { 92 | dObj[key] = map[key]; 93 | } 94 | }); 95 | } 96 | }); 97 | }); 98 | } else if (typeof nestedVal === "object") { 99 | Object.values(nestedVal).forEach((dObjArr) => { 100 | if (Array.isArray(dObjArr)) { 101 | dObjArr.forEach((dObj) => { 102 | if (dObj[key] !== undefined) { 103 | dObj[key] = map[key]; 104 | } 105 | }); 106 | } 107 | }); 108 | } 109 | }); 110 | }); 111 | 112 | return newObj; 113 | } 114 | } 115 | 116 | module.exports = requestHelper -------------------------------------------------------------------------------- /lib/template/commonjs/schema.dot: -------------------------------------------------------------------------------- 1 | export const schema = { 2 | "success": 3 | { 4 | "example": "example" 5 | }, 6 | "failed": 7 | { 8 | "example": "example" 9 | } 10 | } -------------------------------------------------------------------------------- /lib/template/commonjs/spec.dot: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect 2 | const chai = require('chai') 3 | chai.use(require('chai-json-schema')){{data_path}} 4 | const Request = require('{{page_path}}') 5 | const config = require('{{config_path}}') 6 | 7 | describe("{{describe}}", () => { 8 | {{test_section}} 9 | }) -------------------------------------------------------------------------------- /lib/template/config.d.ts: -------------------------------------------------------------------------------- 1 | export declare const defaultEslintConfig: string; 2 | export declare const defaultMochaConfig: string; 3 | export declare const prettierIgnoreConfig: string; 4 | export declare const babelConfig: string; 5 | export declare const JSConfig: string; 6 | -------------------------------------------------------------------------------- /lib/template/config.js: -------------------------------------------------------------------------------- 1 | export const defaultEslintConfig = `{ 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": "latest" 9 | }, 10 | "rules": { 11 | "no-undef": 0, 12 | "no-prototype-builtins": 0 13 | } 14 | }`; 15 | export const defaultMochaConfig = ` 16 | const runTestsList = {{runner}} 17 | 18 | const ignoreTestsList = {{ignorelist}} 19 | 20 | function getSpecsList() { 21 | const runOptArgument = process.argv.indexOf('--specs') 22 | const runOpt = runOptArgument !== -1 ? process.argv[runOptArgument+1] : 'Regression' 23 | 24 | if (runOpt.includes("/") || runOpt in runTestsList) { 25 | return runTestsList[runOpt]; 26 | } 27 | 28 | if (runOpt.includes(",")) { 29 | return runOpt.split(",").flatMap(key => runTestsList[key]); 30 | } 31 | } 32 | 33 | module.exports = { 34 | require: ['@babel/register'], 35 | jobs: 1, 36 | package: './package.json', 37 | reporter: 'spec', 38 | ignore: ignoreTestsList, 39 | spec: getSpecsList(), 40 | 'trace-warnings': true, 41 | ui: 'bdd', 42 | } 43 | `; 44 | export const prettierIgnoreConfig = `**/.git 45 | **/.svn 46 | **/.hg 47 | **.md 48 | **/node_modules 49 | **/logs 50 | .babelrc 51 | .eslintrc.json 52 | .prettierignore 53 | package-lock.json 54 | package.json 55 | prettier.config.js 56 | `; 57 | export const babelConfig = `{ 58 | "presets": [ 59 | [ 60 | "@babel/preset-env", 61 | { 62 | "targets": { 63 | "node": 18 64 | } 65 | } 66 | ] 67 | ], 68 | "plugins": [ 69 | [ 70 | "module-resolver", 71 | { 72 | "root": ["."], 73 | "alias": { 74 | "@root": ".", 75 | "@tests": "./tests", 76 | "@scenario": "./tests/scenarios", 77 | "@page": "./tests/pages", 78 | "@schema": "./tests/schemas", 79 | "@helper": "./tests/helpers", 80 | "@data": "./tests/data", 81 | "@util": "./tests/utils" 82 | } 83 | } 84 | ] 85 | ] 86 | } 87 | `; 88 | export const JSConfig = ` 89 | { 90 | "compilerOptions": { 91 | "module": "ES6", 92 | "target": "ES6", 93 | "baseUrl": "./tests", 94 | "paths": { 95 | "*": [ 96 | "tests/*" 97 | ], 98 | "@scenario/*": ["scenarios/*"], 99 | "@page/*": ["pages/*"], 100 | "@schema/*": ["schemas/*"], 101 | "@helper/*": ["helpers/*"], 102 | "@data/*": ["data/*"], 103 | "@util/*": ["utils/*"] 104 | }, 105 | "allowSyntheticDefaultImports": true, 106 | "declaration": true, 107 | }, 108 | "include": [ 109 | "tests/**/*.js" 110 | ], 111 | "exclude": [ 112 | "node_modules" 113 | ] 114 | } 115 | `; 116 | -------------------------------------------------------------------------------- /lib/template/jsimport/config.dot: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | export class Config { 4 | constructor() { 5 | // Write your constructor here, if you need 6 | } 7 | 8 | env() { 9 | // change according to your need 10 | dotenv.config({ path: __dirname + `/../../.env.${process.env.NODE_ENV}` }); 11 | // Defining an object named 'env', contained your variables needed 12 | return { 13 | host: process.env.MAIN 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/template/jsimport/custom_file_transport.dot: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import winston from "winston"; 3 | 4 | const logDir = './logs'; 5 | if (!fs.existsSync(logDir)) { 6 | fs.mkdirSync(logDir); 7 | } 8 | 9 | export class CustomFileTransport extends winston.transports.File { 10 | constructor(options) { 11 | // Pass the options to the parent class constructor 12 | super({ ...options, dirname: logDir }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/template/jsimport/logger.dot: -------------------------------------------------------------------------------- 1 | import winston, { createLogger, format, transports } from "winston"; 2 | import { CustomFileTransport } from "./custom_file_transport"; 3 | const { combine, timestamp, printf } = format; 4 | const colorizer = format.colorize(); 5 | 6 | const logFormat = combine( 7 | timestamp({ 8 | format: "YYYY-MM-DD HH:mm:ss" 9 | }), 10 | printf((output) => 11 | colorizer.colorize( 12 | output.level, 13 | `[${output.timestamp}] ${output.level.toUpperCase()}: ${output.message}` 14 | ) 15 | ) 16 | ); 17 | 18 | export class Logger { 19 | constructor() { 20 | // Write your constructor here, if you need 21 | } 22 | 23 | console = ({ service = "example", level = "debug" }) => 24 | createLogger({ 25 | level, 26 | defaultMeta: { service }, 27 | format: logFormat, 28 | transports: [ 29 | // Output logs to console in simple message format 30 | new transports.Console() 31 | ] 32 | }); 33 | 34 | file = ({ service = "example", type = "response", level = "debug" }) => 35 | createLogger({ 36 | level, 37 | defaultMeta: { service }, 38 | format: logFormat, 39 | transports: [ 40 | // Output logs to console in simple message format 41 | new CustomFileTransport({ 42 | format: winston.format.json(), 43 | filename: `./logs/${type}.json` 44 | }) 45 | ] 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /lib/template/jsimport/pages.dot: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import chaiHttp from 'chai-http' 3 | import requestHelper from '{{path_helper}}' 4 | import { Config } from '{{path_config}}' 5 | import { schema } from '{{path_schema}}'{{path_data}} 6 | import { Logger } from '{{path_logger}}' 7 | chai.use(chaiHttp) 8 | 9 | const logger = new Logger() 10 | 11 | class Request { 12 | constructor() { 13 | // Write your constructor here, if you nee 14 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 15 | this.host = new Config().env().host; 16 | this.url = "{{url}}" // Set up the API path to the route endpoint 17 | } 18 | 19 | get request() { 20 | return chai.request(this.host) 21 | } 22 | 23 | // This method handles making the HTTP request based on given arguments. 24 | async api(...args) { 25 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 26 | try { 27 | const response = await this.request.{{method}}(this.url){{query}}{{header}}{{payload}} 28 | 29 | await this.createLogging(response); 30 | return response; 31 | } catch (err) { 32 | await this.createLogging(0, err); 33 | } 34 | } 35 | {{body_section}} 36 | 37 | async createLogging(response, error) { 38 | const isError = error !== undefined; 39 | const logLevel = isError ? 'error' : 'debug'; 40 | const statusMessage = isError ? 'Failed' : 'Successful'; 41 | const endpoint = `${this.host}${this.url}`; 42 | const requestPayload = isError ? error.response.request : response.request; 43 | const responsePayload = isError ? error.response.body : response.body; 44 | 45 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 46 | 47 | logger.file({ type: 'request', level: logLevel }).http({ 48 | endpoint: endpoint, 49 | data: requestPayload 50 | }); 51 | 52 | logger.file({ type: 'response', level: logLevel }).http({ 53 | endpoint: endpoint, 54 | data: responsePayload 55 | }); 56 | } 57 | 58 | // This method used for provide expectation and return json schema 59 | expectedSchema(cases="success") { 60 | return new requestHelper().getSchema(schema, cases) 61 | } 62 | } 63 | 64 | export default Request -------------------------------------------------------------------------------- /lib/template/jsimport/pages_attach.dot: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import chaiHttp from 'chai-http' 3 | import requestHelper from '{{path_helper}}' 4 | import { Config } from '{{path_config}}' 5 | import { schema } from '{{path_schema}}'{{path_data}} 6 | import { Logger } from '{{path_logger}}' 7 | chai.use(chaiHttp) 8 | 9 | const logger = new Logger() 10 | 11 | class Request { 12 | constructor() { 13 | // Write your constructor here, if you nee 14 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 15 | this.host = new Config().env().host; 16 | this.url = "{{url}}" // Set up the API path to the route endpoint 17 | } 18 | 19 | get request() { 20 | return chai.request(this.host) 21 | } 22 | 23 | // This method handles making the HTTP request based on given arguments. 24 | async api(...args) { 25 | const payload = new requestHelper().getPayload(args) 26 | const attachment = new requestHelper().getAttachment(args) 27 | 28 | try { 29 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 30 | let response = await this.request.{{method}}(this.url){{query}} 31 | .set("Content-Type", "multipart/form-data"){{header}} 32 | 33 | Object.keys(await this.getMappedBody(payload)).forEach( async (key) => { 34 | response = await response.field(key, JSON.stringify(await this.getMappedBody(payload)[key])) 35 | }) 36 | 37 | Object.keys(await this.getMappedAttachment(attachment)).forEach(async (key) => { 38 | if( typeof await this.getMappedAttachment(attachment)[key] != 'object') { 39 | const raw = await new requestHelper().getFile(await this.getMappedAttachment(attachment)[key]) 40 | response = await response.attach(key, raw.file, raw.name) 41 | } else { 42 | await this.getMappedAttachment(attachment)[key].forEach(async (val) => { 43 | const raw = await new requestHelper().getFile(val) 44 | response = await response.attach(key, raw.file, raw.name) 45 | }) 46 | } 47 | }) 48 | 49 | await this.createLogging(response); 50 | return response 51 | } catch (err) { 52 | await this.createLogging(0, err); 53 | } 54 | } 55 | {{body_section}} 56 | 57 | // This method used for provide attachment file and return object 58 | async getMappedAttachment(...args) { 59 | const defaultData = new requestHelper().getDefaultData(data.{{data_attach_name}}) 60 | const dataMapped = await new requestHelper().mapObject(defaultData.driven.attachment, args); 61 | 62 | return dataMapped 63 | } 64 | 65 | async createLogging(response, error) { 66 | const isError = error !== undefined; 67 | const logLevel = isError ? 'error' : 'debug'; 68 | const statusMessage = isError ? 'Failed' : 'Successful'; 69 | const endpoint = `${this.host}${this.url}`; 70 | const requestPayload = isError ? error.response.request : response.request; 71 | const responsePayload = isError ? error.response.body : response.body; 72 | 73 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 74 | 75 | logger.file({ type: 'request', level: logLevel }).http({ 76 | endpoint: endpoint, 77 | data: requestPayload 78 | }); 79 | 80 | logger.file({ type: 'response', level: logLevel }).http({ 81 | endpoint: endpoint, 82 | data: responsePayload 83 | }); 84 | } 85 | 86 | // This method used for provide expectation and return json schema 87 | expectedSchema(cases="success") { 88 | return new requestHelper().getSchema(schema, cases) 89 | } 90 | } 91 | 92 | export default Request -------------------------------------------------------------------------------- /lib/template/jsimport/request_helper.dot: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import toJsonSchema from "to-json-schema"; 3 | 4 | class requestHelper { 5 | constructor() { 6 | // Write your constructor here, if you need 7 | this.waitFor = (ms) => new Promise(r => setTimeout(r, ms)) 8 | } 9 | 10 | // This method used for get parameter which send from test file 11 | async getPayload(arg) { 12 | let data = {} 13 | arg.forEach((datas) => { 14 | if(typeof datas == 'object') { 15 | data = Object.assign({}, datas) 16 | } 17 | }) 18 | return data 19 | } 20 | 21 | // If your test using attachment, this method used for get parameter of attachment file like path and keys 22 | async getAttachment(arg) { 23 | let data = {} 24 | arg.forEach((datas) => { 25 | if(typeof datas == 'object') { 26 | if(datas.hasOwnProperty('attachment')) { 27 | data = Object.assign({}, datas.attachment) 28 | } 29 | } 30 | }) 31 | return data 32 | } 33 | 34 | getDefaultData(data_array) { 35 | return data_array.find(item => item.case.default); 36 | } 37 | 38 | 39 | // If your test using attachment, this method used for get binary file from your computer 40 | async getFile(data) { 41 | const path = data.split('/') 42 | const name = path[path.length - 1] 43 | const file = await fs.readFile(data) 44 | 45 | return { 46 | file, 47 | name, 48 | } 49 | } 50 | 51 | // This method used for convert json file to json schema 52 | getSchema(json_responses, cases) { 53 | const options = { 54 | objects: { 55 | postProcessFnc: (schema, obj, defaultFnc) => ({...defaultFnc(schema, obj), required: Object.getOwnPropertyNames(obj)}) 56 | } 57 | } 58 | 59 | if (json_responses.hasOwnProperty(cases)) { 60 | return toJsonSchema(json_responses[cases], options) 61 | } else { 62 | throw new Error('JSON Schema: '+cases+', does not exist!') 63 | } 64 | 65 | } 66 | 67 | // This method user for mapping keys object which you want to be change/replace the object 68 | async mapObject(obj, arg) { 69 | let newObj = {}; 70 | let map = arg[0]; 71 | 72 | if (typeof obj === "object") { 73 | newObj = { ...obj }; 74 | } 75 | 76 | Object.keys(map).forEach((key) => { 77 | if (newObj[key] !== undefined) { 78 | newObj[key] = map[key]; 79 | } 80 | 81 | Object.entries(newObj).forEach(([nestedKey, nestedVal]) => { 82 | if (Array.isArray(nestedVal)) { 83 | nestedVal.forEach((innerObj) => { 84 | if (innerObj[key] !== undefined) { 85 | innerObj[key] = map[key]; 86 | } 87 | 88 | Object.values(innerObj).forEach((dObjArr) => { 89 | if (Array.isArray(dObjArr)) { 90 | dObjArr.forEach((dObj) => { 91 | if (dObj[key] !== undefined) { 92 | dObj[key] = map[key]; 93 | } 94 | }); 95 | } 96 | }); 97 | }); 98 | } else if (typeof nestedVal === "object") { 99 | Object.values(nestedVal).forEach((dObjArr) => { 100 | if (Array.isArray(dObjArr)) { 101 | dObjArr.forEach((dObj) => { 102 | if (dObj[key] !== undefined) { 103 | dObj[key] = map[key]; 104 | } 105 | }); 106 | } 107 | }); 108 | } 109 | }); 110 | }); 111 | 112 | return newObj; 113 | } 114 | } 115 | 116 | export default requestHelper -------------------------------------------------------------------------------- /lib/template/jsimport/schema.dot: -------------------------------------------------------------------------------- 1 | export const schema = { 2 | "success": 3 | { 4 | "example": "example" 5 | }, 6 | "failed": 7 | { 8 | "example": "example" 9 | } 10 | } -------------------------------------------------------------------------------- /lib/template/jsimport/spec.dot: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import chai from 'chai' 3 | import chaiJsonSchema from 'chai-json-schema' 4 | import Request from '{{page_path}}' 5 | import config from '{{config_path}}'{{data_path}} 6 | chai.use(chaiJsonSchema) 7 | 8 | describe("{{describe}}", () => { 9 | {{test_section}} 10 | }) -------------------------------------------------------------------------------- /lib/utils/foreach.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description looping any array 3 | * @param {Array} array array to be loop 4 | * @param {Promise} callback ooping item 5 | * @returns {Promise} 6 | */ 7 | export declare function asyncForEach(array: T[], callback: (item: T, index: number, array: T[]) => Promise): Promise; 8 | -------------------------------------------------------------------------------- /lib/utils/foreach.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | /** 11 | * @description looping any array 12 | * @param {Array} array array to be loop 13 | * @param {Promise} callback ooping item 14 | * @returns {Promise} 15 | */ 16 | export function asyncForEach(array, callback) { 17 | return __awaiter(this, void 0, void 0, function* () { 18 | for (const item of array) { 19 | yield callback(item, array.indexOf(item), array); 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/utils/logs.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description coloring console log 3 | * @param {string} text - some text to console log 4 | * @param {any} color - color 5 | * @returns {void} 6 | */ 7 | export declare function log(text: string, color: any): void; 8 | -------------------------------------------------------------------------------- /lib/utils/logs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description coloring console log 3 | * @param {string} text - some text to console log 4 | * @param {any} color - color 5 | * @returns {void} 6 | */ 7 | export function log(text, color) { 8 | const colorList = { 9 | red: `${'\x1b[31m'}${text}${'\x1b[0m'}`, 10 | green: `${'\x1b[32m'}${text}${'\x1b[0m'}`, 11 | yellow: `${'\x1b[33m'}${text}${'\x1b[0m'}`, 12 | blue: `${'\x1b[34m'}${text}${'\x1b[0m'}`, 13 | }; 14 | const key = color; 15 | return console.log(colorList[key]); 16 | } 17 | -------------------------------------------------------------------------------- /lib/utils/modul.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Check the file is existed or not 3 | * @param {string} path the path for destination 4 | * @param {string} fileName filename which want to search 5 | * @returns {Promise} 6 | */ 7 | export declare function isFileExisted(path: string, fileName: string): Promise<[boolean, string | null]>; 8 | /** 9 | * @description Check JS module type 10 | * @returns {Promise} 11 | */ 12 | export declare const existModuleType: () => Promise; 13 | /** 14 | * @description Rebuild script package 15 | * @returns {Promise} 16 | */ 17 | export declare const rebuildPackagejson: () => Promise; 18 | -------------------------------------------------------------------------------- /lib/utils/modul.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { log } from './logs.js'; 12 | /** 13 | * @description Check the file is existed or not 14 | * @param {string} path the path for destination 15 | * @param {string} fileName filename which want to search 16 | * @returns {Promise} 17 | */ 18 | export function isFileExisted(path, fileName) { 19 | return __awaiter(this, void 0, void 0, function* () { 20 | const data = fs.readdirSync(path); 21 | for (let file of data) { 22 | const curPath = path + '/' + file; 23 | if (file === 'node_modules') { 24 | continue; 25 | } 26 | else if (fs.statSync(curPath).isDirectory()) { 27 | const res = yield isFileExisted(curPath, fileName); 28 | if (res[0]) 29 | return [true, res[1]]; 30 | } 31 | else if (file === fileName) { 32 | return [true, curPath]; 33 | } 34 | } 35 | return [false, null]; 36 | }); 37 | } 38 | /** 39 | * @description Check JS module type 40 | * @returns {Promise} 41 | */ 42 | export const existModuleType = () => __awaiter(void 0, void 0, void 0, function* () { 43 | try { 44 | const checkConfigImport = fs.readFileSync('./tests/utils/config.js').toString(); 45 | const checkHelperImport = fs.readFileSync('./tests/helpers/request.helper.js').toString(); 46 | if (checkConfigImport.includes('import dotenv from "dotenv"') || checkHelperImport.includes('import fs from "fs"')) 47 | return "Javascript modules (import/export)"; 48 | return "CommonJS (require/exports)"; 49 | } 50 | catch (e) { 51 | return true; 52 | } 53 | }); 54 | /** 55 | * @description Rebuild script package 56 | * @returns {Promise} 57 | */ 58 | export const rebuildPackagejson = () => __awaiter(void 0, void 0, void 0, function* () { 59 | const scriptName = 'regression:dev'; // Name of your new script 60 | const scriptCommand = 'cross-env NODE_ENV=dev mocha --specs Regression --timeout 15000'; // Command to execute your script 61 | try { 62 | // Read the package.json answers.jsonFileQ 63 | const packageJson = yield JSON.parse(fs.readFileSync('./package.json', 'utf8')); 64 | // Add the new script to the scripts object 65 | packageJson['scripts'] = packageJson['scripts'] || {}; // Initialize 'scripts' object if it doesn't exist 66 | packageJson.scripts[scriptName] = scriptCommand; // Assign the script command to the given script name 67 | // Write the updated package.json answers.jsonFileQ 68 | fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2)); 69 | log(`Script updated in package.json`, 'green'); 70 | } 71 | catch (err) { 72 | console.error('Failed to update package.json:', err); 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /lib/utils/path.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description base path in debug or production 3 | * @returns {string} 4 | */ 5 | export default function basePath(): string; 6 | /** 7 | * @description base dir 8 | * @returns {string} 9 | */ 10 | export declare function basedir(): string; 11 | -------------------------------------------------------------------------------- /lib/utils/path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description base path in debug or production 3 | * @returns {string} 4 | */ 5 | export default function basePath() { 6 | const isProd = true; 7 | return isProd ? './node_modules/@dot.indonesia/po-gen/' : '../'; 8 | } 9 | /** 10 | * @description base dir 11 | * @returns {string} 12 | */ 13 | export function basedir() { 14 | return process.cwd(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/utils/question.d.ts: -------------------------------------------------------------------------------- 1 | import { CLIAutomationQuestionInterface } from "../interface/question.interface.js"; 2 | /** 3 | * @description questions list for automation generation 4 | * @param {CLIAutomationQuestionInterface} automationQuestionParams included the script arguments and needed package states 5 | * @returns {Promise} 6 | */ 7 | export declare const CLIAutomationQuestion: (automationQuestionParams: CLIAutomationQuestionInterface) => Promise; 8 | /** 9 | * @description questions list for json selected options 10 | * @param {any} answers options 11 | * @returns {Promise} 12 | */ 13 | export declare const CLIJSONQuestion: (answers: any) => Promise; 14 | /** 15 | * @description questions list for environment generation 16 | * @returns {Promise} 17 | */ 18 | export declare const CLIEnvironmentQuestion: () => Promise; 19 | -------------------------------------------------------------------------------- /lib/utils/question.js: -------------------------------------------------------------------------------- 1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | import fs from 'fs'; 11 | import { promisify } from 'util'; 12 | import inquirer from 'inquirer'; 13 | import { envNameValidation, jsonFileValidation, mochawesomeValidation, projectModulesValidation } from "./validation.js"; 14 | const readFile = promisify(fs.readFile); 15 | /** 16 | * @description questions list for automation generation 17 | * @param {CLIAutomationQuestionInterface} automationQuestionParams included the script arguments and needed package states 18 | * @returns {Promise} 19 | */ 20 | export const CLIAutomationQuestion = (automationQuestionParams) => __awaiter(void 0, void 0, void 0, function* () { 21 | const { argument, packagesList, mochaExist, eslintExist } = automationQuestionParams; 22 | const commonQuestions = [ 23 | { 24 | type: 'list', 25 | name: 'moduleQ', 26 | message: 'What type of modules does your project use?', 27 | choices: ["Javascript modules (import/export)", "CommonJS (require/exports)"], 28 | when: () => projectModulesValidation() 29 | }, 30 | { 31 | type: 'input', 32 | name: 'jsonFileQ', 33 | message: 'Type your json file to be generate (example.json):', 34 | default: 'example.json', 35 | validate: (answers) => jsonFileValidation(answers) 36 | } 37 | ]; 38 | const fullQuestions = [ 39 | { 40 | type: 'list', 41 | name: 'frameworkQ', 42 | message: 'What framework will be used?', 43 | choices: ["Mocha chai"], 44 | when: () => mochaExist 45 | }, 46 | { 47 | type: 'list', 48 | name: 'eslintQ', 49 | message: 'Do you want to install ESlint?', 50 | choices: ["Yes", "No"], 51 | when: () => eslintExist 52 | }, 53 | { 54 | type: 'list', 55 | name: 'mochaweQ', 56 | message: 'Do you want to install Mochawesome?', 57 | choices: ["Yes", "No"], 58 | when: (answers) => mochawesomeValidation(answers, packagesList) 59 | }, 60 | ...commonQuestions, 61 | ]; 62 | const questions = (argument === "generate") ? commonQuestions : fullQuestions; 63 | return questions; 64 | }); 65 | /** 66 | * @description questions list for json selected options 67 | * @param {any} answers options 68 | * @returns {Promise} 69 | */ 70 | export const CLIJSONQuestion = (answers) => __awaiter(void 0, void 0, void 0, function* () { 71 | try { 72 | const data = yield readFile(answers.jsonFileQ.includes('"') ? answers.jsonFileQ.replace(/"/g, '') : answers.jsonFileQ, 'utf8'); 73 | const { item: items } = JSON.parse(data); 74 | // Assuming that we want to push items with the 'item' property to the end of the array 75 | const sortedArr = items.sort((a, b) => { 76 | return (a.item === undefined ? -1 : 0) + (b.item === undefined ? 0 : 1); 77 | }); 78 | const option = sortedArr.map((item) => ({ 79 | name: `${item.name} - ${item.hasOwnProperty('item') ? '(suite)' : '(test)'}` 80 | })); 81 | return inquirer.prompt([ 82 | { 83 | type: 'checkbox', 84 | name: 'customKey', 85 | message: 'Select one or more case or suite:', 86 | pageSize: 10, 87 | choices: option, 88 | validate: function (value) { 89 | if (value.length === 0) { 90 | return 'Please select at least one case or suite'; 91 | } 92 | return true; 93 | }, 94 | }, 95 | ]); 96 | } 97 | catch (error) { 98 | console.error(`Error processing file: ${error.message}`); 99 | } 100 | }); 101 | /** 102 | * @description questions list for environment generation 103 | * @returns {Promise} 104 | */ 105 | export const CLIEnvironmentQuestion = () => __awaiter(void 0, void 0, void 0, function* () { 106 | return [ 107 | { 108 | type: 'input', 109 | name: 'jsonFileQ', 110 | message: 'Input your json file to be generate (example.json):', 111 | default: 'example-env.json', 112 | validate: (answers) => jsonFileValidation(answers) 113 | }, 114 | { 115 | type: 'input', 116 | name: 'envQ', 117 | message: 'Input your environment name:', 118 | default: 'dev', 119 | validate: (answers) => envNameValidation(answers) 120 | } 121 | ]; 122 | }); 123 | -------------------------------------------------------------------------------- /lib/utils/string.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description convert string to camelcase 3 | * @param {string} text some text change to camelcase 4 | * @returns {string} camelcase text tranformed 5 | */ 6 | export declare function toCamelCase(text: string): string; 7 | /** 8 | * @description convert string to lowercase 9 | * @param {string} text some text change to lowercase 10 | * @returns {string} lowercase text tranformed 11 | */ 12 | export declare function toLowerCase(text: string): string; 13 | -------------------------------------------------------------------------------- /lib/utils/string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description convert string to camelcase 3 | * @param {string} text some text change to camelcase 4 | * @returns {string} camelcase text tranformed 5 | */ 6 | export function toCamelCase(text) { 7 | const words = text.split(' '); 8 | const firstWord = words[0].toLowerCase(); 9 | const restOfWords = words.slice(1).map(word => { 10 | return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); 11 | }); 12 | return firstWord + restOfWords.join(''); 13 | } 14 | /** 15 | * @description convert string to lowercase 16 | * @param {string} text some text change to lowercase 17 | * @returns {string} lowercase text tranformed 18 | */ 19 | export function toLowerCase(text) { 20 | let str = (text).toLowerCase().replace(/\s/g, ''); 21 | str = str.replace(/\//g, ''); 22 | return str; 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/validation.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description validate import type on exist file 3 | * @returns {boolean} validation result 4 | */ 5 | export declare const projectModulesValidation: () => boolean; 6 | /** 7 | * @description check mocha package is existed 8 | * @param {any} answers question answer 9 | * @param {any} packagesExist existing package list 10 | * @returns {boolean} check result 11 | */ 12 | export declare const mochawesomeValidation: (answers: any, packagesExist: any) => boolean; 13 | /** 14 | * @description json file question validation (.json) 15 | * @param {any} input question answer 16 | * @returns {boolean | string} check result 17 | */ 18 | export declare const jsonFileValidation: (input: any) => boolean | string; 19 | /** 20 | * @description env name question validation, should lowercase 21 | * @param {any} input question answer 22 | * @returns {boolean | string} check result 23 | */ 24 | export declare const envNameValidation: (input: any) => boolean | string; 25 | -------------------------------------------------------------------------------- /lib/utils/validation.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | /** 3 | * @description check import type on exist file 4 | * @param {string} filePath file path 5 | * @param {string[]} searchStrings string will be searched 6 | * @returns {boolean} check result 7 | */ 8 | const checkImportType = (filePath, searchStrings) => { 9 | try { 10 | const fileContent = fs.readFileSync(filePath, 'utf8'); 11 | return searchStrings.some((searchString) => fileContent.includes(searchString)); 12 | } 13 | catch (_a) { 14 | return 0; 15 | } 16 | }; 17 | /** 18 | * @description validate import type on exist file 19 | * @returns {boolean} validation result 20 | */ 21 | export const projectModulesValidation = () => { 22 | const dotenvImportChecks = ['import dotenv from "dotenv"', 'const dotenv = require("dotenv")']; 23 | const fsImportChecks = ['import fs from "fs"', 'const fs = require("fs")']; 24 | let result; 25 | result = checkImportType('./tests/utils/config.js', dotenvImportChecks) === 0 ? true : false; 26 | result = checkImportType('./tests/helpers/request.helper.js', fsImportChecks) === 0 ? true : false; 27 | return result; 28 | }; 29 | /** 30 | * @description check mocha package is existed 31 | * @param {any} answers question answer 32 | * @param {any} packagesExist existing package list 33 | * @returns {boolean} check result 34 | */ 35 | export const mochawesomeValidation = (answers, packagesExist) => { 36 | if (answers.hasOwnProperty('frameworkQ')) { 37 | return answers.frameworkQ == "Mocha chai" && !packagesExist.includes('mochawesome') ? true : false; 38 | } 39 | else { 40 | return !packagesExist.includes('mochawesome') ? true : false; 41 | } 42 | }; 43 | /** 44 | * @description json file question validation (.json) 45 | * @param {any} input question answer 46 | * @returns {boolean | string} check result 47 | */ 48 | export const jsonFileValidation = (input) => { 49 | return input.includes('json') ? true : 'Please type correct answer, the file must be json format!'; 50 | }; 51 | /** 52 | * @description env name question validation, should lowercase 53 | * @param {any} input question answer 54 | * @returns {boolean | string} check result 55 | */ 56 | export const envNameValidation = (input) => { 57 | return /[A-Z]/.test(input) ? 'Input must be lowercase!' : true; 58 | }; 59 | -------------------------------------------------------------------------------- /lib/utils/wait.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description explicit time for wait 3 | * @param {number} ms number of time in millisecond 4 | * @returns {Promise} 5 | */ 6 | export declare const waitFor: (ms: number) => Promise; 7 | -------------------------------------------------------------------------------- /lib/utils/wait.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description explicit time for wait 3 | * @param {number} ms number of time in millisecond 4 | * @returns {Promise} 5 | */ 6 | export const waitFor = (ms) => new Promise(r => setTimeout(r, ms)); 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dot.indonesia/po-gen", 3 | "version": "3.0.7", 4 | "main": "lib/index.js", 5 | "scripts": { 6 | "test": "mocha runner/regression.js --timeout 15000", 7 | "build": "rm -rf lib && tsc && tsc --build tsconfig.build.json && tsc-alias && tscp" 8 | }, 9 | "type": "module", 10 | "bin": { 11 | "generate": "lib/index.js" 12 | }, 13 | "files": [ 14 | "lib", 15 | "README" 16 | ], 17 | "keywords": [ 18 | "qa", 19 | "sdet", 20 | "automation", 21 | "mocha", 22 | "chai", 23 | "generate", 24 | "generator", 25 | "postman", 26 | "collection", 27 | "testing", 28 | "oop", 29 | "pom", 30 | "webdriverio", 31 | "playwright" 32 | ], 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/hadiindrawan/automation-api-generator.git" 36 | }, 37 | "author": "Hadi Indrawan (https://github.com/hadiindrawan)", 38 | "license": "MIT", 39 | "description": "This project has created to relieve work load as SDET or Automation Test Engineer. You just export the postman collection, and run this generator to write the automation code.", 40 | "dependencies": { 41 | "inquirer": "^9.1.4" 42 | }, 43 | "devDependencies": { 44 | "@types/inquirer": "^9.0.7", 45 | "@types/node": "^20.10.6", 46 | "@typescript-eslint/eslint-plugin": "^6.17.0", 47 | "@typescript-eslint/parser": "^6.17.0", 48 | "eslint": "^8.56.0", 49 | "ts-node": "^10.9.2", 50 | "tsc-alias": "^1.8.8", 51 | "typescript": "^5.3.3", 52 | "typescript-cp": "^0.1.9" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/command/package_install.command.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | import { runPrettier } from './prettier.command'; 3 | import { rebuildPackagejson } from 'utils/modul'; 4 | import { devPackageInstallInterface, packageInstallInterface } from 'interface/package.interface'; 5 | import { generate } from 'generator/generate'; 6 | 7 | /** 8 | * @description package execution 9 | * @param {packageInstallInterface} packageInterface included packacge, json file, and module type 10 | * @returns {Promise} 11 | */ 12 | export const installPackage = async (packageInterface: packageInstallInterface): Promise => { 13 | const { 14 | stringPackage, 15 | stringDevPackage, 16 | jsonfile, 17 | moduleType, 18 | prettierExist, 19 | } = packageInterface; 20 | 21 | const installProcess = exec('npm install ' + stringPackage, (err, stdout) => { 22 | if (err) console.log(err); 23 | }); 24 | //This code is registering a listener to the exit event of installProcess 25 | installProcess.on('exit', async (code) => { 26 | //checking if npm install failed or succeeded by checking exit code 27 | if (code !== 0) { 28 | //if the exit code is not 0, it means the installation has failed. So, print error message and return. 29 | console.error(`${'\x1b[31m'}npm install failed with code ${code}${'\x1b[0m'}`) 30 | return; 31 | } 32 | 33 | if (stringDevPackage != '') { 34 | await installDevPackge({ 35 | stringDevPackage, 36 | jsonfile, 37 | moduleType, 38 | prettierExist 39 | }) 40 | } else { 41 | //If the program reaches here, it means the install process was successful. Print a success message. 42 | console.log(`${'\x1b[32m'}Installation completed successfully!${'\x1b[0m'}`) 43 | 44 | //Print message indicating automation test generation has started.. 45 | console.log(`${'\x1b[34m'}Generating automation test..${'\x1b[0m'}`) 46 | 47 | //Call the generate function to generate automation tests. 48 | await generate(jsonfile, moduleType) 49 | 50 | await runPrettier(prettierExist) 51 | // write test script for run the regression test 52 | await rebuildPackagejson() 53 | } 54 | }) 55 | } 56 | /** 57 | * @description dev package execution 58 | * @param {devPackageInstallInterface} devPackageInterface included packacge, json file, and module type 59 | * @returns {Promise} 60 | */ 61 | export const installDevPackge = async (devPackageInterface: devPackageInstallInterface): Promise => { 62 | const { 63 | stringDevPackage, 64 | jsonfile, 65 | moduleType, 66 | prettierExist, 67 | } = devPackageInterface; 68 | 69 | const installOption = exec('npm install' + stringDevPackage + ' --save-dev', (err, stdout) => { 70 | if (err) console.log(err); 71 | }); 72 | installOption.on('exit', async (res) => { 73 | //checking if npm install failed or succeeded by checking exit code 74 | if (res !== 0) { 75 | //if the exit code is not 0, it means the installation has failed. So, print error message and return. 76 | console.error(`${'\x1b[31m'}npm install failed with code ${res}${'\x1b[0m'}`) 77 | return; 78 | } 79 | 80 | //If the program reaches here, it means the install process was successful. Print a success message. 81 | console.log(`${'\x1b[32m'}Installation completed successfully!${'\x1b[0m'}`) 82 | 83 | //Print message indicating automation test generation has started.. 84 | console.log(`${'\x1b[34m'}Generating automation test..${'\x1b[0m'}`) 85 | 86 | //Call the generate function to generate automation tests. 87 | await generate(jsonfile, moduleType) 88 | 89 | await runPrettier(prettierExist) 90 | // write test script for run the regression test 91 | await rebuildPackagejson() 92 | }) 93 | } -------------------------------------------------------------------------------- /src/command/pogen.command.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { promisify } from 'util'; 3 | import { exec } from 'child_process'; 4 | import inquirer from 'inquirer'; 5 | import { CLIAutomationQuestion, CLIEnvironmentQuestion, CLIJSONQuestion } from 'utils/question'; 6 | import { existModuleType } from 'utils/modul'; 7 | import { runPrettier } from './prettier.command'; 8 | import { rebuildPackagejson } from 'utils/modul'; 9 | import { installDevPackge, installPackage } from './package_install.command'; 10 | import { log } from 'utils/logs'; 11 | import { generateEnv } from 'generator/generate_env'; 12 | import { defaultEslintConfig } from 'template/config'; 13 | import { generate } from 'generator/generate'; 14 | 15 | export class PogenCommand { 16 | private scriptArg?: string | undefined; 17 | 18 | constructor(scriptArg: string = "") { 19 | this.scriptArg = scriptArg; 20 | } 21 | 22 | initiation = async (): Promise<{ [key: string]: boolean | string[] | string }> => { 23 | const execAsync = promisify(exec); 24 | 25 | try { 26 | const { stdout } = await execAsync('npm list --json'); 27 | const packageList = JSON.parse(stdout).dependencies || {}; 28 | const packagesExist = Object.keys(packageList); 29 | 30 | // Define required packages 31 | const needPackage = [ 32 | '@babel/preset-env', 33 | '@babel/register', 34 | 'babel-plugin-module-resolver', 35 | 'chai', 36 | 'mocha', 37 | 'chai-http', 38 | 'chai-json-schema', 39 | 'dotenv', 40 | 'to-json-schema', 41 | 'cross-env', 42 | 'winston', 43 | ]; 44 | 45 | // Filter packages that are not installed 46 | const matchedPack = needPackage.filter(key => !packagesExist.includes(key)); 47 | 48 | // Join packages into a string 49 | const stringPackage = matchedPack.join(' '); 50 | 51 | return { 52 | packagesExist, 53 | stringPackage, 54 | mochaExist: !packagesExist.includes('mocha'), 55 | eslintExist: !packagesExist.includes('eslint'), 56 | prettierExist: !packagesExist.includes('prettier'), 57 | }; 58 | } catch (error) { 59 | console.error(`exec error: ${error}`); 60 | throw new Error('Failed to check packages.'); 61 | } 62 | } 63 | 64 | automation = async (): Promise => { 65 | const { 66 | packagesExist, 67 | stringPackage, 68 | mochaExist, 69 | eslintExist, 70 | prettierExist, 71 | } = await this.initiation(); 72 | 73 | let allAnswer: any = {}; 74 | inquirer 75 | .prompt( 76 | await CLIAutomationQuestion({ 77 | argument: this.scriptArg, 78 | packagesList: packagesExist, 79 | mochaExist, 80 | eslintExist 81 | }) 82 | ) 83 | .then(async (answers) => { 84 | Object.assign(allAnswer, answers); 85 | return await CLIJSONQuestion(answers); 86 | }) 87 | .then(async (answers) => { 88 | Object.assign(allAnswer, answers); 89 | 90 | const moduleType = allAnswer.moduleQ || await existModuleType() 91 | let stringDevPackage = '' 92 | if (allAnswer.eslintQ == 'Yes') { 93 | stringDevPackage += ' eslint' 94 | 95 | // Write eslint configuration 96 | let newEslintConfig = defaultEslintConfig; 97 | if (moduleType == "Javascript modules (import/export)") { 98 | const jsonConfig = JSON.parse(defaultEslintConfig) 99 | jsonConfig.parserOptions = { ecmaVersion: 'latest', sourceType: 'module' } 100 | newEslintConfig = JSON.stringify(jsonConfig, null, 2) 101 | } 102 | 103 | fs.writeFile('.eslintrc.json', newEslintConfig, function (err) { if (err) throw err; }); 104 | } 105 | 106 | if (allAnswer.mochaweQ == 'Yes') stringDevPackage += ' mochawesome' 107 | 108 | switch (true) { 109 | case stringPackage !== '' && stringDevPackage !== '': 110 | console.log("Installing dependencies..."); 111 | await installPackage({ 112 | stringPackage, 113 | stringDevPackage, 114 | jsonfile: allAnswer, 115 | moduleType, 116 | prettierExist 117 | }) 118 | break; 119 | 120 | case stringPackage !== '' && stringDevPackage === '': 121 | console.log("Installing dependencies..."); 122 | await installPackage({ 123 | stringPackage, 124 | stringDevPackage, 125 | jsonfile: allAnswer, 126 | moduleType, 127 | prettierExist 128 | }) 129 | break; 130 | 131 | case stringPackage === '' && stringDevPackage !== '': 132 | console.log("Installing dependencies..."); 133 | await installDevPackge({ 134 | stringDevPackage, 135 | jsonfile: allAnswer, 136 | moduleType, 137 | prettierExist 138 | }) 139 | break; 140 | 141 | case stringPackage === '' && stringDevPackage === '': 142 | console.log(`${'\x1b[32m'}Dependencies already installed${'\x1b[0m'}`) 143 | await generate(allAnswer, moduleType); 144 | await runPrettier(prettierExist); 145 | await rebuildPackagejson(); 146 | break; 147 | 148 | default: 149 | break; 150 | } 151 | }) 152 | .catch(async (err) => { 153 | log('Please type correct answer!', 'yellow') 154 | await this.automation() 155 | }) 156 | } 157 | 158 | environment = async (): Promise => { 159 | inquirer 160 | .prompt(await CLIEnvironmentQuestion()) 161 | .then(async (answers) => { 162 | //Print message indicating environment test generation has started.. 163 | log(`Generating environment test..`, 'blue') 164 | 165 | //Call the generate function to generate environment tests. 166 | await generateEnv(answers.jsonFileQ.includes('"') ? answers.jsonFileQ.replace(/"/g, '') : answers.jsonFileQ, answers.envQ) 167 | }) 168 | .catch(async (err) => { 169 | log('Please type correct answer!', 'yellow') 170 | await this.environment() 171 | }) 172 | } 173 | } -------------------------------------------------------------------------------- /src/command/prettier.command.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | 3 | /** 4 | * @description prettier plugin execution 5 | * @param {boolean | string[] | string} prettierExist prettier exist condition 6 | * @returns {Promise} 7 | */ 8 | export const runPrettier = async (prettierExist: boolean | string[] | string): Promise => { 9 | const command = 'npx prettier . --write --trailing-comma none'; 10 | if (!prettierExist) { 11 | const installProcess = exec('npm install --save-dev --save-exact prettier', (err, stdout) => { 12 | if (err) console.log(err); 13 | }); 14 | //This code is registering a listener to the exit event of installProcess 15 | installProcess.on('exit', async (code) => { 16 | exec(command, (err, stdout) => { 17 | if (err) console.log(err); 18 | }); 19 | }) 20 | } else { 21 | exec(command, (err, stdout) => { 22 | if (err) console.log(err); 23 | }) 24 | } 25 | } -------------------------------------------------------------------------------- /src/component/configs.component.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { asyncForEach } from 'utils/foreach'; 3 | import { isFileExisted } from 'utils/modul'; 4 | import { basedir } from 'utils/path'; 5 | import { waitFor } from 'utils/wait'; 6 | 7 | /** 8 | * @description asynchronous function to write config into directory 9 | * @param {any[]} configList config list will be write 10 | * @returns {Promise} 11 | */ 12 | export const writeConfigs = async (configList: any[]): Promise => { 13 | await asyncForEach(configList, async (item: any) => { 14 | try { 15 | const [fileExists] = await isFileExisted(basedir(), `${item.filename}`); 16 | if (!fileExists) { 17 | // create file test 18 | fs.writeFileSync( 19 | basedir() + `/${item.filename}`, item.template, 20 | "utf8" 21 | ) 22 | await waitFor(500) 23 | } 24 | } catch (err) { 25 | console.log(err); 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /src/component/data.component.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { asyncForEach } from "utils/foreach"; 3 | import { isFileExisted } from "utils/modul"; 4 | import { basedir } from "utils/path"; 5 | import { toLowerCase } from "utils/string"; 6 | import { waitFor } from "utils/wait"; 7 | 8 | interface dataComponentInterface { 9 | element: any, 10 | path: string, 11 | moduleType?: string, 12 | } 13 | 14 | interface ddtTemplateInterface { 15 | case: ddtCaseInterface, 16 | driven?: any, 17 | attachment?: any | undefined, 18 | } 19 | 20 | interface ddtCaseInterface { 21 | name: string, 22 | schema: string, 23 | status: number, 24 | default?: boolean 25 | } 26 | /** 27 | * @description asynchronous function to write data into directory 28 | * @param {dataComponentInterface} writeDataParams included element json, path and module type 29 | * @returns {Promise} 30 | */ 31 | export const writeData = async (writeDataParams: dataComponentInterface): Promise => { 32 | const { 33 | element, 34 | path, 35 | moduleType 36 | } = writeDataParams; 37 | 38 | // Here, a constant variable dataDir is declared and assigned to the string 'tests/data/file'. 39 | // console.log({ element, path }); 40 | let template = `export const {{var_name}} = [{{ddt}}]` 41 | 42 | const ddt: ddtTemplateInterface = { 43 | case: { 44 | name: "Successful login", 45 | schema: "success", 46 | status: 200, 47 | default: true 48 | }, 49 | driven: {}, 50 | attachment: {} 51 | } 52 | 53 | if (element.request.hasOwnProperty('body')) { 54 | try { 55 | fs.mkdirSync(path, { recursive: true }); 56 | } catch (err) { 57 | console.log(err); 58 | } 59 | } 60 | 61 | // write body 62 | let bodyRaw = '' 63 | let attKey = '' 64 | 65 | if (element.request.hasOwnProperty('body')) { 66 | if (element.request.body?.mode == 'raw') { 67 | bodyRaw = await element.request.body.raw 68 | } else 69 | if (element.request.body?.mode == 'formdata') { 70 | let data = await element.request.body.formdata 71 | let first = true 72 | let first1 = true 73 | bodyRaw += '{' + '\r\n' + '\t\t\t' 74 | attKey += '{' + '\r\n' + '\t\t\t' 75 | 76 | if (data.some((datas: any) => datas['type'] === 'file')) { 77 | await asyncForEach(data, async (body: any) => { 78 | // console.log(body); 79 | if (body.disabled !== true) { 80 | let value = await body.value; 81 | 82 | if (body.type === 'text') { 83 | bodyRaw += `"${body.key}": "${value}"`; 84 | } else 85 | if (body.type === 'text' && (value.includes('{') || value.includes('['))) { 86 | bodyRaw += `"${body.key}": ${value}`; 87 | } else { 88 | let src = (typeof body.src !== 'object') ? body.src : JSON.stringify(body.src); 89 | attKey += `${first1 === false ? ',' : ''}\r\n\t\t\t"${body.key}": "${src}"`; 90 | first1 = false; 91 | } 92 | } 93 | }); 94 | } else { 95 | await asyncForEach( 96 | data.filter((body: any) => body.disabled !== true), 97 | async (body: any) => { 98 | if (first === false) { 99 | bodyRaw += ',\r\n\t\t\t'; 100 | } 101 | bodyRaw += `"${body.key}": "${body.value}"`; 102 | first = false; 103 | } 104 | ); 105 | } 106 | 107 | bodyRaw += '\r\n' + '\t\t' + '}' 108 | attKey += '\r\n' + '\t\t' + '}' 109 | await waitFor(50); 110 | } 111 | } 112 | 113 | let name = toLowerCase(element.name) 114 | 115 | if (element.request.hasOwnProperty('body')) { 116 | ddt.driven = JSON.parse(bodyRaw) 117 | if(attKey != '') ddt.attachment = JSON.parse(attKey) 118 | template = template.replace('{{var_name}}', `${name.replace('-', '_').replace('(', '').replace(')', '')}_data`) 119 | template = template.replace('{{ddt}}', JSON.stringify(ddt)) 120 | await waitFor(50); 121 | 122 | const path_split = path.split('/'); 123 | if (path_split.length > 2) { 124 | const suite_name = path_split[2].toLowerCase().replace(/\s/g, '') + '.data.js'; 125 | try { 126 | const [fileExists] = await isFileExisted(path, suite_name); 127 | if (!fileExists) { 128 | // create file test 129 | fs.writeFileSync(`${path}/${suite_name}`, template, 'utf8'); 130 | 131 | await waitFor(200); 132 | } else { 133 | let current_contents = fs.readFileSync(basedir() + `/${path}/${suite_name}`, 'utf8'); 134 | const regex = /(?<=export const\s)\w+(?=\s=)/g; 135 | const var_name_list = current_contents.match(regex) || ''; 136 | 137 | if (!var_name_list.includes(`${name.replace('-', '_')}_data`)) { 138 | current_contents += '\n\n' + template; 139 | } 140 | 141 | fs.writeFileSync(`${path}/${suite_name}`, current_contents, 'utf8'); 142 | await waitFor(200); 143 | } 144 | } catch (err) { 145 | console.log(err); 146 | } 147 | } else { 148 | const fileName = `${name}.data.js`; 149 | try { 150 | const [fileExists] = await isFileExisted(path, fileName); 151 | 152 | if (!fileExists) { 153 | // create file test 154 | fs.writeFileSync(`${path}/${fileName}`, template, 'utf8'); 155 | await waitFor(500); 156 | } 157 | } catch (err) { 158 | console.log(err); 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/component/helper.component.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { promisify } from 'util'; 3 | import { isFileExisted } from 'utils/modul'; 4 | import basePath from 'utils/path'; 5 | import { waitFor } from 'utils/wait'; 6 | 7 | /** 8 | * @description asynchronous function to write helper into directory 9 | * @param {string} moduleType module type will be used 10 | * @returns {Promise} 11 | */ 12 | export const writeHelper = async (moduleType: string): Promise => { 13 | try { 14 | const writeFile = promisify(fs.writeFile); 15 | // template dir name 16 | const templateDirRequest = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/request_helper.dot" : "lib/template/commonjs/request_helper.dot" 17 | // create helper directory if it doesn't exists 18 | const helperDir = "tests/helpers"; 19 | fs.mkdirSync(helperDir, { recursive: true }); 20 | // Check if a file named 'request.helper.js' exists in the tests/helper dir 21 | // If it does not exist then create a new file based on the template file 'requestHelper.dot' 22 | const [fileExists] = await isFileExisted(helperDir, "request.helper.js"); 23 | if (!fileExists) { 24 | // create file test 25 | writeFile( 26 | "tests/helpers/request.helper.js", 27 | fs.readFileSync(basePath() + templateDirRequest, "utf8") 28 | ) 29 | await waitFor(500) 30 | } 31 | } catch (err) { 32 | console.log(err); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/component/pages.component.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { asyncForEach } from 'utils/foreach'; 3 | import { isFileExisted } from 'utils/modul'; 4 | import basePath from 'utils/path'; 5 | import { toLowerCase } from 'utils/string'; 6 | import { waitFor } from 'utils/wait'; 7 | 8 | interface pagesComponentInterface { 9 | element: any, 10 | path: string, 11 | schemaPath: string, 12 | dataPath: string, 13 | helperPath: string, 14 | moduleType: string, 15 | configPath: string, 16 | loggerPath: string, 17 | } 18 | /** 19 | * @description asynchronous function to write pages into directory 20 | * @param {pagesComponentInterface} writePagesParams included element json and all needed path 21 | * @returns {Promise} 22 | */ 23 | export const writePages = async (writePagesParams: pagesComponentInterface): Promise => { 24 | const { 25 | element, path, schemaPath, dataPath, helperPath, moduleType, configPath, loggerPath 26 | } = writePagesParams; 27 | // template dir name 28 | const templateDir = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/pages.dot" : "lib/template/commonjs/pages.dot" 29 | const templateDirAttach = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/pages_attach.dot" : "lib/template/commonjs/pages_attach.dot" 30 | 31 | // read template file 32 | let contents = fs.readFileSync(basePath() + templateDir, 'utf8'); 33 | 34 | let name = toLowerCase(element.name) 35 | 36 | // write method 37 | let method 38 | if (element.request.hasOwnProperty('method')) { 39 | method = await element.request.method 40 | } 41 | 42 | // write body 43 | let payload = ''; 44 | const request = element.request; 45 | const requestBody = request.body; 46 | 47 | if (element.request.hasOwnProperty('body')) { 48 | if (requestBody?.mode === 'raw') { 49 | payload = '\r\n\t\t.send(await this.getMappedBody(await new requestHelper().getPayload(args)))'; 50 | } else if (requestBody?.mode === 'formdata') { 51 | const formData = requestBody.formdata; 52 | if (formData.some((data: any) => data.type === 'file')) { 53 | contents = fs.readFileSync(basePath() + templateDirAttach, 'utf8'); 54 | } else { 55 | contents = fs.readFileSync(basePath() + templateDir, 'utf8'); 56 | payload = '\r\n\t\t.send(await this.getMappedBody(await new requestHelper().getPayload(args)))'; 57 | } 58 | } 59 | } 60 | 61 | 62 | // write headers 63 | let headers = ''; 64 | if (element.request.hasOwnProperty('header')) { 65 | await asyncForEach(element.request.header, async (header: any) => { 66 | if (!header.disabled) { 67 | headers += '\r\n'+'\t\t'+'.set("' + header.key + '", "' + header.value + '")'; 68 | } 69 | }) 70 | } 71 | // if any auth 72 | if (element.request.hasOwnProperty('auth')){ 73 | let auth = await element.request.auth 74 | if (auth.hasOwnProperty('bearer')){ 75 | headers += '\r\n'+'\t\t'+'.set("Authorization", "' + (auth.type).replace(/\w\S*/g, (auth.type).charAt(0).toUpperCase() + (auth.type).substr(1).toLowerCase()) + ' ' + auth.bearer[0].value + '")'; 76 | } 77 | } 78 | 79 | // write endpoint 80 | let queries = '' 81 | if (element.request.hasOwnProperty('url')) { 82 | if (element.request.url.hasOwnProperty('query')) { 83 | let firstData = true 84 | queries += '\r\n'+'\t\t'+ '.query({ ' 85 | await asyncForEach(element.request.url.query, async (query: any) => { 86 | if (query.disabled != true) { 87 | if (firstData === false) queries += ', '; 88 | queries += query.key + ': "' + query.value + '"'; 89 | firstData = false; 90 | } 91 | }) 92 | queries += ' })' 93 | } 94 | } 95 | 96 | // write url 97 | let url = '' 98 | if (element.request.hasOwnProperty('url')) { 99 | let raw_url = await element.request.url.raw 100 | if (raw_url.includes('http')) { 101 | url = new URL(raw_url).pathname 102 | } else { 103 | const fakeBase = 'https://fake.dot' 104 | let base = raw_url.split('/') 105 | url = new URL((raw_url).replace(base[0], fakeBase)).pathname 106 | } 107 | } 108 | 109 | let code = contents.replace("{{method}}", (method).toLowerCase()) 110 | code = code.replace("{{url}}", url) 111 | code = code.replace("{{query}}", queries) 112 | code = code.replace("{{header}}", headers) 113 | code = code.replace("{{payload}}", payload) 114 | 115 | if (element.request.hasOwnProperty('body')) { 116 | code = code.replace("{{body_section}}", ` 117 | // This method used for provide body or payload of the request and return object 118 | async getMappedBody(...args) { 119 | const defaultData = new requestHelper().getDefaultData(data.${name.replace('-', '_').replace('(', '').replace(')', '')}_data) 120 | const dataMapped = await new requestHelper().mapObject(defaultData.driven, args); 121 | 122 | return dataMapped 123 | } 124 | `) 125 | } else { 126 | code = code.replace("{{body_section}}", '') 127 | } 128 | 129 | if (payload == '') code = code.replace("{{data_attach_name}}", `${name.replace('-', '_')}_data`) 130 | 131 | code = code.replace("{{path_schema}}", schemaPath.replace(/\\/g, "/") + '/' + method + '_' + name + '.schema.js') 132 | 133 | if (element.request.hasOwnProperty('body')) { 134 | let importStatement = moduleType === "Javascript modules (import/export)" ? "\n import * as data from '" : "\n const data = require('"; 135 | let endPath = moduleType === "Javascript modules (import/export)" ? ".data.js'" : ".data.js')"; 136 | 137 | if (dataPath.replace(/\\/g, "/").split('/').length >= 2) { 138 | code = code.replace("{{path_data}}", importStatement + dataPath.replace(/\\/g, "/").split('/').slice(0, 2).join('/') + `/${toLowerCase(dataPath.replace(/\\/g, "/").split('/')[1])}` + endPath) 139 | } else { 140 | code = code.replace("{{path_data}}", importStatement + dataPath.replace(/\\/g, "/") + `/${name}` + endPath) 141 | } 142 | } else { 143 | code = code.replace("{{path_data}}", '') 144 | } 145 | 146 | code = code.replace("{{path_helper}}", helperPath) 147 | code = code.replace("{{path_config}}", configPath) 148 | code = code.replace("{{path_logger}}", loggerPath) 149 | 150 | // check if pages file exists 151 | if (element.request.hasOwnProperty('url')) { 152 | try { 153 | const [fileExists] = await isFileExisted(path, method + '_' + name + '.pages.js'); 154 | if (!fileExists) { 155 | // create file test 156 | fs.writeFileSync(path + '/' + method + '_' + name + '.pages.js', code, 'utf8'); 157 | await waitFor(500) 158 | } 159 | } catch (err) { 160 | console.log(err); 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /src/component/runner.component.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { defaultMochaConfig } from 'template/config'; 3 | import { isFileExisted } from 'utils/modul'; 4 | import { basedir } from 'utils/path'; 5 | import { waitFor } from 'utils/wait'; 6 | 7 | /** 8 | * @description asynchronous function to write runner into directory 9 | * @param {any[]} testsPath all generated test path 10 | * @returns {Promise} 11 | */ 12 | export const writeRunner = async (testsPath: any[]): Promise => { 13 | const mapTestbySuite = await testsPath.reduce((result: any, element: any): Promise => { 14 | const parts = element.split('/'); 15 | const key = parts[2].includes('.js') ? 'Base' : parts[2].replace(/\s/g, ''); 16 | if (!result[key]) { 17 | result[key] = []; 18 | } 19 | result[key].push(element); 20 | return result; 21 | }, {}); 22 | 23 | try { 24 | const [fileExists] = await isFileExisted(basedir(), ".mocharc.js"); 25 | if (!fileExists) { 26 | mapTestbySuite['Regression'] = 'tests/scenarios/**/*.spec.js' 27 | let contents = defaultMochaConfig.replace('{{runner}}', JSON.stringify(mapTestbySuite)) 28 | contents = contents.replace('{{ignorelist}}', '[\n// write your ignore tests here \n]') 29 | // create file test 30 | fs.writeFileSync( 31 | basedir() + '/.mocharc.js', contents, 32 | 'utf8' 33 | ) 34 | await waitFor(500) 35 | } else { 36 | let current_contents = fs.readFileSync(basedir() + `/.mocharc.js`, 'utf8'); 37 | let contents = defaultMochaConfig; 38 | 39 | const getTestListVariable = /const\s+runTestsList\s+=\s+(\{[\s\S]*?\});/; 40 | const match = getTestListVariable.exec(current_contents); 41 | 42 | const getIgnoreListVariable = /const\s+ignoreTestsList\s+=\s+(\[[\s\S]*?\]);/; 43 | const matchIgnore = getIgnoreListVariable.exec(current_contents); 44 | 45 | if (match && match[1]) { 46 | const convertObjectKey = /(\w+)\s*:/g; 47 | const finalString = match[1].replace(convertObjectKey, '"$1":'); 48 | const runTestsList = JSON.parse(finalString); 49 | contents = contents.replace('{{runner}}', JSON.stringify(Object.assign(runTestsList, mapTestbySuite))) 50 | } else { 51 | console.log("Variable ignoreTestsList not found in the input string."); 52 | } 53 | 54 | if (matchIgnore && matchIgnore[1]) { 55 | contents = contents.replace('{{ignorelist}}', matchIgnore[1]) 56 | } else { 57 | console.log("Variable runTestsList not found in the input string."); 58 | } 59 | // create file test 60 | fs.writeFileSync( 61 | basedir() + '/.mocharc.js', contents, 62 | 'utf8' 63 | ) 64 | await waitFor(500) 65 | } 66 | } catch (err) { 67 | console.log(err); 68 | } 69 | } -------------------------------------------------------------------------------- /src/component/schema.component.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import { isFileExisted } from "utils/modul.js"; 3 | import basePath from "utils/path"; 4 | import { waitFor } from "utils/wait"; 5 | 6 | interface schemaComponentInterface { 7 | element:any, 8 | path: string, 9 | moduleType: string 10 | } 11 | /** 12 | * @description asynchronous function to write schema into directory 13 | * @param {schemaComponentInterface} writeSchemaParams included element json, path, and module type 14 | * @returns {Promise} 15 | */ 16 | export const writeSchema = async (writeSchemaParams: schemaComponentInterface): Promise => { 17 | const { 18 | element, path, moduleType 19 | } = writeSchemaParams; 20 | // template dir name 21 | const templateDir = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/schema.dot" : "lib/template/commonjs/schema.dot" 22 | // The following code creates a variable called 'name' and assigns it the value obtained from the 'name' property of the 'element' object, which is then converted to lowercase and all spaces in it are removed. 23 | let name = (element.name).toLowerCase().replace(/\s/g, ''); 24 | name = name.replace(/\//g, ''); 25 | // A variable called 'method' is created and assigned the value obtained from the 'method' property of the 'element.request' object. 26 | let method 27 | if (element.request.hasOwnProperty('method')) { 28 | method = element.request.method; 29 | } 30 | // check if file exists 31 | if (element.request.hasOwnProperty('url')) { 32 | try { 33 | const [fileExists] = await isFileExisted(path, method + '_' + name + '.schema.js'); 34 | if (!fileExists) { 35 | // create file test 36 | fs.writeFileSync(path + '/' + method + '_' + name + '.schema.js', 37 | fs.readFileSync(basePath() + templateDir, 'utf8'), 38 | 'utf8' 39 | ); 40 | await waitFor(500) 41 | } 42 | } catch (err) { 43 | console.log(err); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/component/tests.component.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import { log } from "utils/logs"; 3 | import { isFileExisted } from "utils/modul"; 4 | import basePath from "utils/path"; 5 | import { toLowerCase } from "utils/string"; 6 | import { waitFor } from "utils/wait"; 7 | 8 | interface testComponentInterface { 9 | element: any, 10 | path: string, 11 | pagesPath: string, 12 | dataPath: string, 13 | moduleType: string, 14 | configPath: string, 15 | } 16 | /** 17 | * @description asynchronous function to write tests into directory 18 | * @param {testComponentInterface} writeTestParams included element json and all needed path 19 | * @returns {Promise} 20 | */ 21 | export const writeTest = async (writeTestParams: testComponentInterface): Promise => { 22 | const { 23 | element, path, pagesPath, dataPath, moduleType, configPath 24 | } = writeTestParams; 25 | // template dir name 26 | const templateDir = moduleType == "Javascript modules (import/export)" ? "lib/template/jsimport/spec.dot" : "lib/template/commonjs/spec.dot" 27 | // read template fileimport { log } from './../utils/logs'; 28 | 29 | let contents = fs.readFileSync(basePath() + templateDir, 'utf8'); 30 | 31 | let name = toLowerCase(element.name) 32 | 33 | const method = element.request?.method ?? ''; 34 | 35 | let testFunc = '' 36 | if (element.request.hasOwnProperty('body')) { 37 | testFunc = ` 38 | data.${toLowerCase(element.name).replace('(', '').replace(')', '')}_data.forEach(async (data) => { 39 | it(data.case.name, async () => { 40 | const response = await new Request().api(data.driven) 41 | 42 | expect(response.status).to.equals(data.case.status) 43 | expect(response.body).to.be.jsonSchema(new Request().expectedSchema(data.case.schema)) 44 | }) 45 | }) 46 | ` 47 | } else { 48 | testFunc = ` 49 | it("Successful case", async () => { 50 | const response = await new Request().api() 51 | 52 | expect(response.status).to.equals(200) 53 | expect(response.body).to.be.jsonSchema(new Request().expectedSchema("success")) 54 | }) 55 | ` 56 | } 57 | 58 | let suiteDataPath = toLowerCase(element.name) 59 | 60 | let code = contents.replace("{{describe}}", 'Test ' + element.name) 61 | code = code.replace("{{page_path}}", pagesPath.replace(/\\/g, "/") + '/' + method + '_' + name + '.pages.js') 62 | 63 | if (element.request.hasOwnProperty('body')) { 64 | let importStatement = moduleType === "Javascript modules (import/export)" ? "\n import * as data from '" : "\n const data = require('"; 65 | let endPath = moduleType === "Javascript modules (import/export)" ? ".data.js'" : ".data.js')"; 66 | 67 | if (dataPath.replace(/\\/g, "/").split('/').length >= 2) { 68 | code = code.replace("{{data_path}}", importStatement + dataPath.replace(/\\/g, "/").split('/').slice(0, 2).join('/') + `/${toLowerCase(dataPath.replace(/\\/g, "/").split('/')[1])}` + endPath) 69 | } else { 70 | code = code.replace("{{data_path}}", importStatement + dataPath.replace(/\\/g, "/") + `/${suiteDataPath}` + endPath) 71 | } 72 | } else { 73 | code = code.replace("{{data_path}}", '') 74 | } 75 | 76 | code = code.replace("{{config_path}}", configPath) 77 | code = code.replace("{{test_section}}", testFunc) 78 | 79 | // check if file exists 80 | if (element.request.hasOwnProperty('url')) { 81 | try { 82 | const [fileExists] = await isFileExisted(path, method + '_' + name + '.spec.js'); 83 | if (!fileExists) { 84 | // create file test 85 | fs.writeFileSync(path + '/' + method + '_' + name + '.spec.js', code, 'utf8'); 86 | await waitFor(500) 87 | log(`ø Generate Test ${path.replace(/\\/g, "/") + '/' + method + '_' + name + '.spec.js'} completed successfully`, 'green') 88 | } else { 89 | // file was existed 90 | log(`ø The request of ${element.name} has already created`, 'yellow') 91 | } 92 | } catch (err) { 93 | console.log(err); 94 | } 95 | } else { 96 | // invalid request 97 | log(`ø ${element.name} was invalid request!`, 'red') 98 | } 99 | } -------------------------------------------------------------------------------- /src/component/utils.component.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { isFileExisted } from 'utils/modul'; 3 | import basePath from 'utils/path'; 4 | import { waitFor } from 'utils/wait'; 5 | 6 | /** 7 | * @description asynchronous function to write utils into directory 8 | * @param {string} moduleType module type will be used 9 | * @returns {Promise} 10 | */ 11 | export const writeUtils = async (moduleType: string): Promise => { 12 | // create helper directory if it doesn't exists 13 | const utilsDir = "tests/utils"; 14 | fs.mkdirSync(utilsDir, { recursive: true }); 15 | 16 | const utilsList = [ 17 | 'config', 18 | 'logger', 19 | 'custom_file_transport' 20 | ] 21 | 22 | for (const util of utilsList) { 23 | // template dir name 24 | const templateDir = moduleType == "Javascript modules (import/export)" ? `lib/template/jsimport/${util}.dot` : `lib/template/commonjs/${util}.dot` 25 | 26 | // Check if a file named 'request.helper.js' exists in the tests/helper dir 27 | // If it does not exist then create a new file based on the template file 'requestHelper.dot' 28 | try { 29 | const [fileExists] = await isFileExisted(utilsDir, `${util}.js`); 30 | if (!fileExists) { 31 | // create file test 32 | fs.writeFileSync( 33 | `tests/utils/${util}.js`, 34 | fs.readFileSync(basePath() + templateDir, "utf8"), 35 | 'utf8' 36 | ) 37 | await waitFor(500) 38 | } 39 | } catch (err) { 40 | console.log(err); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/generator/generate.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { promisify } from 'util'; 3 | import path from 'path' 4 | import { waitFor } from 'utils/wait'; 5 | import { toLowerCase } from 'utils/string'; 6 | import { log } from 'utils/logs'; 7 | import { writeHelper } from 'component/helper.component'; 8 | import { writeUtils } from 'component/utils.component'; 9 | import { writeRunner } from 'component/runner.component'; 10 | import { writeConfigs } from 'component/configs.component'; 11 | import { asyncForEach } from 'utils/foreach'; 12 | import { JSConfig, babelConfig, prettierIgnoreConfig } from 'template/config'; 13 | import { writePages } from 'component/pages.component'; 14 | import { writeSchema } from 'component/schema.component'; 15 | import { writeData } from 'component/data.component'; 16 | import { writeTest } from 'component/tests.component'; 17 | 18 | 19 | const readFile = promisify(fs.readFile); 20 | const writeFile = promisify(fs.writeFile); 21 | 22 | interface optionInterface { 23 | customKey: string[], 24 | jsonFileQ: string 25 | } 26 | 27 | interface writeParamsInterface { 28 | items: any, 29 | testPath: string, 30 | testPathAlias: string, 31 | pagesPath: string, 32 | pagesPathAlias: string, 33 | schemaPath: string, 34 | schemaPathAlias: string, 35 | dataPath: string, 36 | dataPathAlias: string, 37 | moduleType: string, 38 | testsListCallback: any 39 | } 40 | 41 | /** 42 | * @description main automation generator 43 | * @param {optionInterface} option included custom key of collection and file (.json) 44 | * @param {any} moduleType module type selected 45 | * @returns {Promise} 46 | */ 47 | export const generate = async (option: optionInterface, moduleType: any): Promise => { 48 | try { 49 | const optionKeys: string[] = option.customKey.map(key => key.split('-')[0].trimEnd()) 50 | 51 | const data: string = await readFile(option.jsonFileQ.includes('"') ? option.jsonFileQ.replace(/"/g, '') : option.jsonFileQ, 'utf8'); 52 | const { item: items } = JSON.parse(data); 53 | 54 | const matchedKeys = items.filter((element: any) => { 55 | return optionKeys.includes(element.name); 56 | }); 57 | 58 | // write helper dir 59 | await writeHelper(moduleType); 60 | // write utils dir 61 | await writeUtils(moduleType); 62 | 63 | // write .example.env file 64 | const envFilename = '.example.env.dev'; 65 | const envFileContent = 'MAIN=your_api_url'; 66 | await writeFile(envFilename, envFileContent); 67 | 68 | const testPath = 'tests/scenarios'; 69 | const testPathAlias = '@scenario'; 70 | const pagesPath = 'tests/pages'; 71 | const pagesPathAlias = '@page'; 72 | const schemaPath = 'tests/schemas'; 73 | const schemaPathAlias = '@schema'; 74 | const dataPath = 'tests/data'; 75 | const dataPathAlias = '@data'; 76 | const testslist: string[] = [] 77 | 78 | function testsListCallback(test: string) { 79 | testslist.push(test) 80 | } 81 | 82 | await loopWrite({ 83 | items: matchedKeys, 84 | testPath, 85 | testPathAlias, 86 | pagesPath, 87 | pagesPathAlias, 88 | schemaPath, 89 | schemaPathAlias, 90 | dataPath, 91 | dataPathAlias, 92 | moduleType, 93 | testsListCallback 94 | }) 95 | await waitFor(500) 96 | 97 | await writeRunner(testslist) 98 | 99 | const configList = [ 100 | { filename: '.babelrc', template: babelConfig }, 101 | { filename: '.prettierignore', template: prettierIgnoreConfig }, 102 | { filename: 'jsconfig.json', template: JSConfig }, 103 | ]; 104 | await writeConfigs(configList); 105 | 106 | setTimeout(() => { 107 | log("~ All test cases have generated ~", "blue") 108 | }, 3000); 109 | } catch (err) { 110 | console.error(err); 111 | } 112 | } 113 | /** 114 | * @description mapping json collection 115 | * @param {writeParamsInterface} writeFileParams included item and all base path 116 | * @returns {Promise} 117 | */ 118 | async function loopWrite(writeFileParams: writeParamsInterface): Promise { 119 | const { 120 | items, testPath, testPathAlias, pagesPath, pagesPathAlias, schemaPath, schemaPathAlias, dataPath, dataPathAlias, moduleType, testsListCallback 121 | } = writeFileParams; 122 | 123 | await asyncForEach(items, async (element: any) => { 124 | // write test dir 125 | const currentTestPath = path.join(testPath, element.name || '') 126 | const currentTestPathAlias = path.join(testPathAlias, element.name || '') 127 | fs.mkdirSync(testPath, { recursive: true }) 128 | // write pages dir 129 | const currentPagesPath = path.join(pagesPath, element.name || '') 130 | const currentPagesPathAlias = path.join(pagesPathAlias, element.name || '') 131 | fs.mkdirSync(pagesPath, { recursive: true }) 132 | // write schema dir 133 | const currentSchemaPath = path.join(schemaPath, element.name || '') 134 | const currentSchemaPathAlis = path.join(schemaPathAlias, element.name || '') 135 | fs.mkdirSync(schemaPath, { recursive: true }) 136 | // write data dir 137 | const currentDataPath = path.join(dataPath, element.name || '') 138 | const currentDataPathAlis = path.join(dataPathAlias, element.name || '') 139 | const suiteDataPath = dataPath.replace(/\\/g, "/").split('/').slice(0, 3).join('/') 140 | 141 | if (element.hasOwnProperty('item')) { 142 | await loopWrite({ 143 | items: element.item, 144 | testPath: currentTestPath, 145 | testPathAlias: currentTestPathAlias, 146 | pagesPath: currentPagesPath, 147 | pagesPathAlias: currentPagesPathAlias, 148 | schemaPath: currentSchemaPath, 149 | schemaPathAlias: currentSchemaPathAlis, 150 | dataPath: currentDataPath, 151 | dataPathAlias: currentDataPathAlis, 152 | moduleType, 153 | testsListCallback 154 | }) 155 | } else { 156 | const configPathAlias = '@util/config.js'; 157 | const loggerPathAlias = '@util/logger.js'; 158 | const helperPathAlias = '@helper/request.helper.js'; 159 | 160 | testsListCallback(testPath.replace(/\\/g, "/") + '/' + element.request.method + '_' + toLowerCase(element.name) + '.spec.js') 161 | 162 | await Promise.all([ 163 | writePages({ 164 | element: element, 165 | path: pagesPath, 166 | schemaPath: schemaPathAlias, 167 | dataPath: dataPathAlias, 168 | helperPath: helperPathAlias, 169 | moduleType, 170 | configPath: configPathAlias, 171 | loggerPath: loggerPathAlias, 172 | }), 173 | writeSchema({ 174 | element: element, 175 | path: schemaPath, 176 | moduleType, 177 | }), 178 | writeData({ 179 | element: element, 180 | path: suiteDataPath, 181 | moduleType, 182 | }), 183 | writeTest({ 184 | element: element, 185 | path: testPath, 186 | pagesPath: pagesPathAlias, 187 | dataPath: dataPathAlias, 188 | moduleType, 189 | configPath: configPathAlias, 190 | }) 191 | ]) 192 | } 193 | }) 194 | } -------------------------------------------------------------------------------- /src/generator/generate_env.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { promisify } from 'util'; 3 | import { asyncForEach } from 'utils/foreach'; 4 | import { isFileExisted } from 'utils/modul.js'; 5 | import { waitFor } from 'utils/wait'; 6 | const readFile = promisify(fs.readFile); 7 | 8 | /** 9 | * @description main environment generator 10 | * @param {any} file json file (.json) 11 | * @param {any} envName environment name 12 | * @returns {Promise} 13 | */ 14 | export const generateEnv = async (file: any, envName: any): Promise => { 15 | let envStr = '' 16 | 17 | const data: string = await readFile(file, 'utf8'); 18 | const { item: items } = JSON.parse(data); 19 | 20 | let first: any; 21 | asyncForEach(items, async (item: any) => { 22 | if (item.enabled) { 23 | if (first === false) envStr += '\r\n' 24 | envStr += item.key + '=' + item.value 25 | first = false; 26 | } 27 | }) 28 | 29 | await waitFor(100) 30 | 31 | // check if pages file exists 32 | isFileExisted(process.cwd(), '.env.' + envName) 33 | .then((data) => { 34 | if (!data[0]) { 35 | // create file test 36 | fs.writeFile('.env.' + envName, 37 | envStr, function (err) { if (err) throw err; }); 38 | 39 | // _postman_isSubFolder 40 | console.log(`${'\x1b[32m'}ø Generate environment file completed successfully${'\x1b[0m'}`) 41 | } else { 42 | console.log(`${'\x1b[33m'}ø The environment file has already created${'\x1b[0m'}`); 43 | } 44 | }) 45 | .catch((err) => console.log(err)); 46 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { PogenCommand } from "command/pogen.command" 4 | 5 | const argument = process.argv[process.argv.length - 1]; 6 | const pogenCommand = new PogenCommand(argument); 7 | 8 | if (argument === 'generate' || argument === '' || argument.includes('/.bin/generate') || argument.includes('\\po-gen\\lib') ) { 9 | console.log('Initiating automation generation'); 10 | pogenCommand.automation(); 11 | } else if (argument === 'env-generate') { 12 | console.log('Initiating environment generation'); 13 | pogenCommand.environment(); 14 | } else { 15 | console.log(`Unknown argument: ${argument}`); 16 | } 17 | -------------------------------------------------------------------------------- /src/interface/package.interface.ts: -------------------------------------------------------------------------------- 1 | export interface packageInstallInterface { 2 | stringPackage: boolean | string[] | string, 3 | stringDevPackage: string, 4 | jsonfile: any, 5 | moduleType: string, 6 | prettierExist: boolean | string[] | string, 7 | } 8 | export interface devPackageInstallInterface { 9 | stringDevPackage: boolean | string[] | string, 10 | jsonfile: any, 11 | moduleType: string, 12 | prettierExist: boolean | string[] | string, 13 | } -------------------------------------------------------------------------------- /src/interface/question.interface.ts: -------------------------------------------------------------------------------- 1 | export interface CLIAutomationQuestionInterface { 2 | argument: string | undefined, 3 | packagesList: boolean | string[] | string, 4 | mochaExist: boolean | string[] | string, 5 | eslintExist: boolean | string[] | string, 6 | } -------------------------------------------------------------------------------- /src/template/commonjs/config.dot: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv"); 2 | 3 | export class Config { 4 | constructor() { 5 | // Write your constructor here, if you need 6 | } 7 | 8 | env() { 9 | // change according to your need 10 | dotenv.config({ path: __dirname + `/../../.env.${process.env.NODE_ENV}` }); 11 | // Defining an object named 'env', contained your variables needed 12 | return { 13 | host: process.env.MAIN 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/template/commonjs/custom_file_transport.dot: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const winston = require("winston"); 3 | 4 | const logDir = './logs'; 5 | if (!fs.existsSync(logDir)) { 6 | fs.mkdirSync(logDir); 7 | } 8 | 9 | export class CustomFileTransport extends winston.transports.File { 10 | constructor(options) { 11 | // Pass the options to the parent class constructor 12 | super({ ...options, dirname: logDir }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/template/commonjs/logger.dot: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const { createLogger, format, transports } = require("winston"); 3 | const { combine, timestamp, printf } = format; 4 | const { CustomFileTransport } = require('./custom_file_transport') 5 | const colorizer = format.colorize(); 6 | 7 | const logFormat = combine( 8 | timestamp({ 9 | format: "YYYY-MM-DD HH:mm:ss" 10 | }), 11 | printf((output) => 12 | colorizer.colorize( 13 | output.level, 14 | `[${output.timestamp}] ${output.level.toUpperCase()}: ${output.message}` 15 | ) 16 | ) 17 | ); 18 | 19 | export class Logger { 20 | constructor() { 21 | // Write your constructor here, if you need 22 | } 23 | 24 | console = ({ service = "example", level = "debug" }) => 25 | createLogger({ 26 | level, 27 | defaultMeta: { service }, 28 | format: logFormat, 29 | transports: [ 30 | // Output logs to console in simple message format 31 | new transports.Console() 32 | ] 33 | }); 34 | 35 | file = ({ service = "example", type = "response", level = "debug" }) => 36 | createLogger({ 37 | level, 38 | defaultMeta: { service }, 39 | format: logFormat, 40 | transports: [ 41 | // Output logs to console in simple message format 42 | new CustomFileTransport({ 43 | format: winston.format.json(), 44 | filename: `./logs/${type}.json` 45 | }) 46 | ] 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /src/template/commonjs/pages.dot: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | chai.use(require('chai-http')) 3 | const requestHelper = require('{{path_helper}}') 4 | const { Config } = require('{{path_config}}'){{path_data}} 5 | const { schema } = require('{{path_schema}}') 6 | const { Logger } = require('{{path_logger}}') 7 | const logger = new Logger(); 8 | 9 | class Request { 10 | constructor() { 11 | // Write your constructor here, if you nee 12 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 13 | this.host = new Config().env().host; 14 | this.url = "{{url}}" // Set up the API path to the route endpoint 15 | } 16 | 17 | get request() { 18 | return chai.request(this.host) 19 | } 20 | 21 | // This method handles making the HTTP request based on given arguments. 22 | async api(...args) { 23 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 24 | try { 25 | const response = await this.request.{{method}}(this.url){{query}}{{header}}{{payload}} 26 | 27 | await this.createLogging(response); 28 | return response; 29 | } catch (err) { 30 | await this.createLogging(0, err); 31 | } 32 | } 33 | {{body_section}} 34 | 35 | async createLogging(response, error) { 36 | const isError = error !== undefined; 37 | const logLevel = isError ? 'error' : 'debug'; 38 | const statusMessage = isError ? 'Failed' : 'Successful'; 39 | const endpoint = `${this.host}${this.url}`; 40 | const requestPayload = isError ? error.response.request : response.request; 41 | const responsePayload = isError ? error.response.body : response.body; 42 | 43 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 44 | 45 | logger.file({ type: 'request', level: logLevel }).http({ 46 | endpoint: endpoint, 47 | data: requestPayload 48 | }); 49 | 50 | logger.file({ type: 'response', level: logLevel }).http({ 51 | endpoint: endpoint, 52 | data: responsePayload 53 | }); 54 | } 55 | 56 | // This method used for provide expectation and return json schema 57 | expectedSchema(cases="success") { 58 | return new requestHelper().getSchema(schema, cases) 59 | } 60 | } 61 | 62 | module.exports = Request -------------------------------------------------------------------------------- /src/template/commonjs/pages_attach.dot: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | chai.use(require('chai-http')) 3 | const requestHelper = require('{{path_helper}}') 4 | const { Config } = require('{{path_config}}'){{path_data}} 5 | const { schema } = require('{{path_schema}}') 6 | const { Logger } = require('{{path_logger}}') 7 | const logger = new Logger(); 8 | 9 | class Request { 10 | constructor() { 11 | // Write your constructor here, if you nee 12 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 13 | this.host = new Config().env().host; 14 | this.url = "{{url}}" // Set up the API path to the route endpoint 15 | } 16 | 17 | get request() { 18 | return chai.request(this.host) 19 | } 20 | 21 | // This method handles making the HTTP request based on given arguments. 22 | async api(...args) { 23 | const payload = new requestHelper().getPayload(args) 24 | const attachment = new requestHelper().getAttachment(args) 25 | 26 | try { 27 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 28 | let response = await this.request.{{method}}(this.url){{query}} 29 | .set("Content-Type", "multipart/form-data"){{header}} 30 | 31 | Object.keys(await this.getMappedBody(payload)).forEach( async (key) => { 32 | response = await response.field(key, JSON.stringify(await this.getMappedBody(payload)[key])) 33 | }) 34 | 35 | Object.keys(await this.getMappedAttachment(attachment)).forEach(async (key) => { 36 | if( typeof await this.getMappedAttachment(attachment)[key] != 'object') { 37 | const raw = await new requestHelper().getFile(await this.getMappedAttachment(attachment)[key]) 38 | response = await response.attach(key, raw.file, raw.name) 39 | } else { 40 | await this.getMappedAttachment(attachment)[key].forEach(async (val) => { 41 | const raw = await new requestHelper().getFile(val) 42 | response = await response.attach(key, raw.file, raw.name) 43 | }) 44 | } 45 | }) 46 | 47 | await this.createLogging(response); 48 | return response 49 | } catch (err) { 50 | await this.createLogging(0, err); 51 | } 52 | } 53 | {{body_section}} 54 | 55 | // This method used for provide attachment file and return object 56 | async getMappedAttachment(...args) { 57 | const defaultData = new requestHelper().getDefaultData(data.{{data_attach_name}}) 58 | const dataMapped = await new requestHelper().mapObject(defaultData.driven.attachment, args); 59 | 60 | return dataMapped 61 | } 62 | 63 | async createLogging(response, error) { 64 | const isError = error !== undefined; 65 | const logLevel = isError ? 'error' : 'debug'; 66 | const statusMessage = isError ? 'Failed' : 'Successful'; 67 | const endpoint = `${this.host}${this.url}`; 68 | const requestPayload = isError ? error.response.request : response.request; 69 | const responsePayload = isError ? error.response.body : response.body; 70 | 71 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 72 | 73 | logger.file({ type: 'request', level: logLevel }).http({ 74 | endpoint: endpoint, 75 | data: requestPayload 76 | }); 77 | 78 | logger.file({ type: 'response', level: logLevel }).http({ 79 | endpoint: endpoint, 80 | data: responsePayload 81 | }); 82 | } 83 | 84 | // This method used for provide expectation and return json schema 85 | expectedSchema(cases="success") { 86 | return new requestHelper().getSchema(schema, cases) 87 | } 88 | } 89 | 90 | module.exports = Request -------------------------------------------------------------------------------- /src/template/commonjs/request_helper.dot: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const toJsonSchema = require('to-json-schema'); 3 | 4 | class requestHelper { 5 | constructor() { 6 | // Write your constructor here, if you need 7 | this.waitFor = (ms) => new Promise(r => setTimeout(r, ms)) 8 | } 9 | 10 | // This method used for get parameter which send from test file 11 | async getPayload(arg) { 12 | let data = {} 13 | arg.forEach((datas) => { 14 | if(typeof datas == 'object') { 15 | data = Object.assign({}, datas) 16 | } 17 | }) 18 | return data 19 | } 20 | 21 | // If your test using attachment, this method used for get parameter of attachment file like path and keys 22 | async getAttachment(arg) { 23 | let data = {} 24 | arg.forEach((datas) => { 25 | if(typeof datas == 'object') { 26 | if(datas.hasOwnProperty('attachment')) { 27 | data = Object.assign({}, datas.attachment) 28 | } 29 | } 30 | }) 31 | return data 32 | } 33 | 34 | getDefaultData(data_array) { 35 | return data_array.find(item => item.case.default); 36 | } 37 | 38 | 39 | // If your test using attachment, this method used for get binary file from your computer 40 | async getFile(data) { 41 | const path = data.split('/') 42 | const name = path[path.length - 1] 43 | const file = await fs.readFile(data) 44 | 45 | return { 46 | file, 47 | name, 48 | } 49 | } 50 | 51 | // This method used for convert json file to json schema 52 | getSchema(json_responses, cases) { 53 | const options = { 54 | objects: { 55 | postProcessFnc: (schema, obj, defaultFnc) => ({...defaultFnc(schema, obj), required: Object.getOwnPropertyNames(obj)}) 56 | } 57 | } 58 | 59 | if (json_responses.hasOwnProperty(cases)) { 60 | return toJsonSchema(json_responses[cases], options) 61 | } else { 62 | throw new Error('JSON Schema: '+cases+', does not exist!') 63 | } 64 | 65 | } 66 | 67 | // This method user for mapping keys object which you want to be change/replace the object 68 | async mapObject(obj, arg) { 69 | let newObj = {}; 70 | let map = arg[0]; 71 | 72 | if (typeof obj === "object") { 73 | newObj = { ...obj }; 74 | } 75 | 76 | Object.keys(map).forEach((key) => { 77 | if (newObj[key] !== undefined) { 78 | newObj[key] = map[key]; 79 | } 80 | 81 | Object.entries(newObj).forEach(([nestedKey, nestedVal]) => { 82 | if (Array.isArray(nestedVal)) { 83 | nestedVal.forEach((innerObj) => { 84 | if (innerObj[key] !== undefined) { 85 | innerObj[key] = map[key]; 86 | } 87 | 88 | Object.values(innerObj).forEach((dObjArr) => { 89 | if (Array.isArray(dObjArr)) { 90 | dObjArr.forEach((dObj) => { 91 | if (dObj[key] !== undefined) { 92 | dObj[key] = map[key]; 93 | } 94 | }); 95 | } 96 | }); 97 | }); 98 | } else if (typeof nestedVal === "object") { 99 | Object.values(nestedVal).forEach((dObjArr) => { 100 | if (Array.isArray(dObjArr)) { 101 | dObjArr.forEach((dObj) => { 102 | if (dObj[key] !== undefined) { 103 | dObj[key] = map[key]; 104 | } 105 | }); 106 | } 107 | }); 108 | } 109 | }); 110 | }); 111 | 112 | return newObj; 113 | } 114 | } 115 | 116 | module.exports = requestHelper -------------------------------------------------------------------------------- /src/template/commonjs/schema.dot: -------------------------------------------------------------------------------- 1 | export const schema = { 2 | "success": 3 | { 4 | "example": "example" 5 | }, 6 | "failed": 7 | { 8 | "example": "example" 9 | } 10 | } -------------------------------------------------------------------------------- /src/template/commonjs/spec.dot: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect 2 | const chai = require('chai') 3 | chai.use(require('chai-json-schema')){{data_path}} 4 | const Request = require('{{page_path}}') 5 | const config = require('{{config_path}}') 6 | 7 | describe("{{describe}}", () => { 8 | {{test_section}} 9 | }) -------------------------------------------------------------------------------- /src/template/config.ts: -------------------------------------------------------------------------------- 1 | export const defaultEslintConfig: string = 2 | `{ 3 | "env": { 4 | "browser": true, 5 | "es2021": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "rules": { 12 | "no-undef": 0, 13 | "no-prototype-builtins": 0 14 | } 15 | }` 16 | 17 | export const defaultMochaConfig: string = 18 | ` 19 | const runTestsList = {{runner}} 20 | 21 | const ignoreTestsList = {{ignorelist}} 22 | 23 | function getSpecsList() { 24 | const runOptArgument = process.argv.indexOf('--specs') 25 | const runOpt = runOptArgument !== -1 ? process.argv[runOptArgument+1] : 'Regression' 26 | 27 | if (runOpt.includes("/") || runOpt in runTestsList) { 28 | return runTestsList[runOpt]; 29 | } 30 | 31 | if (runOpt.includes(",")) { 32 | return runOpt.split(",").flatMap(key => runTestsList[key]); 33 | } 34 | } 35 | 36 | module.exports = { 37 | require: ['@babel/register'], 38 | jobs: 1, 39 | package: './package.json', 40 | reporter: 'spec', 41 | ignore: ignoreTestsList, 42 | spec: getSpecsList(), 43 | 'trace-warnings': true, 44 | ui: 'bdd', 45 | } 46 | ` 47 | 48 | export const prettierIgnoreConfig: string = 49 | `**/.git 50 | **/.svn 51 | **/.hg 52 | **.md 53 | **/node_modules 54 | **/logs 55 | .babelrc 56 | .eslintrc.json 57 | .prettierignore 58 | package-lock.json 59 | package.json 60 | prettier.config.js 61 | ` 62 | 63 | export const babelConfig: string = 64 | `{ 65 | "presets": [ 66 | [ 67 | "@babel/preset-env", 68 | { 69 | "targets": { 70 | "node": 18 71 | } 72 | } 73 | ] 74 | ], 75 | "plugins": [ 76 | [ 77 | "module-resolver", 78 | { 79 | "root": ["."], 80 | "alias": { 81 | "@root": ".", 82 | "@tests": "./tests", 83 | "@scenario": "./tests/scenarios", 84 | "@page": "./tests/pages", 85 | "@schema": "./tests/schemas", 86 | "@helper": "./tests/helpers", 87 | "@data": "./tests/data", 88 | "@util": "./tests/utils" 89 | } 90 | } 91 | ] 92 | ] 93 | } 94 | ` 95 | export const JSConfig: string = 96 | ` 97 | { 98 | "compilerOptions": { 99 | "module": "ES6", 100 | "target": "ES6", 101 | "baseUrl": "./tests", 102 | "paths": { 103 | "*": [ 104 | "tests/*" 105 | ], 106 | "@scenario/*": ["scenarios/*"], 107 | "@page/*": ["pages/*"], 108 | "@schema/*": ["schemas/*"], 109 | "@helper/*": ["helpers/*"], 110 | "@data/*": ["data/*"], 111 | "@util/*": ["utils/*"] 112 | }, 113 | "allowSyntheticDefaultImports": true, 114 | "declaration": true, 115 | }, 116 | "include": [ 117 | "tests/**/*.js" 118 | ], 119 | "exclude": [ 120 | "node_modules" 121 | ] 122 | } 123 | ` -------------------------------------------------------------------------------- /src/template/jsimport/config.dot: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | export class Config { 4 | constructor() { 5 | // Write your constructor here, if you need 6 | } 7 | 8 | env() { 9 | // change according to your need 10 | dotenv.config({ path: __dirname + `/../../.env.${process.env.NODE_ENV}` }); 11 | // Defining an object named 'env', contained your variables needed 12 | return { 13 | host: process.env.MAIN 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/template/jsimport/custom_file_transport.dot: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import winston from "winston"; 3 | 4 | const logDir = './logs'; 5 | if (!fs.existsSync(logDir)) { 6 | fs.mkdirSync(logDir); 7 | } 8 | 9 | export class CustomFileTransport extends winston.transports.File { 10 | constructor(options) { 11 | // Pass the options to the parent class constructor 12 | super({ ...options, dirname: logDir }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/template/jsimport/logger.dot: -------------------------------------------------------------------------------- 1 | import winston, { createLogger, format, transports } from "winston"; 2 | import { CustomFileTransport } from "./custom_file_transport"; 3 | const { combine, timestamp, printf } = format; 4 | const colorizer = format.colorize(); 5 | 6 | const logFormat = combine( 7 | timestamp({ 8 | format: "YYYY-MM-DD HH:mm:ss" 9 | }), 10 | printf((output) => 11 | colorizer.colorize( 12 | output.level, 13 | `[${output.timestamp}] ${output.level.toUpperCase()}: ${output.message}` 14 | ) 15 | ) 16 | ); 17 | 18 | export class Logger { 19 | constructor() { 20 | // Write your constructor here, if you need 21 | } 22 | 23 | console = ({ service = "example", level = "debug" }) => 24 | createLogger({ 25 | level, 26 | defaultMeta: { service }, 27 | format: logFormat, 28 | transports: [ 29 | // Output logs to console in simple message format 30 | new transports.Console() 31 | ] 32 | }); 33 | 34 | file = ({ service = "example", type = "response", level = "debug" }) => 35 | createLogger({ 36 | level, 37 | defaultMeta: { service }, 38 | format: logFormat, 39 | transports: [ 40 | // Output logs to console in simple message format 41 | new CustomFileTransport({ 42 | format: winston.format.json(), 43 | filename: `./logs/${type}.json` 44 | }) 45 | ] 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/template/jsimport/pages.dot: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import chaiHttp from 'chai-http' 3 | import requestHelper from '{{path_helper}}' 4 | import { Config } from '{{path_config}}' 5 | import { schema } from '{{path_schema}}'{{path_data}} 6 | import { Logger } from '{{path_logger}}' 7 | chai.use(chaiHttp) 8 | 9 | const logger = new Logger() 10 | 11 | class Request { 12 | constructor() { 13 | // Write your constructor here, if you nee 14 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 15 | this.host = new Config().env().host; 16 | this.url = "{{url}}" // Set up the API path to the route endpoint 17 | } 18 | 19 | get request() { 20 | return chai.request(this.host) 21 | } 22 | 23 | // This method handles making the HTTP request based on given arguments. 24 | async api(...args) { 25 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 26 | try { 27 | const response = await this.request.{{method}}(this.url){{query}}{{header}}{{payload}} 28 | 29 | await this.createLogging(response); 30 | return response; 31 | } catch (err) { 32 | await this.createLogging(0, err); 33 | } 34 | } 35 | {{body_section}} 36 | 37 | async createLogging(response, error) { 38 | const isError = error !== undefined; 39 | const logLevel = isError ? 'error' : 'debug'; 40 | const statusMessage = isError ? 'Failed' : 'Successful'; 41 | const endpoint = `${this.host}${this.url}`; 42 | const requestPayload = isError ? error.response.request : response.request; 43 | const responsePayload = isError ? error.response.body : response.body; 44 | 45 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 46 | 47 | logger.file({ type: 'request', level: logLevel }).http({ 48 | endpoint: endpoint, 49 | data: requestPayload 50 | }); 51 | 52 | logger.file({ type: 'response', level: logLevel }).http({ 53 | endpoint: endpoint, 54 | data: responsePayload 55 | }); 56 | } 57 | 58 | // This method used for provide expectation and return json schema 59 | expectedSchema(cases="success") { 60 | return new requestHelper().getSchema(schema, cases) 61 | } 62 | } 63 | 64 | export default Request -------------------------------------------------------------------------------- /src/template/jsimport/pages_attach.dot: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import chaiHttp from 'chai-http' 3 | import requestHelper from '{{path_helper}}' 4 | import { Config } from '{{path_config}}' 5 | import { schema } from '{{path_schema}}'{{path_data}} 6 | import { Logger } from '{{path_logger}}' 7 | chai.use(chaiHttp) 8 | 9 | const logger = new Logger() 10 | 11 | class Request { 12 | constructor() { 13 | // Write your constructor here, if you nee 14 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 15 | this.host = new Config().env().host; 16 | this.url = "{{url}}" // Set up the API path to the route endpoint 17 | } 18 | 19 | get request() { 20 | return chai.request(this.host) 21 | } 22 | 23 | // This method handles making the HTTP request based on given arguments. 24 | async api(...args) { 25 | const payload = new requestHelper().getPayload(args) 26 | const attachment = new requestHelper().getAttachment(args) 27 | 28 | try { 29 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 30 | let response = await this.request.{{method}}(this.url){{query}} 31 | .set("Content-Type", "multipart/form-data"){{header}} 32 | 33 | Object.keys(await this.getMappedBody(payload)).forEach( async (key) => { 34 | response = await response.field(key, JSON.stringify(await this.getMappedBody(payload)[key])) 35 | }) 36 | 37 | Object.keys(await this.getMappedAttachment(attachment)).forEach(async (key) => { 38 | if( typeof await this.getMappedAttachment(attachment)[key] != 'object') { 39 | const raw = await new requestHelper().getFile(await this.getMappedAttachment(attachment)[key]) 40 | response = await response.attach(key, raw.file, raw.name) 41 | } else { 42 | await this.getMappedAttachment(attachment)[key].forEach(async (val) => { 43 | const raw = await new requestHelper().getFile(val) 44 | response = await response.attach(key, raw.file, raw.name) 45 | }) 46 | } 47 | }) 48 | 49 | await this.createLogging(response); 50 | return response 51 | } catch (err) { 52 | await this.createLogging(0, err); 53 | } 54 | } 55 | {{body_section}} 56 | 57 | // This method used for provide attachment file and return object 58 | async getMappedAttachment(...args) { 59 | const defaultData = new requestHelper().getDefaultData(data.{{data_attach_name}}) 60 | const dataMapped = await new requestHelper().mapObject(defaultData.driven.attachment, args); 61 | 62 | return dataMapped 63 | } 64 | 65 | async createLogging(response, error) { 66 | const isError = error !== undefined; 67 | const logLevel = isError ? 'error' : 'debug'; 68 | const statusMessage = isError ? 'Failed' : 'Successful'; 69 | const endpoint = `${this.host}${this.url}`; 70 | const requestPayload = isError ? error.response.request : response.request; 71 | const responsePayload = isError ? error.response.body : response.body; 72 | 73 | logger.console({ level: logLevel }).http(`${statusMessage} to hit login endpoint ${endpoint}`); 74 | 75 | logger.file({ type: 'request', level: logLevel }).http({ 76 | endpoint: endpoint, 77 | data: requestPayload 78 | }); 79 | 80 | logger.file({ type: 'response', level: logLevel }).http({ 81 | endpoint: endpoint, 82 | data: responsePayload 83 | }); 84 | } 85 | 86 | // This method used for provide expectation and return json schema 87 | expectedSchema(cases="success") { 88 | return new requestHelper().getSchema(schema, cases) 89 | } 90 | } 91 | 92 | export default Request -------------------------------------------------------------------------------- /src/template/jsimport/request_helper.dot: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import toJsonSchema from "to-json-schema"; 3 | 4 | class requestHelper { 5 | constructor() { 6 | // Write your constructor here, if you need 7 | this.waitFor = (ms) => new Promise(r => setTimeout(r, ms)) 8 | } 9 | 10 | // This method used for get parameter which send from test file 11 | async getPayload(arg) { 12 | let data = {} 13 | arg.forEach((datas) => { 14 | if(typeof datas == 'object') { 15 | data = Object.assign({}, datas) 16 | } 17 | }) 18 | return data 19 | } 20 | 21 | // If your test using attachment, this method used for get parameter of attachment file like path and keys 22 | async getAttachment(arg) { 23 | let data = {} 24 | arg.forEach((datas) => { 25 | if(typeof datas == 'object') { 26 | if(datas.hasOwnProperty('attachment')) { 27 | data = Object.assign({}, datas.attachment) 28 | } 29 | } 30 | }) 31 | return data 32 | } 33 | 34 | getDefaultData(data_array) { 35 | return data_array.find(item => item.case.default); 36 | } 37 | 38 | 39 | // If your test using attachment, this method used for get binary file from your computer 40 | async getFile(data) { 41 | const path = data.split('/') 42 | const name = path[path.length - 1] 43 | const file = await fs.readFile(data) 44 | 45 | return { 46 | file, 47 | name, 48 | } 49 | } 50 | 51 | // This method used for convert json file to json schema 52 | getSchema(json_responses, cases) { 53 | const options = { 54 | objects: { 55 | postProcessFnc: (schema, obj, defaultFnc) => ({...defaultFnc(schema, obj), required: Object.getOwnPropertyNames(obj)}) 56 | } 57 | } 58 | 59 | if (json_responses.hasOwnProperty(cases)) { 60 | return toJsonSchema(json_responses[cases], options) 61 | } else { 62 | throw new Error('JSON Schema: '+cases+', does not exist!') 63 | } 64 | 65 | } 66 | 67 | // This method user for mapping keys object which you want to be change/replace the object 68 | async mapObject(obj, arg) { 69 | let newObj = {}; 70 | let map = arg[0]; 71 | 72 | if (typeof obj === "object") { 73 | newObj = { ...obj }; 74 | } 75 | 76 | Object.keys(map).forEach((key) => { 77 | if (newObj[key] !== undefined) { 78 | newObj[key] = map[key]; 79 | } 80 | 81 | Object.entries(newObj).forEach(([nestedKey, nestedVal]) => { 82 | if (Array.isArray(nestedVal)) { 83 | nestedVal.forEach((innerObj) => { 84 | if (innerObj[key] !== undefined) { 85 | innerObj[key] = map[key]; 86 | } 87 | 88 | Object.values(innerObj).forEach((dObjArr) => { 89 | if (Array.isArray(dObjArr)) { 90 | dObjArr.forEach((dObj) => { 91 | if (dObj[key] !== undefined) { 92 | dObj[key] = map[key]; 93 | } 94 | }); 95 | } 96 | }); 97 | }); 98 | } else if (typeof nestedVal === "object") { 99 | Object.values(nestedVal).forEach((dObjArr) => { 100 | if (Array.isArray(dObjArr)) { 101 | dObjArr.forEach((dObj) => { 102 | if (dObj[key] !== undefined) { 103 | dObj[key] = map[key]; 104 | } 105 | }); 106 | } 107 | }); 108 | } 109 | }); 110 | }); 111 | 112 | return newObj; 113 | } 114 | } 115 | 116 | export default requestHelper -------------------------------------------------------------------------------- /src/template/jsimport/schema.dot: -------------------------------------------------------------------------------- 1 | export const schema = { 2 | "success": 3 | { 4 | "example": "example" 5 | }, 6 | "failed": 7 | { 8 | "example": "example" 9 | } 10 | } -------------------------------------------------------------------------------- /src/template/jsimport/spec.dot: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import chai from 'chai' 3 | import chaiJsonSchema from 'chai-json-schema' 4 | import Request from '{{page_path}}' 5 | import config from '{{config_path}}'{{data_path}} 6 | chai.use(chaiJsonSchema) 7 | 8 | describe("{{describe}}", () => { 9 | {{test_section}} 10 | }) -------------------------------------------------------------------------------- /src/utils/foreach.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description looping any array 3 | * @param {Array} array array to be loop 4 | * @param {Promise} callback ooping item 5 | * @returns {Promise} 6 | */ 7 | export async function asyncForEach(array: T[], callback: (item: T, index: number, array: T[]) => Promise): Promise { 8 | for (const item of array) { 9 | await callback(item, array.indexOf(item), array); 10 | } 11 | } -------------------------------------------------------------------------------- /src/utils/logs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description coloring console log 3 | * @param {string} text - some text to console log 4 | * @param {any} color - color 5 | * @returns {void} 6 | */ 7 | export function log(text: string, color: any): void { 8 | const colorList = { 9 | red: `${'\x1b[31m'}${text}${'\x1b[0m'}`, 10 | green: `${'\x1b[32m'}${text}${'\x1b[0m'}`, 11 | yellow: `${'\x1b[33m'}${text}${'\x1b[0m'}`, 12 | blue: `${'\x1b[34m'}${text}${'\x1b[0m'}`, 13 | }; 14 | const key: keyof typeof colorList = color; 15 | 16 | return console.log(colorList[key]); 17 | } -------------------------------------------------------------------------------- /src/utils/modul.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { log } from './logs'; 3 | /** 4 | * @description Check the file is existed or not 5 | * @param {string} path the path for destination 6 | * @param {string} fileName filename which want to search 7 | * @returns {Promise} 8 | */ 9 | export async function isFileExisted(path: string, fileName: string): Promise<[boolean, string | null]> { 10 | const data: string[] = fs.readdirSync(path); 11 | 12 | for (let file of data) { 13 | const curPath: string = path + '/' + file; 14 | 15 | if (file === 'node_modules') { 16 | continue; 17 | } else if (fs.statSync(curPath).isDirectory()) { 18 | const res: [boolean, string | null] = await isFileExisted(curPath, fileName); 19 | 20 | if (res[0]) return [true, res[1]]; 21 | } else if (file === fileName) { 22 | return [true, curPath]; 23 | } 24 | } 25 | return [false, null]; 26 | } 27 | /** 28 | * @description Check JS module type 29 | * @returns {Promise} 30 | */ 31 | export const existModuleType = async (): Promise =>{ 32 | try { 33 | const checkConfigImport = fs.readFileSync('./tests/utils/config.js').toString() 34 | const checkHelperImport = fs.readFileSync('./tests/helpers/request.helper.js').toString() 35 | 36 | if (checkConfigImport.includes('import dotenv from "dotenv"') || checkHelperImport.includes('import fs from "fs"')) return "Javascript modules (import/export)" 37 | 38 | return "CommonJS (require/exports)" 39 | } catch (e) { 40 | return true 41 | } 42 | } 43 | /** 44 | * @description Rebuild script package 45 | * @returns {Promise} 46 | */ 47 | export const rebuildPackagejson = async (): Promise => { 48 | const scriptName = 'regression:dev'; // Name of your new script 49 | const scriptCommand = 'cross-env NODE_ENV=dev mocha --specs Regression --timeout 15000'; // Command to execute your script 50 | 51 | try { 52 | // Read the package.json answers.jsonFileQ 53 | const packageJson = await JSON.parse(fs.readFileSync('./package.json', 'utf8')); 54 | 55 | // Add the new script to the scripts object 56 | packageJson['scripts'] = packageJson['scripts'] || {}; // Initialize 'scripts' object if it doesn't exist 57 | packageJson.scripts[scriptName] = scriptCommand; // Assign the script command to the given script name 58 | 59 | // Write the updated package.json answers.jsonFileQ 60 | fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2)); 61 | 62 | log(`Script updated in package.json`, 'green'); 63 | } catch (err) { 64 | console.error('Failed to update package.json:', err); 65 | } 66 | } -------------------------------------------------------------------------------- /src/utils/path.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description base path in debug or production 3 | * @returns {string} 4 | */ 5 | export default function basePath(): string { 6 | const isProd = true 7 | return isProd ? './node_modules/@dot.indonesia/po-gen/' : '../' 8 | } 9 | /** 10 | * @description base dir 11 | * @returns {string} 12 | */ 13 | export function basedir(): string { 14 | return process.cwd() 15 | } -------------------------------------------------------------------------------- /src/utils/question.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { promisify } from 'util'; 3 | import inquirer from 'inquirer'; 4 | import { CLIAutomationQuestionInterface } from "interface/question.interface"; 5 | import { envNameValidation, jsonFileValidation, mochawesomeValidation, projectModulesValidation } from "./validation"; 6 | const readFile = promisify(fs.readFile); 7 | 8 | /** 9 | * @description questions list for automation generation 10 | * @param {CLIAutomationQuestionInterface} automationQuestionParams included the script arguments and needed package states 11 | * @returns {Promise} 12 | */ 13 | export const CLIAutomationQuestion = async (automationQuestionParams: CLIAutomationQuestionInterface): Promise => { 14 | const { 15 | argument, 16 | packagesList, 17 | mochaExist, 18 | eslintExist 19 | } = automationQuestionParams; 20 | 21 | const commonQuestions = [ 22 | { 23 | type: 'list', 24 | name: 'moduleQ', 25 | message: 'What type of modules does your project use?', 26 | choices: ["Javascript modules (import/export)", "CommonJS (require/exports)"], 27 | when: () => projectModulesValidation() 28 | }, 29 | { 30 | type: 'input', 31 | name: 'jsonFileQ', 32 | message: 'Type your json file to be generate (example.json):', 33 | default: 'example.json', 34 | validate: (answers: any) => jsonFileValidation(answers) 35 | } 36 | ] 37 | 38 | const fullQuestions = [ 39 | { 40 | type: 'list', 41 | name: 'frameworkQ', 42 | message: 'What framework will be used?', 43 | choices: ["Mocha chai"], 44 | when: () => mochaExist 45 | }, 46 | { 47 | type: 'list', 48 | name: 'eslintQ', 49 | message: 'Do you want to install ESlint?', 50 | choices: ["Yes", "No"], 51 | when: () => eslintExist 52 | }, 53 | { 54 | type: 'list', 55 | name: 'mochaweQ', 56 | message: 'Do you want to install Mochawesome?', 57 | choices: ["Yes", "No"], 58 | when: (answers: any) => mochawesomeValidation(answers, packagesList) 59 | }, 60 | ...commonQuestions, 61 | ] 62 | 63 | const questions = (argument === "generate") ? commonQuestions : fullQuestions; 64 | 65 | return questions 66 | } 67 | /** 68 | * @description questions list for json selected options 69 | * @param {any} answers options 70 | * @returns {Promise} 71 | */ 72 | export const CLIJSONQuestion = async (answers: any): Promise => { 73 | interface Item { 74 | name: string; 75 | item?: any; 76 | } 77 | 78 | try { 79 | const data = await readFile(answers.jsonFileQ.includes('"') ? answers.jsonFileQ.replace(/"/g, '') : answers.jsonFileQ, 'utf8'); 80 | const { item: items } = JSON.parse(data) 81 | 82 | // Assuming that we want to push items with the 'item' property to the end of the array 83 | const sortedArr: Item[] = items.sort((a: Item, b: Item) => { 84 | return (a.item === undefined ? -1 : 0) + (b.item === undefined ? 0 : 1); 85 | }); 86 | 87 | const option = sortedArr.map((item: Item) => ({ 88 | name: `${item.name} - ${item.hasOwnProperty('item') ? '(suite)' : '(test)'}` 89 | })); 90 | 91 | return inquirer.prompt([ 92 | { 93 | type: 'checkbox', 94 | name: 'customKey', 95 | message: 'Select one or more case or suite:', 96 | pageSize: 10, 97 | choices: option, 98 | validate: function (value: any) { 99 | if (value.length === 0) { 100 | return 'Please select at least one case or suite'; 101 | } 102 | 103 | return true; 104 | }, 105 | }, 106 | ]); 107 | } catch (error: any) { 108 | console.error(`Error processing file: ${error.message}`); 109 | } 110 | }; 111 | /** 112 | * @description questions list for environment generation 113 | * @returns {Promise} 114 | */ 115 | export const CLIEnvironmentQuestion = async (): Promise => { 116 | return [ 117 | { 118 | type: 'input', 119 | name: 'jsonFileQ', 120 | message: 'Input your json file to be generate (example.json):', 121 | default: 'example-env.json', 122 | validate: (answers: any) => jsonFileValidation(answers) 123 | }, 124 | { 125 | type: 'input', 126 | name: 'envQ', 127 | message: 'Input your environment name:', 128 | default: 'dev', 129 | validate: (answers: any) => envNameValidation(answers) 130 | } 131 | ] 132 | } -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description convert string to camelcase 3 | * @param {string} text some text change to camelcase 4 | * @returns {string} camelcase text tranformed 5 | */ 6 | export function toCamelCase(text: string): string { 7 | const words: string[] = text.split(' '); 8 | const firstWord: string = words[0].toLowerCase(); 9 | const restOfWords: string[] = words.slice(1).map(word => { 10 | return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); 11 | }); 12 | 13 | return firstWord + restOfWords.join(''); 14 | } 15 | /** 16 | * @description convert string to lowercase 17 | * @param {string} text some text change to lowercase 18 | * @returns {string} lowercase text tranformed 19 | */ 20 | export function toLowerCase(text: string): string { 21 | let str: string = (text).toLowerCase().replace(/\s/g, ''); 22 | str = str.replace(/\//g, ''); 23 | return str; 24 | } -------------------------------------------------------------------------------- /src/utils/validation.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | /** 4 | * @description check import type on exist file 5 | * @param {string} filePath file path 6 | * @param {string[]} searchStrings string will be searched 7 | * @returns {boolean} check result 8 | */ 9 | const checkImportType = (filePath: string, searchStrings: string[]): boolean | number => { 10 | try { 11 | const fileContent = fs.readFileSync(filePath, 'utf8'); 12 | return searchStrings.some((searchString) => fileContent.includes(searchString)); 13 | } catch { 14 | return 0; 15 | } 16 | } 17 | /** 18 | * @description validate import type on exist file 19 | * @returns {boolean} validation result 20 | */ 21 | export const projectModulesValidation = (): boolean => { 22 | const dotenvImportChecks = ['import dotenv from "dotenv"', 'const dotenv = require("dotenv")']; 23 | const fsImportChecks = ['import fs from "fs"', 'const fs = require("fs")']; 24 | 25 | let result 26 | result = checkImportType('./tests/utils/config.js', dotenvImportChecks) === 0 ? true : false; 27 | result = checkImportType('./tests/helpers/request.helper.js', fsImportChecks) === 0 ? true : false; 28 | 29 | return result; 30 | } 31 | /** 32 | * @description check mocha package is existed 33 | * @param {any} answers question answer 34 | * @param {any} packagesExist existing package list 35 | * @returns {boolean} check result 36 | */ 37 | export const mochawesomeValidation = (answers: any, packagesExist: any): boolean => { 38 | if (answers.hasOwnProperty('frameworkQ')) { 39 | return answers.frameworkQ == "Mocha chai" && !packagesExist.includes('mochawesome') ? true : false 40 | } else { 41 | return !packagesExist.includes('mochawesome') ? true : false 42 | } 43 | } 44 | /** 45 | * @description json file question validation (.json) 46 | * @param {any} input question answer 47 | * @returns {boolean | string} check result 48 | */ 49 | export const jsonFileValidation = (input: any): boolean | string => { 50 | return input.includes('json') ? true : 'Please type correct answer, the file must be json format!' 51 | } 52 | /** 53 | * @description env name question validation, should lowercase 54 | * @param {any} input question answer 55 | * @returns {boolean | string} check result 56 | */ 57 | export const envNameValidation = (input: any): boolean | string => { 58 | return /[A-Z]/.test(input) ? 'Input must be lowercase!' : true 59 | } -------------------------------------------------------------------------------- /src/utils/wait.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description explicit time for wait 3 | * @param {number} ms number of time in millisecond 4 | * @returns {Promise} 5 | */ 6 | export const waitFor = (ms: number): Promise => new Promise(r => setTimeout(r, ms)); -------------------------------------------------------------------------------- /test/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": 18 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": [ 13 | [ 14 | "module-resolver", 15 | { 16 | "root": ["."], 17 | "alias": { 18 | "@root": ".", 19 | "@tests": "./tests", 20 | "@scenario": "./tests/scenarios", 21 | "@page": "./tests/pages", 22 | "@schema": "./tests/schemas", 23 | "@helper": "./tests/helpers", 24 | "@data": "./tests/data", 25 | "@util": "./tests/utils" 26 | } 27 | } 28 | ] 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/.example.env.dev: -------------------------------------------------------------------------------- 1 | MAIN=your_api_url -------------------------------------------------------------------------------- /test/.mocharc.js: -------------------------------------------------------------------------------- 1 | const runTestsList = { 2 | Auth: [ 3 | "tests/scenarios/Auth/POST_login.spec.js", 4 | "tests/scenarios/Auth/POST_logout.spec.js" 5 | ], 6 | User: [ 7 | "tests/scenarios/User/GET_getuserprofile.spec.js", 8 | "tests/scenarios/User/POST_adduser.spec.js", 9 | "tests/scenarios/User/PATCH_updateuser.spec.js", 10 | "tests/scenarios/User/DELETE_deleteuser.spec.js" 11 | ], 12 | Contact: [ 13 | "tests/scenarios/Contact/POST_addcontact.spec.js", 14 | "tests/scenarios/Contact/GET_getcontactlist.spec.js", 15 | "tests/scenarios/Contact/GET_getcontact.spec.js", 16 | "tests/scenarios/Contact/PUT_updatecontact.spec.js", 17 | "tests/scenarios/Contact/DELETE_deletecontact.spec.js" 18 | ], 19 | Regression: "tests/scenarios/**/*.spec.js" 20 | }; 21 | 22 | const ignoreTestsList = [ 23 | // write your ignore tests here 24 | ]; 25 | 26 | function getSpecsList() { 27 | const runOptArgument = process.argv.indexOf("--specs"); 28 | const runOpt = 29 | runOptArgument !== -1 ? process.argv[runOptArgument + 1] : "Regression"; 30 | 31 | if (runOpt.includes("/") || runOpt in runTestsList) { 32 | return runTestsList[runOpt]; 33 | } 34 | 35 | if (runOpt.includes(",")) { 36 | return runOpt.split(",").flatMap((key) => runTestsList[key]); 37 | } 38 | } 39 | 40 | module.exports = { 41 | require: ["@babel/register"], 42 | jobs: 1, 43 | package: "./package.json", 44 | reporter: "spec", 45 | ignore: ignoreTestsList, 46 | spec: getSpecsList(), 47 | "trace-warnings": true, 48 | ui: "bdd" 49 | }; 50 | -------------------------------------------------------------------------------- /test/.prettierignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/.svn 3 | **/.hg 4 | **.md 5 | **/node_modules 6 | **/logs 7 | .babelrc 8 | .eslintrc.json 9 | .prettierignore 10 | package-lock.json 11 | package.json 12 | prettier.config.js 13 | -------------------------------------------------------------------------------- /test/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES6", 4 | "target": "ES6", 5 | "baseUrl": "./tests", 6 | "paths": { 7 | "*": ["tests/*"], 8 | "@scenario/*": ["scenarios/*"], 9 | "@page/*": ["pages/*"], 10 | "@schema/*": ["schemas/*"], 11 | "@helper/*": ["helpers/*"], 12 | "@data/*": ["data/*"], 13 | "@util/*": ["utils/*"] 14 | }, 15 | "allowSyntheticDefaultImports": true, 16 | "declaration": true 17 | }, 18 | "include": ["tests/**/*.js"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "regression:dev": "cross-env NODE_ENV=dev mocha --specs Regression --timeout 15000", 9 | "auth:dev": "cross-env NODE_ENV=dev mocha --specs Auth --timeout 15000" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@babel/preset-env": "^7.23.8", 16 | "@babel/register": "^7.23.7", 17 | "babel-plugin-module-resolver": "^5.0.0", 18 | "chai": "^4.4.1", 19 | "chai-http": "^4.4.0", 20 | "chai-json-schema": "^1.5.1", 21 | "cross-env": "^7.0.3", 22 | "dotenv": "^16.4.0", 23 | "mocha": "^10.2.0", 24 | "to-json-schema": "^0.2.5", 25 | "winston": "^3.11.0" 26 | }, 27 | "devDependencies": { 28 | "prettier": "3.2.4" 29 | } 30 | } -------------------------------------------------------------------------------- /test/tests/data/Auth/auth.data.js: -------------------------------------------------------------------------------- 1 | export const login_data = [ 2 | { 3 | case: { 4 | name: "Successful login", 5 | schema: "success", 6 | status: 200, 7 | default: true 8 | }, 9 | driven: { email: "fashion@mail.com", password: "" }, 10 | attachment: {} 11 | } 12 | ]; 13 | -------------------------------------------------------------------------------- /test/tests/helpers/request.helper.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const toJsonSchema = require("to-json-schema"); 3 | 4 | class requestHelper { 5 | constructor() { 6 | // Write your constructor here, if you need 7 | this.waitFor = (ms) => new Promise((r) => setTimeout(r, ms)); 8 | } 9 | 10 | // This method used for get parameter which send from test file 11 | async getPayload(arg) { 12 | let data = {}; 13 | arg.forEach((datas) => { 14 | if (typeof datas == "object") { 15 | data = Object.assign({}, datas); 16 | } 17 | }); 18 | return data; 19 | } 20 | 21 | // If your test using attachment, this method used for get parameter of attachment file like path and keys 22 | async getAttachment(arg) { 23 | let data = {}; 24 | arg.forEach((datas) => { 25 | if (typeof datas == "object") { 26 | if (datas.hasOwnProperty("attachment")) { 27 | data = Object.assign({}, datas.attachment); 28 | } 29 | } 30 | }); 31 | return data; 32 | } 33 | 34 | getDefaultData(data_array) { 35 | return data_array.find((item) => item.case.default); 36 | } 37 | 38 | // If your test using attachment, this method used for get binary file from your computer 39 | async getFile(data) { 40 | const path = data.split("/"); 41 | const name = path[path.length - 1]; 42 | const file = await fs.readFile(data); 43 | 44 | return { 45 | file, 46 | name 47 | }; 48 | } 49 | 50 | // This method used for convert json file to json schema 51 | getSchema(json_responses, cases) { 52 | const options = { 53 | objects: { 54 | postProcessFnc: (schema, obj, defaultFnc) => ({ 55 | ...defaultFnc(schema, obj), 56 | required: Object.getOwnPropertyNames(obj) 57 | }) 58 | } 59 | }; 60 | 61 | if (json_responses.hasOwnProperty(cases)) { 62 | return toJsonSchema(json_responses[cases], options); 63 | } else { 64 | throw new Error("JSON Schema: " + cases + ", does not exist!"); 65 | } 66 | } 67 | 68 | // This method user for mapping keys object which you want to be change/replace the object 69 | async mapObject(obj, arg) { 70 | let newObj = {}; 71 | let map = arg[0]; 72 | 73 | if (typeof obj === "object") { 74 | newObj = { ...obj }; 75 | } 76 | 77 | Object.keys(map).forEach((key) => { 78 | if (newObj[key] !== undefined) { 79 | newObj[key] = map[key]; 80 | } 81 | 82 | Object.entries(newObj).forEach(([nestedKey, nestedVal]) => { 83 | if (Array.isArray(nestedVal)) { 84 | nestedVal.forEach((innerObj) => { 85 | if (innerObj[key] !== undefined) { 86 | innerObj[key] = map[key]; 87 | } 88 | 89 | Object.values(innerObj).forEach((dObjArr) => { 90 | if (Array.isArray(dObjArr)) { 91 | dObjArr.forEach((dObj) => { 92 | if (dObj[key] !== undefined) { 93 | dObj[key] = map[key]; 94 | } 95 | }); 96 | } 97 | }); 98 | }); 99 | } else if (typeof nestedVal === "object") { 100 | Object.values(nestedVal).forEach((dObjArr) => { 101 | if (Array.isArray(dObjArr)) { 102 | dObjArr.forEach((dObj) => { 103 | if (dObj[key] !== undefined) { 104 | dObj[key] = map[key]; 105 | } 106 | }); 107 | } 108 | }); 109 | } 110 | }); 111 | }); 112 | 113 | return newObj; 114 | } 115 | } 116 | 117 | module.exports = requestHelper; 118 | -------------------------------------------------------------------------------- /test/tests/pages/Auth/POST_login.pages.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | chai.use(require("chai-http")); 3 | const requestHelper = require("@helper/request.helper.js"); 4 | const { Config } = require("@util/config.js"); 5 | const data = require("@data/Auth/auth.data.js"); 6 | const { schema } = require("@schema/Auth/POST_login.schema.js"); 7 | const { Logger } = require("@util/logger.js"); 8 | const logger = new Logger(); 9 | 10 | class Request { 11 | constructor() { 12 | // Write your constructor here, if you nee 13 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 14 | this.host = new Config().env().host; 15 | this.url = "/users/login"; // Set up the API path to the route endpoint 16 | } 17 | 18 | get request() { 19 | return chai.request(this.host); 20 | } 21 | 22 | // This method handles making the HTTP request based on given arguments. 23 | async api(...args) { 24 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 25 | try { 26 | const response = await this.request 27 | .post(this.url) 28 | .send( 29 | await this.getMappedBody(await new requestHelper().getPayload(args)) 30 | ); 31 | 32 | await this.createLogging(response); 33 | return response; 34 | } catch (err) { 35 | await this.createLogging(0, err); 36 | } 37 | } 38 | 39 | // This method used for provide body or payload of the request and return object 40 | async getMappedBody(...args) { 41 | const defaultData = new requestHelper().getDefaultData(data.login_data); 42 | const dataMapped = await new requestHelper().mapObject( 43 | defaultData.driven, 44 | args 45 | ); 46 | 47 | return dataMapped; 48 | } 49 | 50 | async createLogging(response, error) { 51 | const isError = error !== undefined; 52 | const logLevel = isError ? "error" : "debug"; 53 | const statusMessage = isError ? "Failed" : "Successful"; 54 | const endpoint = `${this.host}${this.url}`; 55 | const requestPayload = isError ? error.response.request : response.request; 56 | const responsePayload = isError ? error.response.body : response.body; 57 | 58 | logger 59 | .console({ level: logLevel }) 60 | .http(`${statusMessage} to hit login endpoint ${endpoint}`); 61 | 62 | logger.file({ type: "request", level: logLevel }).http({ 63 | endpoint: endpoint, 64 | data: requestPayload 65 | }); 66 | 67 | logger.file({ type: "response", level: logLevel }).http({ 68 | endpoint: endpoint, 69 | data: responsePayload 70 | }); 71 | } 72 | 73 | // This method used for provide expectation and return json schema 74 | expectedSchema(cases = "success") { 75 | return new requestHelper().getSchema(schema, cases); 76 | } 77 | } 78 | 79 | module.exports = Request; 80 | -------------------------------------------------------------------------------- /test/tests/pages/Auth/POST_logout.pages.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | chai.use(require("chai-http")); 3 | const requestHelper = require("@helper/request.helper.js"); 4 | const { Config } = require("@util/config.js"); 5 | const { schema } = require("@schema/Auth/POST_logout.schema.js"); 6 | const { Logger } = require("@util/logger.js"); 7 | const logger = new Logger(); 8 | 9 | class Request { 10 | constructor() { 11 | // Write your constructor here, if you nee 12 | // Set up the api with the endpoint based on the environment and change this according to endpoint service 13 | this.host = new Config().env().host; 14 | this.url = "/users/logout"; // Set up the API path to the route endpoint 15 | } 16 | 17 | get request() { 18 | return chai.request(this.host); 19 | } 20 | 21 | // This method handles making the HTTP request based on given arguments. 22 | async api(...args) { 23 | // Send HTTP POST request to the specified path and send the required body with params extracted from args. 24 | try { 25 | const response = await this.request 26 | .post(this.url) 27 | .set( 28 | "Authorization", 29 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGMwZGNhYWM4OGU3NzAwMTM0MjBkN2MiLCJpYXQiOjE2OTAzNjEwMjl9.9-2QzLIfOUoF_oTYHLZwoLMFxyk_2PvarfavApza4Us" 30 | ); 31 | 32 | await this.createLogging(response); 33 | return response; 34 | } catch (err) { 35 | await this.createLogging(0, err); 36 | } 37 | } 38 | 39 | async createLogging(response, error) { 40 | const isError = error !== undefined; 41 | const logLevel = isError ? "error" : "debug"; 42 | const statusMessage = isError ? "Failed" : "Successful"; 43 | const endpoint = `${this.host}${this.url}`; 44 | const requestPayload = isError ? error.response.request : response.request; 45 | const responsePayload = isError ? error.response.body : response.body; 46 | 47 | logger 48 | .console({ level: logLevel }) 49 | .http(`${statusMessage} to hit login endpoint ${endpoint}`); 50 | 51 | logger.file({ type: "request", level: logLevel }).http({ 52 | endpoint: endpoint, 53 | data: requestPayload 54 | }); 55 | 56 | logger.file({ type: "response", level: logLevel }).http({ 57 | endpoint: endpoint, 58 | data: responsePayload 59 | }); 60 | } 61 | 62 | // This method used for provide expectation and return json schema 63 | expectedSchema(cases = "success") { 64 | return new requestHelper().getSchema(schema, cases); 65 | } 66 | } 67 | 68 | module.exports = Request; 69 | -------------------------------------------------------------------------------- /test/tests/scenarios/Auth/POST_login.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; 2 | const chai = require("chai"); 3 | chai.use(require("chai-json-schema")); 4 | const data = require("@data/Auth/auth.data.js"); 5 | const Request = require("@page/Auth/POST_login.pages.js"); 6 | const config = require("@util/config.js"); 7 | 8 | describe("Test Login", () => { 9 | data.login_data.forEach(async (data) => { 10 | it(data.case.name, async () => { 11 | const response = await new Request().api(data.driven); 12 | 13 | expect(response.status).to.equals(data.case.status); 14 | expect(response.body).to.be.jsonSchema( 15 | new Request().expect(data.case.schema) 16 | ); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/tests/scenarios/Auth/POST_logout.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; 2 | const chai = require("chai"); 3 | chai.use(require("chai-json-schema")); 4 | const Request = require("@page/Auth/POST_logout.pages.js"); 5 | const config = require("@util/config.js"); 6 | 7 | describe("Test Logout", () => { 8 | it("Successful case", async () => { 9 | const response = await new Request().api(); 10 | 11 | expect(response.status).to.equals(200); 12 | expect(response.body).to.be.jsonSchema(new Request().expect("success")); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/tests/schemas/Auth/POST_login.schema.js: -------------------------------------------------------------------------------- 1 | export const schema = { 2 | success: { 3 | example: "example" 4 | }, 5 | failed: { 6 | example: "example" 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /test/tests/schemas/Auth/POST_logout.schema.js: -------------------------------------------------------------------------------- 1 | export const schema = { 2 | success: { 3 | example: "example" 4 | }, 5 | failed: { 6 | example: "example" 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /test/tests/utils/config.js: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv"); 2 | 3 | export class Config { 4 | constructor() { 5 | // Write your constructor here, if you need 6 | } 7 | 8 | env() { 9 | // change according to your need 10 | dotenv.config({ path: __dirname + `/../../.env.${process.env.NODE_ENV}` }); 11 | // Defining an object named 'env', contained your variables needed 12 | return { 13 | host: process.env.MAIN 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/tests/utils/custom_file_transport.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const winston = require("winston"); 3 | 4 | const logDir = "./logs"; 5 | if (!fs.existsSync(logDir)) { 6 | fs.mkdirSync(logDir); 7 | } 8 | 9 | export class CustomFileTransport extends winston.transports.File { 10 | constructor(options) { 11 | // Pass the options to the parent class constructor 12 | super({ ...options, dirname: logDir }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/tests/utils/logger.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const { createLogger, format, transports } = require("winston"); 3 | const { combine, timestamp, printf } = format; 4 | const { CustomFileTransport } = require("./custom_file_transport"); 5 | const colorizer = format.colorize(); 6 | 7 | const logFormat = combine( 8 | timestamp({ 9 | format: "YYYY-MM-DD HH:mm:ss" 10 | }), 11 | printf((output) => 12 | colorizer.colorize( 13 | output.level, 14 | `[${output.timestamp}] ${output.level.toUpperCase()}: ${output.message}` 15 | ) 16 | ) 17 | ); 18 | 19 | export class Logger { 20 | constructor() { 21 | // Write your constructor here, if you need 22 | } 23 | 24 | console = ({ service = "example", level = "debug" }) => 25 | createLogger({ 26 | level, 27 | defaultMeta: { service }, 28 | format: logFormat, 29 | transports: [ 30 | // Output logs to console in simple message format 31 | new transports.Console() 32 | ] 33 | }); 34 | 35 | file = ({ service = "example", type = "response", level = "debug" }) => 36 | createLogger({ 37 | level, 38 | defaultMeta: { service }, 39 | format: logFormat, 40 | transports: [ 41 | // Output logs to console in simple message format 42 | new CustomFileTransport({ 43 | format: winston.format.json(), 44 | filename: `./logs/${type}.json` 45 | }) 46 | ] 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | ] 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "ES6", 5 | "declaration": true, 6 | "outDir": "lib", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "types": [ 14 | "node", 15 | ], 16 | "baseUrl": "./src", 17 | "moduleResolution": "node" 18 | }, 19 | "tsc-alias": { 20 | "resolveFullPaths": true, 21 | }, 22 | "include": [ 23 | "src/**/*.ts" 24 | ], 25 | 26 | } --------------------------------------------------------------------------------