"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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const defaultEnv_1 = require("@mablhq-internal/runtime/env/defaultEnv");
const execution_1 = require("@mablhq-internal/runtime/execution");
const mobileUtil_1 = require("@mablhq-internal/mobile-runtime/execution/mobile/util/mobileUtil");
const systemChecks_1 = require("@mablhq-internal/mobile-runtime/execution/mobile/util/systemChecks");
const mablApi_1 = require("@mablhq-internal/runtime/mablApi");
const loggingProvider_1 = require("@mablhq-internal/runtime/providers/logging/loggingProvider");
const util_1 = require("../../commandUtil/util");
const cliUtil_1 = require("@mablhq-internal/runtime/core/cliUtil");
const constants_1 = require("@mablhq-internal/runtime/core/constants");
const constants_2 = require("../../constants");
const testsUtil_1 = require("@mablhq-internal/runtime/core/execution/testsUtil");
const runUtils_1 = require("./runUtils");
const MobileAppFileCache_1 = require("@mablhq-internal/runtime/util/MobileAppFileCache");
const fs = __importStar(require("fs"));
const resourceUtil_1 = require("@mablhq-internal/runtime/util/resourceUtil");
const mablApiClientFactory_1 = require("@mablhq-internal/runtime/api/mablApiClientFactory");
const chalk = require('chalk');
const mobileRuntime = require('@mablhq-internal/mobile-runtime/execution/MobileTestsRunner');
exports.command = `run-mobile`;
exports.describe = 'Run a mobile test locally';
exports.builder = (yargs) => {
    yargs
        .example('$0 tests run-mobile --id <id> --app-file <path> --platform <platform>', 'run mobile test locally by id')
        .option(constants_1.CommandArgId, {
        describe: 'The id of the test to run',
        type: 'string',
    })
        .option(constants_2.CommandArgMobileBuildFilePath, {
        alias: 'app-file',
        describe: 'The path to the mobile application binary',
        type: 'string',
    })
        .option(constants_1.CommandArgMobileBuildFileId, {
        describe: 'The ID of a previously uploaded mobile app file',
        type: 'string',
    })
        .option(constants_2.CommandArgMobilePlatform, {
        describe: 'The mobile platform',
        defaultDescription: 'inferred from app binary extension',
        type: 'string',
        choices: [mablApi_1.MobilePlatformEnum.Android, mablApi_1.MobilePlatformEnum.Ios],
    })
        .option(constants_1.CommandArgWorkspaceId, {
        alias: constants_2.CommandArgAliases.WorkspaceId,
        describe: 'Workspace to run 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_1.CommandArgEnvironmentId, {
        alias: constants_2.CommandArgAliases.EnvironmentId,
        describe: `Mabl environment to run under. Specify to ensure the test runs with environment variables.`,
        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_2.CommandArgReporter, {
        describe: 'Output test run information for specified reporter',
        hidden: true,
        type: 'string',
        choices: [constants_1.Reporter.Mochawesome],
    })
        .option(constants_1.CommandArgTestRunId, {
        describe: 'The id of the test run to pull config from',
        type: 'string',
    })
        .option(constants_2.CommandArgReporterOptions, {
        describe: 'Reporter options as comma separated key/values pairs. e.g. "reportDir=path/to,json=true"',
        hidden: true,
        type: 'string',
    })
        .option(constants_2.CommandArgMobileDeviceName, {
        describe: 'Full name of the device to use for the test',
        hidden: false,
        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_2.CommandArgRecordVideoPath, {
        hidden: true,
        describe: 'The path of a directory to save a video recording of the test to',
        type: 'string',
    })
        .option('allow-billable-features', {
        describe: 'Allow execution of billable features like AI assertions in CLI runs',
        type: 'boolean',
        default: false,
    })
        .implies(constants_2.CommandArgReporterOptions, constants_2.CommandArgReporter)
        .middleware(inferMobilePlatform)
        .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, undefined);
        validateMobileCommand(argv[constants_2.CommandArgMobileBuildFilePath], argv[constants_1.CommandArgMobileBuildFileId]);
        return true;
    });
};
const exitCodeOnError = 1;
exports.handler = (0, cliUtil_1.failWrapper)(run, exitCodeOnError);
function validateMobileCommand(appFile, appFileId) {
    if (appFile && appFileId) {
        throw new Error(`Only one of { --${constants_2.CommandArgMobileBuildFilePath}, --${constants_1.CommandArgMobileBuildFileId} } may be specified`);
    }
}
async function run(parsed) {
    const commandStartTime = Date.now();
    let workspaceId;
    try {
        workspaceId = await (0, util_1.getWorkspaceId)(parsed);
    }
    catch {
    }
    const parentDirOfNodeModulesDir = (0, resourceUtil_1.findNodeModulesDirectories)()[0];
    const mobileToolsInstalled = fs.existsSync((0, path_1.resolve)(parentDirOfNodeModulesDir, 'node_modules/appium-uiautomator2-driver'));
    if (!mobileToolsInstalled) {
        loggingProvider_1.logger.error(`[ERROR] Missing the mobile tools for running tests, run ${chalk.magenta('mabl config install mobile-tools')} to install them`);
        return;
    }
    let platformName;
    if (parsed[constants_1.CommandArgTestRunId]) {
        const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
        const testRun = await apiClient.getJourneyRun(parsed[constants_1.CommandArgTestRunId]);
        platformName = (0, runUtils_1.getPlatformFromTestRun)(testRun);
    }
    else if (parsed[constants_2.CommandArgMobilePlatform]) {
        platformName = (0, mobileUtil_1.parsePlatformName)(parsed[constants_2.CommandArgMobilePlatform]);
    }
    if (!platformName) {
        loggingProvider_1.logger.error(`[ERROR] Unable to determine mobile platform. Please provide a --${constants_2.CommandArgMobilePlatform} argument or a --${constants_1.CommandArgTestRunId} argument.`);
        return;
    }
    await validateSystemRequirements(platformName, parsed[constants_2.CommandArgMobileDeviceName]);
    const testRunnerConfig = {
        _cliCreated: true,
        allowLocalTestBilling: parsed['allow-billable-features'],
        branchName: parsed[constants_1.CommandArgMablBranch],
        branchChangesOnly: parsed[constants_2.CommandArgMablBranchChangesOnly],
        credentialsId: parsed[constants_1.CommandArgCredentials],
        environmentId: parsed[constants_1.CommandArgEnvironmentId],
        filterHttpRequests: false,
        fromPlanId: parsed[constants_1.CommandArgFromPlanId],
        labelsExclude: parsed[constants_1.CommandArgLabelsExclude],
        labelsInclude: parsed[constants_1.CommandArgLabelsInclude],
        runId: parsed[constants_1.CommandArgTestRunId],
        testId: parsed.id,
        workspaceId,
        mobileConfig: {
            mobileAppFileId: parsed[constants_1.CommandArgMobileBuildFileId],
            path: parsed[constants_2.CommandArgMobileBuildFilePath],
            platformName,
            avdName: parsed[constants_2.CommandArgMobileDeviceName],
            recordVideoPath: parsed[constants_2.CommandArgRecordVideoPath],
            udid: parsed[constants_2.CommandArgMobileDeviceName],
        },
    };
    loggingProvider_1.logger.info('Warming up test runner...');
    const mablTestsRunner = await mobileRuntime.createMobileTestsRunner(testRunnerConfig);
    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), mablTestsRunner.apiClient);
    if (!results.success && !testRunnerConfig.keepBrowserOpen) {
        process.exit(1);
    }
}
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;
}
async function validateSystemRequirements(platform, deviceName) {
    let requirements;
    if (platform === mablApi_1.MobilePlatformEnum.Ios) {
        requirements = await (0, systemChecks_1.systemIsSimulatorReady)(deviceName);
    }
    else {
        requirements = await (0, systemChecks_1.systemIsEmulatorReady)(deviceName);
    }
    if (!requirements.requirementsMet) {
        const errorMessages = [];
        Object.values(requirements.checks).forEach((requirement) => {
            if (!requirement.requirementsMet && requirement.informationalText) {
                errorMessages.push(requirement.informationalText);
            }
        });
        loggingProvider_1.logger.error('Failed while checking system requirements for testing mobile code. Errors: ');
        for (const errorMessage of errorMessages) {
            loggingProvider_1.logger.error(` * ${errorMessage}`);
        }
        process.exit(exitCodeOnError);
    }
}
async function inferMobilePlatform(argv) {
    if (!argv[constants_2.CommandArgMobilePlatform] && argv[constants_1.CommandArgMobileBuildFileId]) {
        if (argv[constants_1.CommandArgMobileBuildFileId]) {
            try {
                const { platform } = await new MobileAppFileCache_1.MobileAppFileCache().getMobileAppFileImmutableFields(argv[constants_1.CommandArgMobileBuildFileId]);
                if (platform) {
                    argv[constants_2.CommandArgMobilePlatform] = platform.toString().toLowerCase();
                    return;
                }
            }
            catch (error) {
                loggingProvider_1.logger.warn(chalk.yellow(`Warning: Unable to fetch mobile app file ${argv[constants_2.CommandArgMobileBuildFilePath]}: ${error}`));
            }
        }
        const extension = (0, path_1.extname)(argv[constants_2.CommandArgMobileBuildFilePath]).toLowerCase();
        switch (extension) {
            case '.apk':
                argv[constants_2.CommandArgMobilePlatform] = mablApi_1.MobilePlatformEnum.Android;
                break;
            case '.ipa':
            case '.app':
                argv[constants_2.CommandArgMobilePlatform] = mablApi_1.MobilePlatformEnum.Ios;
                break;
            default:
                loggingProvider_1.logger.warn(chalk.yellow(`Warning: Unable to infer the mobile platform from the file extension: '${extension}'  Please provide a --platform command line argument.`));
        }
    }
}
