UNPKG

@puppedo/core

Version:

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

267 lines (226 loc) 8.07 kB
import path from 'path'; import fs from 'fs'; import { Arguments } from './Arguments'; import Screenshot from './Screenshot'; import { ColorsType, Element, LogEntrieType, LogEntry, LogInputType, LogOptionsType, LogPipe, Outputs, TreeEntryDataType, } from './global.d'; import { Environment } from './Environment'; import Singleton from './Singleton'; import { getNowDateTime } from './Helpers'; const LEVELS: ColorsType[] = ['raw', 'timer', 'debug', 'info', 'test', 'warn', 'error', 'env']; export class LogExports { envsId!: string; constructor(envsId: string) { this.envsId = envsId; } saveToFile(fileName: string, text: string): void { const { folderLatest, folder } = new Environment().getOutput(this.envsId); fs.writeFileSync(path.join(folder, fileName), text); fs.writeFileSync(path.join(folderLatest, fileName), text); } appendToFile(fileName: string, text: string): void { const { folderLatest, folder } = new Environment().getOutput(this.envsId); fs.appendFileSync(path.join(folder, fileName), text); fs.appendFileSync(path.join(folderLatest, fileName), text); } static resolveOutputHtmlFile(): string { const outputSourceRaw = path.resolve(path.join('dist', 'output.html')); const outputSourceModule = path.resolve( path.join(__dirname, '..', 'node_modules', '@puppedo', 'core', 'dist', 'output.html'), ); const outputSource = fs.existsSync(outputSourceRaw) ? outputSourceRaw : outputSourceModule; return outputSource; } static initOutput(envsId: string): Outputs { const { PPD_OUTPUT: output } = new Arguments().args; const now = getNowDateTime(); if (!fs.existsSync(output)) { fs.mkdirSync(output); } const folder = path.join(output, `${now}_${envsId}`); const folderLatest = path.join(output, 'latest'); if (!fs.existsSync(folder)) { fs.mkdirSync(folder); } // Create latest log path if (!fs.existsSync(folderLatest)) { fs.mkdirSync(folderLatest); } else { const filesExists = fs.readdirSync(folderLatest); for (const fileExists of filesExists) { fs.unlinkSync(path.join(folderLatest, fileExists)); } } fs.copyFileSync(LogExports.resolveOutputHtmlFile(), path.join(folderLatest, 'output.html')); fs.copyFileSync(LogExports.resolveOutputHtmlFile(), path.join(folder, 'output.html')); return { output, name: envsId, folder, folderFull: path.resolve(folder), folderLatest, folderLatestFull: path.resolve(folderLatest), }; } } type LogOptionsOptionsType = { stdOut?: boolean; loggerPipes?: LogPipe[] }; export class LogOptions extends Singleton { options!: LogOptionsOptionsType; constructor(options: LogOptionsOptionsType = {}, reInit = false) { super(); if (reInit || !this.options) { this.options = options; if (!this.options.loggerPipes) { this.options.loggerPipes = []; } } } bindOptions(data: { stdOut?: boolean; loggerPipes?: LogPipe[] } = {}): void { this.options = { ...this.options, ...data }; } addLogPipe(pipe: LogPipe): void { this.options.loggerPipes.push(pipe); } } export class Log { private envsId: string; exporter!: LogExports; output!: Outputs; constructor(envsId: string) { this.envsId = envsId; this.output = LogExports.initOutput(envsId); this.exporter = new LogExports(envsId); } static checkLevel(level: ColorsType): boolean { const { PPD_LOG_LEVEL_TYPE_IGNORE } = new Arguments().args; return !(PPD_LOG_LEVEL_TYPE_IGNORE.includes(level) || !LEVELS.includes(level)); } static isManualSkipEntry(level: ColorsType, logThis: boolean, logShowFlag: boolean, levelIndent: number): boolean { const { PPD_LOG_DISABLED, PPD_LOG_LEVEL_NESTED } = new Arguments().args; const manualSkipEntry = !Log.checkLevel(level) || !logThis || (level !== 'error' && !logShowFlag) || (level !== 'error' && PPD_LOG_LEVEL_NESTED && levelIndent > PPD_LOG_LEVEL_NESTED) || // SKIP LOG BY LEVELS (level !== 'error' && PPD_LOG_DISABLED); // NO LOG FILES ONLY STDOUT return manualSkipEntry; } async getScreenshots( logOptions: LogOptionsType, level: ColorsType, levelIndent: number, extendInfo: boolean, element: Element, ): Promise<string[]> { const { PPD_LOG_SCREENSHOT, PPD_LOG_FULLPAGE } = new Arguments().args; const { screenshot = false, fullpage = false, fullpageName, screenshotName } = logOptions; if (!Log.checkLevel(level)) { return []; } // TODO: 2020-02-05 S.Starodubov get values from env.yaml let isScreenshot = PPD_LOG_SCREENSHOT ? screenshot : false; let isFullpage = PPD_LOG_FULLPAGE ? fullpage : false; // SCREENSHOT ON ERROR ONLY ONES if (level === 'error' && levelIndent === 0) { [isScreenshot, isFullpage] = [true, true]; } const screenshots = await new Screenshot(this.envsId).getScreenshotsLogEntry( isFullpage && !extendInfo, isScreenshot && !extendInfo, element, fullpageName, screenshotName, ); return screenshots; } async bulkLog(data: LogInputType[]): Promise<void> { for (const entry of data) { await this.log(entry); } } async runPipes(logEntries: LogEntry[], manualSkipEntry = false): Promise<void> { const { loggerPipes, stdOut = true } = new LogOptions().options; for (const logEntry of logEntries) { for (const pipe of loggerPipes) { try { const transformedEntry = await pipe.transformer(logEntry); const formatedEntry = await pipe.formatter(logEntry, transformedEntry); await pipe.exporter(logEntry, formatedEntry as LogEntrieType[][], formatedEntry as string, { envsId: this.envsId, skipThis: !stdOut || manualSkipEntry, }); } catch (e) { console.log(`Error in logger pipe: ${e.message}`); } } } } private updateTree(logEntries: LogEntry[]): void { const { testTree } = new Environment().getEnvInstance(this.envsId); for (const logEntry of logEntries) { const payload: Partial<TreeEntryDataType> = {}; if (logEntry.level === 'timer') { payload.timeStart = logEntry.logMeta?.timeStart; payload.timeEnd = logEntry.logMeta?.timeEnd; } testTree.updateStep({ stepId: logEntry.stepId, payload }); } } async log({ text = '', level = 'raw', levelIndent = 0, element, error = null, stepId = '', logMeta = {}, logOptions = {}, }: LogInputType): Promise<void> { const texts = [text].flat(); const { textColor = 'sane', backgroundColor = 'sane', logThis = true, logShowFlag = true } = logOptions; const { funcFile = '', testFile = '', extendInfo = false } = logMeta; const manualSkipEntry = Log.isManualSkipEntry(level, logThis, logShowFlag, levelIndent); const screenshots = await this.getScreenshots(logOptions, level, levelIndent, extendInfo, element); try { const logEntries = texts.map((textString) => { const logEntry: LogEntry = { text: textString, level: level ?? ('raw' as ColorsType), levelIndent, time: new Date(), screenshots, funcFile, testFile, extendInfo, error, textColor, backgroundColor: level === 'error' ? 'sane' : backgroundColor, stepId, breadcrumbs: logMeta.breadcrumbs ?? [], repeat: logMeta.repeat ?? 1, logMeta, }; return logEntry; }); this.updateTree(logEntries); await this.runPipes(logEntries, manualSkipEntry); } catch (err) { const { PPD_DEBUG_MODE } = new Arguments().args; const socket = new Environment().getSocket(this.envsId); err.message += ' || error in log'; err.socket = socket; err.debug = PPD_DEBUG_MODE; err.stepId = stepId ?? ''; throw err; } } }