From da0158304b3aa8189351730066575596dc4f81f7 Mon Sep 17 00:00:00 2001 From: wswebcreation Date: Tue, 24 Feb 2026 19:45:57 +0100 Subject: [PATCH] fix: avoid creating empty diff folders when no visual differences exist (#879) The diff directory was eagerly created by `prepareComparisonFilePaths` via `getAndCreatePath` before any comparison took place. This resulted in empty diff folder trees even when tests passed or baselines were auto-saved. Extract a new `getPath` function that resolves the folder path without calling `mkdirSync`. Use it for the diff folder in `prepareComparisonFilePaths` so the directory is only created on disk when `saveBase64Image` actually writes a diff image. Co-authored-by: Cursor --- .changeset/fix-empty-diff-folders.md | 9 ++ .../src/helpers/utils.test.ts | 105 +++++++++++++++++- .../src/helpers/utils.ts | 16 ++- 3 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 .changeset/fix-empty-diff-folders.md diff --git a/.changeset/fix-empty-diff-folders.md b/.changeset/fix-empty-diff-folders.md new file mode 100644 index 00000000..7917d133 --- /dev/null +++ b/.changeset/fix-empty-diff-folders.md @@ -0,0 +1,9 @@ +--- +"@wdio/image-comparison-core": patch +--- + +Stop creating empty diff folders when no visual differences exist. The diff directory is now only created on disk when a diff image is actually saved, instead of being eagerly created during path preparation. Fixes #879. + +# Committers: 1 + +- Wim Selles ([@wswebcreation](https://github.com/wswebcreation)) diff --git a/packages/image-comparison-core/src/helpers/utils.test.ts b/packages/image-comparison-core/src/helpers/utils.test.ts index d4955065..3bb9e146 100644 --- a/packages/image-comparison-core/src/helpers/utils.test.ts +++ b/packages/image-comparison-core/src/helpers/utils.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { existsSync } from 'node:fs' +import { existsSync, mkdirSync } from 'node:fs' import { join } from 'node:path' vi.mock('node:fs', async () => { @@ -33,12 +33,14 @@ import { getMethodOrWicOption, getMobileScreenSize, getMobileViewPortPosition, + getPath, getToolBarShadowPadding, hasResizeDimensions, isObject, isStorybook, loadBase64Html, logAllDeprecatedCompareOptions, + prepareComparisonFilePaths, updateVisualBaseline, } from './utils.js' import type { FormatFileNameOptions, GetAndCreatePathOptions, ExtractCommonCheckVariablesOptions } from './utils.interfaces.js' @@ -147,6 +149,107 @@ describe('utils', () => { }) }) + describe('getPath', () => { + const folder = join(process.cwd(), '/.tmp/utils') + + it('should return the folder path for a mobile device with savePerInstance', () => { + const options: GetAndCreatePathOptions = { + browserName: '', + deviceName: 'deviceName', + isMobile: true, + savePerInstance: true, + } + expect(getPath(folder, options)).toEqual(join(folder, options.deviceName)) + }) + + it('should return the folder path for a desktop browser with savePerInstance', () => { + const options: GetAndCreatePathOptions = { + browserName: 'browser', + deviceName: '', + isMobile: false, + savePerInstance: true, + } + expect(getPath(folder, options)).toEqual(join(folder, `desktop_${options.browserName}`)) + }) + + it('should return the base folder when savePerInstance is false', () => { + const options: GetAndCreatePathOptions = { + browserName: 'browser', + deviceName: '', + isMobile: false, + savePerInstance: false, + } + expect(getPath(folder, options)).toEqual(folder) + }) + + it('should not create any directories on disk', () => { + vi.mocked(mkdirSync).mockClear() + const options: GetAndCreatePathOptions = { + browserName: 'chrome', + deviceName: '', + isMobile: false, + savePerInstance: true, + } + getPath(folder, options) + expect(mkdirSync).not.toHaveBeenCalled() + }) + }) + + describe('prepareComparisonFilePaths', () => { + beforeEach(() => { + vi.mocked(mkdirSync).mockClear() + }) + + it('should create actual and baseline folders but not the diff folder', () => { + const options = { + actualFolder: '/tmp/actual', + baselineFolder: '/tmp/baseline', + diffFolder: '/tmp/diff', + browserName: 'chrome', + deviceName: '', + isMobile: false, + savePerInstance: false, + fileName: 'test.png', + } + + const result = prepareComparisonFilePaths(options) + + expect(result.actualFolderPath).toEqual('/tmp/actual') + expect(result.baselineFolderPath).toEqual('/tmp/baseline') + expect(result.diffFolderPath).toEqual('/tmp/diff') + expect(result.actualFilePath).toEqual(join('/tmp/actual', 'test.png')) + expect(result.baselineFilePath).toEqual(join('/tmp/baseline', 'test.png')) + expect(result.diffFilePath).toEqual(join('/tmp/diff', 'test.png')) + + const mkdirCalls = vi.mocked(mkdirSync).mock.calls.map(call => call[0]) + expect(mkdirCalls).toContain('/tmp/actual') + expect(mkdirCalls).toContain('/tmp/baseline') + expect(mkdirCalls).not.toContain('/tmp/diff') + }) + + it('should include instance subfolder in paths when savePerInstance is true', () => { + const options = { + actualFolder: '/tmp/actual', + baselineFolder: '/tmp/baseline', + diffFolder: '/tmp/diff', + browserName: 'chrome', + deviceName: '', + isMobile: false, + savePerInstance: true, + fileName: 'test.png', + } + + const result = prepareComparisonFilePaths(options) + + expect(result.actualFolderPath).toEqual(join('/tmp/actual', 'desktop_chrome')) + expect(result.baselineFolderPath).toEqual(join('/tmp/baseline', 'desktop_chrome')) + expect(result.diffFolderPath).toEqual(join('/tmp/diff', 'desktop_chrome')) + + const mkdirCalls = vi.mocked(mkdirSync).mock.calls.map(call => call[0]) + expect(mkdirCalls).not.toContain(join('/tmp/diff', 'desktop_chrome')) + }) + }) + describe('formatFileName', () => { const formatFileOptions: FormatFileNameOptions = { browserName: '', diff --git a/packages/image-comparison-core/src/helpers/utils.ts b/packages/image-comparison-core/src/helpers/utils.ts index e667c750..39398951 100644 --- a/packages/image-comparison-core/src/helpers/utils.ts +++ b/packages/image-comparison-core/src/helpers/utils.ts @@ -33,9 +33,9 @@ import type { BaseDimensions } from '../base.interfaces.js' const log = logger('@wdio/visual-service:@wdio/image-comparison-core:utils') /** - * Get and create a folder + * Resolve the folder path without creating it on disk */ -export function getAndCreatePath(folder: string, options: GetAndCreatePathOptions): string { +export function getPath(folder: string, options: GetAndCreatePathOptions): string { const { browserName = NOT_KNOWN, deviceName = NOT_KNOWN, @@ -44,7 +44,15 @@ export function getAndCreatePath(folder: string, options: GetAndCreatePathOption } = options const instanceName = (isMobile ? deviceName : `${DESKTOP}_${browserName}`).replace(/ /g, '_') const subFolder = savePerInstance ? instanceName : '' - const folderName = join(folder, subFolder) + + return join(folder, subFolder) +} + +/** + * Get and create a folder + */ +export function getAndCreatePath(folder: string, options: GetAndCreatePathOptions): string { + const folderName = getPath(folder, options) mkdirSync(folderName, { recursive: true }) @@ -707,7 +715,7 @@ export function prepareComparisonFilePaths(options: PrepareComparisonFilePathsOp const createFolderOptions = { browserName, deviceName, isMobile, savePerInstance } const actualFolderPath = getAndCreatePath(actualFolder, createFolderOptions) const baselineFolderPath = getAndCreatePath(baselineFolder, createFolderOptions) - const diffFolderPath = getAndCreatePath(diffFolder, createFolderOptions) + const diffFolderPath = getPath(diffFolder, createFolderOptions) const actualFilePath = join(actualFolderPath, fileName) const baselineFilePath = join(baselineFolderPath, fileName) const diffFilePath = join(diffFolderPath, fileName)