"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const browserLauncher_1 = require("../browserLauncher");
const pureUtil_1 = require("../util/pureUtil");
const loggingProvider_1 = require("../providers/logging/loggingProvider");
const sleep_1 = require("../util/sleep");
const Observer_1 = require("./Observer");
const ObservationDispatcher_1 = require("./ObservationDispatcher");
const logUtils_1 = require("../util/logUtils");
const PLAYWRIGHT_SERIALIZED_ERROR_STRING = 'JSHandle@error';
const PUPPETEER_UNSERIALIZABLE_REGEX = /JSHandle@object/g;
const UNSERIALIZABLE_REPLACEMENT_STRING = '[unserializable object]';
const OBSERVATION_ARTIFACT_NAME = 'console_logs.json';
class ConsoleLogObserver extends Observer_1.DeferredObserver {
    constructor(dispatcher) {
        super(Observer_1.ObservationType.BROWSER_LOG, dispatcher);
        this._capturedConsoleData = {
            console_logs: [],
            javascript_exceptions: [],
        };
        this.promiseQueue = [];
        this.promisesDidNotReturnInTime = `Waited over ${ObservationDispatcher_1.OBSERVATION_MAX_WAIT_TIME_MS}ms but promises did not return in time`;
        this.onPageCreated = this.onPageCreated.bind(this);
        this.onPageDestroyed = this.onPageDestroyed.bind(this);
        this.observeDeferredFunction = this.observeDeferredFunction.bind(this);
        this.clearCapturedConsoleData = this.clearCapturedConsoleData.bind(this);
    }
    onPageCreated(page, pageId) {
        page
            .on(browserLauncher_1.PageEvent.Console, (message) => {
            const promise = new Promise(async (resolve) => {
                try {
                    const timestamp = Date.now();
                    const titlePromise = page.title();
                    let text = message.text();
                    text = text.replace(PUPPETEER_UNSERIALIZABLE_REGEX, UNSERIALIZABLE_REPLACEMENT_STRING);
                    text = await this.maybeReplaceUnserializableConsoleArguments(text, message);
                    text = await this.maybeAppendErrorMessages(text, message);
                    const pageTitle = await Promise.race([titlePromise, (0, sleep_1.sleep)(2000)]);
                    if (typeof pageTitle === 'number') {
                        loggingProvider_1.logger.error('Page title was undefined');
                        return;
                    }
                    const serializedMessage = {
                        event_timestamp: timestamp,
                        level: message.type(),
                        page_uri: page.url(),
                        stack_trace: message.stackTrace(),
                        tab_id: pageId,
                        page_title: pageTitle,
                        text,
                    };
                    this._capturedConsoleData.console_logs.push(serializedMessage);
                }
                catch (e) {
                    loggingProvider_1.logger.error(`Error caught while handling PageEvent.Console page event: ${e}`);
                }
                finally {
                    resolve();
                }
            });
            this.promiseQueue.push(promise);
        })
            .on(browserLauncher_1.PageEvent.PageError, (capturedError) => {
            const promise = new Promise(async (resolve) => {
                try {
                    const timestamp = Date.now();
                    const titlePromise = page.title();
                    const pageTitle = await Promise.race([titlePromise, (0, sleep_1.sleep)(2000)]);
                    if (typeof pageTitle === 'number') {
                        loggingProvider_1.logger.error('Page title was undefined');
                        return;
                    }
                    this._capturedConsoleData.javascript_exceptions.push({
                        error_message: capturedError.stack
                            ? capturedError.stack
                            : capturedError.message,
                        event_timestamp: timestamp,
                        page_uri: page.url(),
                        page_title: pageTitle,
                        tab_id: pageId,
                    });
                }
                catch (e) {
                    loggingProvider_1.logger.error(`Error caught while handling PageEvent.PageError page event: ${e}`);
                }
                finally {
                    resolve();
                }
            });
            this.promiseQueue.push(promise);
        })
            .on(browserLauncher_1.PageEvent.Error, (_capturedError) => {
            loggingProvider_1.logger.error('Browser page under test crashed');
        });
        return Promise.resolve();
    }
    async maybeReplaceUnserializableConsoleArguments(text, message) {
        if (text.includes(PLAYWRIGHT_SERIALIZED_ERROR_STRING)) {
            const args = await Promise.all(message.args().map((arg) => arg.evaluate((arg) => {
                if (arg instanceof Error) {
                    return `[${arg.name}: ${arg.message}]`;
                }
                return;
            })));
            const jsHandleReplacements = args.filter(pureUtil_1.isDefined);
            jsHandleReplacements.forEach((replacement) => {
                text = text.replace(PLAYWRIGHT_SERIALIZED_ERROR_STRING, replacement);
            });
        }
        return text;
    }
    async maybeAppendErrorMessages(text, message) {
        const args = await Promise.all(message.args().map((arg) => arg.evaluate((arg) => {
            if (arg instanceof Error) {
                return arg.stack?.includes(arg.message)
                    ? arg.stack
                    : `Error: ${arg.message} at ${arg.stack}`;
            }
            return;
        })));
        const jsHandleReplacements = args.filter(pureUtil_1.isDefined);
        jsHandleReplacements.forEach((replacement) => {
            if (!text.includes(replacement)) {
                text += `\n${replacement}>`;
            }
        });
        return text;
    }
    async observeDeferredFunction(_context) {
        const startTime = Date.now();
        const copyOfPromises = [...this.promiseQueue];
        this.promiseQueue = [];
        if (copyOfPromises.length === 0) {
            return {};
        }
        await Promise.race([
            Promise.allSettled(copyOfPromises).then(logUtils_1.logPromiseSettledRejections),
            (0, sleep_1.sleep)(ObservationDispatcher_1.OBSERVATION_MAX_WAIT_TIME_MS),
        ]).then((result) => {
            if (typeof result === 'number') {
                loggingProvider_1.logger.warn(this.promisesDidNotReturnInTime);
            }
        });
        loggingProvider_1.logger.info(`Waited ${Date.now() - startTime}ms for PageFunction promises`);
        if (this.capturedConsoleData.console_logs.length === 0 &&
            this.capturedConsoleData.javascript_exceptions.length === 0) {
            loggingProvider_1.logger.warn(`Waited for ${copyOfPromises.length} PageFunction promises, but we did not store any in capturedConsoleData`);
            return {};
        }
        const copyOfData = {
            ...this.capturedConsoleData,
        };
        this.clearCapturedConsoleData();
        const consoleLogResults = JSON.stringify(copyOfData);
        const consoleLogResultsTmpFile = await this.writeTempFile(OBSERVATION_ARTIFACT_NAME, consoleLogResults);
        const filesByObservationType = {};
        if (consoleLogResultsTmpFile) {
            filesByObservationType[this.observationType] = {
                uri: consoleLogResultsTmpFile,
            };
        }
        return { filesByObservationType };
    }
    clearCapturedConsoleData() {
        this._capturedConsoleData.console_logs = [];
        this._capturedConsoleData.javascript_exceptions = [];
    }
    get capturedConsoleData() {
        return this._capturedConsoleData;
    }
}
exports.default = ConsoleLogObserver;
