"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.processSingleTraceFile = processSingleTraceFile;
exports.processTracesPath = processTracesPath;
const env_1 = require("@mablhq-internal/runtime/env/env");
const pluralize_1 = __importDefault(require("pluralize"));
const inquirer = __importStar(require("inquirer"));
const mablApiClientFactory_1 = require("@mablhq-internal/runtime/api/mablApiClientFactory");
const util_1 = require("../../../commandUtil/util");
const cliUtil_1 = require("@mablhq-internal/runtime/core/cliUtil");
const mablApi_1 = require("@mablhq-internal/runtime/mablApi");
const pureUtil_1 = require("@mablhq-internal/runtime/util/pureUtil");
const constants_1 = require("@mablhq-internal/runtime/core/constants");
const constants_2 = require("../../../constants");
const loggingProvider_1 = require("@mablhq-internal/runtime/providers/logging/loggingProvider");
const chromiumBrowserEngine_1 = require("@mablhq-internal/runtime/browserEngines/chromiumBrowserEngine");
const RichPromise_1 = __importDefault(require("@mablhq-internal/runtime/util/RichPromise"));
const { spawn } = require('child_process');
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const adm_zip_1 = __importDefault(require("adm-zip"));
const execution_1 = require("@mablhq-internal/runtime/execution");
const projectInfoTestFile = `
import { test } from "@playwright/test";
test("mablImportInfo", async ({baseURL, trace}) => {
  console.log('@mabl project info',
    JSON.stringify({
      baseUrl: baseURL,
    })
  );
});
`;
var PostImportActions;
(function (PostImportActions) {
    PostImportActions["Discard"] = "Discard the test";
    PostImportActions["Run"] = "Run test to activate auto-heal";
    PostImportActions["Save"] = "Save the test";
    PostImportActions["View"] = "View the test description";
})(PostImportActions || (PostImportActions = {}));
exports.command = 'playwright';
exports.describe =
    '[LABS]: Import an existing Playwright test.\nThis functionality is in an experimental stage.';
exports.builder = (yargs) => {
    yargs
        .option(constants_2.CommandArgAuto, {
        default: false,
        describe: 'Automatically save imported test when complete',
        requiresArg: false,
        type: 'boolean',
    })
        .option(constants_1.CommandArgWorkspaceId, {
        alias: constants_2.CommandArgAliases.WorkspaceId,
        describe: 'Workspace into which tests should be imported',
        nargs: 1,
        requiresArg: true,
        type: 'string',
    })
        .option(constants_2.CommandArgPath, {
        describe: 'Project location. Current directory is default',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgProject, {
        describe: 'Name of the Playwright project to run. You will find the list of your playwright projects in the playwright.config.ts file under the projects array. This project must have tracing enabled.',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgGrep, {
        describe: 'Grep argument to pass to Playwright to filter tests',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgExtraArguments, {
        describe: 'Extra arguments to pass to playwright',
        nargs: 1,
        type: 'string',
    })
        .option(constants_2.CommandArgTestPath, {
        describe: 'Path where the tests are located',
        type: 'string',
        default: 'tests',
    })
        .option(constants_2.CommandArgTraceFile, {
        describe: 'Path of a single playwright trace file to import',
        type: 'string',
    })
        .option(constants_2.CommandArgTracesPath, {
        describe: 'Path of the directory containing playwright trace files to import',
        type: 'string',
    })
        .check((argv) => {
        if (!argv[constants_2.CommandArgTraceFile] &&
            !argv[constants_2.CommandArgTracesPath] &&
            !argv[constants_2.CommandArgPath]) {
            throw new Error(`Please pass in one of --${constants_2.CommandArgTraceFile}, --${constants_2.CommandArgTracesPath}, or ${constants_2.CommandArgPath}`);
        }
        if (argv[constants_2.CommandArgTraceFile] && argv[constants_2.CommandArgTracesPath]) {
            throw new Error(`Only one of --${constants_2.CommandArgTraceFile} or --${constants_2.CommandArgTracesPath} can be provided`);
        }
        return true;
    });
};
exports.handler = (0, cliUtil_1.failWrapper)(processCommand);
async function processCommand(parsed) {
    const workspaceId = await (0, util_1.getWorkspaceId)(parsed);
    const apiClient = await mablApiClientFactory_1.MablApiClientFactory.createApiClient();
    const { importedTests, playwrightProject } = await importTests(parsed);
    if (!importedTests.length) {
        return Promise.reject(new Error(`No tests were imported.`));
    }
    if (importedTests.length > 1) {
        loggingProvider_1.logger.info(`Imported ${importedTests.length} tests.\n`);
    }
    else {
        loggingProvider_1.logger.info(`Imported ${(0, pluralize_1.default)('step', importedTests[0].steps.length, true)}.\n`);
    }
    return handlePostImportActions(workspaceId, apiClient, importedTests, playwrightProject, parsed[constants_2.CommandArgAuto]);
}
async function importTests(args) {
    const cwd = args[constants_2.CommandArgPath] ?? process.cwd();
    const tracesPath = args[constants_2.CommandArgTracesPath] ?? path_1.default.join(cwd, 'test-results');
    const playwrightProject = args[constants_2.CommandArgProject]
        ? await getProjectInfo(args)
        : { baseUrl: '' };
    if (!args[constants_2.CommandArgTraceFile] && !args[constants_2.CommandArgTracesPath]) {
        await runPlaywrightProject(args, tracesPath, cwd);
    }
    let importedTests = [];
    const converter = new execution_1.PlaywrightToMablStepConverter();
    if (args[constants_2.CommandArgTraceFile]) {
        importedTests = processSingleTraceFile(args[constants_2.CommandArgTraceFile], playwrightProject, converter);
    }
    else {
        importedTests = processTracesPath(tracesPath, playwrightProject, converter);
    }
    converter.printMissingApis(loggingProvider_1.logger);
    return { importedTests, playwrightProject };
}
async function handlePostImportActions(workspaceId, apiClient, importedTests, playwrightProject, autoSave) {
    let nextAction;
    do {
        nextAction = await getNextAction(autoSave);
        loggingProvider_1.logger.logNewLine();
        switch (nextAction) {
            case PostImportActions.Save:
                return saveTests(workspaceId, apiClient, importedTests, playwrightProject).then(() => {
                    loggingProvider_1.logger.info('Import complete.');
                });
            case PostImportActions.View:
                displayTestDescriptions(importedTests);
                break;
            case PostImportActions.Run:
                await runTests(workspaceId, apiClient, importedTests, playwrightProject);
                break;
            case PostImportActions.Discard:
                loggingProvider_1.logger.info('Exiting without saving.');
                return;
        }
        loggingProvider_1.logger.logNewLine();
    } while ([PostImportActions.View, PostImportActions.Run].includes(nextAction));
}
async function getNextAction(autoSave) {
    if (autoSave) {
        return PostImportActions.Save;
    }
    const answer = await inquirer.prompt([
        {
            type: 'list',
            name: 'action',
            hint: false,
            message: 'What do you want to do next?',
            choices: Object.values(PostImportActions),
        },
    ]);
    return answer.action;
}
function saveTests(workspaceId, apiClient, importedTests, playwrightProject) {
    loggingProvider_1.logger.info(`Saving ${(0, pluralize_1.default)('test', importedTests.length, true)}...`);
    const savePromises = importedTests.map((test) => apiClient
        .createFlow(stepsToFlow(workspaceId, test, playwrightProject))
        .then((flow) => apiClient.createJourney(flowToJourney(test.testName, flow)))
        .then((journey) => {
        loggingProvider_1.logger.info(`Saved test ${journey.invariant_id} : ${env_1.BASE_APP_URL}/workspaces/${journey.organization_id}/train/tests/${journey.invariant_id}/current`);
        return journey;
    }));
    return Promise.all(savePromises);
}
function displayTestDescriptions(importedTests) {
    importedTests.forEach((test, index) => {
        if (importedTests.length > 1) {
            loggingProvider_1.logger.info(`Test ${index + 1} of ${importedTests.length}: '${test.testName}'`);
        }
        loggingProvider_1.logger.info(test.steps.map((step) => step.description).join('\n'));
        loggingProvider_1.logger.logNewLine();
    });
}
async function runTests(workspaceId, apiClient, importedTests, playwrightProject) {
    const tests = importedTests.map((test) => ({
        test,
        flow: stepsToFlow(workspaceId, test, playwrightProject),
    }));
    const firstUrl = playwrightProject.baseUrl ?? tests.find((test) => test.flow.url)?.flow?.url;
    const browserPath = await new chromiumBrowserEngine_1.ChromiumBrowserEngine().findBrowserExecutable();
    for (let testIndex = 0; testIndex < tests.length; testIndex++) {
        const testToRun = tests[testIndex];
        loggingProvider_1.logger.info(`Running test '${testToRun.test.testName}' ${testIndex + 1} of ${tests.length}.`);
        const journey = {
            organization_id: workspaceId,
            name: testToRun.test.testName,
            flows: [],
            url: testToRun.flow.url ?? firstUrl,
        };
        const testRunner = await createTestRunner(apiClient, journey, testToRun.flow, journey.url, workspaceId, browserPath);
        const results = await testRunner.run();
        loggingProvider_1.logger.info(`Test result: ${results.status}`);
        updateMablscript(testRunner, testToRun.test);
    }
}
function updateMablscript(testRunner, test) {
    let mablscript = '';
    for (const step of testRunner.steps ?? []) {
        mablscript += step.toMablscript() + '\n';
    }
    test.updatedMablscript = mablscript;
}
function stepsToFlow(workspaceId, test, playwrightProject) {
    const steps = test.steps;
    const prototype = {
        import_source_id: mablApi_1.ImportSourceId.SeleniumNodeProxy,
        import_source_version: (0, pureUtil_1.getCliVersion)(),
        flow_type: mablApi_1.FlowTypeEnum.Mablscript,
        organization_id: workspaceId,
        reusable: false,
        selectors: steps
            .map((step) => step.selector)
            .map((selector) => selector?.toMablscriptSelector())
            .filter((selector) => selector)
            .map((selector) => selector),
        script: test.updatedMablscript ?? steps.map((step) => step.mablscript).join('\n'),
        script_description: steps.map((step) => step.description).join('\n'),
        url: playwrightProject.baseUrl ??
            steps.map((step) => step.url).find((url) => url),
    };
    return prototype;
}
function flowToJourney(name, flow) {
    const prototype = {
        organization_id: flow.organization_id,
        name,
        flows: [flow.id],
        url: flow.url,
    };
    return prototype;
}
async function createTestRunner(apiClient, test, flow, url, workspaceId, browserPath) {
    const runner = new execution_1.BrowserTestRunner({
        testRunConfig: {
            _cliCreated: true,
            imported: true,
            filterHttpRequests: false,
            url,
            workspaceId,
        },
        mablApiClient: apiClient,
    });
    await runner.initializeTestRunner(test, [flow], 'master', apiClient, false, browserPath, {
        enableCustomFindConversions: true,
        url,
    });
    return runner;
}
function processSingleTraceFile(traceFile, playwrightProject, converter) {
    const importedTests = [];
    if (!fs_1.default.existsSync(traceFile)) {
        throw new Error(`Trace file not found: ${traceFile}`);
    }
    const importedTest = processTestResult(traceFile, playwrightProject, converter);
    if (importedTest) {
        importedTests.push(importedTest);
    }
    return importedTests;
}
function processTracesPath(tracesPath, playwrightProject, converter) {
    const importedTests = [];
    const testResults = fs_1.default.readdirSync(tracesPath);
    console.log(`Found ${testResults.length} test results`);
    for (const testResult of testResults) {
        const testResultPath = path_1.default.join(tracesPath, testResult);
        const tracePath = path_1.default.join(testResultPath, 'trace.zip');
        const importedTest = processTestResult(tracePath, playwrightProject, converter);
        if (importedTest) {
            importedTests.push(importedTest);
        }
    }
    return importedTests;
}
function processTestResult(traceZip, playwrightProject, converter) {
    const testResultPath = path_1.default.dirname(traceZip);
    if (!fs_1.default.existsSync(traceZip)) {
        return;
    }
    const zip = new adm_zip_1.default(traceZip);
    const zipEntries = zip.getEntries();
    const lines = [];
    const traceInfoFiles = zipEntries.filter((entry) => entry.entryName.endsWith('-trace.trace'));
    traceInfoFiles.sort((a, b) => {
        const aNum = parseInt(a.entryName.split('-')[0]);
        const bNum = parseInt(b.entryName.split('-')[0]);
        return aNum - bNum;
    });
    for (const entry of traceInfoFiles) {
        const entryContent = entry.getData().toString('utf8');
        fs_1.default.writeFileSync(path_1.default.join(testResultPath, entry.entryName), entryContent);
        lines.push(...entryContent.split('\n'));
    }
    const traceLines = lines
        .filter((line) => line.trim() !== '')
        .map((line) => {
        try {
            return JSON.parse(line);
        }
        catch (e) {
            return;
        }
    })
        .filter((line) => line !== undefined);
    const testName = getTestNameFromTraceInfo(traceLines);
    const steps = converter.getStepsFromTraceInfo(traceLines, playwrightProject.baseUrl);
    return { testName, steps };
}
function getTestNameFromTraceInfo(traceLines) {
    const contextOptionsLine = traceLines.find((trace) => trace.type === 'context-options');
    return contextOptionsLine?.title ?? '';
}
async function getProjectInfo(args) {
    const consoleLines = [];
    const commandArgs = [
        'playwright',
        'test',
        '--project',
        args[constants_2.CommandArgProject],
        '--grep',
        'mablImportInfo',
    ];
    const testFile = path_1.default.join(args[constants_2.CommandArgPath] ?? process.cwd(), args[constants_2.CommandArgTestPath] ?? 'tests', 'mablImportInfo.spec.ts');
    fs_1.default.writeFileSync(testFile, projectInfoTestFile);
    const options = {
        cwd: args[constants_2.CommandArgPath] ?? process.cwd(),
        env: {
            ...process.env,
        },
    };
    if (process.platform === 'win32') {
        options.shell = true;
    }
    const processClosedPromise = new RichPromise_1.default();
    const playwrightProcess = spawn('npx', commandArgs, options);
    playwrightProcess.stdout.on('data', (data) => {
        const lineHint = '@mabl project info';
        consoleLines.push(data.toString());
        if (data.toString().includes(lineHint) &&
            processClosedPromise.isPending()) {
            try {
                const line = data.toString().split(lineHint)[1].split('\n')[0].trim();
                const info = JSON.parse(line);
                processClosedPromise.resolve(info);
            }
            catch {
            }
        }
    });
    playwrightProcess.on('close', (code) => {
        if (code !== 0) {
            processClosedPromise.reject(new Error(`Playwright process exited with code ${code}`));
            console.log(consoleLines.join('\n'));
        }
        else {
            processClosedPromise.resolve();
        }
    });
    const projectInfo = await processClosedPromise;
    fs_1.default.unlinkSync(testFile);
    return projectInfo ?? { baseUrl: '' };
}
async function runPlaywrightProject(args, tracesPath, cwd) {
    const processClosedPromise = new RichPromise_1.default();
    const commandArgs = ['playwright', 'test', '--trace', 'on'];
    if (args[constants_2.CommandArgProject]) {
        commandArgs.push('--project', args[constants_2.CommandArgProject]);
    }
    if (args.grep) {
        commandArgs.push('--grep', args.grep);
    }
    if (args[constants_2.CommandArgExtraArguments]) {
        commandArgs.push(args[constants_2.CommandArgExtraArguments]);
    }
    const options = {
        cwd,
        env: {
            ...process.env,
        },
    };
    if (process.platform === 'win32') {
        options.shell = true;
    }
    loggingProvider_1.logger.info('Checking playwright project...');
    const playwrightProjectFile = path_1.default.join(options.cwd.toString(), 'playwright.config.ts');
    if (!fs_1.default.existsSync(playwrightProjectFile)) {
        throw new Error('No playwright project found. Please ensure you are in the root directory of a playwright project.');
    }
    loggingProvider_1.logger.info('Deleting existing traces...');
    if (fs_1.default.existsSync(tracesPath)) {
        fs_1.default.rmSync(tracesPath, { recursive: true });
    }
    loggingProvider_1.logger.info('Importing tests from Playwright...');
    loggingProvider_1.logger.info('This may take a few minutes.');
    const playwrightProcess = spawn('npx', commandArgs, options);
    playwrightProcess.stdout.on('data', (data) => {
        loggingProvider_1.logger.info(data.toString());
    });
    playwrightProcess.on('close', (code) => {
        if (code !== 0) {
            processClosedPromise.reject(new Error(`Playwright process exited with code ${code}`));
        }
        else {
            processClosedPromise.resolve();
        }
    });
    await processClosedPromise;
    loggingProvider_1.logger.info('Test import complete. Processing output');
}
