@wdio/image-comparison-core
Version:
Image comparison core module for @wdio/visual-service - WebdriverIO visual testing framework
901 lines (900 loc) • 44.1 kB
JavaScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
vi.mock('node:fs', async () => {
const actual = await vi.importActual('node:fs');
return {
...actual,
existsSync: vi.fn(),
mkdirSync: vi.fn(),
};
});
import logger from '@wdio/logger';
import { buildBaseExecuteCompareOptions, buildFolderOptions, calculateDprData, canUseBidiScreenshot, checkAndroidChromeDriverScreenshot, checkAndroidNativeWebScreenshot, checkTestInBrowser, checkTestInMobileBrowser, createConditionalProperty, executeNativeClick, extractCommonCheckVariables, formatFileName, getAddressBarShadowPadding, getAndCreatePath, getBase64ScreenshotSize, getBooleanOption, getDevicePixelRatio, getIosBezelImageNames, getMethodOrWicOption, getMobileScreenSize, getMobileViewPortPosition, getToolBarShadowPadding, hasResizeDimensions, isObject, isStorybook, loadBase64Html, logAllDeprecatedCompareOptions, updateVisualBaseline, } from './utils.js';
import { IMAGE_STRING } from '../mocks/image.js';
import { DEVICE_RECTANGLES } from './constants.js';
import { getMobileWebviewClickAndDimensions } from '../clientSideScripts/getMobileWebviewClickAndDimensions.js';
import { checkMetaTag } from '../clientSideScripts/checkMetaTag.js';
vi.mock('../clientSideScripts/injectWebviewOverlay.js', () => ({
injectWebviewOverlay: Symbol('injectWebviewOverlay'),
}));
vi.mock('../clientSideScripts/getMobileWebviewClickAndDimensions.js', () => ({
getMobileWebviewClickAndDimensions: Symbol('getMobileWebviewClickAndDimensions'),
}));
vi.mock('../clientSideScripts/checkMetaTag.js', () => ({
checkMetaTag: Symbol('checkMetaTag'),
}));
const log = logger('test');
vi.mock('@wdio/logger', () => import(join(process.cwd(), '__mocks__', '@wdio/logger')));
vi.mock('@wdio/globals', () => ({
browser: {
execute: vi.fn(),
browsingContextCaptureScreenshot: vi.fn(),
getWindowHandle: vi.fn(),
}
}));
describe('utils', () => {
const createMockBrowserInstance = () => {
return {
execute: vi.fn(),
browsingContextCaptureScreenshot: vi.fn(),
getWindowHandle: vi.fn(),
isBidi: true,
getOrientation: vi.fn().mockResolvedValue('PORTRAIT'),
getWindowSize: vi.fn().mockResolvedValue({ width: 375, height: 667 }),
getUrl: vi.fn().mockResolvedValue('http://example.com'),
url: vi.fn(),
};
};
describe('getAndCreatePath', () => {
const folder = join(process.cwd(), '/.tmp/utils');
beforeEach(() => {
vi.mocked(existsSync).mockClear();
});
it('should create the folder and return the folder name for a device that needs to have its own folder', () => {
const options = {
browserName: '',
deviceName: 'deviceName',
isMobile: true,
savePerInstance: true,
};
const expectedFolderName = join(folder, options.deviceName);
vi.mocked(existsSync).mockReturnValueOnce(false);
expect(existsSync(expectedFolderName)).toMatchSnapshot();
vi.mocked(existsSync).mockReturnValue(true);
expect(getAndCreatePath(folder, options)).toEqual(expectedFolderName);
expect(existsSync(expectedFolderName)).toMatchSnapshot();
});
it('should create the folder and return the folder name for a browser that needs to have its own folder', () => {
const options = {
browserName: 'browser',
deviceName: '',
isMobile: false,
savePerInstance: true,
};
const expectedFolderName = join(folder, `desktop_${options.browserName}`);
vi.mocked(existsSync).mockReturnValueOnce(false);
expect(existsSync(expectedFolderName)).toMatchSnapshot();
vi.mocked(existsSync).mockReturnValue(true);
expect(getAndCreatePath(folder, options)).toEqual(expectedFolderName);
expect(existsSync(expectedFolderName)).toMatchSnapshot();
});
it('should create the folder and return the folder name for a browser', () => {
const options = {
browserName: 'browser',
deviceName: '',
isMobile: false,
savePerInstance: false,
};
vi.mocked(existsSync).mockReturnValueOnce(false);
expect(existsSync(folder)).toMatchSnapshot();
vi.mocked(existsSync).mockReturnValue(true);
expect(getAndCreatePath(folder, options)).toEqual(folder);
expect(existsSync(folder)).toMatchSnapshot();
});
});
describe('formatFileName', () => {
const formatFileOptions = {
browserName: '',
browserVersion: '',
deviceName: '',
devicePixelRatio: 2,
formatImageName: '',
isMobile: false,
isTestInBrowser: true,
logName: '',
name: '',
outerHeight: 768,
outerWidth: 1366,
platformName: '',
platformVersion: '',
screenHeight: 900,
screenWidth: 1400,
tag: 'theTag',
};
it('should format a string with all options provided', () => {
formatFileOptions.formatImageName =
'browser.{browserName}-{browserVersion}-platform.{platformName}-{platformVersion}-dpr.{dpr}-{height}-{logName}-{name}-{tag}-{width}';
formatFileOptions.browserName = 'chrome';
formatFileOptions.browserVersion = '74';
formatFileOptions.logName = 'chrome-latest';
formatFileOptions.name = 'chrome-name';
formatFileOptions.platformName = 'osx';
formatFileOptions.platformVersion = '12';
expect(formatFileName(formatFileOptions)).toMatchSnapshot();
});
it('should format a string for mobile app', () => {
formatFileOptions.formatImageName = '{tag}-{mobile}-{dpr}-{width}x{height}';
formatFileOptions.deviceName = 'iPhoneX';
formatFileOptions.isMobile = true;
formatFileOptions.isTestInBrowser = false;
expect(formatFileName(formatFileOptions)).toMatchSnapshot();
});
it('should format a string for mobile browser', () => {
formatFileOptions.formatImageName = '{tag}-{mobile}-{dpr}-{width}x{height}';
formatFileOptions.browserName = 'chrome';
formatFileOptions.deviceName = 'iPhoneX';
formatFileOptions.isMobile = true;
formatFileOptions.isTestInBrowser = true;
expect(formatFileName(formatFileOptions)).toMatchSnapshot();
});
});
describe('checkTestInBrowser', () => {
const testCases = [
{ browserName: 'chrome', expected: true },
{ browserName: '', expected: false },
];
testCases.forEach(({ browserName, expected }) => {
it(`should return ${expected} for browserName:'${browserName}'`, () => {
expect(checkTestInBrowser(browserName)).toMatchSnapshot();
});
});
});
describe('checkTestInMobileBrowser', () => {
const testCases = [
{ isMobile: false, browserName: 'chrome', expected: false },
{ isMobile: true, browserName: '', expected: false },
{ isMobile: true, browserName: 'chrome', expected: true },
];
testCases.forEach(({ isMobile, browserName, expected }) => {
it(`should return ${expected} for isMobile:'${isMobile}' and browserName:'${browserName}'`, () => {
expect(checkTestInMobileBrowser(isMobile, browserName)).toMatchSnapshot();
});
});
});
describe('checkAndroidNativeWebScreenshot', () => {
const testCases = [
{ isAndroid: false, nativeWeb: false, expected: false },
{ isAndroid: true, nativeWeb: true, expected: true },
{ isAndroid: true, nativeWeb: false, expected: false },
];
testCases.forEach(({ isAndroid, nativeWeb, expected }) => {
it(`should return ${expected} for isAndroid:'${isAndroid}' and nativeWeb:${nativeWeb}`, () => {
expect(checkAndroidNativeWebScreenshot(isAndroid, nativeWeb)).toMatchSnapshot();
});
});
});
describe('checkAndroidChromeDriverScreenshot', () => {
const testCases = [
{ isAndroid: false, nativeWeb: false, expected: false },
{ isAndroid: true, nativeWeb: true, expected: false },
{ isAndroid: true, nativeWeb: false, expected: true },
];
testCases.forEach(({ isAndroid, nativeWeb, expected }) => {
it(`should return ${expected} for isAndroid:'${isAndroid}' and nativeWeb:${nativeWeb}`, () => {
expect(checkAndroidChromeDriverScreenshot(isAndroid, nativeWeb)).toMatchSnapshot();
});
});
});
describe('getAddressBarShadowPadding', () => {
const baseOptions = {
isAndroid: false,
isIOS: false,
isMobile: false,
browserName: '',
nativeWebScreenshot: false,
addressBarShadowPadding: 6,
addShadowPadding: false,
};
const testCases = [
{ ...baseOptions, browserName: 'chrome', description: 'desktop browser', expected: 0 },
{ ...baseOptions, isAndroid: true, description: 'Android app', expected: 0 },
{ ...baseOptions, isIOS: true, description: 'iOS app', expected: 0 },
{ ...baseOptions, isAndroid: true, nativeWebScreenshot: true, description: 'Android native web without shadow padding', expected: 0 },
{ ...baseOptions, isAndroid: true, nativeWebScreenshot: true, addShadowPadding: true, description: 'Android native web with shadow padding', expected: 6 },
{ ...baseOptions, isIOS: true, addShadowPadding: true, description: 'iOS with shadow padding', expected: 6 },
];
testCases.forEach(({ description, expected, ...options }) => {
it(`should return ${expected} for ${description}`, () => {
expect(getAddressBarShadowPadding(options)).toMatchSnapshot();
});
});
});
describe('getToolBarShadowPadding', () => {
const baseOptions = {
isAndroid: false,
isIOS: false,
isMobile: false,
browserName: '',
nativeWebScreenshot: false,
toolBarShadowPadding: 6,
addShadowPadding: false,
};
const testCases = [
{ ...baseOptions, browserName: 'chrome', description: 'desktop browser', expected: 0 },
{ ...baseOptions, isAndroid: true, isMobile: true, description: 'Android app', expected: 0 },
{ ...baseOptions, isIOS: true, isMobile: true, description: 'iOS app', expected: 0 },
{ ...baseOptions, isAndroid: true, isMobile: true, addShadowPadding: true, description: 'Android app with shadow padding', expected: 0 },
{ ...baseOptions, isAndroid: true, isMobile: true, browserName: 'chrome', addShadowPadding: true, description: 'Android browser with shadow padding', expected: 6 },
{ ...baseOptions, isIOS: true, isMobile: true, browserName: 'safari', addShadowPadding: true, description: 'iOS with shadow padding', expected: 15 },
];
testCases.forEach(({ description, expected, ...options }) => {
it(`should return ${expected} for ${description}`, () => {
expect(getToolBarShadowPadding(options)).toMatchSnapshot();
});
});
});
describe('calculateDprData', () => {
it('should multiply all number values by the dpr value', () => {
const data = {
a: 1,
b: 2,
1: 3,
a1: 9,
bool: true,
string: 'string',
};
expect(calculateDprData(data, 2)).toMatchSnapshot();
});
});
describe('getBase64ScreenshotSize', () => {
const testCases = [
{ dpr: undefined, description: 'default DPR' },
{ dpr: 2, description: 'DPR 2' },
];
testCases.forEach(({ dpr, description }) => {
it(`should get the screenshot size with ${description}`, () => {
expect(getBase64ScreenshotSize(IMAGE_STRING, dpr)).toMatchSnapshot();
});
});
});
describe('getDevicePixelRatio', () => {
const testCases = [
{ deviceSize: { width: 32, height: 64 }, expected: 1, description: 'equal width' },
{ deviceSize: { width: 16, height: 32 }, expected: 2, description: 'double width' },
{ deviceSize: { width: 17, height: 32 }, expected: 'rounded', description: 'rounded result' },
];
testCases.forEach(({ deviceSize, description }) => {
it(`should return correct ratio for ${description}`, () => {
expect(getDevicePixelRatio(IMAGE_STRING, deviceSize)).toMatchSnapshot();
});
});
});
describe('getIosBezelImageNames', () => {
const supportedDevices = [
'iphonex', 'iphonexs', 'iphonexsmax', 'iphonexr', 'iphone11', 'iphone11pro', 'iphone11promax',
'iphone12', 'iphone12mini', 'iphone12pro', 'iphone12promax', 'iphone13', 'iphone13mini',
'iphone13pro', 'iphone13promax', 'iphone14', 'iphone14plus', 'iphone14pro', 'iphone14promax',
'iphone15', 'ipadmini', 'ipadair', 'ipadpro11', 'ipadpro129',
];
supportedDevices.forEach((device) => {
it(`should return bezel image names for "${device}"`, () => {
expect(getIosBezelImageNames(device)).toMatchSnapshot();
});
});
it('should throw an error for unsupported device names', () => {
expect(() => getIosBezelImageNames('unsupportedDevice')).toThrowErrorMatchingSnapshot();
});
});
describe('isObject', () => {
const testCases = [
{ value: {}, expected: true, description: 'plain object' },
{ value: () => { }, expected: true, description: 'function' },
{ value: [], expected: true, description: 'array' },
{ value: null, expected: false, description: 'null' },
{ value: undefined, expected: false, description: 'undefined' },
{ value: 'string', expected: false, description: 'string' },
{ value: 123, expected: false, description: 'number' },
{ value: true, expected: false, description: 'boolean' },
];
testCases.forEach(({ value, expected, description }) => {
it(`should return ${expected} for ${description}`, () => {
expect(isObject(value)).toBe(expected);
});
});
});
describe('process.argv dependent functions', () => {
const originalArgv = [...process.argv];
const processArgvTests = [
{ functionName: 'isStorybook', testFunction: isStorybook, flag: '--storybook' },
{ functionName: 'updateVisualBaseline', testFunction: updateVisualBaseline, flag: '--update-visual-baseline' },
];
afterEach(() => {
process.argv = [...originalArgv];
});
processArgvTests.forEach(({ functionName, testFunction, flag }) => {
describe(functionName, () => {
it(`should return true when "${flag}" is in process.argv`, () => {
process.argv.push(flag);
expect(testFunction()).toBe(true);
});
it(`should return false when "${flag}" is not in process.argv`, () => {
process.argv = originalArgv.filter(arg => arg !== flag);
expect(testFunction()).toBe(false);
});
});
});
});
describe('getMobileScreenSize', () => {
let mockBrowserInstance;
beforeEach(() => {
mockBrowserInstance = createMockBrowserInstance();
});
const testCases = [
{
description: 'iOS in portrait',
isIOS: true,
orientation: 'PORTRAIT',
mockResponse: { screenSize: { width: 390, height: 844 } },
expected: { width: 390, height: 844 }
},
{
description: 'iOS in landscape',
isIOS: true,
orientation: 'LANDSCAPE',
mockResponse: { screenSize: { width: 390, height: 844 } },
expected: { width: 844, height: 390 }
},
{
description: 'Android in portrait',
isIOS: false,
orientation: 'PORTRAIT',
mockResponse: { realDisplaySize: '1080x2400' },
expected: { width: 1080, height: 2400 }
}
];
testCases.forEach(({ description, isIOS, orientation, mockResponse, expected }) => {
it(`should return correct screen size for ${description}`, async () => {
vi.mocked(mockBrowserInstance.getOrientation).mockResolvedValue(orientation);
vi.mocked(mockBrowserInstance.execute).mockResolvedValue(mockResponse);
const result = await getMobileScreenSize({
browserInstance: mockBrowserInstance,
isIOS,
isNativeContext: true
});
expect(result).toEqual(expected);
});
});
it('should fall back to web context for iOS', async () => {
vi.mocked(mockBrowserInstance.execute)
.mockRejectedValueOnce(new Error('Missing screenSize'))
.mockResolvedValueOnce({ width: 800, height: 1200 });
vi.mocked(mockBrowserInstance.getOrientation).mockResolvedValue('PORTRAIT');
const result = await getMobileScreenSize({
browserInstance: mockBrowserInstance,
isIOS: true,
isNativeContext: false
});
expect(result).toEqual({ width: 800, height: 1200 });
});
it('should fall back to getWindowSize in native context', async () => {
vi.mocked(mockBrowserInstance.execute).mockRejectedValue(new Error('Boom'));
vi.mocked(mockBrowserInstance.getOrientation).mockResolvedValue('PORTRAIT');
vi.mocked(mockBrowserInstance.getWindowSize).mockResolvedValue({ width: 123, height: 456 });
const result = await getMobileScreenSize({
browserInstance: mockBrowserInstance,
isIOS: true,
isNativeContext: true
});
expect(result).toEqual({ width: 123, height: 456 });
});
});
describe('loadBase64Html', () => {
let mockBrowserInstance;
beforeEach(() => {
mockBrowserInstance = createMockBrowserInstance();
});
it('should call browserInstance.execute with blob URL creation for all platforms', async () => {
await loadBase64Html({ browserInstance: mockBrowserInstance, isIOS: false });
expect(mockBrowserInstance.execute).toHaveBeenCalledTimes(1);
expect(mockBrowserInstance.execute).toHaveBeenCalledWith(expect.any(Function), expect.any(String));
});
it('should call browserInstance.execute with blob URL creation and checkMetaTag for iOS', async () => {
await loadBase64Html({ browserInstance: mockBrowserInstance, isIOS: true });
expect(mockBrowserInstance.execute).toHaveBeenCalledTimes(2);
expect(mockBrowserInstance.execute).toHaveBeenNthCalledWith(1, expect.any(Function), expect.any(String));
expect(mockBrowserInstance.execute).toHaveBeenNthCalledWith(2, checkMetaTag);
});
});
describe('executeNativeClick', () => {
let mockBrowserInstance;
const coords = { x: 100, y: 200 };
beforeEach(() => {
mockBrowserInstance = createMockBrowserInstance();
});
it('should call browserInstance.execute with "mobile: tap" on iOS', async () => {
await executeNativeClick({ browserInstance: mockBrowserInstance, isIOS: true, ...coords });
expect(mockBrowserInstance.execute).toHaveBeenCalledWith('mobile: tap', coords);
});
it('should call browserInstance.execute with "mobile: clickGesture" on Android (Appium 2)', async () => {
await executeNativeClick({ browserInstance: mockBrowserInstance, isIOS: false, ...coords });
expect(mockBrowserInstance.execute).toHaveBeenCalledWith('mobile: clickGesture', coords);
});
it('should fall back to "doubleClickGesture" when clickGesture fails (Appium 1)', async () => {
vi.mocked(mockBrowserInstance.execute)
.mockRejectedValueOnce(new Error('WebDriverError: Unknown mobile command: clickGesture'))
.mockResolvedValueOnce(undefined);
await executeNativeClick({ browserInstance: mockBrowserInstance, isIOS: false, ...coords });
expect(mockBrowserInstance.execute).toHaveBeenCalledWith('mobile: clickGesture', coords);
expect(mockBrowserInstance.execute).toHaveBeenCalledWith('mobile: doubleClickGesture', coords);
});
it('should throw the error if it\'s not a known Appium command error', async () => {
vi.mocked(mockBrowserInstance.execute).mockRejectedValue(new Error('Some unexpected error'));
await expect(executeNativeClick({ browserInstance: mockBrowserInstance, isIOS: false, ...coords }))
.rejects
.toThrowError('Some unexpected error');
});
});
describe('getMobileViewPortPosition', () => {
let mockBrowserInstance;
const baseOptions = {
isAndroid: false,
isIOS: true,
isNativeContext: false,
nativeWebScreenshot: true,
screenHeight: 800,
screenWidth: 400,
initialDeviceRectangles: DEVICE_RECTANGLES,
};
beforeEach(() => {
mockBrowserInstance = createMockBrowserInstance();
});
it('should return correct device rectangles for iOS WebView flow', async () => {
vi.mocked(mockBrowserInstance.execute)
.mockResolvedValueOnce(undefined) // loadBase64Html
.mockResolvedValueOnce(undefined) // checkMetaTag
.mockResolvedValueOnce(undefined) // injectWebviewOverlay
.mockResolvedValueOnce(undefined) // executeNativeClick
.mockResolvedValueOnce({ x: 150, y: 300, width: 100, height: 100 }); // getMobileWebviewClickAndDimensions
const result = await getMobileViewPortPosition({
browserInstance: mockBrowserInstance,
...baseOptions,
});
expect(mockBrowserInstance.getUrl).toHaveBeenCalled();
expect(mockBrowserInstance.url).toHaveBeenCalledWith('http://example.com');
expect(mockBrowserInstance.execute).toHaveBeenCalledWith(getMobileWebviewClickAndDimensions, '[data-test="ics-overlay"]');
expect(result).toMatchSnapshot();
});
it('should return initialDeviceRectangles if not WebView (native context)', async () => {
const result = await getMobileViewPortPosition({
browserInstance: mockBrowserInstance,
...baseOptions,
isNativeContext: true,
});
expect(result).toEqual(DEVICE_RECTANGLES);
expect(mockBrowserInstance.execute).not.toHaveBeenCalled();
});
it('should return initialDeviceRectangles if Android + not nativeWebScreenshot', async () => {
const result = await getMobileViewPortPosition({
browserInstance: mockBrowserInstance,
...baseOptions,
isAndroid: true,
isIOS: false,
nativeWebScreenshot: false,
});
expect(result).toEqual(DEVICE_RECTANGLES);
expect(mockBrowserInstance.getUrl).not.toHaveBeenCalled();
});
});
describe('canUseBidiScreenshot', () => {
it('should return true when both required methods are functions', () => {
const mockBrowserInstance = createMockBrowserInstance();
expect(canUseBidiScreenshot(mockBrowserInstance)).toBe(true);
});
it('should return false if browsingContextCaptureScreenshot is missing', () => {
const mockBrowserInstance = createMockBrowserInstance();
delete mockBrowserInstance.browsingContextCaptureScreenshot;
expect(canUseBidiScreenshot(mockBrowserInstance)).toBe(false);
});
it('should return false if getWindowHandle is missing', () => {
const mockBrowserInstance = createMockBrowserInstance();
delete mockBrowserInstance.getWindowHandle;
expect(canUseBidiScreenshot(mockBrowserInstance)).toBe(false);
});
it('should return false if either is not a function', () => {
const mockBrowserInstance = createMockBrowserInstance();
mockBrowserInstance.browsingContextCaptureScreenshot = 'notAFunction';
expect(canUseBidiScreenshot(mockBrowserInstance)).toBe(false);
});
});
describe('logAllDeprecatedCompareOptions', () => {
const allDeprecatedOptions = {
blockOutSideBar: true,
blockOutStatusBar: true,
blockOutToolBar: true,
createJsonReportFiles: true,
diffPixelBoundingBoxProximity: 5,
ignoreAlpha: true,
ignoreAntialiasing: true,
ignoreColors: true,
ignoreLess: true,
ignoreNothing: true,
rawMisMatchPercentage: true,
returnAllCompareData: true,
saveAboveTolerance: 100,
scaleImagesToSameSize: true,
};
it('should log a deprecation warning for each deprecated key', () => {
const warnSpy = vi.spyOn(log, 'warn').mockImplementation(() => { });
logAllDeprecatedCompareOptions(allDeprecatedOptions);
expect(warnSpy).toHaveBeenCalledTimes(1);
expect(warnSpy.mock.calls[0][0]).toMatchSnapshot();
});
it('should return a subset of CompareOptions with deprecated keys only', () => {
const result = logAllDeprecatedCompareOptions(allDeprecatedOptions);
expect(result).toMatchSnapshot();
});
});
describe('getMethodOrWicOption', () => {
const defaultOptions = { foo: 'bar', count: 42, isEnabled: true };
const testCases = [
{ method: { foo: 'baz' }, key: 'foo', expected: 'baz', description: 'value from method if defined' },
{ method: undefined, key: 'count', expected: 42, description: 'value from wic if method is undefined' },
{ method: { foo: undefined }, key: 'foo', expected: 'bar', description: 'value from wic if key in method is undefined' },
{ method: { isEnabled: false }, key: 'isEnabled', expected: false, description: 'boolean value from method if defined' },
{ method: {}, key: 'count', expected: 42, description: 'value from wic for missing key in method' },
];
testCases.forEach(({ method, key, expected, description }) => {
it(`should return ${description}`, () => {
const result = getMethodOrWicOption(method, defaultOptions, key);
expect(result).toBe(expected);
});
});
});
describe('getBooleanOption', () => {
const testCases = [
{ options: { autoElementScroll: true }, key: 'autoElementScroll', defaultValue: false, expected: true, description: 'boolean value when property exists' },
{ options: {}, key: 'disableBlinkingCursor', defaultValue: true, expected: true, description: 'default value when property does not exist' },
{ options: { autoElementScroll: 'truthy' }, key: 'autoElementScroll', defaultValue: false, expected: true, description: 'truthy values to true' },
{ options: { autoElementScroll: 0 }, key: 'autoElementScroll', defaultValue: true, expected: false, description: 'falsy values to false' },
{ options: { autoElementScroll: undefined }, key: 'autoElementScroll', defaultValue: true, expected: true, description: 'default when property is undefined' },
{ options: { autoElementScroll: null }, key: 'autoElementScroll', defaultValue: false, expected: false, description: 'default when property is null' },
];
testCases.forEach(({ options, key, defaultValue, expected, description }) => {
it(`should return ${description}`, () => {
const result = getBooleanOption(options, key, defaultValue);
expect(result).toBe(expected);
});
});
});
describe('createConditionalProperty', () => {
const testCases = [
{ condition: true, key: 'testKey', value: 'testValue', expected: { testKey: 'testValue' }, description: 'object with property when condition is true' },
{ condition: false, key: 'testKey', value: 'testValue', expected: {}, description: 'empty object when condition is false' },
{ condition: true, key: 'number', value: 42, expected: { number: 42 }, description: 'number value' },
{ condition: true, key: 'boolean', value: false, expected: { boolean: false }, description: 'boolean value' },
{ condition: true, key: 'object', value: { nested: 'value' }, expected: { object: { nested: 'value' } }, description: 'object value' },
{ condition: true, key: 'undefined', value: undefined, expected: { undefined: undefined }, description: 'undefined value' },
{ condition: true, key: 'null', value: null, expected: { null: null }, description: 'null value' },
];
testCases.forEach(({ condition, key, value, expected, description }) => {
it(`should return ${description}`, () => {
const result = createConditionalProperty(condition, key, value);
expect(result).toEqual(expected);
});
});
it('should always return empty object when condition is false regardless of value', () => {
const values = ['value', null, undefined, { complex: 'object' }];
values.forEach(value => {
expect(createConditionalProperty(false, 'key', value)).toEqual({});
});
});
});
describe('hasResizeDimensions', () => {
it('should return true when any value is non-zero', () => {
expect(hasResizeDimensions({ top: 10, right: 0, bottom: 0, left: 0 })).toBe(true);
expect(hasResizeDimensions({ top: 0, right: 0, bottom: 0, left: -5 })).toBe(true);
});
it('should return false when all values are zero', () => {
expect(hasResizeDimensions({ top: 0, right: 0, bottom: 0, left: 0 })).toBe(false);
});
it('should return falsy when input is falsy', () => {
expect(hasResizeDimensions(null)).toBe(null);
expect(hasResizeDimensions(undefined)).toBe(undefined);
expect(hasResizeDimensions(false)).toBe(false);
});
it('should return false for empty object', () => {
expect(hasResizeDimensions({})).toBe(false);
});
});
describe('extractCommonCheckVariables', () => {
const baseFolders = {
actualFolder: '/path/to/actual',
baselineFolder: '/path/to/baseline',
diffFolder: '/path/to/diff',
};
const baseInstanceData = {
browserName: 'chrome',
deviceName: 'iPhone 12',
deviceRectangles: { screenSize: { width: 390, height: 844 } },
isAndroid: false,
isMobile: true,
nativeWebScreenshot: true,
};
const baseWicOptions = {
autoSaveBaseline: true,
savePerInstance: false,
};
it('should extract all required common variables', () => {
const options = {
folders: baseFolders,
instanceData: baseInstanceData,
wicOptions: baseWicOptions,
};
const result = extractCommonCheckVariables(options);
expect(result).toEqual({
actualFolder: '/path/to/actual',
baselineFolder: '/path/to/baseline',
diffFolder: '/path/to/diff',
browserName: 'chrome',
deviceName: 'iPhone 12',
deviceRectangles: { screenSize: { width: 390, height: 844 } },
isAndroid: false,
isMobile: true,
isAndroidNativeWebScreenshot: true,
autoSaveBaseline: true,
savePerInstance: false,
});
});
it('should include optional fields when they exist', () => {
const options = {
folders: baseFolders,
instanceData: {
...baseInstanceData,
platformName: 'iOS',
isIOS: true,
},
wicOptions: {
...baseWicOptions,
isHybridApp: true,
},
};
const result = extractCommonCheckVariables(options);
expect(result).toEqual({
actualFolder: '/path/to/actual',
baselineFolder: '/path/to/baseline',
diffFolder: '/path/to/diff',
browserName: 'chrome',
deviceName: 'iPhone 12',
deviceRectangles: { screenSize: { width: 390, height: 844 } },
isAndroid: false,
isMobile: true,
isAndroidNativeWebScreenshot: true,
platformName: 'iOS',
isIOS: true,
autoSaveBaseline: true,
savePerInstance: false,
isHybridApp: true,
});
});
it('should exclude optional fields when they are falsy or undefined', () => {
const options = {
folders: baseFolders,
instanceData: {
...baseInstanceData,
platformName: null,
isIOS: undefined,
},
wicOptions: {
...baseWicOptions,
isHybridApp: undefined,
},
};
const result = extractCommonCheckVariables(options);
expect(result).toEqual({
actualFolder: '/path/to/actual',
baselineFolder: '/path/to/baseline',
diffFolder: '/path/to/diff',
browserName: 'chrome',
deviceName: 'iPhone 12',
deviceRectangles: { screenSize: { width: 390, height: 844 } },
isAndroid: false,
isMobile: true,
isAndroidNativeWebScreenshot: true,
autoSaveBaseline: true,
savePerInstance: false,
});
});
it('should handle partial optional fields correctly', () => {
const options = {
folders: baseFolders,
instanceData: {
...baseInstanceData,
platformName: 'Android',
isIOS: false,
},
wicOptions: baseWicOptions,
};
const result = extractCommonCheckVariables(options);
expect(result).toEqual({
actualFolder: '/path/to/actual',
baselineFolder: '/path/to/baseline',
diffFolder: '/path/to/diff',
browserName: 'chrome',
deviceName: 'iPhone 12',
deviceRectangles: { screenSize: { width: 390, height: 844 } },
isAndroid: false,
isMobile: true,
isAndroidNativeWebScreenshot: true,
platformName: 'Android',
isIOS: false,
autoSaveBaseline: true,
savePerInstance: false,
});
});
it('should handle Android device correctly', () => {
const options = {
folders: baseFolders,
instanceData: {
browserName: 'chromium',
deviceName: 'Pixel 4',
deviceRectangles: { screenSize: { width: 412, height: 869 } },
isAndroid: true,
isMobile: true,
nativeWebScreenshot: false,
},
wicOptions: baseWicOptions,
};
const result = extractCommonCheckVariables(options);
expect(result.isAndroid).toBe(true);
expect(result.isAndroidNativeWebScreenshot).toBe(false);
expect(result.browserName).toBe('chromium');
expect(result.deviceName).toBe('Pixel 4');
});
});
describe('buildFolderOptions', () => {
it('should build folder options from common check variables', () => {
const commonCheckVariables = {
actualFolder: '/path/to/actual',
baselineFolder: '/path/to/baseline',
diffFolder: '/path/to/diff',
browserName: 'chrome',
deviceName: 'iPhone 12',
deviceRectangles: { screenSize: { width: 390, height: 844 } },
isAndroid: false,
isMobile: true,
isAndroidNativeWebScreenshot: true,
autoSaveBaseline: true,
savePerInstance: false,
};
const result = buildFolderOptions({ commonCheckVariables });
expect(result).toMatchSnapshot();
});
it('should handle all properties correctly', () => {
const commonCheckVariables = {
actualFolder: '/test/actual',
baselineFolder: '/test/baseline',
diffFolder: '/test/diff',
browserName: 'firefox',
deviceName: 'Desktop',
deviceRectangles: { screenSize: { width: 1920, height: 1080 } },
isAndroid: true,
isMobile: false,
isAndroidNativeWebScreenshot: false,
autoSaveBaseline: false,
savePerInstance: true,
platformName: 'Android',
isIOS: false,
isHybridApp: true,
};
const result = buildFolderOptions({ commonCheckVariables });
expect(result).toMatchSnapshot();
});
});
describe('buildBaseExecuteCompareOptions', () => {
const baseCommonCheckVariables = {
actualFolder: '/path/to/actual',
baselineFolder: '/path/to/baseline',
diffFolder: '/path/to/diff',
browserName: 'chrome',
deviceName: 'iPhone 12',
deviceRectangles: { screenSize: { width: 390, height: 844 } },
isAndroid: false,
isMobile: true,
isAndroidNativeWebScreenshot: true,
autoSaveBaseline: true,
savePerInstance: false,
};
const baseWicCompareOptions = {
ignoreAlpha: false,
ignoreAntialiasing: false,
blockOutSideBar: true,
blockOutStatusBar: true,
blockOutToolBar: true,
};
const baseMethodCompareOptions = {
ignoreColors: false,
scaleImagesToSameSize: false,
};
it('should build base execute compare options', () => {
const result = buildBaseExecuteCompareOptions({
commonCheckVariables: baseCommonCheckVariables,
wicCompareOptions: baseWicCompareOptions,
methodCompareOptions: baseMethodCompareOptions,
devicePixelRatio: 2,
fileName: 'test-screenshot.png',
});
expect(result).toMatchSnapshot();
});
it('should handle element screenshot correctly (blockOut options set to false)', () => {
const result = buildBaseExecuteCompareOptions({
commonCheckVariables: baseCommonCheckVariables,
wicCompareOptions: baseWicCompareOptions,
methodCompareOptions: baseMethodCompareOptions,
devicePixelRatio: 2,
fileName: 'test-element.png',
isElementScreenshot: true,
});
expect(result.compareOptions.wic.blockOutSideBar).toBe(false);
expect(result.compareOptions.wic.blockOutStatusBar).toBe(false);
expect(result.compareOptions.wic.blockOutToolBar).toBe(false);
expect(result).toMatchSnapshot();
});
it('should include optional properties from commonCheckVariables', () => {
const commonCheckVariablesWithOptional = {
...baseCommonCheckVariables,
platformName: 'iOS',
isIOS: true,
isHybridApp: true,
};
const result = buildBaseExecuteCompareOptions({
commonCheckVariables: commonCheckVariablesWithOptional,
wicCompareOptions: baseWicCompareOptions,
methodCompareOptions: baseMethodCompareOptions,
devicePixelRatio: 2,
fileName: 'test-screenshot.png',
});
expect(result.platformName).toBe('iOS');
expect(result.isIOS).toBe(true);
expect(result.isHybridApp).toBe(true);
expect(result).toMatchSnapshot();
});
it('should add additional properties correctly', () => {
const additionalProperties = {
ignoreRegions: [{ x: 0, y: 0, width: 100, height: 100 }],
customProperty: 'test-value',
};
const result = buildBaseExecuteCompareOptions({
commonCheckVariables: baseCommonCheckVariables,
wicCompareOptions: baseWicCompareOptions,
methodCompareOptions: baseMethodCompareOptions,
devicePixelRatio: 2,
fileName: 'test-screenshot.png',
additionalProperties,
});
expect(result.ignoreRegions).toEqual([{ x: 0, y: 0, width: 100, height: 100 }]);
expect(result.customProperty).toBe('test-value');
expect(result).toMatchSnapshot();
});
it('should handle Android device correctly', () => {
const androidCommonCheckVariables = {
...baseCommonCheckVariables,
isAndroid: true,
isMobile: true,
isAndroidNativeWebScreenshot: false,
platformName: 'Android',
};
const result = buildBaseExecuteCompareOptions({
commonCheckVariables: androidCommonCheckVariables,
wicCompareOptions: baseWicCompareOptions,
methodCompareOptions: baseMethodCompareOptions,
devicePixelRatio: 1.5,
fileName: 'test-android.png',
});
expect(result.isAndroid).toBe(true);
expect(result.isAndroidNativeWebScreenshot).toBe(false);
expect(result.platformName).toBe('Android');
expect(result).toMatchSnapshot();
});
});
});