UNPKG

@puppedo/core

Version:

PuppeDo is a runner for tests E2E in YAML style. With power of Playwright or Puppeteer.

252 lines (222 loc) 8.52 kB
import path from 'path'; import fs from 'fs'; import yaml from 'js-yaml'; import Singleton from './Singleton'; import { Arguments } from './Arguments'; import { merge, walkSync } from './Helpers'; import { TestType, RunnerType, DataType, TestTypeYaml, TestExtendType, AllDataType } from './global.d'; export const BLANK_TEST: TestType = { afterTest: [], allowOptions: [], allowResults: [], beforeTest: [], bindData: {}, bindDescription: '', bindResults: {}, bindSelectors: {}, data: {}, dataExt: [], debug: false, debugInfo: false, description: '', descriptionExtend: [], disable: false, engineSupports: [], errorIf: '', errorIfResult: '', frame: '', if: '', inlineJS: '', logOptions: {}, name: '', needData: [], needSelectors: [], needEnvParams: [], options: {}, repeat: 1, runTest: [], selectors: {}, selectorsExt: [], tags: [], todo: '', type: 'test', while: '', breakParentIfResult: '', }; const resolveTest = (test: TestTypeYaml): TestType => ({ ...BLANK_TEST, ...test }); export default class TestsContent extends Singleton { allData!: AllDataType; constructor(reInit = false) { super(); if (reInit || !this.allData) { this.allData = this.getAllData(); } } static getPaths(): string[] { const { PPD_ROOT, PPD_ROOT_ADDITIONAL, PPD_ROOT_IGNORE, PPD_FILES_IGNORE } = new Arguments().args; const rootFolder = path.normalize(PPD_ROOT); const additionalFolders = PPD_ROOT_ADDITIONAL.map((v: string) => path.normalize(v)); const ignoreFolders = PPD_ROOT_IGNORE.map((v: string) => path.normalize(v)); const ignoreFiles = PPD_FILES_IGNORE.map((v: string) => path.join(rootFolder, path.normalize(v))); // TODO: 2022-11-07 S.Starodubov move to Arguments const PPD_FILES_EXTENSIONS_AVAILABLE = ['.yaml', '.yml', '.ppd', '.json']; const folders = [rootFolder, ...additionalFolders].map((v) => path.normalize(v)); const paths = folders .map((folder) => { if (fs.existsSync(folder)) { return walkSync(folder, { ignoreFolders, ignoreFiles, extensions: PPD_FILES_EXTENSIONS_AVAILABLE }); } return []; }) .flat(); return paths; } static checkDuplicates<T extends TestExtendType | RunnerType | DataType>(tests: Array<T>): Array<T> { const blankNames = tests.filter((v) => !v.name); if (blankNames.length) { throw new Error(`There is blank 'name' value in files:\n${blankNames.map((v) => v.testFile).join('\n')}`); } const dubs = tests.reduce((s: Record<string, string[]>, v: T) => { const collector = { ...s }; if (!v.testFile) { return collector; } collector[v.name] = !s[v.name] ? [v.testFile] : [...s[v.name], v.testFile]; return collector; }, {}); const isThrow = Object.values(dubs).some((v) => v.length > 1); if (Object.keys(dubs).length && isThrow) { const key = tests[0].type; let message = `There is duplicates of '${key}':\n`; Object.entries(dubs).forEach((dub) => { const [keyDub, valueDub] = dub; if (valueDub.length > 1) { message += ` - Name: '${keyDub}'.\n`; message += valueDub.map((v) => ` * '${v}'\n`).join(''); } }); throw new Error(message); } return tests; } getAllData(force = false): AllDataType { const { PPD_IGNORE_TESTS_WITHOUT_NAME } = new Arguments().args; if (force || !this.allData) { const allContent: Array<TestType | RunnerType | DataType> = []; const paths = TestsContent.getPaths(); paths.forEach((filePath) => { let testData: Partial<TestTypeYaml>[] = []; if (filePath.endsWith('.json')) { try { testData = __non_webpack_require__(filePath); if (!Array.isArray(testData)) { testData = [testData]; } } catch (e) { console.log( `\u001B[41mError JSON read. File: '${filePath}'. Try to check it on https://jsonlint.com/ or add this file into PPD_FILES_IGNORE of folder into PPD_ROOT_IGNORE`, ); } } else { try { testData = yaml.loadAll(fs.readFileSync(filePath, 'utf8')) as Partial<TestTypeYaml>[]; } catch (e) { console.log( `\u001B[41mError YAML read. File: '${filePath}'. Try to check it on https://yamlchecker.com/ or add this file into PPD_FILES_IGNORE of folder into PPD_ROOT_IGNORE`, ); } } testData.forEach((v) => { const { name } = v; if (!name && !PPD_IGNORE_TESTS_WITHOUT_NAME) { throw new Error('Every test need name'); } if (!name) { return; } const collect = { ...{ type: 'test', name }, ...v, ...{ testFile: filePath }, }; if (['test'].includes(collect.type)) { allContent.push(resolveTest(collect as TestTypeYaml)); } else { allContent.push(collect as RunnerType | DataType); } }); }); const atoms: Array<TestType> = TestsContent.checkDuplicates( allContent.filter((v): v is TestType => v.type === 'atom'), ); const tests: Array<TestType> = TestsContent.checkDuplicates( allContent.filter((v): v is TestType => v.type === 'test'), ); const data: Array<DataType> = TestsContent.checkDuplicates( allContent.filter((v): v is DataType => v.type === 'data'), ); const selectors: Array<DataType> = TestsContent.checkDuplicates( allContent.filter((v): v is DataType => v.type === 'selectors'), ); const runners: Array<RunnerType> = TestsContent.checkDuplicates( allContent.filter((v): v is RunnerType => v.type === 'runner'), ); const runnersResolved = TestsContent.resolveRunners(runners, data, selectors); this.allData = { allFiles: paths, allContent, atoms, tests, runners: runnersResolved, data, selectors }; } return this.allData; } static resolveRunners( runnersAll: Array<RunnerType>, dataAll: Array<DataType>, selectorsAll: Array<DataType>, ): Array<RunnerType> { return runnersAll.map((runner: RunnerType) => { const runnerUpdated = runner; const { dataExt = [], selectorsExt = [], runnersExt = [], data: dataEnv = {}, selectors: selectorsEnv = {}, } = runner; runnersExt.forEach((runnersExtName: string) => { const runnersResolved: RunnerType | undefined = runnersAll.find((g: RunnerType) => g.name === runnersExtName); if (runnersResolved) { if (runnersResolved.browser) { runnerUpdated.browser = merge(runnerUpdated.browser, runnersResolved.browser); } runnerUpdated.log = { ...(runnerUpdated.log || {}), ...(runnersResolved.log || {}) }; runnerUpdated.data = { ...(runnerUpdated.data || {}), ...(runnersResolved.data || {}) }; runnerUpdated.selectors = { ...(runnerUpdated.selectors || {}), ...(runnersResolved.selectors || {}) }; runnerUpdated.description = `${runnerUpdated.description || ''} -> ${runnersResolved.description || ''}`; } else { throw new Error(`PuppeDo can't resolve extended runner '${runnersExtName}' in runner '${runner.name}'`); } }); dataExt.forEach((dataExtName: string) => { const dataResolved: DataType | undefined = dataAll.find((g: DataType) => g.name === dataExtName); if (dataResolved) { runnerUpdated.data = { ...(runnerUpdated.data || {}), ...(dataResolved.data || {}), ...dataEnv }; } else { throw new Error(`PuppeDo can't resolve extended data '${dataExtName}' in runner '${runner.name}'`); } }); selectorsExt.forEach((selectorsExtName: string) => { const selectorsResolved: DataType | undefined = selectorsAll.find((g: DataType) => g.name === selectorsExtName); if (selectorsResolved) { runnerUpdated.selectors = { ...(runnerUpdated.selectors || {}), ...(selectorsResolved.data || {}), ...selectorsEnv, }; } else { throw new Error(`PuppeDo can't resolve extended selectors '${selectorsExtName}' in runner '${runner.name}'`); } }); return runnerUpdated; }); } }