@wdio/image-comparison-core
Version:
Image comparison core module for @wdio/visual-service - WebdriverIO visual testing framework
187 lines (186 loc) • 8.87 kB
JavaScript
import { DEFAULT_COMPARE_OPTIONS, DEFAULT_FORMAT_STRING, DEFAULT_SHADOW, DEFAULT_TABBABLE_OPTIONS, FULL_PAGE_SCROLL_TIMEOUT, STORYBOOK_FORMAT_STRING, } from './constants.js';
import { logAllDeprecatedCompareOptions, isStorybook, getBooleanOption, createConditionalProperty, getMethodOrWicOption, } from './utils.js';
/**
* Determine the default options by merging user options with sensible defaults
*/
export function defaultOptions(options) {
const isStorybookMode = isStorybook();
return {
/**
* Module-specific options
*/
addressBarShadowPadding: options.addressBarShadowPadding ?? DEFAULT_SHADOW.ADDRESS_BAR,
autoElementScroll: getBooleanOption(options, 'autoElementScroll', true),
addIOSBezelCorners: options.addIOSBezelCorners ?? false,
autoSaveBaseline: options.autoSaveBaseline ?? true,
clearFolder: options.clearRuntimeFolder ?? false,
userBasedFullPageScreenshot: options.userBasedFullPageScreenshot ?? false,
enableLegacyScreenshotMethod: options.enableLegacyScreenshotMethod ?? false,
formatImageName: options.formatImageName ?? (isStorybookMode ? STORYBOOK_FORMAT_STRING : DEFAULT_FORMAT_STRING),
isHybridApp: options.isHybridApp ?? false,
// Running in storybook mode with multiple browsers can generate many images
// Defaulting this to true provides better overview for users
savePerInstance: options.savePerInstance ?? isStorybookMode,
toolBarShadowPadding: options.toolBarShadowPadding ?? DEFAULT_SHADOW.TOOL_BAR,
/**
* Module and method options
*/
disableBlinkingCursor: options.disableBlinkingCursor ?? false,
disableCSSAnimation: options.disableCSSAnimation ?? false,
enableLayoutTesting: options.enableLayoutTesting ?? false,
fullPageScrollTimeout: options.fullPageScrollTimeout ?? FULL_PAGE_SCROLL_TIMEOUT,
// Default to false for storybook mode as element screenshots use W3C protocol without scrollbars
// This also saves an extra webdriver call
hideScrollBars: getBooleanOption(options, 'hideScrollBars', !isStorybookMode),
waitForFontsLoaded: options.waitForFontsLoaded ?? true,
alwaysSaveActualImage: options.alwaysSaveActualImage ?? true,
/**
* Compare options (merged sequentially):
* 1. Default options (fallback)
* 2. Root compareOptions (deprecated but supported)
* 3. User-provided compareOptions
*/
compareOptions: {
...DEFAULT_COMPARE_OPTIONS,
...logAllDeprecatedCompareOptions(options),
...options.compareOptions,
},
/**
* Tabbable options with deep merging
*/
tabbableOptions: {
circle: {
...DEFAULT_TABBABLE_OPTIONS.circle,
...options.tabbableOptions?.circle,
},
line: {
...DEFAULT_TABBABLE_OPTIONS.line,
...options.tabbableOptions?.line,
},
},
};
}
/**
* Determine the screen method compare options with proper type safety
*/
export function screenMethodCompareOptions(options) {
return {
...createConditionalProperty('blockOutSideBar' in options, 'blockOutSideBar', options.blockOutSideBar),
...createConditionalProperty('blockOutStatusBar' in options, 'blockOutStatusBar', options.blockOutStatusBar),
...createConditionalProperty('blockOutToolBar' in options, 'blockOutToolBar', options.blockOutToolBar),
...methodCompareOptions(options),
};
}
/**
* Determine the method compare options with improved type safety
*/
export function methodCompareOptions(options) {
const compareOptionKeys = [
'blockOut',
'ignoreAlpha',
'ignoreAntialiasing',
'ignoreColors',
'ignoreLess',
'ignoreNothing',
'rawMisMatchPercentage',
'returnAllCompareData',
'saveAboveTolerance',
'scaleImagesToSameSize',
];
return compareOptionKeys.reduce((result, key) => {
if (key in options && options[key] !== undefined) {
result[key] = options[key]; // Type assertion needed due to union types
}
return result;
}, {});
}
/**
* Creates BeforeScreenshotOptions by extracting common options from method and wic configurations
*/
export function createBeforeScreenshotOptions(instanceData, methodOptions, wicOptions) {
return {
instanceData,
addressBarShadowPadding: wicOptions.addressBarShadowPadding,
disableBlinkingCursor: getMethodOrWicOption(methodOptions, wicOptions, 'disableBlinkingCursor') || false,
disableCSSAnimation: getMethodOrWicOption(methodOptions, wicOptions, 'disableCSSAnimation') || false,
enableLayoutTesting: getMethodOrWicOption(methodOptions, wicOptions, 'enableLayoutTesting') || false,
hideElements: methodOptions.hideElements || [],
noScrollBars: getMethodOrWicOption(methodOptions, wicOptions, 'hideScrollBars') || false,
removeElements: methodOptions.removeElements || [],
toolBarShadowPadding: wicOptions.toolBarShadowPadding,
waitForFontsLoaded: getMethodOrWicOption(methodOptions, wicOptions, 'waitForFontsLoaded') || false,
};
}
/**
* Builds AfterScreenshotOptions consistently across all commands
* Handles differences between native and web commands automatically
*/
export function buildAfterScreenshotOptions({ base64Image, folders, tag, isNativeContext, instanceData, enrichedInstanceData, beforeOptions, wicOptions }) {
// Use enriched data when available (web commands), fallback to instance data (native commands)
const dataSource = enrichedInstanceData || instanceData;
// Extract common properties with smart fallbacks
const { browserName, browserVersion, deviceName, platformName, platformVersion, } = dataSource;
// Handle dimension data - enriched data has nested structure, instance data is flat
const dimensions = enrichedInstanceData?.dimensions?.window;
const devicePixelRatio = dimensions?.devicePixelRatio ?? instanceData.devicePixelRatio;
const isLandscape = dimensions?.isLandscape ?? false;
const outerHeight = dimensions?.outerHeight;
const outerWidth = dimensions?.outerWidth;
const screenHeight = dimensions?.screenHeight ?? instanceData.deviceRectangles?.screenSize?.height;
const screenWidth = dimensions?.screenWidth ?? instanceData.deviceRectangles?.screenSize?.width;
// Handle metadata with smart defaults
const isMobile = enrichedInstanceData?.isMobile ?? instanceData.isMobile;
const isTestInBrowser = enrichedInstanceData?.isTestInBrowser ?? !isNativeContext;
const logName = enrichedInstanceData?.logName ?? instanceData.logName;
const name = enrichedInstanceData?.name ?? instanceData.name;
const { formatImageName, savePerInstance, alwaysSaveActualImage } = wicOptions;
const afterOptions = {
actualFolder: folders.actualFolder,
base64Image,
filePath: {
browserName,
deviceName,
isMobile,
savePerInstance,
},
fileName: {
browserName,
browserVersion,
deviceName,
devicePixelRatio: devicePixelRatio || NaN,
formatImageName,
isMobile,
isTestInBrowser,
logName,
name,
outerHeight: outerHeight || NaN,
outerWidth: outerWidth || NaN,
platformName,
platformVersion,
screenHeight: screenHeight || NaN,
screenWidth: screenWidth || NaN,
tag,
},
isLandscape,
isNativeContext,
platformName: instanceData.platformName,
alwaysSaveActualImage: alwaysSaveActualImage ?? true,
};
// Add browser state options only for web commands (when beforeOptions is provided)
if (beforeOptions) {
afterOptions.disableBlinkingCursor = beforeOptions.disableBlinkingCursor;
afterOptions.disableCSSAnimation = beforeOptions.disableCSSAnimation;
afterOptions.enableLayoutTesting = beforeOptions.enableLayoutTesting;
afterOptions.hideElements = beforeOptions.hideElements;
afterOptions.hideScrollBars = beforeOptions.noScrollBars;
afterOptions.removeElements = beforeOptions.removeElements;
}
return afterOptions;
}
/**
* Prepare ignore options for resemble.js comparison
*/
export function prepareIgnoreOptions(imageCompareOptions) {
const resembleIgnoreDefaults = ['alpha', 'antialiasing', 'colors', 'less', 'nothing'];
return resembleIgnoreDefaults.filter((option) => Object.keys(imageCompareOptions).find((key) => key.toLowerCase().includes(option) && imageCompareOptions[key]));
}