"use strict";
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScreenshotErrorReporter = exports.ScreenshotError = void 0;
const node_crypto_1 = require("node:crypto");
const fs = __importStar(require("node:fs"));
const os = __importStar(require("node:os"));
const path = __importStar(require("node:path"));
const DiffUtils = __importStar(require("./diff-utils.js"));
const paths_js_1 = require("./paths.js");
class ScreenshotError extends Error {
    // The max length of the summary is 4000, but we need to leave some room for
    // the rest of the HTML formatting (e.g. <pre> and </pre>).
    static SUMMARY_LENGTH_CUTOFF = 3900;
    static errors = [];
    screenshots;
    screenshotPath;
    constructor(screenshots, message, cause, screenshotPath) {
        message = [message, cause?.message, cause?.cause?.message].filter(x => x).join('\n\n');
        super(message);
        this.cause = cause;
        this.stack = cause?.stack ?? '';
        this.screenshots = screenshots;
        // To show Diffs mocha Spec reporter expects some properties.
        // See node_modules/mocha/lib/reporters/base.js.
        // @ts-expect-error forwarding error properties for Mocha.
        this.showDiff = cause?.showDiff;
        // @ts-expect-error forwarding error properties for Mocha.
        this.actual = cause?.actual;
        // @ts-expect-error forwarding error properties for Mocha.
        this.expected = cause?.expected;
        // @ts-expect-error forwarding error properties for Mocha.
        this.operator = cause?.operator;
        this.screenshotPath = screenshotPath;
    }
    /**
     * Creates a ScreenshotError when a reference golden does not exists.
     */
    static fromGeneratedScreenshot(message, generatedImgPath) {
        const screenshots = {
            generated: { filePath: this.stashArtifact(generatedImgPath, 'generated') },
        };
        const screenshotPath = path.join('test/goldens', path.relative(path.join(paths_js_1.GEN_DIR, 'test/.generated'), generatedImgPath));
        return new ScreenshotError(screenshots, message, undefined, screenshotPath);
    }
    /**
     * Creates a ScreenshotError when a generated screenshot is different from
     * the golden.
     */
    static fromScreenshotAssertionError(error, goldenImgPath, generatedImgPath, diffImgPath) {
        const screenshots = {
            expected_image: { filePath: this.stashArtifact(goldenImgPath, 'expected') },
            actual_image: { filePath: this.stashArtifact(generatedImgPath, 'actual') },
            image_diff: { filePath: this.stashArtifact(diffImgPath, 'diff') },
        };
        const screenshotPath = path.relative(paths_js_1.SOURCE_ROOT, goldenImgPath);
        return new ScreenshotError(screenshots, undefined, error, screenshotPath);
    }
    /**
     * Creates a ScreenshotError when an unexpected error occurs. Screenshots are
     * taken for both the inspected page and the DevTools page.
     */
    static fromBase64Images(error, inspectedPageScreenshot, devToolsPageScreenshot, collectedScreenshots) {
        if (!inspectedPageScreenshot || !devToolsPageScreenshot) {
            console.error('No artifacts to save.');
            return error;
        }
        const screenshots = {
            inspectedPage: { filePath: this.saveArtifact(inspectedPageScreenshot) },
            devToolsPage: { filePath: this.saveArtifact(devToolsPageScreenshot) },
            ...ScreenshotError.saveArtifacts(collectedScreenshots),
        };
        return new ScreenshotError(screenshots, undefined, error);
    }
    static saveArtifacts(collectedScreenshots) {
        const screenshots = {};
        for (const name in collectedScreenshots) {
            screenshots[name] = { filePath: this.saveArtifact(collectedScreenshots[name]) };
        }
        return screenshots;
    }
    /**
     * Costructs a summary for Milo at resultdb publication time.
     */
    toMiloSummary() {
        if ('expected_image' in this.screenshots) {
            // no summary; autogenerated by Milo based on artifact name convention
            return '';
        }
        if ('generated' in this.screenshots) {
            // TODO(liviurau): embed image once Milo supports it
            return '<pre>' + this.message.slice(0, ScreenshotError.SUMMARY_LENGTH_CUTOFF) +
                '</pre><p>Screenshot generated (see below)</p>';
        }
        // TODO(liviurau): embed images once Milo supports it
        const message = (this.message + '\n\n' + this.stack);
        const assertionDiff = DiffUtils.resultAssertionsDiff([this]);
        const diffText = DiffUtils.formatDiffText(assertionDiff);
        let summary = DiffUtils.formatSummary(message, diffText, ScreenshotError.SUMMARY_LENGTH_CUTOFF);
        summary += '<p>Unexpected error. See inspected and DevTools pages screenshots below.</p>';
        return summary;
    }
    /**
     * Copy artifacts in tmp folder so they remain available
     * at resultdb publication time.
     */
    static stashArtifact(originalFile, tag) {
        const stashedFileName = tag + '-' + path.basename(originalFile);
        const artifactPath = path.join(os.tmpdir(), stashedFileName);
        fs.copyFileSync(originalFile, artifactPath);
        return artifactPath;
    }
    /**
     * Save base64 image in tmp folder to make it available at resultdb
     * publication time.
     */
    static saveArtifact(base64Image) {
        base64Image = base64Image.replace(/^data:image\/png;base64,/, '');
        const fileName = (0, node_crypto_1.createHash)('sha256').update(base64Image).digest('hex');
        const artifactPath = path.join(os.tmpdir(), fileName);
        fs.writeFileSync(artifactPath, base64Image, { encoding: 'base64' });
        return artifactPath;
    }
}
exports.ScreenshotError = ScreenshotError;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const ScreenshotErrorReporter = function (baseReporterDecorator) {
    const JSON_PATH = path.join(paths_js_1.GEN_DIR, 'test', '.generated', 'errors.js');
    baseReporterDecorator(this);
    this.onRunComplete = () => {
        const screenshotErrors = ScreenshotError.errors.splice(0).map(
        /* eslint-disable-next-line @typescript-eslint/naming-convention */
        ({ screenshotPath, screenshots: { expected_image, actual_image, image_diff } }) => ({ screenshotPath, expected_image, actual_image, image_diff }));
        fs.writeFileSync(JSON_PATH, `window.SCREENSHOT_ERRORS = ${JSON.stringify(screenshotErrors)}`);
    };
};
exports.ScreenshotErrorReporter = ScreenshotErrorReporter;
exports.ScreenshotErrorReporter.$inject = ['baseReporterDecorator'];
//# sourceMappingURL=screenshot-error.js.map