From 80fc68616ea01fff6f8e08122b0680cf49c83574 Mon Sep 17 00:00:00 2001 From: wswebcreation Date: Tue, 24 Feb 2026 20:09:44 +0100 Subject: [PATCH] fix: use dirname() for resolveSnapshotPath to avoid EISDIR conflict (#984) The visual-service was using the resolveSnapshotPath return value directly as a baseline directory, but this path is also read as a file by expect-webdriverio's SnapshotService. Wrapping with dirname() extracts the parent directory so the two consumers no longer collide. Co-authored-by: Cursor --- .../fix-resolve-snapshot-path-eisdir.md | 10 +++++ .gitignore | 3 ++ packages/visual-service/src/service.ts | 2 +- packages/visual-service/tests/service.test.ts | 38 ++++++++++++++++++- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-resolve-snapshot-path-eisdir.md diff --git a/.changeset/fix-resolve-snapshot-path-eisdir.md b/.changeset/fix-resolve-snapshot-path-eisdir.md new file mode 100644 index 00000000..4d941fd6 --- /dev/null +++ b/.changeset/fix-resolve-snapshot-path-eisdir.md @@ -0,0 +1,10 @@ +--- +"@wdio/visual-service": patch +--- + +Fix `EISDIR` error when using `resolveSnapshotPath` with the visual service. The service now uses `dirname()` of the resolved path as the baseline folder, preventing it from creating a directory at a path that `expect-webdriverio`'s snapshot service expects to be a file. Fixes #984. + +# Committers: 1 + +- Wim Selles ([@wswebcreation](https://github.com/wswebcreation)) + diff --git a/.gitignore b/.gitignore index 376056b7..7cc05ab7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ dist *.tgz bugreport*.zip + +# IDE +.cursor/ diff --git a/packages/visual-service/src/service.ts b/packages/visual-service/src/service.ts index 7fc43450..6397d403 100644 --- a/packages/visual-service/src/service.ts +++ b/packages/visual-service/src/service.ts @@ -130,7 +130,7 @@ export default class WdioImageComparisonService extends BaseClass { * We also check `this.#config` because for standalone usage of the service, the config is not available */ if (this.#config && typeof this.#config.resolveSnapshotPath === 'function' && this.#currentFile && isDefaultBaselineFolder) { - return this.#config.resolveSnapshotPath(this.#currentFile, '.png') + return dirname(this.#config.resolveSnapshotPath(this.#currentFile, '.png')) } return baselineFolder diff --git a/packages/visual-service/tests/service.test.ts b/packages/visual-service/tests/service.test.ts index 7a8954e7..68cc1faa 100644 --- a/packages/visual-service/tests/service.test.ts +++ b/packages/visual-service/tests/service.test.ts @@ -1,4 +1,4 @@ -import { join } from 'node:path' +import { dirname, join, normalize } from 'node:path' import logger from '@wdio/logger' import { expect as wdioExpect } from '@wdio/globals' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -199,5 +199,41 @@ describe('@wdio/visual-service', () => { const [saveScreenOptions] = vi.mocked(saveScreen).mock.calls[0] expect((saveScreenOptions as any).saveScreenOptions?.wic?.alwaysSaveActualImage).toBe(true) }) + + it('should use dirname() of resolveSnapshotPath result as baselineFolder to avoid EISDIR conflicts', async () => { + vi.mocked(saveScreen).mockResolvedValue({} as any) + const resolveSnapshotPath = vi.fn().mockReturnValue('/custom/snapshots/specs/test.e2e.png') + const config = { + framework: 'mocha', + resolveSnapshotPath, + } as unknown as WebdriverIO.Config + const service = new VisualService({}, {}, config) + ;(service as any).defaultOptions = {} + ;(service as any).folders = { baselineFolder: normalize('./__snapshots__/') } + const browser = { + isMultiremote: false, + addCommand: vi.fn((name, fn) => { + (browser as any)[name] = fn + }), + capabilities: {}, + requestedCapabilities: {}, + on: vi.fn(), + execute: vi.fn().mockResolvedValue(1), + } as any as WebdriverIO.Browser + + await service.before({}, [], browser) + service.beforeTest({ + file: '/project/specs/test.e2e.ts', + parent: 'suite', + title: 'test', + } as any) + await (browser as any).saveScreen('tag') + + expect(resolveSnapshotPath).toHaveBeenCalledWith('/project/specs/test.e2e.ts', '.png') + const [saveScreenOptions] = vi.mocked(saveScreen).mock.calls[0] + expect((saveScreenOptions as any).folders.baselineFolder).toBe( + dirname('/custom/snapshots/specs/test.e2e.png') + ) + }) }) })