UNPKG

@wdio/visual-service

Version:

Image comparison / visual regression testing for WebdriverIO

123 lines (122 loc) 6.82 kB
import { rmdirSync } from 'node:fs'; import logger from '@wdio/logger'; import { SevereServiceError } from 'webdriverio'; import { BaseClass } from '@wdio/image-comparison-core'; import { createStorybookCapabilities, createTestFiles, getArgvValue, isCucumberFramework, isStorybookMode, parseSkipStories, scanStorybook, } from './utils.js'; import { CLIP_SELECTOR, NUM_SHARDS, V6_CLIP_SELECTOR } from '../constants.js'; import generateVisualReport from '../reporter.js'; const log = logger('@wdio/visual-service'); export default class VisualLauncher extends BaseClass { #options; constructor(options) { super(options); this.#options = options; } async onPrepare(config, capabilities) { const isStorybook = isStorybookMode(); const framework = config.framework; const isCucumber = isCucumberFramework(framework); if (isCucumber && isStorybook) { throw new SevereServiceError('\n\nRunning Storybook in combination with the cucumber framework adapter is not supported.\nOnly Jasmine and Mocha are supported.\n\n'); } else if (isStorybook) { log.info('Running `@wdio/visual-service` in Storybook mode.'); const { storiesJson, storybookUrl, tempDir } = await scanStorybook(config, this.#options); // Set an environment variable so it can be used in the onComplete hook process.env.VISUAL_STORYBOOK_TEMP_SPEC_FOLDER = tempDir; // Add the storybook URL to the environment variables process.env.VISUAL_STORYBOOK_URL = storybookUrl; // Check the capabilities // Multiremote capabilities are not supported if (typeof capabilities === 'object' && !Array.isArray(capabilities)) { throw new SevereServiceError('\n\nRunning Storybook in combination with Multiremote is not supported.\nRemove your `capabilities` property from your config or assign an empty array to it like `capabilities: [],`.\n\n'); } // Clear the capabilities capabilities.length = 0; log.info('Clearing the current capabilities.'); // Get compare options from config const compareOptions = { blockOutSideBar: this.#options.blockOutSideBar, blockOutStatusBar: this.#options.blockOutStatusBar, blockOutToolBar: this.#options.blockOutToolBar, ignoreAlpha: this.#options.ignoreAlpha, ignoreAntialiasing: this.#options.ignoreAntialiasing, ignoreColors: this.#options.ignoreColors, ignoreLess: this.#options.ignoreLess, ignoreNothing: this.#options.ignoreNothing, rawMisMatchPercentage: this.#options.rawMisMatchPercentage, returnAllCompareData: this.#options.returnAllCompareData, saveAboveTolerance: this.#options.saveAboveTolerance, scaleImagesToSameSize: this.#options.scaleImagesToSameSize, }; // Determine some run options // --version const versionOption = this.#options?.storybook?.version; const versionArgv = getArgvValue('--version', value => Math.floor(parseFloat(value))); const version = versionOption ?? versionArgv ?? 7; // --numShards const maxInstances = config?.maxInstances ?? 1; const numShardsOption = this.#options?.storybook?.numShards; const numShardsArgv = getArgvValue('--numShards', value => parseInt(value, 10)); const numShards = Math.min(numShardsOption || numShardsArgv || NUM_SHARDS, maxInstances); // --clip const clipOption = this.#options?.storybook?.clip; const clipArgv = getArgvValue('--clip', value => value !== 'false'); const clip = clipOption ?? clipArgv ?? true; // --clipSelector const clipSelectorOption = this.#options?.storybook?.clipSelector; const clipSelectorArgv = getArgvValue('--clipSelector', value => value); // V6 has '#root' as the root element, V7 has '#storybook-root' const clipSelector = (clipSelectorOption ?? clipSelectorArgv) ?? (version === 6 ? V6_CLIP_SELECTOR : CLIP_SELECTOR); // Add the clip selector to the environment variables process.env.VISUAL_STORYBOOK_CLIP_SELECTOR = clipSelector; // --skipStories const skipStoriesOption = this.#options?.storybook?.skipStories; const skipStoriesArgv = getArgvValue('--skipStories', value => value); const skipStories = skipStoriesOption ?? skipStoriesArgv ?? []; const parsedSkipStories = parseSkipStories(skipStories); // --additionalSearchParams const additionalSearchParamsOption = this.#options?.storybook?.additionalSearchParams; const additionalSearchParamsArgv = getArgvValue('--additionalSearchParams', value => new URLSearchParams(value)); const additionalSearchParams = additionalSearchParamsOption ?? additionalSearchParamsArgv ?? new URLSearchParams(); const getStoriesBaselinePath = this.#options?.storybook?.getStoriesBaselinePath; // Create the test files createTestFiles({ additionalSearchParams, clip, clipSelector, compareOptions, directoryPath: tempDir, folders: this.folders, framework, getStoriesBaselinePath, numShards, skipStories: parsedSkipStories, storiesJson, storybookUrl, }); // Create the capabilities createStorybookCapabilities(capabilities); } } async onComplete() { const tempDir = process.env.VISUAL_STORYBOOK_TEMP_SPEC_FOLDER; if (tempDir) { log.info(`Cleaning up temporary folder for storybook specs: ${tempDir}`); try { rmdirSync(tempDir, { recursive: true }); log.info(`Temporary folder for storybook specs has been removed: ${tempDir}`); } catch (err) { log.error(`Failed to remove temporary folder for storybook specs: ${tempDir} due to: ${err.message}`); } // Remove the environment variables delete process.env.VISUAL_STORYBOOK_TEMP_SPEC_FOLDER; delete process.env.VISUAL_STORYBOOK_URL; delete process.env.VISUAL_STORYBOOK_CLIP_SELECTOR; } if (this.#options.createJsonReportFiles) { new generateVisualReport({ directoryPath: this.folders.actualFolder }).generate(); } } }