wdio-visual-regression-tracker-service
Version:
WDIO Service for the Visual Regression Tracker
173 lines (145 loc) • 6.47 kB
text/typescript
import * as fs from 'fs';
import logger from '@wdio/logger';
import { VisualRegressionTracker, Config, TestStatus, BuildResponse } from '@visual-regression-tracker/sdk-js';
import TestRunResult from '@visual-regression-tracker/sdk-js/dist/testRunResult';
import VrtOptions from './VrtOptions';
export interface VrtTrackOptions {
diffTolerancePercent?: number;
os?: string;
browser?: string;
viewport?: string;
device?: string;
ignoreAreas?: [
{
x: number;
y: number;
width: number;
height: number;
},
];
captureFullPage?: boolean;
}
export default class WDIOServiceService {
private options: VrtOptions;
private vrt: VisualRegressionTracker;
private log: {
info: (msg: string) => void;
debug: (msg: string) => void;
error: (msg: string) => void;
warn: (msg: string) => void;
};
private browser: WebdriverIO.Browser;
private vrtCiName: string;
constructor(serviceOptions: VrtOptions, _caps, config: Partial<{ vrtCiName: string }>) {
this.options = serviceOptions;
this.log = logger('visual-regression-tracker-service');
this.log.info('Visual Regression Tracker Service Added');
this.log.debug(`Using Visual Regression Tracker config ${this.options as Config}`);
this.vrtCiName = config.vrtCiName;
}
async takeScreenshotFullPage(): Promise<string> {
// Full credits go to https://www.automatetheplanet.com/full-page-screenshots-webdriver-html2canvas/
// for this implementation
const isHtml2CanvasRegistered = async (): Promise<boolean> => {
try {
const result: boolean = await browser.execute('return typeof html2canvas !== "undefined"');
return result;
} catch {
return false;
}
};
if (!(await isHtml2CanvasRegistered())) {
const html2canvasjs = fs
.readFileSync(require.resolve('html2canvas').replace('html2canvas.js', 'html2canvas.min.js'))
.toString();
await browser.execute(html2canvasjs);
}
await browser.execute(`
function genScreenshot() {
let canvasImgContentDecoded;
html2canvas(document.body).then(function (canvas) {
window.canvasImgContentDecoded = canvas.toDataURL('image/png');
});
}
genScreenshot();
`);
await browser.waitUntil(async () => {
try {
const result: boolean = await browser.execute('return typeof canvasImgContentDecoded !== "undefined"');
return result;
} catch {
return false;
}
});
const image: string = await browser.execute('return canvasImgContentDecoded');
const validImageBase64 = image.replace('data:image/png;base64,', '');
return validImageBase64;
}
async before(_caps: unknown, _specs: string[], browser: WebdriverIO.Browser): Promise<BuildResponse> {
this.browser = browser;
this.log.debug('Connecting to Visual Regression Tracker');
this.vrt = new VisualRegressionTracker({ ...this.options, ciBuildId: this.vrtCiName });
this.log.debug('Adding vrtSnapshotPage command to browser');
this.browser.addCommand(
'vrtTrackPage',
async (name: string, options?: VrtTrackOptions): Promise<TestRunResult> => {
const imageBase64 = options?.captureFullPage
? await this.takeScreenshotFullPage()
: await browser.takeScreenshot();
this.log.debug(`Uploading snapshot for test ${name}`);
const result: TestRunResult = await this.vrt.track({
name,
imageBase64,
diffTollerancePercent: options?.diffTolerancePercent || this.options.diffTolerancePercent,
os: options?.os,
browser: options?.browser,
viewport: options?.viewport,
device: options?.device,
ignoreAreas: options?.ignoreAreas,
});
if (result.testRunResponse.status === TestStatus.unresolved) {
this.log.warn(
`Snapshot for ${name} showed ${result.testRunResponse.pixelMisMatchCount} pixel mismatch count.\nView the result at ${result.testRunResponse.url}`,
);
} else if (result.testRunResponse.status === TestStatus.new) {
this.log.warn(
`Snapshot for ${name} has no baseline image set\nUpdate the baseline at ${result.testRunResponse.url}`,
);
}
this.log.debug(`Test ${name} returned with status ${result.testRunResponse.status}`);
return result;
},
);
this.browser.addCommand(
'vrtTrackElement',
async function (name: string, options?: VrtTrackOptions): Promise<TestRunResult> {
const vrt = await (browser as any).vrtInstance();
if (!this.isExisting()) {
throw new Error(`Unable to find element ${this} for snapshot test ${name}`);
}
const imageBase64 = await browser.takeElementScreenshot(this.elementId);
const result: TestRunResult = await vrt.track({
name,
imageBase64,
diffTollerancePercent: options?.diffTolerancePercent || this.options.diffTolerancePercent,
os: options?.os,
browser: options?.browser,
viewport: options?.viewport,
device: options?.device,
ignoreAreas: options?.ignoreAreas,
});
return result;
},
true,
);
this.browser.addCommand('vrtInstance', (): VisualRegressionTracker => {
return this.vrt;
});
this.log.info('Starting Visual Regression Tracker');
return this.vrt.start();
}
async after(): Promise<void> {
this.log.info('Stopping Visual Regression Tracker');
return this.vrt.stop();
}
}