From 43d4fb6768f79ff9a860a1ad83a30200e5b7b855 Mon Sep 17 00:00:00 2001 From: Feoktist Shovchko Date: Wed, 11 Sep 2024 11:57:41 +0300 Subject: [PATCH] chore(e2e): code refactoring --- e2e/jest.config.js | 2 +- e2e/reporters/printers.js | 2 +- e2e/reporters/reporter.js | 23 ++--- ...t-matcher.ts => image-snapshot.matcher.ts} | 6 +- .../serializers/image-snapshot.pocessor.ts | 79 ++++++++++++++++++ .../sharp.ts => image-snapshot.sharp.ts} | 0 .../{image.ts => image-snapshot.ts} | 6 +- e2e/setup/serializers/image/diff-builder.ts | 42 ---------- .../serializers/image/image-processor.ts | 60 ------------- .../serializers/image/utils/directory.ts | 5 -- e2e/setup/serializers/image/utils/name.ts | 3 - ...-fine-test-homepage-footer-on-desktop.jpg} | Bin ...s-fine-test-homepage-footer-on-mobile.jpg} | Bin ...est-homepage-hamburger-menu-on-mobile.jpg} | Bin ...s-fine-test-homepage-screen-on-mobile.jpg} | Bin ...epage-looks-fine-test-homepage-screen.jpg} | Bin ...test-page-looks-fine-test-page-screen.jpg} | Bin e2e/utils/directory.d.ts | 1 + e2e/utils/directory.js | 9 ++ e2e/utils/image-snapshot.name.d.ts | 7 ++ e2e/utils/image-snapshot.name.js | 32 +++++++ 21 files changed, 145 insertions(+), 132 deletions(-) rename e2e/setup/serializers/{image/snapshot-matcher.ts => image-snapshot.matcher.ts} (92%) create mode 100644 e2e/setup/serializers/image-snapshot.pocessor.ts rename e2e/setup/serializers/{image/sharp.ts => image-snapshot.sharp.ts} (100%) rename e2e/setup/serializers/{image.ts => image-snapshot.ts} (90%) delete mode 100644 e2e/setup/serializers/image/diff-builder.ts delete mode 100644 e2e/setup/serializers/image/image-processor.ts delete mode 100644 e2e/setup/serializers/image/utils/directory.ts delete mode 100644 e2e/setup/serializers/image/utils/name.ts rename e2e/tests/__image_snapshots__/{homepage-feature-feature-homepage-looks-fine-test-homepage-footer-on-desktop.jpg => homepage.feature/feature-homepage-looks-fine-test-homepage-footer-on-desktop.jpg} (100%) rename e2e/tests/__image_snapshots__/{homepage-feature-feature-homepage-looks-fine-test-homepage-footer-on-mobile.jpg => homepage.feature/feature-homepage-looks-fine-test-homepage-footer-on-mobile.jpg} (100%) rename e2e/tests/__image_snapshots__/{homepage-feature-feature-homepage-looks-fine-test-homepage-hamburger-menu-on-mobile.jpg => homepage.feature/feature-homepage-looks-fine-test-homepage-hamburger-menu-on-mobile.jpg} (100%) rename e2e/tests/__image_snapshots__/{homepage-feature-feature-homepage-looks-fine-test-homepage-screen-on-mobile.jpg => homepage.feature/feature-homepage-looks-fine-test-homepage-screen-on-mobile.jpg} (100%) rename e2e/tests/__image_snapshots__/{homepage-feature-feature-homepage-looks-fine-test-homepage-screen.jpg => homepage.feature/feature-homepage-looks-fine-test-homepage-screen.jpg} (100%) rename e2e/tests/__image_snapshots__/{test-feature-feature-test-page-looks-fine-test-page-screen.jpg => test.feature/feature-test-page-looks-fine-test-page-screen.jpg} (100%) create mode 100644 e2e/utils/directory.d.ts create mode 100644 e2e/utils/directory.js create mode 100644 e2e/utils/image-snapshot.name.d.ts create mode 100644 e2e/utils/image-snapshot.name.js diff --git a/e2e/jest.config.js b/e2e/jest.config.js index ce2ae0c6f..2cb310523 100644 --- a/e2e/jest.config.js +++ b/e2e/jest.config.js @@ -15,7 +15,7 @@ module.exports = { roots: ['./tests/'], testRegex: ['(.+)\\.(spec|test)\\.ts$', '(.+).feature'], moduleFileExtensions: ['ts', 'js', 'feature'], - setupFilesAfterEnv: ['./setup/serializers/image.ts', './setup/scenarios.ts'], + setupFilesAfterEnv: ['./setup/serializers/image-snapshot.ts', './setup/scenarios.ts'], reporters: [ ['./reporters/reporter.js', { diffDir: './.diff', diff --git a/e2e/reporters/printers.js b/e2e/reporters/printers.js index 3bd132962..9ffbf71ec 100644 --- a/e2e/reporters/printers.js +++ b/e2e/reporters/printers.js @@ -35,7 +35,7 @@ function printFiles(fileStat, basePath) { text += `${test.name}:${test.title}${statusTest}${timeStr}\n`; if (test.status !== 'passed' && test.hasSnapshot) { - text += `Test Diff ${test.snapshot}`; + text += `Test Diff ${test.snapshot}`; } if (test.status !== 'passed' && !test.hasSnapshot) { text += `\n`; diff --git a/e2e/reporters/reporter.js b/e2e/reporters/reporter.js index 48dbcba2a..baac043d6 100644 --- a/e2e/reporters/reporter.js +++ b/e2e/reporters/reporter.js @@ -2,14 +2,12 @@ const fs = require('fs'); const path = require('path'); const {print} = require('./printers'); - -const sanitize = (str) => (str || '').replace(/\W+/g, '-').toLowerCase(); +const {buildSnapshotName, getDiffDir, getDirName} = require('../utils/image-snapshot.name'); +const {mkDir} = require('../utils/directory'); const writeFileSafe = (filename, data) => { const dirname = path.dirname(filename); - if (!fs.existsSync(dirname)) { - fs.mkdirSync(dirname, {recursive: true}); - } + mkDir(dirname) fs.writeFileSync(filename, data); }; @@ -44,20 +42,17 @@ class SnapshotAwareReporter { } buildTestStat(test, testPath) { - const {ancestorTitles, status, title, duration} = test; + const {status, title, duration, fullName} = test; const filename = path.basename(testPath); - const name = ancestorTitles.join(' > '); - const statBase = {name, filename, status, title, time: duration}; + const statBase = {status, filename, title, time: duration}; if (status === 'passed') return statBase; - - const snapshotParts = [filename, ...ancestorTitles, title, 'diff']; - const snapshotName = snapshotParts.map(sanitize).join('-') + '.jpg'; - const snapshotPath = path.join(this._options.diffDir, snapshotName); - const snapshotExists = fs.existsSync(snapshotPath); + const snapshotName = buildSnapshotName(fullName, 'diff'); + const snapshotExists = fs.existsSync(path.join(getDiffDir(testPath), snapshotName)); return Object.assign(statBase, { message: test.failureMessages[0], messages: test.failureMessages, hasSnapshot: snapshotExists, + dirPath: getDirName(testPath), snapshot: snapshotExists ? snapshotName : null }); } @@ -67,7 +62,7 @@ class SnapshotAwareReporter { const basePath = path.resolve(this._globalConfig.rootDir); for (const result of results.testResults) { const filepath = path.relative(basePath, result.testFilePath); - const tests = result.testResults.map(test => this.buildTestStat(test, filepath)); + const tests = result.testResults.map(test => this.buildTestStat(test, result.testFilePath)); testResults.push({filepath, tests}); } return testResults; diff --git a/e2e/setup/serializers/image/snapshot-matcher.ts b/e2e/setup/serializers/image-snapshot.matcher.ts similarity index 92% rename from e2e/setup/serializers/image/snapshot-matcher.ts rename to e2e/setup/serializers/image-snapshot.matcher.ts index 1be6d047b..055efb4a1 100644 --- a/e2e/setup/serializers/image/snapshot-matcher.ts +++ b/e2e/setup/serializers/image-snapshot.matcher.ts @@ -1,7 +1,7 @@ import pixelmatch from 'pixelmatch'; -import {SnapshotDataProcessor} from './image-processor'; +import {SnapshotDataProcessor} from './image-snapshot.pocessor'; -import type {SnapshotData} from './image-processor'; +import type {SnapshotData} from './image-snapshot.pocessor'; export type SnapshotMatcherOptions = pixelmatch.PixelmatchOptions; @@ -49,7 +49,7 @@ export class SnapshotMatcher { try { const numDiffPixel = pixelmatch(prevImg.data, currImg.data, diffBuffer, width, height, this.config); if (numDiffPixel > width * height * SnapshotMatcher.MIN_DIFF_THRESHOLD) { - return {reason: 'content', path: await SnapshotDataProcessor.saveDiff(this.received, diffBuffer)}; + return {reason: 'content', path: await SnapshotDataProcessor.saveDiffImage(this.received, diffBuffer)}; } } catch (e) { return {reason: 'error'}; diff --git a/e2e/setup/serializers/image-snapshot.pocessor.ts b/e2e/setup/serializers/image-snapshot.pocessor.ts new file mode 100644 index 000000000..ea57771a3 --- /dev/null +++ b/e2e/setup/serializers/image-snapshot.pocessor.ts @@ -0,0 +1,79 @@ +import path from 'path'; +import fs from 'fs'; +import sharp from 'sharp'; + +import {mkDir} from '../../utils/directory'; +import {getDiffDir, getSnapshotDir, buildSnapshotName} from '../../utils/image-snapshot.name'; +import {SharpService} from './image-snapshot.sharp'; + +import type {SharpContext} from './image-snapshot.sharp'; + +export interface SnapshotData { + snapshotPath: string; + diffPath: string; + diffDir: string; + + current: { + img: sharp.Sharp; + buffer: SharpContext; + }; + + previous: { + buffer: SharpContext | undefined; + }; +} + +export class SnapshotDataProcessor { + public static async process(context: jest.MatcherContext, received: Buffer): Promise { + const {testPath, currentTestName} = context; + + const snapshotDir = getSnapshotDir(testPath!); + const diffDir = getDiffDir(testPath!); + mkDir(snapshotDir); + + const snapshotPath = path.join(snapshotDir, buildSnapshotName(currentTestName!)); + const diffPath = path.join(diffDir, buildSnapshotName(currentTestName!, 'diff')); + + const shouldUpdate = !fs.existsSync(snapshotPath) || context.snapshotState._updateSnapshot === 'all'; + const recievedJPG = SharpService.toJPEG(received); + return { + diffPath, + snapshotPath, + diffDir, + + current: { + img: recievedJPG, + buffer: await (SharpService.toRawBuffered(await recievedJPG.toBuffer())) + }, + + previous: { + buffer: shouldUpdate ? undefined : await (SharpService.toRawBuffered(snapshotPath)) + } + }; + } + + public static async saveDiffImage(data: SnapshotData, diffBuffer: Buffer): Promise { + const {current, previous, diffDir, diffPath} = data; + const {width, height} = previous.buffer!.info; + mkDir(diffDir); + + const rawOptions = {raw: {width, height, channels: 4}}; + await sharp({ + create: { + width: width * 3, + height, + channels: 4, + background: {r: 0, g: 0, b: 0, alpha: 0} + } + }) + .composite([ + {input: await SharpService.toJPEGBufferd(previous.buffer!.data, rawOptions), left: 0, top: 0}, + {input: await SharpService.toJPEGBufferd(diffBuffer, rawOptions), left: width, top: 0}, + {input: await SharpService.toJPEGBufferd(current.buffer.data, rawOptions), left: width * 2, top: 0} + ]) + .jpeg() + .toFile(diffPath); + + return diffPath; + } +} diff --git a/e2e/setup/serializers/image/sharp.ts b/e2e/setup/serializers/image-snapshot.sharp.ts similarity index 100% rename from e2e/setup/serializers/image/sharp.ts rename to e2e/setup/serializers/image-snapshot.sharp.ts diff --git a/e2e/setup/serializers/image.ts b/e2e/setup/serializers/image-snapshot.ts similarity index 90% rename from e2e/setup/serializers/image.ts rename to e2e/setup/serializers/image-snapshot.ts index bc82e34b8..a747b6294 100644 --- a/e2e/setup/serializers/image.ts +++ b/e2e/setup/serializers/image-snapshot.ts @@ -1,6 +1,6 @@ -import {SnapshotDataProcessor} from './image/image-processor'; -import {SnapshotMatcher} from './image/snapshot-matcher'; -import type {SnapshotMatcherOptions} from './image/snapshot-matcher'; +import {SnapshotDataProcessor} from './image-snapshot.pocessor'; +import {SnapshotMatcher} from './image-snapshot.matcher'; +import type {SnapshotMatcherOptions} from './image-snapshot.matcher'; expect.extend({async toMatchImageSnapshot(received: Buffer, options: SnapshotMatcherOptions = {}) { const imageList = await SnapshotDataProcessor.process(this, received); diff --git a/e2e/setup/serializers/image/diff-builder.ts b/e2e/setup/serializers/image/diff-builder.ts deleted file mode 100644 index ea51983e6..000000000 --- a/e2e/setup/serializers/image/diff-builder.ts +++ /dev/null @@ -1,42 +0,0 @@ -import sharp from 'sharp'; -import {SharpService} from './sharp'; - -import type {SharpContext} from './sharp'; - -type DiffBuilderConfig = { - diffPath: string; - img1: SharpContext; - img2: SharpContext; - diffBuffer: Buffer; -}; - -export class DiffImageComposer { - - protected imgInfo: {width: number, height: number}; - - constructor(protected config: DiffBuilderConfig) { - this.imgInfo = this.config.img1.info; - } - - public async save(): Promise { - const {diffPath, img1, img2, diffBuffer} = this.config; - const {width, height} = this.imgInfo; - - const rawOptions = {raw: {width, height, channels: 4}}; - await sharp({ - create: { - width: width * 3, - height, - channels: 4, - background: {r: 0, g: 0, b: 0, alpha: 0} - } - }) - .composite([ - {input: await SharpService.toJPEGBufferd(img1.data, rawOptions), left: 0, top: 0}, - {input: await SharpService.toJPEGBufferd(diffBuffer, rawOptions), left: width, top: 0}, - {input: await SharpService.toJPEGBufferd(img2.data, rawOptions), left: width * 2, top: 0} - ]) - .jpeg() - .toFile(diffPath); - } -} diff --git a/e2e/setup/serializers/image/image-processor.ts b/e2e/setup/serializers/image/image-processor.ts deleted file mode 100644 index 33d30f80c..000000000 --- a/e2e/setup/serializers/image/image-processor.ts +++ /dev/null @@ -1,60 +0,0 @@ -import path from 'path'; -import fs from 'fs'; - -import {mkDir} from './utils/directory'; -import {SharpService} from './sharp'; -import {DiffImageComposer} from './diff-builder'; -import {buildSnapshotName} from './utils/name'; - -import type {SharpContext} from './sharp'; -import type sharp from 'sharp'; - -export interface SnapshotData { - snapshotPath: string; - testName: string; - - current: { - img: sharp.Sharp; - buffer: SharpContext; - }; - - previous: { - buffer: SharpContext | undefined; - }; -} - -export class SnapshotDataProcessor { - protected static readonly DIFF_DIR = path.join(process.cwd(), '.diff'); - protected static readonly SNAPSHOT_DIR = path.join(process.cwd(), 'tests', '__image_snapshots__'); - - public static async process(context: jest.MatcherContext, received: Buffer): Promise { - const {testPath, currentTestName} = context; - mkDir(SnapshotDataProcessor.SNAPSHOT_DIR); - const testName = buildSnapshotName(testPath!.split('/').pop()!, currentTestName!); - const prevImgPath = path.join(SnapshotDataProcessor.SNAPSHOT_DIR, `${testName}.jpg`); - const shouldUpdate = !fs.existsSync(prevImgPath) || context.snapshotState._updateSnapshot === 'all'; - - const recievedJPG = SharpService.toJPEG(received); - return { - testName, - snapshotPath: prevImgPath, - - current: { - img: recievedJPG, - buffer: await (SharpService.toRawBuffered(await recievedJPG.toBuffer())) - }, - - previous: { - buffer: shouldUpdate ? undefined : await (SharpService.toRawBuffered(prevImgPath)) - } - }; - } - - public static async saveDiff(list: SnapshotData, diffBuffer: Buffer): Promise { - const {current, previous} = list; - mkDir(SnapshotDataProcessor.DIFF_DIR); - const diffPath = path.join(SnapshotDataProcessor.DIFF_DIR, `${buildSnapshotName(list.testName, 'diff')}.jpg`); - await new DiffImageComposer({diffPath, img1: previous.buffer!, img2: current.buffer, diffBuffer}).save(); - return diffPath; - } -} diff --git a/e2e/setup/serializers/image/utils/directory.ts b/e2e/setup/serializers/image/utils/directory.ts deleted file mode 100644 index cf7ff54a9..000000000 --- a/e2e/setup/serializers/image/utils/directory.ts +++ /dev/null @@ -1,5 +0,0 @@ -import fs from 'fs'; - -export function mkDir(dirPath: string): void { - if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, {recursive: true}); -} diff --git a/e2e/setup/serializers/image/utils/name.ts b/e2e/setup/serializers/image/utils/name.ts deleted file mode 100644 index 21e4f221e..000000000 --- a/e2e/setup/serializers/image/utils/name.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const sanitize = (str: string): string => (str || '').replace(/\W+/g, '-').toLowerCase(); - -export const buildSnapshotName = (...snapshotParts: string[]): string => snapshotParts.map(sanitize).join('-'); diff --git a/e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-footer-on-desktop.jpg b/e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-footer-on-desktop.jpg similarity index 100% rename from e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-footer-on-desktop.jpg rename to e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-footer-on-desktop.jpg diff --git a/e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-footer-on-mobile.jpg b/e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-footer-on-mobile.jpg similarity index 100% rename from e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-footer-on-mobile.jpg rename to e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-footer-on-mobile.jpg diff --git a/e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-hamburger-menu-on-mobile.jpg b/e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-hamburger-menu-on-mobile.jpg similarity index 100% rename from e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-hamburger-menu-on-mobile.jpg rename to e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-hamburger-menu-on-mobile.jpg diff --git a/e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-screen-on-mobile.jpg b/e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-screen-on-mobile.jpg similarity index 100% rename from e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-screen-on-mobile.jpg rename to e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-screen-on-mobile.jpg diff --git a/e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-screen.jpg b/e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-screen.jpg similarity index 100% rename from e2e/tests/__image_snapshots__/homepage-feature-feature-homepage-looks-fine-test-homepage-screen.jpg rename to e2e/tests/__image_snapshots__/homepage.feature/feature-homepage-looks-fine-test-homepage-screen.jpg diff --git a/e2e/tests/__image_snapshots__/test-feature-feature-test-page-looks-fine-test-page-screen.jpg b/e2e/tests/__image_snapshots__/test.feature/feature-test-page-looks-fine-test-page-screen.jpg similarity index 100% rename from e2e/tests/__image_snapshots__/test-feature-feature-test-page-looks-fine-test-page-screen.jpg rename to e2e/tests/__image_snapshots__/test.feature/feature-test-page-looks-fine-test-page-screen.jpg diff --git a/e2e/utils/directory.d.ts b/e2e/utils/directory.d.ts new file mode 100644 index 000000000..9e25a0698 --- /dev/null +++ b/e2e/utils/directory.d.ts @@ -0,0 +1 @@ +export declare function mkDir(dirPath: string): void; diff --git a/e2e/utils/directory.js b/e2e/utils/directory.js new file mode 100644 index 000000000..5425369fd --- /dev/null +++ b/e2e/utils/directory.js @@ -0,0 +1,9 @@ +const fs = require('fs'); + +function mkDir(dirPath) { + if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, {recursive: true}); +} + +module.exports = { + mkDir +}; \ No newline at end of file diff --git a/e2e/utils/image-snapshot.name.d.ts b/e2e/utils/image-snapshot.name.d.ts new file mode 100644 index 000000000..8c3070309 --- /dev/null +++ b/e2e/utils/image-snapshot.name.d.ts @@ -0,0 +1,7 @@ +export declare function buildSnapshotName(...snapshotParts: string[]): string; + +export declare function getSnapshotDir(testPath: string): string; + +export declare function getDiffDir(testPath: string): string; + +export declare function getDirName(testPath: string): string; diff --git a/e2e/utils/image-snapshot.name.js b/e2e/utils/image-snapshot.name.js new file mode 100644 index 000000000..829309e04 --- /dev/null +++ b/e2e/utils/image-snapshot.name.js @@ -0,0 +1,32 @@ +const path = require('path'); +const jestConfig = require(path.resolve(process.cwd(), 'jest.config.js')); + +function buildSnapshotName(...snapshotParts) { + const sanitize = (str) => (str || '').replace(/\W+/g, '-').toLowerCase(); + return snapshotParts.map(sanitize).join('-') + '.jpg'; +} + +const getRootDir = (testPath) => jestConfig.roots.find((root) => path.resolve(testPath).includes(path.resolve(root))); + +function getDirName(testPath) { + const rootDir = getRootDir(testPath); + const basePath = path.resolve(rootDir); + return path.relative(basePath, testPath) +} + +function getSnapshotDir(testPath) { + const snapshotDir = path.resolve(getRootDir(testPath), '__image_snapshots__'); + return path.join(snapshotDir, getDirName(testPath)); +} + +function getDiffDir(testPath) { + const diffDir = path.resolve('.diff'); + return path.join(diffDir, getDirName(testPath)); +} + +module.exports = { + buildSnapshotName, + getSnapshotDir, + getDiffDir, + getDirName +};