import * as path from 'path'; import * as fs from 'fs'; import { Image, ImageOptions } from './image-utils'; import { IComparerOptions } from './options'; export function ensureFolderExists(artifactsPath: string): void { if (!fs.existsSync(artifactsPath)) { fs.mkdirSync(artifactsPath, { recursive: true }); } } export function getNameWithSuffix(source: string, suffix: string): string { const ext = path.extname(source); return `${source.slice(0, -ext.length)}${suffix}${ext}`; } export class ScreenshotLocation { constructor( basePath: string, fileName: string, key: string, options: ImageOptions, ) { this.key = key; this.directory = basePath; this.name = fileName; this.filePath = path.join(basePath, fileName); this.maskPath = path.join(basePath, fileName.replace('.png', '_mask.png')); this.diffPath = path.join(basePath, fileName.replace('.png', '_diff.png')); this.options = options; } private readonly options: ImageOptions; key: string; directory: string; name: string; filePath: string; maskPath: string; diffPath: string; private throwNotFound() { throw new Error(`${this.key} file not found: ${this.filePath}`); } patchFileName(changeFileName: (fn: string) => string): void { const file = path.parse(this.filePath); this.filePath = path.join(file.dir, changeFileName(file.name) + file.ext); } read(key: 'file' | 'mask' | 'diff'): Image { return new Image(this.readBuffer(key), this.options); } tryRead(key: 'file' | 'mask' | 'diff'): Image { const buffer = this.tryReadBuffer(key); return buffer && new Image(buffer, this.options); } private readBuffer(key: 'file' | 'mask' | 'diff'): Buffer { const result = this.tryReadBuffer(key); if (!result) this.throwNotFound(); return result; } private tryReadBuffer(key: 'file' | 'mask' | 'diff'): Buffer { if (!this.exists(key, false)) return null; return fs.readFileSync(this[`${key}Path`]); } exists(key: 'file' | 'mask' | 'diff', throwError = false): boolean { const result = fs.existsSync(this[`${key}Path`]); if (!result && throwError) { this.throwNotFound(); } return result; } copyTo(destination: ScreenshotLocation): void { ['file', 'mask', 'diff'].forEach((element) => { const propertyKey = `${element}Path`; if (!fs.existsSync(this[propertyKey])) return; fs.copyFileSync(this[propertyKey], destination[propertyKey]); }); } } export class ScreenshotLocations { name: string; source: ScreenshotLocation; destination: ScreenshotLocation; temp: ScreenshotLocation; tempText: ScreenshotLocation; constructor( t: TestController, screenshotName: string, { path: testRoot, screenshotsRelativePath, destinationRelativePath, ignoreSizeDifference, }: Pick< IComparerOptions, | 'path' | 'screenshotsRelativePath' | 'destinationRelativePath' | 'ignoreSizeDifference' >, ) { const screenshotFileNameWithExtension = screenshotName.endsWith('.png') ? screenshotName : `${screenshotName}.png`; const options = { ignoreSizeDifference }; this.name = screenshotName; this.source = new ScreenshotLocation( path.join( path.dirname( (t as unknown as { testRun }).testRun.test.testFile.filename, ), 'etalons', ), screenshotFileNameWithExtension, 'etalon', options, ); this.destination = new ScreenshotLocation( path.join(testRoot, destinationRelativePath), screenshotFileNameWithExtension, 'artifact', options, ); this.temp = new ScreenshotLocation( path.join(testRoot, screenshotsRelativePath), screenshotFileNameWithExtension, 'screenshot', options, ); this.tempText = new ScreenshotLocation( this.temp.directory, getNameWithSuffix(this.temp.name, '_text'), 'text', options, ); } }