"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.run = run;
const util_1 = require("../../commandUtil/util");
const cliUtil_1 = require("@mablhq-internal/runtime/core/cliUtil");
const testsUtil_1 = require("@mablhq-internal/runtime/core/execution/testsUtil");
const os_1 = __importDefault(require("os"));
const promises_1 = require("fs/promises");
const path_1 = __importDefault(require("path"));
const uuid_1 = require("uuid");
const ObservationDispatcher_1 = require("@mablhq-internal/runtime/observers/ObservationDispatcher");
const BrowserScreenshotObserver_1 = require("@mablhq-internal/runtime/observers/BrowserScreenshotObserver");
const PageLifecycleHandlerImpl_1 = __importDefault(require("@mablhq-internal/runtime/observers/PageLifecycleHandlerImpl"));
const Observer_1 = require("@mablhq-internal/runtime/observers/Observer");
const constants_1 = require("@mablhq-internal/runtime/core/constants");
const constants_2 = require("../../constants");
const browserTypes_1 = require("@mablhq-internal/runtime/core/browserTypes");
const loggingProvider_1 = require("@mablhq-internal/runtime/providers/logging/loggingProvider");
const mablApi_1 = require("@mablhq-internal/runtime/mablApi");
const execution_1 = require("@mablhq-internal/runtime/execution");
const defaultEnv_1 = require("@mablhq-internal/runtime/env/defaultEnv");
const runUtils_1 = require("./runUtils");
const mablApiClientFactory_1 = require("@mablhq-internal/runtime/api/mablApiClientFactory");
const execution = __importStar(require("@mablhq-internal/runtime/execution/index"));
exports.command = `run`;
exports.describe =
    "Run a test locally with headless support\nPre v1.0.0 'run' command behavior moved to 'run-legacy'";
exports.builder = (yargs) => {
    yargs
        .example('$0 tests run --id <id> --headless', 'run test locally by id in headless mode')
        .example('$0 tests run --run-id <test-run-id>', 'run a test locally using the test configuration from a cloud run')
        .option(constants_1.CommandArgId, {
        describe: 'The id of the test to run',
        type: 'string',
    })
        .option(constants_2.CommandArgDebug, {
        describe: 'Enable debug mode, which increases log verbosity and captures additional debugging information',
        type: 'boolean',
        default: false,
        hidden: true,
    })
        .option(constants_2.CommandArgTestFile, {
        describe: 'the source file of the test to run',
        type: 'string',
        hidden: true,
    })
        .option(constants_1.CommandArgTestRunId, {
        describe: 'The id of the test run to pull config from',
        type: 'string',
    })
        .option(constants_1.CommandArgFromPlanId, {
        describe: `The plan id to get a list of tests from.
Note: running tests with the --from-plan-id option is not a plan run. Advanced plan configurations, such as stages, browser settings or shared variables, are not applied.`,
        type: 'string',
    })
        .option(constants_1.CommandArgMablBranch, {
        describe: 'Mabl branch to run test against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgMablBranchChangesOnly, {
        describe: 'Only execute tests changed on specified mabl branch',
        type: 'boolean',
        default: false,
    })
        .implies(constants_2.CommandArgMablBranchChangesOnly, constants_1.CommandArgMablBranch)
        .option(constants_1.CommandArgLabelsInclude, {
        describe: 'Space delimited test labels. Run tests that match any label.',
        type: 'array',
        conflicts: [constants_1.CommandArgId],
    })
        .option(constants_1.CommandArgLabelsExclude, {
        describe: 'Space delimited test labels. Exclude tests that match any label.',
        type: 'array',
        conflicts: [constants_1.CommandArgId],
    })
        .option(constants_1.CommandArgWorkspaceId, {
        alias: constants_2.CommandArgAliases.WorkspaceId,
        describe: 'Workspace to run against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgApplicationId, {
        alias: constants_2.CommandArgAliases.ApplicationId,
        describe: `Mabl application used to report test results.`,
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgEnvironmentId, {
        alias: constants_2.CommandArgAliases.EnvironmentId,
        describe: `Mabl environment to run under. Specify to ensure the test runs with environment variables and the latest find information.
Note: Setting the environment does not override the default URL. Please use the (—url) command to override the URL.`,
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgLocale, {
        describe: 'Locale to run the test in, e.g. en-US',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgTimezoneID, {
        describe: 'Identifier of the timezone to run the test in, e.g. America/Buenos_Aires',
        nargs: 1,
        type: 'string',
    })
        .option('width', {
        describe: 'Set the browser width in pixels',
        default: constants_2.DEFAULT_BROWSER_WIDTH,
        nargs: 1,
        type: 'number',
    })
        .option('height', {
        describe: 'Set the browser height in pixels',
        default: constants_2.DEFAULT_BROWSER_HEIGHT,
        nargs: 1,
        type: 'number',
    })
        .option(constants_1.CommandArgUrl, {
        alias: constants_2.CommandArgAliases.Url,
        describe: 'URL to run the mabl test against',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgCredentials, {
        alias: constants_2.CommandArgAliases.Credentials,
        describe: 'Credentials ID to run the test with',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgDataTableId, {
        describe: 'Run test using all the scenarios from the Data table ID',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgScenarioId, {
        describe: 'Run test using an specific scenario',
        nargs: 1,
        type: 'string',
    })
        .option(constants_1.CommandArgBasicAuthCredentials, {
        alias: constants_2.CommandArgAliases.BasicAuthCredentials,
        describe: 'Basic Authentication credentials for adding basic auth headers to test run',
        nargs: 1,
        type: 'string',
    })
        .option('headless', {
        describe: 'Should the test run headless or not',
        default: false,
        type: 'boolean',
    })
        .option('highlights', {
        describe: 'Highlight elements before interacting with them. Highlighting introduces a small delay for each step.',
        default: false,
        type: 'boolean',
    })
        .option('keep-browser-open', {
        describe: 'Prevent the browser from closing at the end of a test.',
        default: false,
        type: 'boolean',
    })
        .option(constants_2.CommandArgReporter, {
        describe: 'Output test run information with specified reporter.',
        type: 'string',
        choices: [constants_1.Reporter.Mochawesome, constants_1.Reporter.Mabl],
    })
        .option(constants_2.CommandArgReporterOptions, {
        describe: 'Reporter options as comma separated key/values pairs. e.g. "reportDir=path/to,json=true"',
        hidden: true,
        type: 'string',
    })
        .implies(constants_2.CommandArgReporterOptions, constants_2.CommandArgReporter)
        .option(constants_2.CommandArgBrowserDisableIsolation, {
        hidden: true,
        describe: 'Disable cross origin isolation',
        default: false,
        type: 'boolean',
    })
        .option(constants_2.CommandArgBrowserIgnoreCertificateErrors, {
        hidden: true,
        describe: 'Ignore certificate errors',
        default: false,
        type: 'boolean',
    })
        .option(constants_2.CommandArgBrowserEnableExtensions, {
        describe: 'Enable browser extensions on [Chrome] and [Edge]',
        default: false,
        type: 'boolean',
    })
        .option(constants_2.CommandArgHttpHeaders, {
        describe: 'Space delimited HTTP headers added to browser requests (e.g. "x-header:foo")',
        type: 'array',
    })
        .option(constants_2.CommandArgUserAgent, {
        describe: 'Emulates a custom user-agent (e.g. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4651.0 Safari/537.36")',
        type: 'string',
    })
        .option(constants_2.CommandArgTestInteractionSpeed, {
        describe: 'Set the speed that mabl interacts with webpages. Overrides test run settings if specified.',
        type: 'string',
        choices: Object.keys(mablApi_1.TestParameters.PageLoadWaitEnum).map((pageLoadWait) => pageLoadWait.toLowerCase()),
    })
        .option(constants_2.CommandArgBrowser, {
        describe: `Target browser to execute the test against, must be one of ${constants_2.ValidBrowserTypesForLocalRuns}, defaults to ${browserTypes_1.DefaultBrowserType}`,
        type: 'string',
        nargs: 1,
        choices: constants_2.ValidBrowserTypesForLocalRuns,
        hidden: true,
    })
        .option(constants_2.CommandArgUseTestExecutionProxy, {
        describe: 'Route test HTTP traffic through a local proxy',
        type: 'boolean',
        hidden: true,
    })
        .option(constants_2.CommandArgVerbose, {
        describe: 'Enable verbose output',
        type: 'boolean',
        default: false,
        hidden: true,
    })
        .option(constants_2.CommandArgTraMode, {
        describe: 'Runtime recovery mode (uses workspace setting when not specified)',
        type: 'string',
        choices: ['off', 'fail_at_end', 'pass'],
        hidden: true,
    })
        .option('allow-billable-features', {
        describe: 'Allow execution of billable features like AI assertions in CLI runs',
        type: 'boolean',
        default: false,
    })
        .option('artifacts-dir', {
        describe: 'Directory to write observation artifacts (screenshots). Defaults to a temp directory.',
        type: 'string',
        nargs: 1,
    })
        .check((argv) => {
        (0, testsUtil_1.validateRunCommandWithLabels)(argv[constants_1.CommandArgId], argv[constants_1.CommandArgLabelsInclude], argv[constants_1.CommandArgLabelsExclude], argv[constants_1.CommandArgTestRunId], argv[constants_1.CommandArgFromPlanId], true, argv[constants_2.CommandArgTestFile]);
        if (argv[constants_2.CommandArgDataTableId] &&
            !argv[constants_1.CommandArgId] &&
            !argv[constants_1.CommandArgFromPlanId]) {
            throw new Error(`The ${constants_2.CommandArgDataTableId} option can only be used with the ${constants_1.CommandArgId} or the ${constants_1.CommandArgFromPlanId} option`);
        }
        if (argv[constants_2.CommandArgDataTableId] && argv[constants_2.CommandArgScenarioId]) {
            throw new Error(`The ${constants_2.CommandArgScenarioId} option cannot be used with the ${constants_2.CommandArgDataTableId} option`);
        }
        const httpHeaders = argv[constants_2.CommandArgHttpHeaders];
        (0, util_1.validateArrayInputs)(httpHeaders, 'HTTP headers must be SPACE delimited, e.g. "--http-headers "foo:bar" "baz:qux"');
        argv[constants_2.CommandArgHttpHeaders] = (0, util_1.validateValuePairInputs)('HTTP header', httpHeaders);
        return true;
    });
};
const exitCodeOnError = 1;
exports.handler = (0, cliUtil_1.failWrapper)(run, exitCodeOnError);
async function run(parsed, killProcessOnFailure = true, apiClient) {
    const commandStartTime = Date.now();
    let workspaceId;
    try {
        workspaceId = await (0, util_1.getWorkspaceId)(parsed);
    }
    catch {
    }
    const extraHttpHeaders = {};
    (parsed['http-headers'] ?? []).forEach((header) => {
        const headerParts = header.split(':');
        if (headerParts.length === 2 && headerParts[0] && headerParts[1]) {
            extraHttpHeaders[headerParts[0].toLowerCase()] = headerParts[1];
        }
    });
    const browserType = (0, testsUtil_1.parseBrowserType)(parsed[constants_2.CommandArgBrowser]);
    let scenario;
    if (parsed[constants_2.CommandArgScenarioId]) {
        apiClient ??= await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
        scenario = await apiClient.getScenario(parsed[constants_2.CommandArgScenarioId]);
        if (!scenario) {
            throw new Error(`Scenario with ID ${parsed[constants_2.CommandArgScenarioId]} not found`);
        }
    }
    const parsedLocale = parsed[constants_2.CommandArgLocale];
    const parsedTimezone = parsed[constants_2.CommandArgTimezoneID];
    const localizationOptions = parsedLocale || parsedTimezone
        ? {
            locale: parsedLocale,
            timezone_identifier: parsedTimezone,
        }
        : undefined;
    const testRunnerConfig = {
        _cliCreated: true,
        allowLocalTestBilling: parsed['allow-billable-features'],
        basicAuthCredentialsId: parsed[constants_1.CommandArgBasicAuthCredentials],
        branchName: parsed['mabl-branch'],
        branchChangesOnly: parsed['branch-changes-only'],
        browserType,
        credentialsId: parsed['credentials-id'],
        dataTableVariables: scenario,
        debug: parsed[constants_2.CommandArgDebug],
        disableIsolation: parsed[constants_2.CommandArgBrowserDisableIsolation],
        enableExtensions: parsed[constants_2.CommandArgBrowserEnableExtensions],
        enableLink: false,
        environmentId: parsed[constants_1.CommandArgEnvironmentId],
        extraHttpHeaders,
        filterHttpRequests: false,
        fromPlanId: parsed['from-plan-id'],
        headless: parsed.headless,
        height: parsed.height,
        highlights: parsed.highlights,
        ignoreCertificateErrors: parsed[constants_2.CommandArgBrowserIgnoreCertificateErrors],
        interactionSpeed: parsed[constants_2.CommandArgTestInteractionSpeed],
        keepBrowserOpen: parsed['keep-browser-open'],
        labelsExclude: parsed['exclude-labels'],
        labelsInclude: parsed.labels,
        localizationOptions,
        logLevel: parsed[constants_2.CommandArgDebug] ? loggingProvider_1.LogLevel.Debug : undefined,
        runId: parsed['run-id'],
        testFile: parsed[constants_2.CommandArgTestFile],
        testId: parsed.id,
        url: parsed.url,
        useTestExecutionProxy: parsed[constants_2.CommandArgUseTestExecutionProxy],
        testRecoveryAgentMode: parsed[constants_2.CommandArgTraMode] ??
            mablApi_1.TestRecoveryAgentModeEnum.Off,
        userAgent: parsed[constants_2.CommandArgUserAgent],
        usingDataTableId: parsed[constants_2.CommandArgDataTableId],
        width: parsed.width,
        workspaceId,
    };
    apiClient ??= await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
    loggingProvider_1.logger.info('Warming up test runner...');
    const artifactsDir = parsed['artifacts-dir'] ??
        path_1.default.join(os_1.default.tmpdir(), 'mabl-artifacts', (0, uuid_1.v4)());
    await (0, promises_1.mkdir)(artifactsDir, { recursive: true });
    (0, ObservationDispatcher_1.setObservationsArtifactsPath)(artifactsDir);
    loggingProvider_1.logger.info(`Artifacts directory: ${artifactsDir}`);
    const observerConfig = createLocalObserverConfig(testRunnerConfig);
    const mablTestsRunner = await execution.createBrowserTestsRunner(testRunnerConfig, apiClient, observerConfig);
    const results = await mablTestsRunner.run();
    if (mablTestsRunner?.mablTestRunners !== undefined) {
        mablTestsRunner.mablTestRunners
            .filter((tr) => tr instanceof execution_1.MablTestRunner)
            .forEach((tr) => {
            const testContext = tr.testContext;
            (0, testsUtil_1.cleanupTestResources)(testContext);
        });
    }
    await (0, runUtils_1.logTestResults)(results, parsed, commandStartTime, generateRunCommandTemplate(parsed, results), apiClient, browserType, parsed[constants_2.CommandArgVerbose]);
    if (!testRunnerConfig.keepBrowserOpen) {
        if (!results.success && killProcessOnFailure) {
            process.exit(1);
        }
    }
    return results;
}
function createLocalObserverConfig(testRunnerConfig) {
    const observers = [];
    if (testRunnerConfig.testRecoveryAgentMode !== mablApi_1.TestRecoveryAgentModeEnum.Off) {
        observers.push(new BrowserScreenshotObserver_1.BrowserScreenshotObserver(true, 0, ObservationDispatcher_1.NoOpDispatcher, false));
    }
    const pageLifecycleHandler = new PageLifecycleHandlerImpl_1.default(observers);
    return {
        observers,
        postRunCallback: Observer_1.captureApplicationState,
        browserLaunchInfo: {
            isHeadless: testRunnerConfig.headless ?? false,
            browserPreferences: {
                plugins: {
                    always_open_pdf_externally: true,
                },
                profile: {
                    default_content_setting_values: {
                        notifications: 1,
                        automatic_downloads: 1,
                    },
                },
            },
        },
        pageLifecycleHandler,
    };
}
function generateRunCommandTemplate(parsed, testResults) {
    const testResult = testResults.testResults[0];
    let templateCommand = `${defaultEnv_1.SCRIPT_NAME} ${parsed._.join(' ')} \\\n`;
    if (testResult?.rerunConfig?.environmentId) {
        templateCommand = `${templateCommand} --${constants_1.CommandArgEnvironmentId} ${testResult?.rerunConfig?.environmentId} \\\n`;
    }
    if (testResult?.rerunConfig?.credentialsId) {
        templateCommand = `${templateCommand} --${constants_1.CommandArgCredentials} ${testResult?.rerunConfig?.credentialsId} \\\n`;
    }
    if (testResult?.rerunConfig?.url) {
        templateCommand = `${templateCommand} --${constants_1.CommandArgUrl} ${testResult?.rerunConfig?.url} \\\n`;
    }
    if (testResult?.rerunConfig?.branch) {
        templateCommand = `${templateCommand} --${constants_1.CommandArgMablBranch} ${testResult.rerunConfig.branch} \\\n`;
    }
    const testIdVal = parsed.id ? parsed.id : '<TEST-ID>';
    templateCommand = `${templateCommand} --${constants_1.CommandArgId} ${testIdVal}`;
    return templateCommand;
}
