import { Application } from "../../automation" ; import * as assert from "assert" ; import * as path from "path" ; import { AppiumClient , AppiumHelper , Platform } from "./helpers/appiumHelper" ; import { SmokeTestsConstants } from "./helpers/smokeTestsConstants" ; import { findFile , findStringInFile , isLoggedInExpo , sleep , waitUntil } from "./helpers/utilities" ; import { androidEmulatorManager , iosSimulatorManager , vscodeManager } from "./main" ; import { TestRunArguments } from "./helpers/testConfigProcessor" ; import { SmokeTestLogger } from "./helpers/smokeTestLogger" ; import TestProject from "./helpers/testProject" ; import AutomationHelper from "./helpers/AutomationHelper" ; import AndroidEmulatorManager from "./helpers/androidEmulatorManager" ; const EXPO_APP_LAUNCH_TIMEOUT = 120000 ; const ExpoSuccessPattern = "Tunnel ready" ; const ExpoFailurePattern = "XDLError" ; const EXPO_APP_PACKAGE_NAME = SmokeTestsConstants . expoPackageName ; const EXPO_APP_ACTIVITY_NAME = `${ SmokeTestsConstants . expoPackageName }.experience.HomeActivity` ; const ExpoTunnelDebugConfigName = "Debug in Exponent (Tunnel)" ; const ExpoLanDebugConfigName = "Debug in Exponent (LAN)" ; const ExpoLocalDebugConfigName = "Debug in Exponent (Local)" ; const ExpoSetBreakpointOnLine = 1 ; const debugExpoTestTime = SmokeTestsConstants . expoTestTimeout ; const expoLogginErrorMessage = "It seems you are not currently logged into Expo account. To successfully pass this test, you must be logged into Expo account" ; interface ExpoLaunch { successful : boolean ; failed : boolean ; } export function startExpoTests ( expoProject , pureProject , testParameters ) { describe ( "Expo tests" , ( ) => { let app ; let expoFirstLaunch = true ; let client ; let automationHelper ; async function initApp ( workspaceOrFolder , sessionName ? , locale ? ) { app = await vscodeManager . runVSCode ( workspaceOrFolder , sessionName , locale ) ; automationHelper = new AutomationHelper ( app ) ; return app ; } async function disposeAll ( ) { try { SmokeTestLogger . info ( "Dispose all ..." ) ; if ( app ) { SmokeTestLogger . info ( "Stopping React Native packager ..." ) ; await automationHelper . runCommandWithRetry ( SmokeTestsConstants . stopPackagerCommand ) ; await sleep ( 3000 ) ; SmokeTestLogger . info ( "Stopping application ..." ) ; await app . stop ( ) ; } if ( client ) { try { SmokeTestLogger . info ( "Closing application ..." ) ; await client . closeApp ( ) ; SmokeTestLogger . info ( "Deleting session ..." ) ; await client . deleteSession ( ) ; } catch ( err ) { SmokeTestLogger . error ( err ) ; } } SmokeTestLogger . info ( "Clearing android application ..." ) ; AndroidEmulatorManager . closeApp ( EXPO_APP_PACKAGE_NAME ) ; } catch ( error ) { SmokeTestLogger . error ( "Error while disposeAll:" ) ; SmokeTestLogger . error ( error ) ; } } afterEach ( async function ( ) { this . timeout ( SmokeTestsConstants . smokeTestAfterEachTimeout ) ; await disposeAll ( ) ; } ) ; async function findExpoSuccessAndFailurePatterns ( filePath , successPattern , failurePattern ) { let result = undefined ; const condition = ( ) => { let expoStarted = findStringInFile ( filePath , successPattern ) ; let expoFailed = findStringInFile ( filePath , failurePattern ) ; SmokeTestLogger . info ( `Searching for Expo launch logging patterns ...` ) ; if ( expoStarted || expoFailed ) { result = { successful : expoStarted , failed : expoFailed } ; SmokeTestLogger . info ( `Expo launch status patterns found: ${ JSON . stringify ( result , null , 2 ) }` ) ; return true ; } else { return false ; } } ; await waitUntil ( condition , EXPO_APP_LAUNCH_TIMEOUT , 5000 ) ; if ( result ) { return result ; } else { SmokeTestLogger . info ( `Expo launch logging patterns are not found` ) ; return { successful : false , failed : false } ; } } function findExpoURLInLogFile ( ) { const match = vscodeManager . findPatternInLogs ( /exp:\/\/\d+\.\d+\.\d+\.\d+\:\d+/gm , SmokeTestsConstants . ReactNativeRunExpoLogFileName ) ; if ( ! match ) return null ; let expoURL = match [ 0 ] ; SmokeTestLogger . info ( `Found Expo URL: ${ expoURL }` ) ; return expoURL ; } async function runExpoDebugScenario ( logFilePath , testName , debugConfigName , triesToLaunchApp ) { SmokeTestLogger . info ( `${ testName }: Starting debugging` ) ; if ( triesToLaunchApp <= 1 ) { await automationHelper . runDebugScenarioWithRetry ( debugConfigName ) ; await automationHelper . runCommandWithRetry ( "Output: Focus on Output View" ) ; } else { for ( let retry = 1 ; retry <= triesToLaunchApp ; retry ++ ) { let expoLaunchStatus ; await automationHelper . runDebugScenarioWithRetry ( debugConfigName ) ; await automationHelper . runCommandWithRetry ( "Output: Focus on Output View" ) ; expoLaunchStatus = await findExpoSuccessAndFailurePatterns ( logFilePath , ExpoSuccessPattern , ExpoFailurePattern ) ; if ( expoLaunchStatus . successful ) { break ; } else { if ( retry === triesToLaunchApp ) { assert . fail ( `App start has failed after ${ retry } retries` ) ; } SmokeTestLogger . warn ( `Attempt to start #${ retry } failed, retrying...` ) ; await automationHelper . stopDebuggingWithRetry ( ) ; } } } } async function expoTest ( project , testName , debugConfigName , platform , triesToLaunchApp ) { let logFilePath = "" ; app = await initApp ( project . workspaceDirectory , testName ) ; SmokeTestLogger . info ( `${ testName }: ${ project . workspaceDirectory } directory is opened in VS Code` ) ; await automationHelper . openFileWithRetry ( project . projectEntryPointFile ) ; await app . workbench . editors . scrollTop ( ) ; SmokeTestLogger . info ( `${ testName }: ${ project . projectEntryPointFile } file is opened` ) ; await app . workbench . debug . setBreakpointOnLine ( ExpoSetBreakpointOnLine ) ; SmokeTestLogger . info ( `${ testName }: Breakpoint is set on line ${ ExpoSetBreakpointOnLine }` ) ; SmokeTestLogger . info ( `${ testName }: Chosen debug configuration: ${ debugConfigName }` ) ; if ( process . env . REACT_NATIVE_TOOLS_LOGS_DIR ) { logFilePath = path . join ( process . env . REACT_NATIVE_TOOLS_LOGS_DIR , SmokeTestsConstants . ReactNativeLogFileName ) ; } else { assert . fail ( "REACT_NATIVE_TOOLS_LOGS_DIR is not defined" ) ; } await runExpoDebugScenario ( logFilePath , testName , debugConfigName , triesToLaunchApp ) ; await app . workbench . editors . waitForTab ( "Expo QR Code" , false , true , 2400 ) ; await app . workbench . editors . waitForActiveTab ( "Expo QR Code" , false , true ) ; SmokeTestLogger . info ( `${ testName }: 'Expo QR Code' tab found` ) ; let expoURL = findExpoURLInLogFile ( ) ; if ( expoURL === null ) { assert . fail ( "Expo URL pattern is not found" ) ; return ; } if ( platform === Platform . iOSExpo ) { const device = iosSimulatorManager . getSimulator ( ) . name ; let appFile = findFile ( SmokeTestsConstants . iOSExpoAppsCacheDir , /.*\.(app)/ ) ; if ( ! appFile ) { throw new Error ( `iOS Expo app is not found in ${ SmokeTestsConstants . iOSExpoAppsCacheDir }` ) ; } const appPath = path . join ( SmokeTestsConstants . iOSExpoAppsCacheDir , appFile ) ; const opts = AppiumHelper . prepareAttachOptsForIosApp ( device , appPath ) ; client = await AppiumHelper . webdriverAttach ( opts ) ; await AppiumHelper . openExpoApplication ( Platform . iOS , client , expoURL , project . workspaceDirectory , expoFirstLaunch ) ; expoFirstLaunch = false ; SmokeTestLogger . info ( `${ testName }: Waiting ${ SmokeTestsConstants . expoAppBuildAndInstallTimeout }ms until Expo app is ready...` ) ; await sleep ( SmokeTestsConstants . expoAppBuildAndInstallTimeout ) ; await AppiumHelper . disableExpoErrorRedBox ( client ) ; await AppiumHelper . disableDevMenuInformationalMsg ( client , Platform . iOSExpo ) ; await AppiumHelper . enableRemoteDebugJS ( client , Platform . iOSExpo ) ; await sleep ( 5 * 1000 ) ; } else { expoURL = expoURL as string ; const opts = AppiumHelper . prepareAttachOptsForAndroidActivity ( EXPO_APP_PACKAGE_NAME , EXPO_APP_ACTIVITY_NAME , androidEmulatorManager . getEmulatorId ( ) ) ; client = await AppiumHelper . webdriverAttach ( opts ) ; await AppiumHelper . openExpoApplication ( Platform . Android , client , expoURL , project . workspaceDirectory ) ; SmokeTestLogger . info ( `${ testName }: Waiting ${ SmokeTestsConstants . expoAppBuildAndInstallTimeout }ms until Expo app is ready...` ) ; await sleep ( SmokeTestsConstants . expoAppBuildAndInstallTimeout ) ; await AppiumHelper . disableDevMenuInformationalMsg ( client , Platform . AndroidExpo ) ; await sleep ( 2 * 1000 ) ; await AppiumHelper . enableRemoteDebugJS ( client , Platform . AndroidExpo ) ; await app . workbench . debug . waitForDebuggingToStart ( ) ; } SmokeTestLogger . info ( `${ testName }: Debugging started` ) ; await app . workbench . editor . waitForEditorFocus ( project . projectEntryPointFile , 1 ) ; await automationHelper . waitForStackFrameWithRetry ( sf => sf . name === project . projectEntryPointFile && sf . lineNumber === ExpoSetBreakpointOnLine , `looking for ${ project . projectEntryPointFile } and line ${ ExpoSetBreakpointOnLine }` ) ; SmokeTestLogger . info ( `${ testName }: Stack frame found` ) ; await app . workbench . debug . stepOver ( ) ; await sleep ( SmokeTestsConstants . debugConsoleSearchTimeout ) ; SmokeTestLogger . info ( `${ testName }: Searching for \"Test output from debuggee\" string in console` ) ; await automationHelper . runCommandWithRetry ( "Debug: Focus on Debug Console View" ) ; let found = await automationHelper . waitForOutputWithRetry ( "Test output from debuggee" ) ; assert . notStrictEqual ( found , false , '"Test output from debuggee" string is missing in debug console' ) ; SmokeTestLogger . success ( `${ testName }: \"Test output from debuggee\" string is found` ) ; await automationHelper . disconnectFromDebuggerWithRetry ( ) ; SmokeTestLogger . info ( `${ testName }: Debugging is stopped` ) ; } if ( testParameters . RunAndroidTests ) { it ( "Android Pure RN app Expo test(LAN)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; await expoTest ( pureProject , "Android pure RN Expo test(LAN)" , ExpoLanDebugConfigName , Platform . AndroidExpo , 1 ) ; } ) ; it ( "Android Expo app Debug test(LAN)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; await expoTest ( expoProject , "Android Expo Debug test(LAN)" , ExpoLanDebugConfigName , Platform . AndroidExpo , 1 ) ; } ) ; it ( "Android Expo app Debug test(Tunnel)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; if ( ! isLoggedInExpo ( ) ) { assert . fail ( expoLogginErrorMessage ) ; } await expoTest ( expoProject , "Android Expo Debug test(Tunnel)" , ExpoTunnelDebugConfigName , Platform . AndroidExpo , 5 ) ; } ) ; it ( "Android Expo app Debug test(localhost)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; await expoTest ( expoProject , "Android Expo Debug test(localhost)" , ExpoLocalDebugConfigName , Platform . AndroidExpo , 1 ) ; } ) ; } if ( process . platform === "darwin" && testParameters . RunIosTests ) { it ( "iOS Pure RN app Expo test(LAN)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; await expoTest ( pureProject , "iOS pure RN Expo test(LAN)" , ExpoLanDebugConfigName , Platform . iOSExpo , 1 ) ; } ) ; it ( "iOS Expo app Debug test(LAN)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; await expoTest ( expoProject , "iOS Expo Debug test(LAN)" , ExpoLanDebugConfigName , Platform . iOSExpo , 1 ) ; } ) ; it ( "iOS Expo app Debug test(localhost)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; await expoTest ( expoProject , "iOS Expo Debug test(localhost)" , ExpoLocalDebugConfigName , Platform . iOSExpo , 1 ) ; } ) ; it ( "iOS Expo app Debug test(Tunnel)" , async function ( ) { this . timeout ( debugExpoTestTime ) ; if ( ! isLoggedInExpo ( ) ) { assert . fail ( expoLogginErrorMessage ) ; } await expoTest ( expoProject , "iOS Expo Debug test(Tunnel)" , ExpoTunnelDebugConfigName , Platform . iOSExpo , 5 ) ; } ) ; } } ) ; }