@augment-vir/test
Version:
A universal testing suite that works with Mocha style test runners _and_ Node.js's built-in test runner.
130 lines (129 loc) • 5.6 kB
JavaScript
import { assert } from '@augment-vir/assert';
import { addSuffix, log } from '@augment-vir/common';
import { writeFileAndDir } from '@augment-vir/node';
import { expect } from '@playwright/test';
import { compareImages, defaultImageComparisonOptions, encodePng, } from '@virmator/test/dist/web-screenshot-plugin/compare-images.js';
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { relative } from 'node:path';
import { assertTestContext, assertWrapTestContext, TestEnv, } from '../augments/universal-testing-suite/universal-test-context.js';
/** This is used for type extraction because Playwright does not export the types we need. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function extractScreenshotMethod() {
assert.never('this function should not be executed, it is only used for types');
// eslint-disable-next-line @typescript-eslint/unbound-method
return expect({}).toHaveScreenshot;
}
/**
* Default internal options for {@link LocatorScreenshotOptions}, used in {@link expectScreenshot}
*
* @category Internal
*/
export const defaultScreenshotOptions = {
animations: 'disabled',
caret: 'hide',
timeout: 10_000,
scale: 'css',
threshold: 0.1,
maxDiffPixelRatio: 0.08,
};
async function takeScreenshotBuffer(testContext, options = {}) {
if (options.locator) {
/** The locator expectation has different options than the page expectation. */
return await options.locator.screenshot({
...defaultScreenshotOptions,
...options,
});
}
else {
assertTestContext(testContext, TestEnv.Playwright);
return await testContext.page.screenshot({
...defaultScreenshotOptions,
...options,
});
}
}
/** @returns The path that the screenshot was saved to. */
async function saveScreenshotBuffer(testContext, screenshotBuffer, screenshotBaseName) {
assertTestContext(testContext, TestEnv.Playwright);
const screenshotPath = getScreenshotPath(testContext, screenshotBaseName);
await writeFileAndDir(screenshotPath, screenshotBuffer);
return screenshotPath;
}
/**
* Get the path to save the given screenshot file name to.
*
* @category Internal
*/
export function getScreenshotPath(testContext, screenshotBaseName) {
assertTestContext(testContext, TestEnv.Playwright);
const screenshotFileName = addSuffix({
value: screenshotBaseName,
suffix: '.png',
});
return testContext.testInfo.snapshotPath(screenshotFileName);
}
/**
* Take and immediately save a screenshot.
*
* @category Internal
* @returns The path that the screenshot was saved to.
*/
export async function takeScreenshot(testContext, options) {
return await saveScreenshotBuffer(testContext, await takeScreenshotBuffer(testContext, options), options.screenshotBaseName);
}
/**
* Similar to Playwright's `expect().toHaveScreenshot` but allows images to have different sizes and
* has default comparison threshold options that are wide enough to allow testing between different
* operating systems without failure (usually).
*
* @category Internal
*/
export async function expectPlaywrightScreenshot(testContext, options) {
assertTestContext(testContext, TestEnv.Playwright);
const currentScreenshotBuffer = await takeScreenshotBuffer(testContext, options);
const screenshotFilePath = getScreenshotPath(testContext, options.screenshotBaseName);
async function writeNewScreenshot() {
log.mutate(`Updated screenshot: ${relative(process.cwd(), screenshotFilePath)}`);
await saveScreenshotBuffer(testContext, currentScreenshotBuffer, options.screenshotBaseName);
}
async function writeExpectationScreenshot(contents, fileName) {
const filePath = assertWrapTestContext(testContext, TestEnv.Playwright).testInfo.outputPath(addSuffix({
value: fileName,
suffix: '.png',
}));
await writeFileAndDir(filePath, contents);
}
if (existsSync(screenshotFilePath)) {
if (testContext.testInfo.config.updateSnapshots === 'changed') {
await writeNewScreenshot();
}
}
else {
if (testContext.testInfo.config.updateSnapshots !== 'none') {
await writeNewScreenshot();
}
await writeExpectationScreenshot(currentScreenshotBuffer, 'actual');
throw new Error(`Baseline screenshot not found: ${screenshotFilePath}. Re-run with --update-snapshots to create it.`);
}
const baseScreenshotBuffer = await readFile(screenshotFilePath);
const result = await compareImages({
baseImageBuffer: baseScreenshotBuffer,
currentImageBuffer: currentScreenshotBuffer,
userOptions: {
threshold: defaultScreenshotOptions.threshold,
maxDiffPixelRatio: defaultScreenshotOptions.maxDiffPixelRatio,
},
});
if (!result.passed) {
if (process.env.CI) {
await writeExpectationScreenshot(encodePng(result.basePng), 'expected');
await writeExpectationScreenshot(encodePng(result.currentPng), 'actual');
await writeExpectationScreenshot(encodePng(result.diffPng), 'diff');
throw new Error(`Screenshot mismatch: ${screenshotFilePath}\n diff=${result.diffPixelCount}px (${(result.diffRatio * 100).toFixed(3)}%) (limit: ${(defaultImageComparisonOptions.maxDiffPixelRatio * 100).toFixed(3)}%). Run with --update-snapshots to update screenshot.`);
}
else {
await writeNewScreenshot();
}
}
}