| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- "use strict";
- var __create = Object.create;
- var __defProp = Object.defineProperty;
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
- var __getOwnPropNames = Object.getOwnPropertyNames;
- var __getProtoOf = Object.getPrototypeOf;
- var __hasOwnProp = Object.prototype.hasOwnProperty;
- var __export = (target, all) => {
- for (var name in all)
- __defProp(target, name, { get: all[name], enumerable: true });
- };
- var __copyProps = (to, from, except, desc) => {
- if (from && typeof from === "object" || typeof from === "function") {
- for (let key of __getOwnPropNames(from))
- if (!__hasOwnProp.call(to, key) && key !== except)
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
- }
- return to;
- };
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
- // If the importer is in node compatibility mode or this is not an ESM
- // file that has been converted to a CommonJS file using a Babel-
- // compatible transform (i.e. "__esModule" has not been set), then set
- // "default" to the CommonJS "module.exports" for node compatibility.
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
- mod
- ));
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
- var toMatchSnapshot_exports = {};
- __export(toMatchSnapshot_exports, {
- toHaveScreenshot: () => toHaveScreenshot,
- toHaveScreenshotStepTitle: () => toHaveScreenshotStepTitle,
- toMatchSnapshot: () => toMatchSnapshot
- });
- module.exports = __toCommonJS(toMatchSnapshot_exports);
- var import_fs = __toESM(require("fs"));
- var import_path = __toESM(require("path"));
- var import_utils = require("playwright-core/lib/utils");
- var import_utils2 = require("playwright-core/lib/utils");
- var import_utilsBundle = require("playwright-core/lib/utilsBundle");
- var import_util = require("../util");
- var import_matcherHint = require("./matcherHint");
- var import_globals = require("../common/globals");
- const NonConfigProperties = [
- "clip",
- "fullPage",
- "mask",
- "maskColor",
- "omitBackground",
- "timeout"
- ];
- class SnapshotHelper {
- constructor(testInfo, matcherName, locator, anonymousSnapshotExtension, configOptions, nameOrOptions, optOptions) {
- let name;
- if (Array.isArray(nameOrOptions) || typeof nameOrOptions === "string") {
- name = nameOrOptions;
- this.options = { ...optOptions };
- } else {
- const { name: nameFromOptions, ...options } = nameOrOptions;
- this.options = options;
- name = nameFromOptions;
- }
- this.name = Array.isArray(name) ? name.join(import_path.default.sep) : name || "";
- const resolvedPaths = testInfo._resolveSnapshotPaths(matcherName === "toHaveScreenshot" ? "screenshot" : "snapshot", name, "updateSnapshotIndex", anonymousSnapshotExtension);
- this.expectedPath = resolvedPaths.absoluteSnapshotPath;
- this.attachmentBaseName = resolvedPaths.relativeOutputPath;
- const outputBasePath = testInfo._getOutputPath(resolvedPaths.relativeOutputPath);
- this.legacyExpectedPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-expected");
- this.previousPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-previous");
- this.actualPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-actual");
- this.diffPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-diff");
- const filteredConfigOptions = { ...configOptions };
- for (const prop of NonConfigProperties)
- delete filteredConfigOptions[prop];
- this.options = {
- ...filteredConfigOptions,
- ...this.options
- };
- if (this.options._comparator) {
- this.options.comparator = this.options._comparator;
- delete this.options._comparator;
- }
- if (this.options.maxDiffPixels !== void 0 && this.options.maxDiffPixels < 0)
- throw new Error("`maxDiffPixels` option value must be non-negative integer");
- if (this.options.maxDiffPixelRatio !== void 0 && (this.options.maxDiffPixelRatio < 0 || this.options.maxDiffPixelRatio > 1))
- throw new Error("`maxDiffPixelRatio` option value must be between 0 and 1");
- this.matcherName = matcherName;
- this.locator = locator;
- this.updateSnapshots = testInfo.config.updateSnapshots;
- this.mimeType = import_utilsBundle.mime.getType(import_path.default.basename(this.expectedPath)) ?? "application/octet-stream";
- this.comparator = (0, import_utils.getComparator)(this.mimeType);
- this.testInfo = testInfo;
- this.kind = this.mimeType.startsWith("image/") ? "Screenshot" : "Snapshot";
- }
- createMatcherResult(message, pass, log) {
- const unfiltered = {
- name: this.matcherName,
- expected: this.expectedPath,
- actual: this.actualPath,
- diff: this.diffPath,
- pass,
- message: () => message,
- log
- };
- return Object.fromEntries(Object.entries(unfiltered).filter(([_, v]) => v !== void 0));
- }
- handleMissingNegated() {
- const isWriteMissingMode = this.updateSnapshots !== "none";
- const message = `A snapshot doesn't exist at ${this.expectedPath}${isWriteMissingMode ? `, matchers using ".not" won't write them automatically.` : "."}`;
- return this.createMatcherResult(message, true);
- }
- handleDifferentNegated() {
- return this.createMatcherResult("", false);
- }
- handleMatchingNegated() {
- const message = [
- import_utils2.colors.red(`${this.kind} comparison failed:`),
- "",
- indent("Expected result should be different from the actual one.", " ")
- ].join("\n");
- return this.createMatcherResult(message, true);
- }
- handleMissing(actual, step) {
- const isWriteMissingMode = this.updateSnapshots !== "none";
- if (isWriteMissingMode)
- writeFileSync(this.expectedPath, actual);
- step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-expected"), contentType: this.mimeType, path: this.expectedPath });
- writeFileSync(this.actualPath, actual);
- step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-actual"), contentType: this.mimeType, path: this.actualPath });
- const message = `A snapshot doesn't exist at ${this.expectedPath}${isWriteMissingMode ? ", writing actual." : "."}`;
- if (this.updateSnapshots === "all" || this.updateSnapshots === "changed") {
- console.log(message);
- return this.createMatcherResult(message, true);
- }
- if (this.updateSnapshots === "missing") {
- this.testInfo._hasNonRetriableError = true;
- this.testInfo._failWithError(new Error(message));
- return this.createMatcherResult("", true);
- }
- return this.createMatcherResult(message, false);
- }
- handleDifferent(actual, expected, previous, diff, header, diffError, log, step) {
- const output = [`${header}${indent(diffError, " ")}`];
- if (this.name) {
- output.push("");
- output.push(` Snapshot: ${this.name}`);
- }
- if (expected !== void 0) {
- writeFileSync(this.legacyExpectedPath, expected);
- step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-expected"), contentType: this.mimeType, path: this.expectedPath });
- }
- if (previous !== void 0) {
- writeFileSync(this.previousPath, previous);
- step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-previous"), contentType: this.mimeType, path: this.previousPath });
- }
- if (actual !== void 0) {
- writeFileSync(this.actualPath, actual);
- step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-actual"), contentType: this.mimeType, path: this.actualPath });
- }
- if (diff !== void 0) {
- writeFileSync(this.diffPath, diff);
- step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-diff"), contentType: this.mimeType, path: this.diffPath });
- }
- if (log?.length)
- output.push((0, import_util.callLogText)(log));
- else
- output.push("");
- return this.createMatcherResult(output.join("\n"), false, log);
- }
- handleMatching() {
- return this.createMatcherResult("", true);
- }
- }
- function toMatchSnapshot(received, nameOrOptions = {}, optOptions = {}) {
- const testInfo = (0, import_globals.currentTestInfo)();
- if (!testInfo)
- throw new Error(`toMatchSnapshot() must be called during the test`);
- if (received instanceof Promise)
- throw new Error("An unresolved Promise was passed to toMatchSnapshot(), make sure to resolve it by adding await to it.");
- if (testInfo._projectInternal.ignoreSnapshots)
- return { pass: !this.isNot, message: () => "", name: "toMatchSnapshot", expected: nameOrOptions };
- const configOptions = testInfo._projectInternal.expect?.toMatchSnapshot || {};
- const helper = new SnapshotHelper(
- testInfo,
- "toMatchSnapshot",
- void 0,
- "." + determineFileExtension(received),
- configOptions,
- nameOrOptions,
- optOptions
- );
- if (this.isNot) {
- if (!import_fs.default.existsSync(helper.expectedPath))
- return helper.handleMissingNegated();
- const isDifferent = !!helper.comparator(received, import_fs.default.readFileSync(helper.expectedPath), helper.options);
- return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
- }
- if (!import_fs.default.existsSync(helper.expectedPath))
- return helper.handleMissing(received, this._stepInfo);
- const expected = import_fs.default.readFileSync(helper.expectedPath);
- if (helper.updateSnapshots === "all") {
- if (!(0, import_utils.compareBuffersOrStrings)(received, expected))
- return helper.handleMatching();
- writeFileSync(helper.expectedPath, received);
- console.log(helper.expectedPath + " is not the same, writing actual.");
- return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
- }
- if (helper.updateSnapshots === "changed") {
- const result2 = helper.comparator(received, expected, helper.options);
- if (!result2)
- return helper.handleMatching();
- writeFileSync(helper.expectedPath, received);
- console.log(helper.expectedPath + " does not match, writing actual.");
- return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
- }
- const result = helper.comparator(received, expected, helper.options);
- if (!result)
- return helper.handleMatching();
- const receiver = (0, import_utils.isString)(received) ? "string" : "Buffer";
- const header = (0, import_matcherHint.matcherHint)(this, void 0, "toMatchSnapshot", receiver, void 0, void 0, void 0);
- return helper.handleDifferent(received, expected, void 0, result.diff, header, result.errorMessage, void 0, this._stepInfo);
- }
- function toHaveScreenshotStepTitle(nameOrOptions = {}, optOptions = {}) {
- let name;
- if (typeof nameOrOptions === "object" && !Array.isArray(nameOrOptions))
- name = nameOrOptions.name;
- else
- name = nameOrOptions;
- return Array.isArray(name) ? name.join(import_path.default.sep) : name || "";
- }
- async function toHaveScreenshot(pageOrLocator, nameOrOptions = {}, optOptions = {}) {
- const testInfo = (0, import_globals.currentTestInfo)();
- if (!testInfo)
- throw new Error(`toHaveScreenshot() must be called during the test`);
- if (testInfo._projectInternal.ignoreSnapshots)
- return { pass: !this.isNot, message: () => "", name: "toHaveScreenshot", expected: nameOrOptions };
- (0, import_util.expectTypes)(pageOrLocator, ["Page", "Locator"], "toHaveScreenshot");
- const [page, locator] = pageOrLocator.constructor.name === "Page" ? [pageOrLocator, void 0] : [pageOrLocator.page(), pageOrLocator];
- const configOptions = testInfo._projectInternal.expect?.toHaveScreenshot || {};
- const helper = new SnapshotHelper(testInfo, "toHaveScreenshot", locator, void 0, configOptions, nameOrOptions, optOptions);
- if (!helper.expectedPath.toLowerCase().endsWith(".png"))
- throw new Error(`Screenshot name "${import_path.default.basename(helper.expectedPath)}" must have '.png' extension`);
- (0, import_util.expectTypes)(pageOrLocator, ["Page", "Locator"], "toHaveScreenshot");
- const style = await loadScreenshotStyles(helper.options.stylePath);
- const timeout = helper.options.timeout ?? this.timeout;
- const expectScreenshotOptions = {
- locator,
- animations: helper.options.animations ?? "disabled",
- caret: helper.options.caret ?? "hide",
- clip: helper.options.clip,
- fullPage: helper.options.fullPage,
- mask: helper.options.mask,
- maskColor: helper.options.maskColor,
- omitBackground: helper.options.omitBackground,
- scale: helper.options.scale ?? "css",
- style,
- isNot: !!this.isNot,
- timeout,
- comparator: helper.options.comparator,
- maxDiffPixels: helper.options.maxDiffPixels,
- maxDiffPixelRatio: helper.options.maxDiffPixelRatio,
- threshold: helper.options.threshold
- };
- const hasSnapshot = import_fs.default.existsSync(helper.expectedPath);
- if (this.isNot) {
- if (!hasSnapshot)
- return helper.handleMissingNegated();
- expectScreenshotOptions.expected = await import_fs.default.promises.readFile(helper.expectedPath);
- const isDifferent = !(await page._expectScreenshot(expectScreenshotOptions)).errorMessage;
- return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
- }
- if (helper.updateSnapshots === "none" && !hasSnapshot)
- return helper.createMatcherResult(`A snapshot doesn't exist at ${helper.expectedPath}.`, false);
- const receiver = locator ? "locator" : "page";
- if (!hasSnapshot) {
- const { actual: actual2, previous: previous2, diff: diff2, errorMessage: errorMessage2, log: log2, timedOut: timedOut2 } = await page._expectScreenshot(expectScreenshotOptions);
- if (errorMessage2) {
- const header2 = (0, import_matcherHint.matcherHint)(this, locator, "toHaveScreenshot", receiver, void 0, void 0, timedOut2 ? timeout : void 0);
- return helper.handleDifferent(actual2, void 0, previous2, diff2, header2, errorMessage2, log2, this._stepInfo);
- }
- return helper.handleMissing(actual2, this._stepInfo);
- }
- const expected = await import_fs.default.promises.readFile(helper.expectedPath);
- expectScreenshotOptions.expected = helper.updateSnapshots === "all" ? void 0 : expected;
- const { actual, previous, diff, errorMessage, log, timedOut } = await page._expectScreenshot(expectScreenshotOptions);
- const writeFiles = () => {
- writeFileSync(helper.expectedPath, actual);
- writeFileSync(helper.actualPath, actual);
- console.log(helper.expectedPath + " is re-generated, writing actual.");
- return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
- };
- if (!errorMessage) {
- if (helper.updateSnapshots === "all" && actual && (0, import_utils.compareBuffersOrStrings)(actual, expected)) {
- console.log(helper.expectedPath + " is re-generated, writing actual.");
- return writeFiles();
- }
- return helper.handleMatching();
- }
- if (helper.updateSnapshots === "changed" || helper.updateSnapshots === "all")
- return writeFiles();
- const header = (0, import_matcherHint.matcherHint)(this, void 0, "toHaveScreenshot", receiver, void 0, void 0, timedOut ? timeout : void 0);
- return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header, errorMessage, log, this._stepInfo);
- }
- function writeFileSync(aPath, content) {
- import_fs.default.mkdirSync(import_path.default.dirname(aPath), { recursive: true });
- import_fs.default.writeFileSync(aPath, content);
- }
- function indent(lines, tab) {
- return lines.replace(/^(?=.+$)/gm, tab);
- }
- function determineFileExtension(file) {
- if (typeof file === "string")
- return "txt";
- if (compareMagicBytes(file, [137, 80, 78, 71, 13, 10, 26, 10]))
- return "png";
- if (compareMagicBytes(file, [255, 216, 255]))
- return "jpg";
- return "dat";
- }
- function compareMagicBytes(file, magicBytes) {
- return Buffer.compare(Buffer.from(magicBytes), file.slice(0, magicBytes.length)) === 0;
- }
- async function loadScreenshotStyles(stylePath) {
- if (!stylePath)
- return;
- const stylePaths = Array.isArray(stylePath) ? stylePath : [stylePath];
- const styles = await Promise.all(stylePaths.map(async (stylePath2) => {
- const text = await import_fs.default.promises.readFile(stylePath2, "utf8");
- return text.trim();
- }));
- return styles.join("\n").trim() || void 0;
- }
- // Annotate the CommonJS export names for ESM import in node:
- 0 && (module.exports = {
- toHaveScreenshot,
- toHaveScreenshotStepTitle,
- toMatchSnapshot
- });
|