"use strict";
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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFinalUrl = getFinalUrl;
exports.createBrowser = createBrowser;
exports.createBrowserForExecutionEngine = createBrowserForExecutionEngine;
exports.getExtensionBackgroundPageWithCliTool = getExtensionBackgroundPageWithCliTool;
exports.cleanUpInitialPages = cleanUpInitialPages;
exports.prepareTrainerForSplitPlayback = prepareTrainerForSplitPlayback;
exports.runTheTest = runTheTest;
exports.editTheTest = editTheTest;
exports.cleanupTestResources = cleanupTestResources;
exports.validateRunEditCommand = validateRunEditCommand;
exports.validateRunCommandWithLabels = validateRunCommandWithLabels;
exports.pullDownTestRunConfig = pullDownTestRunConfig;
exports.extractTestRunConfig = extractTestRunConfig;
exports.getLinkCrawlerUrlOverride = getLinkCrawlerUrlOverride;
exports.headerArrayToRecord = headerArrayToRecord;
exports.calculateTotalTimeSeconds = calculateTotalTimeSeconds;
exports.milliSecondsToSeconds = milliSecondsToSeconds;
exports.logTestInfoIfPresent = logTestInfoIfPresent;
exports.toBasicHttpAuthenticationCredentials = toBasicHttpAuthenticationCredentials;
exports.parseBrowserType = parseBrowserType;
const cli_table3_1 = __importDefault(require("cli-table3"));
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const sleep_1 = require("../../util/sleep");
const browserLauncher_1 = require("../../browserLauncher");
const trainingSessionActions_1 = require("../messaging/actions/trainingSessionActions");
const logLineMessaging_1 = require("../messaging/logLineMessaging");
const messaging_1 = require("../messaging/messaging");
const mablApi_1 = require("../../mablApi");
const cliConfigProvider_1 = require("../../providers/cliConfigProvider");
const loggingProvider_1 = require("../../providers/logging/loggingProvider");
const fileUploadUtil_1 = require("../../util/fileUploadUtil");
const logUtils_1 = require("../../util/logUtils");
const browserTypes_1 = require("../browserTypes");
const constants_1 = require("../constants");
const datatableUtil_1 = require("../../util/datatableUtil");
const trainerUtil_1 = require("../../util/trainerUtil");
const chalk = require('chalk');
const linkCrawlerStartUrlParam = 'mabl.link_crawler.starting_url';
let RUNNING_TEST = false;
function getFinalUrl(test, parsedUrl) {
    const finalUrl = parsedUrl || test.url;
    if (!finalUrl && test.test_type !== mablApi_1.TestTypeEnum.Performance) {
        throw new Error('No default URL found on test, please specify a URL using the -u or --url flag');
    }
    return finalUrl;
}
async function launchBrowserInstance(options) {
    const optionsProxy = await maybeGetProxyOptions(options);
    if (optionsProxy) {
        options = {
            ...options,
            proxy: optionsProxy,
        };
    }
    try {
        const browser = await maybeLaunchBrowser(options);
        if (!browser) {
            throw new Error('Browser launch returned undefined');
        }
        return browser;
    }
    catch (error) {
        if (error.message.includes('Running as root without --no-sandbox is not supported')) {
            options.commandLineArgs?.push('--no-sandbox');
            messaging_1.mablEventEmitter.log('Unable to initialize browser with standard settings, attempting to run with --no-sandbox setting', Date.now(), logLineMessaging_1.LogLineColor.yellow);
            try {
                const browser = await maybeLaunchBrowser(options);
                if (!browser) {
                    throw new Error('Browser launch returned undefined');
                }
                return browser;
            }
            catch (retryError) {
                messaging_1.mablEventEmitter.log('Browser launch failed', Date.now(), logLineMessaging_1.LogLineColor.red);
                messaging_1.mablEventEmitter.log(retryError.message);
                throw retryError;
            }
        }
        messaging_1.mablEventEmitter.log('Browser launch failed', Date.now(), logLineMessaging_1.LogLineColor.red);
        messaging_1.mablEventEmitter.log(error.message);
        throw error;
    }
}
async function maybeGetProxyOptions(options) {
    if (options?.proxy) {
        return {
            server: options.proxy.server,
            username: options.proxy.username,
            password: options.proxy.password,
        };
    }
    const proxyServer = (await cliConfigProvider_1.CliConfigProvider.getCliConfig()).http.test
        .proxyHost;
    if (proxyServer) {
        return {
            server: proxyServer.href,
            username: proxyServer.username,
            password: proxyServer.password,
        };
    }
    return;
}
function maybeLaunchBrowser(options) {
    return browserLauncher_1.BrowserLauncherFactory.createRunner(options?.runnerType, options?.loggerFunc).launch(options);
}
async function createBrowser(engine, options) {
    const userDataDir = await engine.prepareBrowserPreferencesDirectory(options.windowPlacement);
    const browserEngineLaunchOptions = engine.getBrowserLaunchOptions(options);
    const browser = await launchBrowserInstance({
        ...options,
        ...browserEngineLaunchOptions,
        downloadPath: createDownloadDirectory(),
        userDataDir,
    });
    browser.on(browserLauncher_1.BrowserEvent.Disconnected, () => browser.removeBrowserPreferencesDirectory());
    return browser;
}
async function createBrowserForExecutionEngine(engine, proxyInfo, options) {
    const userDataDir = await engine.prepareBrowserPreferencesDirectory(undefined, proxyInfo);
    const launchOptions = engine.getExecutionEngineBrowserLaunchOptions(options, proxyInfo);
    const proxy = engine.getProxySpec(proxyInfo);
    (0, logUtils_1.logCliOutput)(loggingProvider_1.LogLevel.Info, `Launching browser with proxy spec: ${proxy ? JSON.stringify(proxy) : '<none>'}`);
    const maybeBrowser = await maybeLaunchBrowser({
        ...options,
        ...launchOptions,
        proxy,
        downloadPath: createDownloadDirectory(),
        userDataDir,
    });
    if (!maybeBrowser) {
        throw new Error('Unable to start browser session');
    }
    (0, logUtils_1.logCliOutput)(loggingProvider_1.LogLevel.Info, `Setting apiProxy server [${proxyInfo.httpProxy}]`);
    maybeBrowser.setApiProxy(proxyInfo.httpProxy);
    return maybeBrowser;
}
async function getExtensionBackgroundPageWithCliTool(browser) {
    const backgroundPage = await browser.getExtensionBackgroundPage((0, trainerUtil_1.getTrainerId)());
    if (backgroundPage) {
        await checkForMablCliTool(backgroundPage);
        await exposeMessagingFunctionToTrainer(backgroundPage, browser);
        return backgroundPage;
    }
    throw new Error('mabl test creation failure, unable to locate the mabl extension. Please try again');
}
async function checkForMablCliTool(backgroundPage) {
    let isReady = false;
    while (!isReady) {
        isReady = (await backgroundPage.evaluate(`!!window.mablCli`));
        if (!isReady) {
            await (0, sleep_1.sleep)(1000);
        }
    }
    return isReady;
}
async function cleanUpInitialPages(browser) {
    const currentPages = await browser.pages();
    const pagePromises = currentPages.map(async (page) => {
        const url = page.url();
        if (url === 'about:blank') {
            const pageCount = await browser.pages();
            if (pageCount.length > 1) {
                await page.close();
            }
        }
    });
    await Promise.all(pagePromises);
}
async function exposeMessagingFunctionToTrainer(backgroundPage, browser) {
    await backgroundPage
        .exposeFunction('postMessageToCli', (message) => handleExtensionMessage(message, browser))
        .catch((error) => messaging_1.mablEventEmitter.log('Error on the handle' + error));
}
function createRunLocalTestObject(test, flows, branchName, url, credentialsId, environment) {
    return {
        test,
        flows,
        branchName,
        url,
        credentialsId,
        environment,
    };
}
async function prepareTrainerForSplitPlayback(backgroundPage, test, flows, branchName, url, credentialsId) {
    const existingTestPayload = JSON.stringify(createRunLocalTestObject(test, flows, branchName, url, credentialsId));
    const script = `window.mablCli.remoteTest(${existingTestPayload});`;
    try {
        await backgroundPage.evaluate(script);
    }
    catch (error) {
        messaging_1.mablEventEmitter.log(chalk.red('Issue preparing the trainer for split playback'));
    }
}
async function runTheTest(trainingBrowser, backgroundPage, test, flows, branchName, url, credentialsId, environment) {
    const runLocalTest = createRunLocalTestObject(test, flows, branchName, url, credentialsId, environment);
    const script = `window.mablCli.runTest(${JSON.stringify(runLocalTest)});`;
    try {
        await backgroundPage.evaluate(script);
        RUNNING_TEST = true;
        while (RUNNING_TEST) {
            await (0, sleep_1.sleep)(1000);
            await cleanUpInitialPages(trainingBrowser);
        }
    }
    catch (error) {
        messaging_1.mablEventEmitter.log(chalk.red(error));
    }
}
async function editTheTest(backgroundPage, test, flows, branchName, url, credentialsId, environment) {
    const editLocalTest = {
        test,
        flows,
        branchName,
        url,
        credentialsId,
        environment,
    };
    const script = `window.mablCli.editTest(${JSON.stringify(editLocalTest)});`;
    try {
        await backgroundPage.evaluate(script);
    }
    catch (error) {
        messaging_1.mablEventEmitter.log(chalk.red(error));
    }
}
async function shutItDown(trainingBrowser) {
    await trainingBrowser.close().catch(() => 'Shutdown was a little bumpy');
}
const TRAINER_SHUTDOWN = 'TRAINER_SHUTDOWN';
const TRAINED_TEST_INFO = 'TRAINED_TEST_INFO';
const LOG_LINE_OUTPUT = 'LOG_LINE_OUTPUT';
const TRAINED_TEST_SAVED = 'TRAINED_TEST_SAVED';
function handleExtensionMessage(message, trainingBrowser) {
    switch (message.action) {
        case TRAINER_SHUTDOWN:
            const shutdownMessage = message;
            if (shutdownMessage.shutdown) {
                trainingSessionActions_1.TrainingEventEmitter.sessionClosed();
                setTimeout(shutItDown, 1000, trainingBrowser);
            }
            RUNNING_TEST = false;
            break;
        case TRAINED_TEST_SAVED:
            const trainedTestSavedMessage = message;
            if (!trainedTestSavedMessage.test.id ||
                !trainedTestSavedMessage.test.invariant_id) {
                break;
            }
            trainingSessionActions_1.TrainingEventEmitter.sessionSaved(trainedTestSavedMessage.test.id, trainedTestSavedMessage.test.invariant_id, trainedTestSavedMessage.branch, trainedTestSavedMessage.planId);
            break;
        case TRAINED_TEST_INFO:
            const trainedTestInfoMessage = message;
            if (!trainedTestInfoMessage.test.id ||
                !trainedTestInfoMessage.test.invariant_id) {
                break;
            }
            trainingSessionActions_1.TrainingEventEmitter.sessionSaved(trainedTestInfoMessage.test.id, trainedTestInfoMessage.test.invariant_id, trainedTestInfoMessage.branch, trainedTestInfoMessage.planId);
            const table = new cli_table3_1.default({
                head: [chalk.yellow('Saved Test Info')],
                wordWrap: true,
            });
            table.push([
                {
                    rowSpan: 1,
                    content: 'ID',
                    vAlign: 'center',
                },
                {
                    rowSpan: 1,
                    content: trainedTestInfoMessage.test.invariant_id,
                    vAlign: 'center',
                },
            ], [
                {
                    rowSpan: 1,
                    content: 'Name',
                    vAlign: 'center',
                },
                {
                    rowSpan: 1,
                    content: trainedTestInfoMessage.test.name,
                    vAlign: 'center',
                },
            ], [
                {
                    rowSpan: 1,
                    content: 'Branch',
                    vAlign: 'center',
                },
                {
                    rowSpan: 1,
                    content: trainedTestInfoMessage.branch,
                    vAlign: 'center',
                },
            ]);
            messaging_1.mablEventEmitter.log(table.toString());
            if (trainedTestInfoMessage.link) {
                messaging_1.mablEventEmitter.log(`View test: ${trainedTestInfoMessage.link}`);
            }
            messaging_1.mablEventEmitter.log('');
            break;
        case LOG_LINE_OUTPUT:
            const logLineMessage = message;
            if (logLineMessage.message) {
                switch (logLineMessage.level) {
                    case 'error':
                        messaging_1.mablEventEmitter.log(chalk.red(`[ERROR] ${logLineMessage.message}`), Date.now());
                        break;
                    case 'debug':
                        messaging_1.mablEventEmitter.debug(chalk.gray(logLineMessage.message), Date.now());
                        break;
                    default:
                        messaging_1.mablEventEmitter.log(logLineMessage.message, Date.now());
                }
            }
            break;
        default:
            break;
    }
}
function cleanupTestResources(testContext) {
    try {
        if (testContext) {
            testContext.getFileUploadDirs().forEach((dir) => (0, fileUploadUtil_1.removeUploadDirs)(dir));
            testContext.clearFileUploadDirs();
        }
    }
    catch (e) {
        const msg = `WARNING: error received when cleaning up after tests: ${e.message}`;
        (0, logUtils_1.logCliOutput)(loggingProvider_1.LogLevel.Warn, msg);
    }
}
function validateRunEditCommand(idArg, testRunIdArg) {
    if (!idArg && !testRunIdArg) {
        throw new Error(chalk.red(`Please specify a test ID (--${constants_1.CommandArgId}) or test run ID (--${constants_1.CommandArgTestRunId})\n`));
    }
    if (idArg && testRunIdArg) {
        throw new Error(chalk.red(`Command does not support specifying both the test id (--${constants_1.CommandArgId})  AND the test run id (--${constants_1.CommandArgTestRunId}), please only supply one\n`));
    }
    return true;
}
function validateRunCommandWithLabels(testId, suppliedLabelsInclude, suppliedLabelsExclude, testRunId, planId, checkForRunIdOrPlanId = false, testFile) {
    function cleanLabels(input) {
        const distinctLabels = new Set();
        input
            .map((label) => label.toString().trim().toLowerCase())
            .forEach((label) => distinctLabels.add(label));
        return [...distinctLabels];
    }
    const labelsInclude = cleanLabels(suppliedLabelsInclude ?? []);
    const labelsExclude = cleanLabels(suppliedLabelsExclude ?? []);
    if (labelsInclude.length > 0 && labelsExclude.length > 0) {
        const intersection = labelsInclude.filter((label) => labelsExclude.includes(label));
        if (intersection.length > 0) {
            throw new Error(`Include labels --${constants_1.CommandArgLabelsInclude} and exclude labels --${constants_1.CommandArgLabelsExclude} have overlapping values [${intersection.join(',')}]`);
        }
    }
    const maybeId = testId;
    if (checkForRunIdOrPlanId) {
        if (maybeId === undefined &&
            testRunId === undefined &&
            planId === undefined &&
            testFile === undefined &&
            labelsInclude.length === 0 &&
            labelsExclude.length === 0) {
            throw new Error(`Please provide at least one of --${constants_1.CommandArgId}, --${constants_1.CommandArgTestRunId},  --${constants_1.CommandArgLabelsInclude}, --${constants_1.CommandArgLabelsExclude}, --${constants_1.CommandArgFromPlanId}`);
        }
    }
    else if (maybeId === undefined &&
        labelsInclude.length === 0 &&
        labelsExclude.length === 0) {
        throw new Error(`Please provide at least one of --${constants_1.CommandArgId}, --${constants_1.CommandArgLabelsInclude}, --${constants_1.CommandArgLabelsExclude}`);
    }
    return true;
}
function buildTestRunConfig(testRun, filterHttpRequests, planRun) {
    const testDatatablevariables = testRun.journey_parameters?.user_variables &&
        (0, datatableUtil_1.variableRowAsScenario)(testRun.journey_parameters?.user_variables);
    return {
        basicAuthCredentialsId: testRun.journey_parameters?.http_auth_credentials_id ??
            (planRun?.run_policy?.http_auth_credentials_required
                ? planRun?.run_policy?.http_auth_credentials_id
                : undefined),
        branchName: testRun.journey_parameters?.source_control_tag,
        credentialsId: testRun.journey_parameters?.credentials_id ??
            (planRun?.run_policy?.credentials_required
                ? planRun?.run_policy?.credentials_id
                : undefined),
        dataTableVariables: testDatatablevariables,
        debug: testRun.debug,
        deviceEmulation: testRun.journey_parameters?.device_emulation,
        environmentId: testRun.journey_parameters?.deployment?.environment_id,
        extraHttpHeaders: planRun?.run_policy?.http_headers_required === true
            ? headerArrayToRecord(planRun?.run_policy?.http_headers)
            : undefined,
        filterHttpRequests,
        importedVariables: testRun.journey_parameters?.imported_variables,
        linkCrawlerStartingUrlOverride: getLinkCrawlerUrlOverride(testRun),
        localizationOptions: testRun.localization_options,
        pageLoadWait: testRun.journey_parameters?.page_load_wait,
        runId: testRun.id,
        testId: testRun.journey?.invariant_id,
        url: testRun.journey_parameters?.deployment?.uri,
    };
}
async function pullDownTestRunConfig(testRunId, apiClient) {
    const testRun = await apiClient.getTestRun(testRunId);
    const planRun = await apiClient.getPlanRun(testRun.parent_execution);
    return buildTestRunConfig(testRun, false, planRun);
}
async function extractTestRunConfig(executionMessage, apiClient) {
    const testRun = executionMessage.journey_run ??
        (await apiClient.getTestRun(executionMessage.journey_run_id));
    const planRun = executionMessage.plan_run;
    const maybeRunnerType = planRun?.run_policy
        ?.nodejs_runtime_variant
        ?
            planRun?.run_policy?.nodejs_runtime_variant
        : undefined;
    const config = buildTestRunConfig(testRun, true, planRun);
    config.runnerType = maybeRunnerType;
    config.testRecoveryAgentMode = executionMessage.test_recovery_agent_mode;
    if (executionMessage.test_type === mablApi_1.TestTypeEnum.Mobile) {
        const mobileMessage = executionMessage;
        config.mobileConfig = {
            ...config.mobileConfig,
            deviceName: mobileMessage.mobile_device.device_name,
            platformName: mobileMessage.mobile_device.platform,
            mobileAppFileId: mobileMessage.mobile_app_file_id,
        };
        if (mobileMessage.mobile_device.platform === mablApi_1.MobilePlatformEnum.Ios) {
            config.mobileConfig.platformVersion =
                mobileMessage.mobile_device.os_version;
        }
    }
    return config;
}
function getLinkCrawlerUrlOverride(journeyRun) {
    return journeyRun?.journey_configuration?.parameters?.find((param) => param.name === linkCrawlerStartUrlParam)?.value;
}
function headerArrayToRecord(headers) {
    return headers?.reduce((headers, header) => ({
        ...headers,
        [header.name]: header.value ?? '',
    }), {});
}
function calculateTotalTimeSeconds(startTimeMillis, endTimeMillis) {
    return (endTimeMillis - startTimeMillis) / 1000;
}
function milliSecondsToSeconds(timeMs) {
    return Math.round((timeMs ?? 0) / 100) / 10;
}
function logTestInfoIfPresent(description, infoArg) {
    if (infoArg !== undefined) {
        messaging_1.mablEventEmitter.log(chalk.cyan(description, chalk.magenta(infoArg.toString())));
    }
}
function createDownloadDirectory() {
    return fs.mkdtempSync(path.join(os.tmpdir(), `mablTestRun-${Date.now()}-`));
}
function toBasicHttpAuthenticationCredentials(credentials) {
    if (credentials?.properties?.username === undefined) {
        return undefined;
    }
    return {
        username: credentials.properties?.username ?? '',
        password: credentials.properties?.password ?? '',
    };
}
function parseBrowserType(browserType) {
    switch (browserType) {
        case 'edge':
            return browserTypes_1.BrowserType.Edge;
        case 'firefox':
            return browserTypes_1.BrowserType.Firefox;
        case 'chrome':
            return browserTypes_1.BrowserType.Chrome;
        case 'webkit':
            return browserTypes_1.BrowserType.Webkit;
        default:
            return browserTypes_1.DefaultBrowserType;
    }
}
