import * as cp from "child_process" ; import { SmokeTestLogger } from "./smokeTestLogger" ; import { SmokeTestsConstants } from "./smokeTestsConstants" ; import { waitUntil , sleep } from "./utilities" ; const XDL = require ( "xdl" ) ; export interface IDevice { id : string ; isOnline : boolean ; } export interface ExpoClientData { url : string ; version : string ; } export default class AndroidEmulatorManager { private static readonly EMULATOR_START_TIMEOUT = 120000 ; private static readonly EMULATOR_TERMINATING_TIMEOUT = 30000 ; private static readonly PACKAGE_INSTALL_TIMEOUT = 600000 ; private static readonly PACKAGE_INIT_TIMEOUT = 10000 ; private emulatorName : string ; private emulatorPort : number ; private emulatorId : string ; constructor ( name = process . env . ANDROID_EMULATOR , port = SmokeTestsConstants . defaultTargetAndroidPort ) { if ( ! name ) { throw new Error ( "Passed Android emulator name and process.env.ANDROID_EMULATOR is not defined!" ) ; } this . emulatorName = name ; this . emulatorPort = port ; this . emulatorId = `emulator-${ port }` ; } public getEmulatorName ( ) { return this . emulatorName ; } public getEmulatorId ( ) { return this . emulatorId ; } public async waitUntilAppIsInstalled ( appPackage ) { SmokeTestLogger . info ( `*** Check if app is being installed with command 'adb shell pm list packages ${ appPackage }' for ${ AndroidEmulatorManager . PACKAGE_INSTALL_TIMEOUT / 1000 } seconds` ) ; const condition = ( ) => { const result = cp . execSync ( `adb shell pm list packages ${ appPackage }` ) . toString ( ) . trim ( ) ; if ( result ) { return true ; } return false ; } ; try { const result = await waitUntil ( condition , AndroidEmulatorManager . PACKAGE_INSTALL_TIMEOUT ) ; if ( result ) { SmokeTestLogger . success ( `*** Installed ${ appPackage } app found, await ${ AndroidEmulatorManager . PACKAGE_INIT_TIMEOUT }ms for initializing...` ) ; await sleep ( AndroidEmulatorManager . PACKAGE_INIT_TIMEOUT ) ; } else { SmokeTestLogger . error ( `${ appPackage } not found after ${ AndroidEmulatorManager . PACKAGE_INSTALL_TIMEOUT } ms` ) ; } return result ; } catch ( e ) { throw `Error occured while check app is installed:\n ${ e }` ; } } public async getExpoAndroidClientForSDK ( expoSdkMajorVersion ) { const sdkVersion = ( await XDL . Versions . sdkVersionsAsync ( ) ) [ `${ expoSdkMajorVersion }.0.0` ] ; return { url : sdkVersion . androidClientUrl , version : sdkVersion . androidClientVersion , } ; } public async installExpoAppOnAndroid ( ) { const expoClientData = await this . getExpoAndroidClientForSDK ( process . env . EXPO_SDK_MAJOR_VERSION || "" ) ; SmokeTestLogger . projectPatchingLog ( `*** Installing Expo app v${ expoClientData . version } on Android emulator using Expo XDL function` ) ; await XDL . Android . installExpoAsync ( { device : { name : this . emulatorId , type : "emulator" , isBooted : true , isAuthorized : true , } , url : expoClientData . url , version : expoClientData . version , } ) ; return this . enableDrawPermitForApp ( SmokeTestsConstants . expoPackageName ) ; } public uninstallTestAppFromEmulator ( appPackage ) { SmokeTestLogger . info ( `*** Uninstalling test app ${ appPackage }' from Emulator` ) ; try { cp . spawnSync ( "adb" , [ "shell" , "pm" , "uninstall" , appPackage ] , { stdio : "inherit" } ) ; } catch ( e ) { SmokeTestLogger . error ( `Error occured while uninstalling test app:\n ${ e . toString ( ) }` ) ; } } private async enableDrawPermitForApp ( packageName ) { const drawPermitCommand = `adb -s ${ this . emulatorId } shell appops set ${ packageName } SYSTEM_ALERT_WINDOW allow` ; SmokeTestLogger . projectPatchingLog ( `*** Enabling permission for drawing over apps via: ${ drawPermitCommand }` ) ; cp . execSync ( drawPermitCommand , { stdio : "inherit" } ) ; } public async runAndroidEmulator ( ) { await this . terminateAndroidEmulator ( ) ; const emulatorOpts = [ "-avd" , < string > this . emulatorName , "-gpu" , "swiftshader_indirect" , "-wipe-data" , "-port" , String ( this . emulatorPort ) , "-no-snapshot-save" , "-no-boot-anim" , "-no-audio" , ] ; SmokeTestLogger . info ( `*** Executing Android emulator with 'emulator ${ emulatorOpts . join ( " " ) }' command...` ) ; const proc = cp . spawn ( "emulator" , emulatorOpts , { stdio : "pipe" } ) ; proc . stdout . on ( "data" , chunk => { process . stdout . write ( chunk ) ; } ) ; proc . stderr . on ( "data" , chunk => { process . stderr . write ( chunk ) ; } ) ; const result = await this . waitUntilEmulatorStarting ( ) ; await sleep ( 60000 ) ; return result ; } public async terminateAndroidEmulator ( ) { let devices = AndroidEmulatorManager . getOnlineDevices ( ) ; SmokeTestLogger . info ( `*** Checking for running emulator with id ${ this . emulatorId }...` ) ; if ( devices . find ( it => it . id === this . emulatorId ) ) { cp . execSync ( `adb -s ${ this . emulatorId } emu kill` , { stdio : "inherit" } ) ; } else { SmokeTestLogger . warn ( "*** No running android emulators found" ) ; } return this . waitUntilEmulatorTerminating ( ) ; } public static async terminateAllAndroidEmulators ( ) { let devices = AndroidEmulatorManager . getOnlineDevices ( ) ; SmokeTestLogger . info ( "*** Checking for running android emulators..." ) ; if ( devices . length !== 0 ) { devices . forEach ( device => { SmokeTestLogger . info ( `*** Terminating Android '${ device . id }'...` ) ; cp . execSync ( `adb -s ${ device . id } emu kill` , { stdio : "inherit" } ) ; } ) ; } else { SmokeTestLogger . warn ( "*** No running android emulators found" ) ; } return AndroidEmulatorManager . waitUntilAllEmulatorTerminating ( ) ; } public async waitUntilEmulatorStarting ( ) { SmokeTestLogger . info ( "*** Wait for Android emulator starting..." ) ; const condition = ( ) => { const runningEmulators = AndroidEmulatorManager . getOnlineDevices ( ) ; if ( runningEmulators . find ( it => it . id === this . emulatorId ) ) { return true ; } return false ; } ; const result = await waitUntil ( condition , AndroidEmulatorManager . EMULATOR_START_TIMEOUT ) ; if ( result ) { SmokeTestLogger . success ( `*** Android emulator has been started.` ) ; } else { SmokeTestLogger . error ( `*** Could not start Android emulator in ${ AndroidEmulatorManager . EMULATOR_START_TIMEOUT } seconds.` ) ; } return result ; } public static adbReversePort ( port ) { SmokeTestLogger . info ( `*** Forwarding port ${ port } on an Android emulator.` ) ; cp . execSync ( `adb reverse tcp:${ port } tcp:${ port }` ) ; } public async waitUntilEmulatorTerminating ( ) { SmokeTestLogger . info ( "*** Wait for Android emulator terminating..." ) ; const condition = ( ) => { const runningEmulators = AndroidEmulatorManager . getOnlineDevices ( ) ; if ( ! runningEmulators . find ( it => it . id === this . emulatorId ) ) { return true ; } return false ; } ; const result = await waitUntil ( condition , AndroidEmulatorManager . EMULATOR_TERMINATING_TIMEOUT ) ; if ( result ) { SmokeTestLogger . success ( `*** Android emulator has been terminated.` ) ; } else { SmokeTestLogger . error ( `*** Could not terminate Android emulator in ${ AndroidEmulatorManager . EMULATOR_START_TIMEOUT } seconds.` ) ; } return result ; } public static async waitUntilSomeEmulatorStarting ( ) { SmokeTestLogger . info ( "*** Wait for Android emulator starting..." ) ; const countOfRunningEmulators = AndroidEmulatorManager . getOnlineDevices ( ) . length ; const condition = ( ) => { if ( AndroidEmulatorManager . getOnlineDevices ( ) . length > countOfRunningEmulators ) { return true ; } return false ; } ; const result = await waitUntil ( condition , AndroidEmulatorManager . EMULATOR_START_TIMEOUT ) ; if ( result ) { SmokeTestLogger . success ( `*** Some Android emulator has been started.` ) ; } else { SmokeTestLogger . error ( `*** Could not start any Android emulator in ${ AndroidEmulatorManager . EMULATOR_START_TIMEOUT } seconds.` ) ; } return result ; } public static async waitUntilAllEmulatorTerminating ( ) { SmokeTestLogger . info ( "*** Wait for Android emulator terminating..." ) ; const condition = ( ) => { if ( AndroidEmulatorManager . getOnlineDevices ( ) . length === 0 ) { return true ; } return false ; } ; const result = await waitUntil ( condition , AndroidEmulatorManager . EMULATOR_TERMINATING_TIMEOUT ) ; if ( result ) { SmokeTestLogger . success ( `*** All Android emulators has been terminated.` ) ; } else { SmokeTestLogger . error ( `*** Could not terminate all Android emulator in ${ AndroidEmulatorManager . EMULATOR_START_TIMEOUT } seconds.` ) ; } return result ; } public static closeApp ( packageName ) { SmokeTestLogger . info ( `*** Clearing installed application with package name ${ packageName }...` ) ; cp . execSync ( `adb shell pm clear ${ packageName }` , { timeout : 320000 } ) ; SmokeTestLogger . info ( `*** The application with package name ${ packageName } has been cleared.` ) ; } private static getOnlineDevices ( ) { const devices = this . getConnectedDevices ( ) ; return devices . filter ( device => device . isOnline ) ; } private static getConnectedDevices ( ) { const devices = cp . execSync ( "adb devices" ) . toString ( ) ; return this . parseConnectedDevices ( devices ) ; } private static parseConnectedDevices ( input ) { let result = [ ] ; let regex = new RegExp ( "^(\\S+)\\t(\\S+)$" , "mg" ) ; let match = regex . exec ( input ) ; while ( match != null ) { result . push ( { id : match [ 1 ] , isOnline : match [ 2 ] === "device" } ) ; match = regex . exec ( input ) ; } return result ; } }