diff --git a/test/config/jest.image.ts b/test/config/jest.image.ts index a6f0ffce60..2bd211c465 100644 --- a/test/config/jest.image.ts +++ b/test/config/jest.image.ts @@ -25,7 +25,7 @@ import { toMatchImageSnapshot } from 'jest-image-snapshot'; import MatcherContext = jest.MatcherContext; import CustomMatcherResult = jest.CustomMatcherResult; -const jestLog = debugLogger('bv:test:jest:img'); +const log = debugLogger('bv:test:jest:img'); const toMatchImageSnapshotWithRealSignature = toMatchImageSnapshot as (received: unknown, options?: MatchImageSnapshotOptions) => CustomMatcherResult; // The path is relative from the jest-html-reporters page to the folder storing the images @@ -59,24 +59,18 @@ class RetriesCounter { const retriesCounter = new RetriesCounter(); -async function saveAndRegisterImages(matcherContext: MatcherContext, options: MatchImageSnapshotOptions): Promise { - const snapshotIdentifier = options.customSnapshotIdentifier as string; - // Manage expected and received images - const baseImagePathWithName = `${options.customDiffDir}/${snapshotIdentifier}`; - const expectedImagePath = `${baseImagePathWithName}-expected.png`; - copyFileSync(`${options.customSnapshotsDir}/${snapshotIdentifier}.png`, expectedImagePath); - // this image is generated by jest-image-snapshot when activating `storeReceivedOnFailure` - const receivedImagePath = `${baseImagePathWithName}-received.png`; - +async function attachImagesForReport(images: LocationOfImagesForTestReport, matcherContext: MatcherContext): Promise { // Attach the images to jest-html-reports // Chain the calls to preserve the attachment order // Create a custom context as the async call can be done whereas the global jest context has changed (another test is currently running). // So the test name and path changed, and the images would be attached to the wrong test. // For the context object structure, see https://github.com/Hazyzh/jest-html-reporters/blob/v3.0.5/helper.ts#L95 const context: Record = {}; + const currentTestName = matcherContext.currentTestName; + log('Attaching images to report for test %s', currentTestName); context[Symbol('bpmn-visualization')] = { state: { - currentTestName: matcherContext.currentTestName, + currentTestName, testPath: matcherContext.testPath, }, matchers: {}, // required by the jest-html-reporters getJestGlobalData function even if not used @@ -84,53 +78,80 @@ async function saveAndRegisterImages(matcherContext: MatcherContext, options: Ma try { await addAttach({ - attach: computeRelativePathFromReportToSnapshots(`${baseImagePathWithName}-diff.png`), + attach: computeRelativePathFromReportToSnapshots(images.diff), description: 'diff', bufferFormat: 'png', context, }); await addAttach({ - attach: computeRelativePathFromReportToSnapshots(expectedImagePath), + attach: computeRelativePathFromReportToSnapshots(images.expected), description: 'expected', bufferFormat: 'png', context, }); await addAttach({ - attach: computeRelativePathFromReportToSnapshots(receivedImagePath), + attach: computeRelativePathFromReportToSnapshots(images.received), description: 'received', bufferFormat: 'png', context, }); + + log('Images attached to report for test %s', currentTestName); } catch (error) { console.error( - `Error while attaching images to test ${snapshotIdentifier}.` + + `Error while attaching images to test ${currentTestName}.` + `The 'jest-html-reporters' reporter is probably not in use. For instance, this occurs when running tests with the IntelliJ/Webstorm Jest runner.`, error, ); } } +type LocationOfImagesForTestReport = { + diff: string; + expected: string; + received: string; +}; + +function saveImages(options: MatchImageSnapshotOptions): LocationOfImagesForTestReport { + const snapshotIdentifier = options.customSnapshotIdentifier as string; + const baseImagePathWithName = `${options.customDiffDir}/${snapshotIdentifier}`; + + const diffImagePath = `${baseImagePathWithName}-diff.png`; + const expectedImagePath = `${baseImagePathWithName}-expected.png`; + copyFileSync(`${options.customSnapshotsDir}/${snapshotIdentifier}.png`, expectedImagePath); + // this image is generated by jest-image-snapshot when activating `storeReceivedOnFailure` + const receivedImagePath = `${options.customReceivedDir}/${snapshotIdentifier}-received.png`; + + return { + diff: diffImagePath, + expected: expectedImagePath, + received: receivedImagePath, + }; +} + // Improve jest-image-snapshot outputs to facilitate debug // The 'options' parameter is mandatory for us, and some properties must be set as well // All options properties used here are always set in bpmn-visualization tests // If the following implementation would be done directly in jest-image-snapshot, this won't be required as it set default values we cannot access here -async function toMatchImageSnapshotCustom(this: MatcherContext, received: Buffer, options: MatchImageSnapshotOptions): Promise { +function toMatchImageSnapshotCustom(this: MatcherContext, received: Buffer, options: MatchImageSnapshotOptions): CustomMatcherResult { const testId = this.currentTestName; retriesCounter.incrementExecutionCount(testId); - jestLog("Test: '%s' (test file path: '%s')", this.currentTestName, this.testPath); + log("Test: '%s' (test file path: '%s')", this.currentTestName, this.testPath); const executionCount = retriesCounter.getExecutionCount(testId); - jestLog('Ready to execute toMatchImageSnapshot, execution count: %s', executionCount); + log('Ready to execute toMatchImageSnapshot, execution count: %s', executionCount); const result = toMatchImageSnapshotWithRealSignature.call(this, received, options); - jestLog('toMatchImageSnapshot executed'); + log('toMatchImageSnapshot executed'); if (result.pass) { - jestLog('Result: success'); + log('Result: success'); } else { - jestLog('Result: failure'); + log('Result: failure'); if (retriesCounter.hasReachMaxRetries(testId)) { - await saveAndRegisterImages(this, options); + const imageLocationInformation = saveImages(options); + log('Images saved and ready to be registered to the report', imageLocationInformation); + attachImagesForReport(imageLocationInformation, this).catch(error => log('Fail to attach images to the report %s', error)); } // Add configured failure threshold in the error message diff --git a/test/e2e/__image_snapshots__/bpmn-rendering/subprocess.03.collapsed.with.elements.png b/test/e2e/__image_snapshots__/bpmn-rendering/subprocess.03.collapsed.with.elements.png index 7e3e1dcaf5..9b2b939344 100644 Binary files a/test/e2e/__image_snapshots__/bpmn-rendering/subprocess.03.collapsed.with.elements.png and b/test/e2e/__image_snapshots__/bpmn-rendering/subprocess.03.collapsed.with.elements.png differ diff --git a/test/e2e/bpmn.rendering.test.ts b/test/e2e/bpmn.rendering.test.ts index afeec55294..8b231359f2 100644 --- a/test/e2e/bpmn.rendering.test.ts +++ b/test/e2e/bpmn.rendering.test.ts @@ -165,12 +165,6 @@ class ImageSnapshotThresholds extends MultiBrowserImageSnapshotThresholds { windows: 0.66 / 100, // 0.6566433292574891% }, ], - [ - 'subprocess.03.collapsed.with.elements', - { - macos: 0.11 / 100, // 0.10203186226079852% - }, - ], ]); } diff --git a/test/e2e/diagram.navigation.fit.test.ts b/test/e2e/diagram.navigation.fit.test.ts index 1a5392f760..78b8dbdb31 100644 --- a/test/e2e/diagram.navigation.fit.test.ts +++ b/test/e2e/diagram.navigation.fit.test.ts @@ -19,9 +19,9 @@ import type { MatchImageSnapshotOptions } from 'jest-image-snapshot'; import path from 'node:path'; -import 'jest-playwright-preset'; +import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds, withCustomOutputDirectory } from './helpers/visu/image-snapshot-config'; -import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds } from './helpers/visu/image-snapshot-config'; +import 'jest-playwright-preset'; import { FitType } from '@lib/component/options'; import { AvailableTestPages, PageTester } from '@test/shared/visu/bpmn-page-utils'; @@ -34,10 +34,13 @@ class FitImageSnapshotConfigurator extends ImageSnapshotConfigurator { fitType: FitType; margin?: number; }): MatchImageSnapshotOptions { - const config = super.getConfig(parameter); - config.customSnapshotsDir = FitImageSnapshotConfigurator.buildSnapshotFitDirectory(config.customSnapshotsDir, parameter.fitType, true, parameter.margin ?? 0); - config.customDiffDir = parameter.buildCustomDiffDir(config, parameter.fitType, parameter.margin); - return config; + return withCustomOutputDirectory( + { + ...super.getConfig(parameter), + customSnapshotsDir: FitImageSnapshotConfigurator.buildSnapshotFitDirectory(super.getConfig(parameter).customSnapshotsDir, parameter.fitType, true, parameter.margin ?? 0), + }, + parameter.buildCustomDiffDir(super.getConfig(parameter), parameter.fitType, parameter.margin), + ); } private static buildSnapshotFitDirectory(parentDirectory: string, fitType: FitType, withMargin = false, margin?: number): string { diff --git a/test/e2e/diagram.navigation.zoom.pan.test.ts b/test/e2e/diagram.navigation.zoom.pan.test.ts index 63c88b7497..626a9d39ba 100644 --- a/test/e2e/diagram.navigation.zoom.pan.test.ts +++ b/test/e2e/diagram.navigation.zoom.pan.test.ts @@ -20,9 +20,9 @@ import type { Point } from '@test/shared/visu/bpmn-page-utils'; import path from 'node:path'; import debugLogger from 'debug'; -import 'jest-playwright-preset'; -import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds } from './helpers/visu/image-snapshot-config'; +import { withCustomOutputDirectory, ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds } from './helpers/visu/image-snapshot-config'; +import 'jest-playwright-preset'; import { ZoomType } from '@lib/component/options'; import { AvailableTestPages, PageTester } from '@test/shared/visu/bpmn-page-utils'; @@ -119,11 +119,15 @@ describe('diagram navigation - zoom and pan with mouse', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: 'initial.zoom', - customDiffDir: path.join(config.customDiffDir, `mouse-zoom-in-out-${xTimes}-times`), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: 'initial.zoom', + }, + path.join(config.customDiffDir, `mouse-zoom-in-out-${xTimes}-times`), + ), + ); }); }); @@ -163,11 +167,15 @@ describe('diagram navigation - zoom with buttons', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: 'initial.zoom', - customDiffDir: path.join(config.customDiffDir, `button-zoom-in-out-${xTimes}-times`), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: 'initial.zoom', + }, + path.join(config.customDiffDir, `button-zoom-in-out-${xTimes}-times`), + ), + ); }); }); @@ -205,11 +213,15 @@ describe('diagram navigation - zoom with buttons and mouse', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: 'initial.zoom', - customDiffDir: path.join(config.customDiffDir, `zoom-button-then-mouse-${firstZoom}-then-${secondZoom}`), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: 'initial.zoom', + }, + path.join(config.customDiffDir, `zoom-button-then-mouse-${firstZoom}-then-${secondZoom}`), + ), + ); }); it.each` @@ -222,10 +234,14 @@ describe('diagram navigation - zoom with buttons and mouse', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: 'initial.zoom', - customDiffDir: path.join(config.customDiffDir, `zoom-mouse-then-button-${firstZoom}-then-${secondZoom}`), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: 'initial.zoom', + }, + path.join(config.customDiffDir, `zoom-mouse-then-button-${firstZoom}-then-${secondZoom}`), + ), + ); }); }); diff --git a/test/e2e/helpers/visu/image-snapshot-config.ts b/test/e2e/helpers/visu/image-snapshot-config.ts index cf3b5b6253..3be42d55fc 100644 --- a/test/e2e/helpers/visu/image-snapshot-config.ts +++ b/test/e2e/helpers/visu/image-snapshot-config.ts @@ -55,14 +55,15 @@ export class ImageSnapshotConfigurator { const fileName = typeof parameter === 'string' ? parameter : parameter.fileName; const failureThreshold = this.getFailureThreshold(fileName); - return { - ...defaultImageSnapshotConfig, - failureThreshold: failureThreshold, - customSnapshotIdentifier: fileName, - customSnapshotsDir: this.defaultCustomSnapshotsDir, - customDiffDir: this.defaultCustomDiffDir, - customReceivedDir: this.defaultCustomDiffDir, - }; + return withCustomOutputDirectory( + { + ...defaultImageSnapshotConfig, + failureThreshold: failureThreshold, + customSnapshotIdentifier: fileName, + customSnapshotsDir: this.defaultCustomSnapshotsDir, + }, + this.defaultCustomDiffDir, + ); } private getFailureThreshold(fileName: string): number { @@ -166,3 +167,12 @@ export class MultiBrowserImageSnapshotThresholds { } } } + +/** + * Set both the customDiffDir and customReceivedDir to the same value. + */ +export const withCustomOutputDirectory = (options: MatchImageSnapshotOptions, customOutputDirectory: string): MatchImageSnapshotOptions => ({ + ...options, + customDiffDir: customOutputDirectory, + customReceivedDir: customOutputDirectory, +}); diff --git a/test/e2e/overlays.rendering.test.ts b/test/e2e/overlays.rendering.test.ts index ea48d53667..74c09282c8 100644 --- a/test/e2e/overlays.rendering.test.ts +++ b/test/e2e/overlays.rendering.test.ts @@ -23,7 +23,7 @@ import path from 'node:path'; import debugLogger from 'debug'; -import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds } from './helpers/visu/image-snapshot-config'; +import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds, withCustomOutputDirectory } from './helpers/visu/image-snapshot-config'; import { ensureIsArray } from '@lib/component/helpers/array-utils'; import { ZoomType } from '@lib/component/options'; @@ -146,12 +146,16 @@ describe('BPMN Shapes with overlays', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: `add.overlay.on.position.${position}`, - customSnapshotsDir: getShapeDirectory(config.customSnapshotsDir), - customDiffDir: getShapeDirectory(config.customDiffDir), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: `add.overlay.on.position.${position}`, + customSnapshotsDir: getShapeDirectory(config.customSnapshotsDir), + }, + getShapeDirectory(config.customDiffDir), + ), + ); }); it(`remove all overlays of Shape`, async () => { @@ -162,12 +166,16 @@ describe('BPMN Shapes with overlays', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: 'remove.all.overlays.of.shape', - customSnapshotsDir: getShapeDirectory(config.customSnapshotsDir), - customDiffDir: getShapeDirectory(config.customDiffDir), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: 'remove.all.overlays.of.shape', + customSnapshotsDir: getShapeDirectory(config.customSnapshotsDir), + }, + getShapeDirectory(config.customDiffDir), + ), + ); }); }); @@ -201,12 +209,16 @@ describe('BPMN Edges with overlays', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: `add.overlay.on.${edgeKind}.flow`, - customSnapshotsDir: getEdgePositionDirectory(config.customSnapshotsDir, position), - customDiffDir: getEdgePositionDirectory(config.customDiffDir, position), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: `add.overlay.on.${edgeKind}.flow`, + customSnapshotsDir: getEdgePositionDirectory(config.customSnapshotsDir, position), + }, + getEdgePositionDirectory(config.customDiffDir, position), + ), + ); }); it(`remove all overlays of ${edgeKind} flow`, async () => { @@ -218,12 +230,16 @@ describe('BPMN Edges with overlays', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: `remove.all.overlays.of.${edgeKind}.flow`, - customSnapshotsDir: getEdgeDirectory(config.customSnapshotsDir), - customDiffDir: getEdgeDirectory(config.customDiffDir), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: `remove.all.overlays.of.${edgeKind}.flow`, + customSnapshotsDir: getEdgeDirectory(config.customSnapshotsDir), + }, + getEdgeDirectory(config.customDiffDir), + ), + ); }); }); }); @@ -374,11 +390,15 @@ describe('Overlay style', () => { const image = await page.screenshot({ fullPage: true }); const config = imageSnapshotConfigurator.getConfig(style); - expect(image).toMatchImageSnapshot({ - ...config, - customSnapshotIdentifier: `add.overlay.with.custom.${style}`, - customSnapshotsDir: path.join(config.customSnapshotsDir, snapshotPath), - customDiffDir: path.join(config.customDiffDir, snapshotPath), - }); + expect(image).toMatchImageSnapshot( + withCustomOutputDirectory( + { + ...config, + customSnapshotIdentifier: `add.overlay.with.custom.${style}`, + customSnapshotsDir: path.join(config.customSnapshotsDir, snapshotPath), + }, + path.join(config.customDiffDir, snapshotPath), + ), + ); }); });