UNPKG

@puppedo/core

Version:

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

189 lines (157 loc) 6.52 kB
/* eslint-disable no-param-reassign */ import path from 'path'; import requireFromString from 'require-from-string'; import Blocker from './Blocker'; import { pick, RUNNER_BLOCK_NAMES } from './Helpers'; import { Test } from './Test'; import Atom from './AtomCore'; import { TestArgsType, TestExtendType, TestExtendTypeKeys, TestFunctionsBlockNames, TestLifecycleFunctionType, TestType, } from './global.d'; import { Environment } from './Environment'; const atoms: Record<string, TestLifecycleFunctionType> = {}; const resolveJS = (testJson: TestExtendType): TestExtendType => { const testJsonNew = testJson; const functions = pick(testJsonNew, RUNNER_BLOCK_NAMES); if (Object.keys(functions).length && !testJsonNew.inlineJS) { return testJson; } try { if (testJsonNew.inlineJS && typeof testJsonNew.inlineJS === 'string') { try { atoms[testJsonNew.inlineJS] = requireFromString( `module.exports = async function atomRun() {\n${testJsonNew.inlineJS}};`, ); } catch (error) { error.message = `Some errors in inlineJS: ${testJsonNew.inlineJS}`; throw error; } } else { const testFileExt = path.parse(testJsonNew.testFile).ext; const funcFile = path.resolve(testJsonNew.testFile.replace(testFileExt, '.js')); atoms[testJsonNew.name] = atoms[testJsonNew.name] || __non_webpack_require__(funcFile)[testJsonNew.name] || __non_webpack_require__(funcFile); testJsonNew.funcFile = path.resolve(funcFile); } const instance = new Atom(); instance.atomRun = atoms[testJsonNew.inlineJS] || atoms[testJsonNew.name]; if (typeof instance.atomRun === 'function') { testJsonNew.runTest = [instance.runTest.bind(instance)]; } } catch (error) { if (error.name === 'SyntaxError') { throw error; } // If there is no JS file it`s fine. testJsonNew.runTest = []; } return testJsonNew; }; const propagateArgumentsObjectsOnAir = ( source: TestExtendType, args: TestArgsType | undefined, list: TestExtendTypeKeys[] = [], ): TestExtendType => { const sourceValues = pick(source || {}, list); const argsValues = pick(args || {}, list); const renamedKeys = Object.fromEntries( Object.entries({ ...sourceValues, ...argsValues }).map((v) => [`${v[0]}Parent`, v[1]]), ); return { ...source, ...renamedKeys }; }; const propagateArgumentsSimpleOnAir = ( source: TestExtendType, args: TestArgsType | undefined, list: (TestExtendTypeKeys | string)[] = [], ): TestExtendType => ({ ...source, ...pick(args || {}, list) }); const getTest = ({ testJsonIncome, envsId, parentTestMetaCollector, // object for share data with sublings }: { testJsonIncome: TestExtendType; envsId: string; parentTestMetaCollector?: Partial<TestExtendType>; }): TestLifecycleFunctionType => { let testJson = testJsonIncome; RUNNER_BLOCK_NAMES.forEach((funcBlock) => { if (testJson[funcBlock] && !Array.isArray(testJson[funcBlock])) { throw new Error(`Block ${funcBlock} must be array. Path: '${(testJson.breadcrumbs || []).join(' -> ')}'`); } }); const functionsBeforeResolve: [TestFunctionsBlockNames, TestExtendType[]][] = RUNNER_BLOCK_NAMES.map((v) => [ v, testJson[v] as TestExtendType[], ]); const socket = new Environment().getSocket(envsId); testJson = resolveJS(testJson); testJson.envsId = envsId; testJson.socket = socket; const blocker = new Blocker(); if (testJson.stepId) { blocker.push({ stepId: testJson.stepId, block: false, breadcrumbs: testJson.breadcrumbs }); } // Test // blocker.push({ stepId: testJson.stepId, block: true, breadcrumbs: testJson.breadcrumbs }); functionsBeforeResolve.forEach((value) => { const [funcKey, funcVal] = value; if (funcVal && !testJson.inlineJS) { const newFunctions = [] as TestLifecycleFunctionType[]; funcVal.forEach((testItem: TestType) => { if (['test', 'atom'].includes(testItem.type)) { const newFunction = getTest({ testJsonIncome: testItem, envsId, parentTestMetaCollector: testJson }); newFunctions.push(newFunction); } }); testJson[funcKey] = newFunctions; } }); const test = new Test(testJson); const testResolver: TestLifecycleFunctionType = async (args?: TestArgsType): Promise<Record<string, unknown>> => { if (parentTestMetaCollector?.stepId !== args?.stepId) { // it`s a magic and I don`t know why is this works, but it fix steps Id hierarchy if (parentTestMetaCollector?.repeat !== args?.repeat) { parentTestMetaCollector.stepId = args.stepId; } parentTestMetaCollector.resultsFromPrevSubling = {}; parentTestMetaCollector.metaFromPrevSubling = {}; } let updatetTestJson: TestExtendType = propagateArgumentsObjectsOnAir( testJson, { ...args, ...(parentTestMetaCollector?.metaFromPrevSubling || {}) }, ['options', 'data', 'selectors', 'logOptions'], ); // TODO: 2022-10-06 S.Starodubov переделать получание этих вещей из значений плагина через хук, чтобы хук возвращал то что надо // TODO: 2023-03-20 S.Starodubov нормальную типизацию const fromPrevSublingSimple = test.plugins.getAllPropogatesAndSublings('fromPrevSublingSimple'); updatetTestJson = propagateArgumentsSimpleOnAir( updatetTestJson, { ...args, ...(parentTestMetaCollector?.metaFromPrevSubling || {}) }, ['debug', 'frame', ...Object.keys(fromPrevSublingSimple)], ); updatetTestJson.resultsFromPrevSubling = parentTestMetaCollector?.resultsFromPrevSubling || {}; updatetTestJson.metaFromPrevSubling = parentTestMetaCollector?.metaFromPrevSubling || {}; const { stepId, name } = testJson; const { stepId: stepIdParent } = args ?? {}; const { testTree } = new Environment().getEnvInstance(testJson.envsId); testTree.createStep({ stepIdParent: stepIdParent ?? null, stepId, payload: { ...fromPrevSublingSimple, name } }); const { result = {}, meta = {} } = await test.run(updatetTestJson); if (parentTestMetaCollector) { parentTestMetaCollector.resultsFromPrevSubling = { ...(parentTestMetaCollector?.resultsFromPrevSubling || {}), ...result, }; parentTestMetaCollector.metaFromPrevSubling = meta; } return result; }; return testResolver; }; export default getTest;