"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.MobileTestEnvironment = exports.DEFAULT_UI_STREAM_PORT_ANDROID = exports.DEFAULT_UI_STREAM_PORT_IOS = exports.DEFAULT_MJPEG_SERVER_HOST = exports.DEFAULT_APPIUM_SERVER_PORT = exports.DEFAULT_APPIUM_SERVER_HOST = void 0;
exports.interpretMobileServerEvent = interpretMobileServerEvent;
const async_retry_1 = __importDefault(require("async-retry"));
const cachedir_1 = __importDefault(require("cachedir"));
const fs = __importStar(require("fs-extra"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const webdriverio_1 = require("webdriverio");
const env_1 = require("@mablhq-internal/runtime/env/env");
const mablApi_1 = require("@mablhq-internal/runtime/mablApi");
const SetOrientationStepDescriptor_1 = require("@mablhq/test-model/types/mobile/SetOrientationStepDescriptor");
const socketTunnel_1 = require("@mablhq-internal/runtime/socketTunnel");
const AppiumServerInstance_1 = require("./AppiumServerInstance");
const AndroidCandidate_1 = require("./actions/find/android/AndroidCandidate");
const iOSCandidate_1 = require("./actions/find/ios/iOSCandidate");
const androidUtil_1 = require("./util/androidUtil");
const iOSUtil_1 = require("./util/iOSUtil");
const mobileUtil_1 = require("./util/mobileUtil");
const webViewUtil_1 = require("./util/webViewUtil");
const androidManifestUtils_1 = require("./util/androidManifestUtils");
exports.DEFAULT_APPIUM_SERVER_HOST = '127.0.0.1';
exports.DEFAULT_APPIUM_SERVER_PORT = 4723;
exports.DEFAULT_MJPEG_SERVER_HOST = '127.0.0.1';
exports.DEFAULT_UI_STREAM_PORT_IOS = 9100;
exports.DEFAULT_UI_STREAM_PORT_ANDROID = 9101;
const IOS_CLOUD_TRAINING_APP_PATH = '/opt/bin/MobileAppFile.app.zip';
const ANDROID_CLOUD_TRAINING_APP_BASE_PATH = '/home/mobile-runner/.cache/mabl-cli/mobile-app-files/';
const ANDROID_CLOUD_TRAINING_APP_FILE_NAME = 'MobileAppFile.apk';
const WDA_RETRY_LIMIT = 100;
const WDA_SETUP_MIN_TIMEOUT_MS = 1_000;
const WDA_SETUP_MAX_TIMEOUT_MS = 10_000;
const WDA_SETUP_RETRY_TIME_MS = 180_000;
class MobileTestEnvironment {
    config;
    driver;
    server;
    testDirectoryPath;
    liveViewInfo;
    currentOrientation;
    windowSize;
    deviceScaleFactor;
    deviceScreenInfo;
    originalAppId;
    androidDeviceSerialId;
    cloudTrainingAndroidAppPath;
    cachedIsIPadDevice;
    cloudTrainingConnections;
    log;
    constructor(config, logger) {
        this.config = config;
        this.testDirectoryPath = this.createTempDirForTestArtifacts();
        this.log = logger;
        let androidHome;
        if (!this.isRemoteAppiumServerConfigured()) {
            if (this.config.app.platformName === mablApi_1.MobilePlatformEnum.Android) {
                androidHome =
                    this.config.androidConfiguration?.homePath ??
                        (0, androidUtil_1.getAndroidHomeDirectoryPath)();
                process.env.ANDROID_HOME = androidHome;
            }
            this.server = new AppiumServerInstance_1.AppiumServerInstance({
                androidHome,
                address: this.config.server.address,
                port: this.config.server.port,
                loglevel: this.config.server.logLevel,
                appiumHome: this.config.server.appiumHome,
                alternativeFork: this.config.server.alternativeFork,
                noPermsCheck: true,
                logHandler: config.server.logHandler,
                serverEventHandler: config.server.serverEventHandler,
            });
        }
    }
    getConfiguredAppiumServerHost() {
        return this.config.server.address ?? exports.DEFAULT_APPIUM_SERVER_HOST;
    }
    getRunningAppiumServerHost() {
        return (this.cloudTrainingConnections?.appium.boundAddress() ??
            this.getConfiguredAppiumServerHost());
    }
    getConfiguredAppiumServerPort() {
        return this.config.server.port ?? exports.DEFAULT_APPIUM_SERVER_PORT;
    }
    getRunningAppiumServerPort() {
        return (this.cloudTrainingConnections?.appium.boundPort() ??
            this.getConfiguredAppiumServerPort());
    }
    getRunningUserInterfaceStreamHost() {
        return (this.cloudTrainingConnections?.mjpeg.boundAddress() ??
            exports.DEFAULT_MJPEG_SERVER_HOST);
    }
    getConfiguredUserInterfaceStreamPort() {
        return (this.config.server.userInterfaceStreamPort ??
            (this.config.app.platformName === mablApi_1.MobilePlatformEnum.Ios
                ? exports.DEFAULT_UI_STREAM_PORT_IOS
                : exports.DEFAULT_UI_STREAM_PORT_ANDROID));
    }
    getRunningUserInterfaceStreamPort() {
        return (this.cloudTrainingConnections?.mjpeg.boundPort() ??
            this.getConfiguredUserInterfaceStreamPort());
    }
    getUserInterfaceStreamUrl() {
        return `http://${this.getRunningUserInterfaceStreamHost()}:${this.getRunningUserInterfaceStreamPort()}`;
    }
    isRemoteAppiumServerConfigured() {
        return (!!this.config.cloudTrainingConfiguration ||
            this.config.server.startAppiumServer === false ||
            this.getConfiguredAppiumServerHost() !== exports.DEFAULT_APPIUM_SERVER_HOST);
    }
    async getWebDriverOptions() {
        const capabilities = {
            platformName: this.config.app.platformName.toString(),
            'appium:platformVersion': this.config.app.platformVersion,
            'appium:app': this.config.app.path,
            'appium:automationName': (0, mobileUtil_1.getMobileAutomation)(this.config.app.platformName),
            'appium:newCommandTimeout': 0,
            'appium:commandTimeouts': '0',
            ...this.config.additionalCapabilities,
        };
        capabilities['appium:settings[shouldUseCompactResponses]'] = false;
        if (this.config.server.streamUIOnStart) {
            capabilities['appium:mjpegServerPort'] =
                this.getRunningUserInterfaceStreamPort();
            const streamUrl = this.getUserInterfaceStreamUrl();
            capabilities['appium:mjpegScreenshotUrl'] = undefined;
            this.liveViewInfo = {
                location: streamUrl,
            };
        }
        if (this.config.app.path && !this.isRemoteAppiumServerConfigured()) {
            capabilities['appium:enforceAppInstall'] = true;
        }
        if (this.config.app.platformName === mablApi_1.MobilePlatformEnum.Android) {
            await this.setAndroidWebDriverOptionsOnCapabilities(capabilities);
        }
        else {
            this.setiOSWebDriverOptionsOnCapabilities(capabilities);
        }
        return {
            hostname: this.getRunningAppiumServerHost(),
            logLevel: (this.config.logLevel ?? 'silent'),
            port: this.getRunningAppiumServerPort(),
            capabilities: {
                ...capabilities,
                ...this.config.additionalCapabilities,
            },
        };
    }
    setiOSWebDriverOptionsOnCapabilities(capabilities) {
        if (this.config.app.udid) {
            capabilities['appium:udid'] = this.config.app.udid;
        }
        if (this.config.app.deviceName) {
            capabilities['appium:deviceName'] = this.config.app.deviceName;
        }
        if (this.isRemoteAppiumServerConfigured()) {
            capabilities['appium:noReset'] = true;
            capabilities['appium:useNewWDA'] = false;
        }
        capabilities['appium:simulatorStartupTimeout'] = 240000;
        capabilities['appium:bundleId'] = this.config.app.package;
        capabilities['appium:settings[elementResponseAttributes]'] =
            iOSCandidate_1.IOS_ELEMENT_RESPONSE_ATTRIBUTES.join(',');
        capabilities['appium:settings[respectSystemAlerts]'] = true;
        capabilities['appium:settings[enforceCustomSnapshots]'] = true;
    }
    async setAndroidWebDriverOptionsOnCapabilities(capabilities) {
        capabilities['appium:appWaitForLaunch'] = false;
        capabilities['appium:enableMultiWindows'] = true;
        if (this.config.app.package) {
            capabilities['appium:appPackage'] = this.config.app.package;
            capabilities['appium:appActivity'] = '.MainActivity';
        }
        if (this.config.app.path) {
            const launchActivity = await (0, androidManifestUtils_1.extractLaunchActivityFromAndroidManifest)(this.config.app.path);
            if (launchActivity) {
                capabilities['appium:appActivity'] = launchActivity;
            }
        }
        if (!this.isRemoteAppiumServerConfigured()) {
            if (!this.androidDeviceSerialId) {
                throw new Error('The current device ID has not been set as expected. It should have been set during initialization.');
            }
            capabilities['appium:udid'] = this.androidDeviceSerialId;
        }
        capabilities['appium:useResourcesForOrientationDetection'] =
            isThisAnAndroidTabletDevice(this.config);
        capabilities['appium:settings[elementResponseAttributes]'] =
            AndroidCandidate_1.ANDROID_ELEMENT_RESPONSE_ATTRIBUTES.join(',');
        capabilities['appium:settings[waitForIdleTimeout]'] = 0;
        capabilities['appium:settings[waitForSelectorTimeout]'] = 0;
        capabilities['appium:disableIdLocatorAutocompletion'] = true;
        capabilities['appium:chromedriverExecutableDir'] =
            this.config.server.chromedriverExecutableDir ??
                this.getOrCreateChromeDriverDownloadDirectory();
    }
    async openCloudTrainingConnections({ session, socketTunnelClientOptions, tunnelAuthorization, }) {
        const httpAuthorization = {
            bearerToken: session.session_key,
            type: 'bearer',
        };
        if (!session.connect_url) {
            throw new Error(`No connect URL found for cloud training session [${session.id}]`);
        }
        this.cloudTrainingConnections = {
            appium: new socketTunnel_1.SocketTunnelClient(session.connect_url, {
                ...socketTunnelClientOptions,
                httpAuthorization,
                localPort: 0,
                remotePort: this.getConfiguredAppiumServerPort(),
                tunnelAuthorization,
            }),
            mjpeg: new socketTunnel_1.SocketTunnelClient(session.connect_url, {
                ...socketTunnelClientOptions,
                httpAuthorization,
                localPort: 0,
                remotePort: this.getConfiguredUserInterfaceStreamPort(),
                tunnelAuthorization,
            }),
        };
        this.log?.info(`Connecting to cloud training session ${session.id}`);
        await Promise.all(Object.values(this.cloudTrainingConnections).map((client) => client.connect()));
        console.log(`Forwarding Appium connections from ${this.getRunningAppiumServerHost()}:${this.getRunningAppiumServerPort()} to cloud training server`);
        console.log(`Forwarding mjpeg connections from ${this.getRunningUserInterfaceStreamHost()}:${this.getRunningUserInterfaceStreamPort()} to cloud training server`);
    }
    async initialize() {
        if (this.isRemoteAppiumServerConfigured()) {
            await this.initializeRemoteAppiumServer();
        }
        else {
            await this.initializeLocalAppiumServer();
        }
    }
    async initializeRemoteAppiumServer() {
        this.config.app = {
            path: this.config.app.path,
            platformName: this.config.app.platformName,
            deviceName: this.config.app.deviceName,
            platformVersion: this.config.app.platformVersion,
        };
        this.log?.debug(`Initializing device [${this.config.app.deviceName}] with platform version: [${this.config.app.platformVersion}]`);
        delete this.config.androidConfiguration;
        if (this.config.cloudTrainingConfiguration) {
            if (this.config.app.platformName === mablApi_1.MobilePlatformEnum.Android) {
                const mobileAppFileId = this.config.cloudTrainingConfiguration.session.mobile_app_file_id;
                if (mobileAppFileId) {
                    this.cloudTrainingAndroidAppPath = `${ANDROID_CLOUD_TRAINING_APP_BASE_PATH}${mobileAppFileId}/${ANDROID_CLOUD_TRAINING_APP_FILE_NAME}`;
                }
                delete this.config.app.path;
            }
            else {
                this.config.app.path = IOS_CLOUD_TRAINING_APP_PATH;
            }
            this.config.server.startAppiumServer = false;
            this.config.server.chromedriverExecutableDir =
                webViewUtil_1.ANDROID_CLOUD_IMAGE_CHROMEDRIVER_EXECUTABLE_DIR;
            await this.openCloudTrainingConnections(this.config.cloudTrainingConfiguration);
        }
        switch (this.config.app.platformName) {
            case mablApi_1.MobilePlatformEnum.Ios:
                this.config.additionalCapabilities = {
                    ...this.config.additionalCapabilities,
                    'appium:webDriverAgentUrl': 'http://localhost:8101',
                };
                break;
            case mablApi_1.MobilePlatformEnum.Android:
                if (this.config.app.avdName && this.config.app.isRealDevice) {
                    this.config.app.avdName = undefined;
                }
                this.log?.debug('Connecting to pre-existing Android emulator on remote appium server');
                break;
            default:
                throw new Error(`Unsupported platform name was supplied for starting simulator/emulator: [${this.config.app.platformName}]`);
        }
        this.log?.debug(`Connecting to remote appium server instance ${this.getRunningAppiumServerHost()}:${this.getRunningAppiumServerPort()}`);
        const webDriverOptions = await this.getWebDriverOptions();
        this.log?.debug(`Launching remote WebDriver session with options: ${JSON.stringify(webDriverOptions)}`);
        await (0, async_retry_1.default)(async () => {
            this.driver = await (0, webdriverio_1.remote)(webDriverOptions);
        }, {
            retries: WDA_RETRY_LIMIT,
            minTimeout: WDA_SETUP_MIN_TIMEOUT_MS,
            maxTimeout: WDA_SETUP_MAX_TIMEOUT_MS,
            maxRetryTime: WDA_SETUP_RETRY_TIME_MS,
        });
        await this.setAppUnderTestId();
    }
    async initializeLocalAppiumServer() {
        if (this.config.app.path) {
            this.log?.debug('Checking app file');
            if (!fs.existsSync(this.config.app.path)) {
                throw new Error(`Mobile app file does not exist or is not accessible: ${this.config.app.path}`);
            }
        }
        else {
            if (!this.config.app.package) {
                throw new Error('No app path or package was provided. Must provide at least one of those.');
            }
        }
        switch (this.config.app.platformName) {
            case mablApi_1.MobilePlatformEnum.Ios:
                if (!this.config.app.udid) {
                    this.config.app.udid = await this.getLastBootedIPhoneSimulatorUdid();
                }
                break;
            case mablApi_1.MobilePlatformEnum.Android:
                if (this.config.app.avdName && this.config.app.isRealDevice) {
                    this.config.app.avdName = undefined;
                }
                const additionalEmulatorArgs = this.config.androidConfiguration?.additionalEmulatorArgs;
                if (this.config.app.avdName) {
                    this.log?.debug(`Starting Android emulator for AVD: ${this.config.app.avdName}`);
                    if (additionalEmulatorArgs) {
                        this.log?.info(`Android emulator args: ${additionalEmulatorArgs}`);
                    }
                    this.androidDeviceSerialId = await (0, androidUtil_1.startAndroidEmulator)(this.config.app.avdName, this.config.headless ?? false, this.config.gpuMode, undefined, additionalEmulatorArgs);
                    this.log?.debug(`Android emulator started. Device ID: ${this.androidDeviceSerialId}`);
                }
                else {
                    this.log?.info('No AVD name supplied. Trying to use a physical device.');
                    this.androidDeviceSerialId = await (0, androidUtil_1.getFirstDeviceSerialId)();
                    if (this.androidDeviceSerialId) {
                        this.log?.debug(`Using Android physical device. Device ID: ${this.androidDeviceSerialId}`);
                    }
                    else {
                        this.log?.info('No physical devices found. Will try to grab first available emulator.');
                        this.config.app.avdName = await this.getFirstEmulatorName();
                        if (additionalEmulatorArgs) {
                            this.log?.debug(`using additional args: ${additionalEmulatorArgs}`);
                        }
                        if (!this.config.app.avdName) {
                            this.androidDeviceSerialId = undefined;
                        }
                        else {
                            this.log?.debug(`Starting Android emulator for AVD: ${this.config.app.avdName}`);
                            this.androidDeviceSerialId = await (0, androidUtil_1.startAndroidEmulator)(this.config.app.avdName, this.config.headless ?? false, this.config.gpuMode, undefined, undefined);
                            this.log?.debug(`Android emulator started. Device ID: ${this.androidDeviceSerialId}`);
                        }
                        if (!this.androidDeviceSerialId) {
                            throw new Error('No AVD name specified. No physical devices or emulators found. Cannot initialize mobile test environment.');
                        }
                    }
                }
                break;
            default:
                throw new Error(`Unsupported platform name was supplied for starting simulator/emulator: [${this.config.app.platformName}]`);
        }
        this.log?.debug('Starting up the appium server instance');
        await this.server?.start();
        this.log?.debug('Appium server instance started');
        const webDriverOptions = await this.getWebDriverOptions();
        this.log?.debug(`Launching local WebDriver session with options: ${JSON.stringify(webDriverOptions)}`);
        this.driver = await (0, webdriverio_1.remote)(webDriverOptions);
        await this.setAppUnderTestId();
    }
    async getAppUnderTestId() {
        await this.maybeUpdateAppUnderTestId();
        return this.originalAppId;
    }
    async maybeUpdateAppUnderTestId() {
        const isPossiblySystemApp = (0, mobileUtil_1.isSystemApp)(this.originalAppId);
        if (!this.originalAppId || isPossiblySystemApp) {
            await this.setAppUnderTestId();
        }
    }
    async setAppUnderTestId() {
        try {
            await (0, async_retry_1.default)(async () => {
                if (this.driver) {
                    switch (this.config.app.platformName) {
                        case mablApi_1.MobilePlatformEnum.Ios:
                            const activeApp = await this.driver.execute('mobile: activeAppInfo');
                            this.originalAppId = activeApp.bundleId;
                            break;
                        case mablApi_1.MobilePlatformEnum.Android:
                            this.originalAppId = await this.driver.execute('mobile: getCurrentPackage');
                            break;
                    }
                }
                if ((0, mobileUtil_1.isSystemApp)(this.originalAppId)) {
                    throw new Error(`Detected system app ID - ${this.originalAppId}, trying again`);
                }
            }, {
                retries: 10,
                minTimeout: 100,
                maxTimeout: 1_000,
                maxRetryTime: 5_000,
            });
        }
        catch (error) {
            this.log?.debug(`Error getting the app ID - [${error}]`);
            this.log?.error(`Unable to track the original app ID that isn't a system ID - [${this.originalAppId}]`);
        }
    }
    async getFirstEmulatorName(includePhysicalDevices) {
        const emulators = await (0, androidUtil_1.getListOfLocalAndroidEmulators)(includePhysicalDevices);
        const emulatorsList = Object.values(emulators);
        if (emulatorsList.length === 0) {
            return undefined;
        }
        const avdName = emulatorsList[0].avdName;
        this.log?.info(`Falling back to ${avdName} emulator`);
        return avdName;
    }
    async enableScreenRecording() {
        if (!this.driver) {
            throw new Error('Unable to enable screen recording, no driver found');
        }
        await this.driver.startRecordingScreen({
            videoType: 'mpeg4',
            timeLimit: 1800,
        });
    }
    async saveScreenRecording(fileName) {
        if (!this.driver) {
            throw new Error('Unable to save screen recording, no driver found');
        }
        await this.driver.saveRecordingScreen(`${fileName}.mp4`);
    }
    async stopScreenRecording() {
        if (!this.driver) {
            throw new Error('Unable to stop screen recording, no driver found');
        }
        await this.driver.stopRecordingScreen();
    }
    async saveScreenshot(filePath) {
        if (!this.driver) {
            throw new Error('Unable to save screenshot, no driver found');
        }
        return this.driver.saveScreenshot(filePath);
    }
    getDriver() {
        if (!this.driver) {
            throw new Error('No driver found');
        }
        return this.driver;
    }
    getLiveViewInfo() {
        return this.liveViewInfo;
    }
    getConfig() {
        return this.config;
    }
    getCloudTrainingAndroidAppPath() {
        return this.cloudTrainingAndroidAppPath;
    }
    async getOrientation() {
        if (!this.currentOrientation) {
            this.currentOrientation =
                (await this.getDriver().getOrientation());
        }
        return this.currentOrientation;
    }
    async setOrientation(orientation) {
        if ((await this.getOrientation()) === orientation) {
            return;
        }
        if (this.config.app.platformName === mablApi_1.MobilePlatformEnum.Android &&
            !this.isRemoteAppiumServerConfigured()) {
            if (!this.androidDeviceSerialId) {
                throw new Error('Cannot rotate device. The current device serial ID is not known.');
            }
            await (0, androidUtil_1.setAndroidOrientation)(this.androidDeviceSerialId, orientation);
            try {
                await (0, async_retry_1.default)(async () => {
                    const currentOrientation = await this.getDriver().getOrientation();
                    if (currentOrientation !== orientation) {
                        throw new Error(`Couldn't set orientation, the current orientation does not yet match the desired orientation. Current: ${currentOrientation}, Desired: ${orientation}`);
                    }
                }, {
                    retries: 4,
                    minTimeout: 10,
                    maxTimeout: 1_000,
                    maxRetryTime: 2_000,
                });
            }
            catch (error) {
                this.log?.error('Unable to set orientation via adb. Falling back to Appium.');
                await this.getDriver().setOrientation(orientation);
            }
        }
        else {
            await this.getDriver().setOrientation(orientation);
        }
        this.currentOrientation = orientation;
    }
    async getWindowSize() {
        const orientation = await this.getOrientation();
        if (!this.windowSize) {
            let windowRect;
            if (this.config.app.platformName === mablApi_1.MobilePlatformEnum.Android) {
                windowRect = await this.getAndroidSessionScreenSize();
            }
            if (!windowRect) {
                if (this.config.app.platformName === mablApi_1.MobilePlatformEnum.Ios) {
                    this.deviceScreenInfo = await this.getDriver().execute('mobile: deviceScreenInfo');
                    this.deviceScaleFactor = this.deviceScreenInfo.scale;
                    windowRect = {
                        height: this.deviceScreenInfo.screenSize.height,
                        width: this.deviceScreenInfo.screenSize.width,
                    };
                }
                else {
                    windowRect = await this.getDriver().getWindowRect();
                }
            }
            if (orientation === SetOrientationStepDescriptor_1.Orientation.Portrait) {
                this.windowSize = {
                    landscape: { height: windowRect.width, width: windowRect.height },
                    portrait: { height: windowRect.height, width: windowRect.width },
                };
            }
            else {
                this.windowSize = {
                    landscape: { height: windowRect.height, width: windowRect.width },
                    portrait: { height: windowRect.width, width: windowRect.height },
                };
            }
        }
        return orientation === SetOrientationStepDescriptor_1.Orientation.Portrait
            ? this.windowSize.portrait
            : this.windowSize.landscape;
    }
    async getDeviceScaleFactor() {
        if (!this.deviceScaleFactor) {
            switch (this.config.app.platformName) {
                case mablApi_1.MobilePlatformEnum.Android:
                    this.deviceScaleFactor = 1;
                    break;
                case mablApi_1.MobilePlatformEnum.Ios:
                    this.deviceScreenInfo = await this.getDriver().execute('mobile: deviceScreenInfo');
                    if (!this.deviceScreenInfo?.scale) {
                        this.log?.error('No device scale factor found. Defaulting to 3.0');
                    }
                    this.deviceScaleFactor = this.deviceScreenInfo?.scale ?? 3;
                    break;
            }
        }
        return this.deviceScaleFactor;
    }
    async getDeviceScreenSizeScaled() {
        const windowSize = await this.getWindowSize();
        const scaleFactor = await this.getDeviceScaleFactor();
        return {
            width: windowSize.width * scaleFactor,
            height: windowSize.height * scaleFactor,
        };
    }
    async getAndroidSessionScreenSize() {
        const sessionInfo = await this.getDriver().getSession();
        const sessionScreenSize = sessionInfo.deviceScreenSize ??
            this.getDriver().capabilities.deviceScreenSize;
        if (sessionScreenSize &&
            typeof sessionScreenSize === 'string' &&
            sessionScreenSize.match(/\d+x\d+/)) {
            const [width, height] = sessionScreenSize.split('x');
            const screenSize = { width: Number(width), height: Number(height) };
            console.log(`Getting device size from session info: ${sessionScreenSize} ${JSON.stringify(screenSize)}`);
            return screenSize;
        }
        console.warn(`Failed to get device size from session info: ${sessionScreenSize}`);
        return undefined;
    }
    async resetEnvironment() {
        if (!this.isRemoteAppiumServerConfigured()) {
            try {
                await this.setOrientation(SetOrientationStepDescriptor_1.Orientation.Portrait);
            }
            catch (error) {
                this.log?.warning(`Unable to reset orientation to PORTRAIT - app may not support PORTRAIT mode: ${error}`);
            }
        }
    }
    async isElementDisplayedInViewport(element) {
        if (!element?.elementId || !(await element.isDisplayed())) {
            return false;
        }
        const driver = this.getDriver();
        const windowSize = await this.getWindowSize();
        const elementRect = await driver.getElementRect(element.elementId);
        const elementMaxX = elementRect.x + elementRect.width;
        const elementMaxY = elementRect.y + elementRect.height;
        return (elementMaxX > 0 &&
            elementRect.x < windowSize.width &&
            elementMaxY > 0 &&
            elementRect.y < windowSize.height);
    }
    async shutdown(leaveDeviceOpen) {
        this.log?.info('Shutting down mobile test environment');
        try {
            await this.driver?.deleteSession();
        }
        catch (error) {
            this.log?.error(`Error deleting and closing appium driver session`, error);
        }
        try {
            if (this.cloudTrainingConnections) {
                this.log?.info('Closing cloud training connections');
                await Promise.all(Object.values(this.cloudTrainingConnections).map((client) => client.disconnect()));
                this.cloudTrainingConnections = undefined;
            }
            if (!this.isRemoteAppiumServerConfigured()) {
                await this.server?.stop();
                this.log?.debug('appium server stopped');
                switch (this.config.app.platformName) {
                    case mablApi_1.MobilePlatformEnum.Ios:
                        if (!this.config.app.udid) {
                            throw new Error('No udid specified, unable to shutdown ios simulator');
                        }
                        if (!leaveDeviceOpen) {
                            this.log?.debug('Shutting down iOS simulator');
                            await (0, iOSUtil_1.shutdownSimulator)(this.config.app.udid);
                        }
                        else {
                            this.log?.debug('Skipping shutdown of simulator');
                        }
                        break;
                    case mablApi_1.MobilePlatformEnum.Android:
                        if (!this.config.app.avdName) {
                            this.log?.info('No avd name specified. Unable to shutdown emulator. Perhaps this was run on physical device.');
                            break;
                        }
                        if (!leaveDeviceOpen) {
                            this.log?.debug('Shutting down Android emulator');
                            await (0, androidUtil_1.shutdownAndroidEmulator)(this.config.app.avdName);
                        }
                        else {
                            this.log?.debug('Skipping shutdown of emulator');
                        }
                        break;
                    default:
                        throw new Error(`Unsupported platform name was supplied for shutting down: [${this.config.app.platformName}]`);
                }
                this.log?.debug('Emulator shutdown');
            }
        }
        finally {
            this.driver = undefined;
            fs.removeSync(this.testDirectoryPath);
            this.log?.debug('Test directory cleaned up');
        }
    }
    createTempDirForTestArtifacts() {
        return fs.mkdtempSync(path.join(os.tmpdir(), `mablTestRun-${this.config.app.platformName}-${Date.now()}-`));
    }
    async getLastBootedIPhoneSimulatorUdid() {
        const simulators = await (0, iOSUtil_1.listSimulators)();
        let udid;
        const iPhoneSimulators = Object.values(simulators).filter((simulator) => simulator.name.includes('iPhone'));
        const sortedBootedIOSSimulators = iPhoneSimulators
            .filter((simulator) => simulator.lastBootedAt)
            .sort((simA, simB) => new Date(simB.lastBootedAt).getTime() -
            new Date(simA.lastBootedAt).getTime());
        if (sortedBootedIOSSimulators.length > 0) {
            udid = sortedBootedIOSSimulators[0].udid;
        }
        else {
            if (iPhoneSimulators.length > 0) {
                udid = iPhoneSimulators[0].udid;
            }
        }
        if (!udid) {
            throw new Error('No udid specified, unable to find iOS simulator');
        }
        this.log?.info(`Falling back to ${udid} simulator`);
        return udid;
    }
    getOrCreateChromeDriverDownloadDirectory() {
        const chromeDriverDir = path.resolve(path.join((0, cachedir_1.default)(env_1.CONF_FILE_PROJECT_NAME), 'chromedriver'));
        if (!fs.existsSync(chromeDriverDir)) {
            fs.mkdirSync(chromeDriverDir, { recursive: true });
        }
        return chromeDriverDir;
    }
    async getMobileDeviceInfo() {
        return this.getDriver().execute('mobile: deviceInfo');
    }
    async isIPadDevice() {
        if (this.cachedIsIPadDevice !== undefined) {
            return this.cachedIsIPadDevice;
        }
        try {
            const deviceInfo = await this.getMobileDeviceInfo();
            const iosInfo = deviceInfo;
            this.cachedIsIPadDevice =
                iosInfo.userInterfaceIdiom === 1 ||
                    iosInfo.model?.toLowerCase().includes('ipad') ||
                    iosInfo.name?.toLowerCase().includes('ipad');
            return this.cachedIsIPadDevice;
        }
        catch {
            return false;
        }
    }
}
exports.MobileTestEnvironment = MobileTestEnvironment;
function interpretMobileServerEvent(mobileAppConfig, event) {
    switch (event) {
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.APK_INSTALLED:
            return `Android APK successfully installed on the device`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.CONNECTED_TO_DEVICE:
            return `Connection to the ${mobileAppConfig.platformName} device has been established`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.CONNECTING_TO_ANDROID_DEVICE:
            return `Attempting to connect to the ${mobileAppConfig.platformName} device`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.CONNECTING_TO_IOS_DEVICE:
            return `Attempting to connect to the ${mobileAppConfig.platformName} device`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.DRIVERS_LOADED:
            return `Test execution drivers successfully loaded`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.LOADING_DRIVERS:
            return `Loading test execution drivers`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.SETTING_UP_SIMULATOR:
            return `Setting up the iOS simulator device - ${mobileAppConfig.udid}`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.SIMULATOR_BOOTED:
            return `iOS Simulator ${mobileAppConfig.udid} has finished booting`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.STARTING_MJPEG_STREAM:
            return `Configuring device screen streaming`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.STILL_CONNECTING_TO_ANDROID_DEVICE:
            return `Waiting for connection to ${mobileAppConfig.platformName} to be established...`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.STILL_CONNECTING_TO_IOS_DEVICE:
            return `Waiting for connection to ${mobileAppConfig.platformName} device to be established... this may take a few minutes`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.WEBVIEW_CHROME_DRIVER_DOWNLOAD:
            return `Downloading chrome driver...`;
        case AppiumServerInstance_1.InferredAppiumServerStatusEvent.WEBVIEW_CHROME_DRIVER_DOWNLOAD_COMPLETE:
            return `Chrome driver download complete`;
        case AppiumServerInstance_1.AppiumSeverEvent.RUN_APPIUM_SERVER_DEAD:
            return `Mobile device server has shutdown`;
        case AppiumServerInstance_1.AppiumSeverEvent.RUN_APPIUM_SERVER_SHUTTING_DOWN:
            return `Shutting down mobile device server`;
        case AppiumServerInstance_1.AppiumSeverEvent.RUN_APPIUM_SERVER_STARTED:
            return `Mobile device server startup complete`;
        case AppiumServerInstance_1.AppiumSeverEvent.RUN_APPIUM_SERVER_STARTING:
            return `Starting up mobile device server`;
        case AppiumServerInstance_1.AppiumSeverEvent.RUN_APPIUM_SERVER_STILL_SHUTTING_DOWN:
            return `Waiting for mobile device server to shutdown`;
        case AppiumServerInstance_1.AppiumSeverEvent.RUN_APPIUM_SERVER_STILL_STARTING:
            return `Waiting for mobile device server to startup`;
    }
}
function isThisAnAndroidTabletDevice(config) {
    const deviceName = config.app.deviceName ??
        config.cloudTrainingConfiguration?.session.mobile_device_configuration
            .device_name ??
        config.app.avdName ??
        '';
    return deviceName.toLowerCase().includes('tablet');
}
