import * as fs from 'fs' ; import * as path from 'path' ; import { Application , SpectronClient as WebClient } from 'spectron' ; import { Screenshot } from '../helpers/screenshot' ; import { SpectronClient } from './client' ; const WEBDRIVER_PORT = 7777 ; const SALESFORCEDX_EXTENSIONS = path . join ( process . cwd ( ) , '..' ) ; export const VSCODE_BINARY_PATH = process . env . VSCODE_BINARY_PATH as string ; /**
 * Wraps Spectron's Application instance with its used methods.
 */ export class SpectronApplication { public client : SpectronClient ; private spectron : Application ; private keybindings : any [ ] = [ ] ; private screenshot : Screenshot ; private readonly pollTrials = 5 ; private readonly pollTimeout = 3 ; constructor ( electronPath , testName , private testRetry , args ? , chromeDriverArgs ? ) { if ( ! args ) { args = [ ] ; } args . push ( '--skip-getting-started' ) ; args . push ( '--skip-release-notes' ) ; args . push ( '--sticky-quickopen' ) ; args . push ( '--disable-telemetry' ) ; args . push ( '--disable-updates' ) ; args . push ( '--disable-crash-reporter' ) ; args . push ( `--extensions-dir=${ SALESFORCEDX_EXTENSIONS }` ) ; this . spectron = new Application ( { port : WEBDRIVER_PORT , path : electronPath , args , chromeDriverArgs , startTimeout : 10000 , requireName : 'nodeRequire' } ) ; this . testRetry += 1 ; this . screenshot = new Screenshot ( this , testName , testRetry ) ; this . client = new SpectronClient ( this . spectron , this . screenshot ) ; this . retrieveKeybindings ( ) ; } public get app ( ) : Application { return this . spectron ; } public async start ( ) { try { await this . spectron . start ( ) ; return this . checkWindowReady ( ) ; } catch ( err ) { throw err ; } } public async stop ( ) { if ( this . spectron && this . spectron . isRunning ( ) ) { return await this . spectron . stop ( ) ; } } public waitFor ( func , args ) { return this . callClientAPI ( func , args ) ; } public wait ( ) { return new Promise ( resolve => setTimeout ( resolve , this . testRetry * this . pollTimeout * 1000 ) ) ; } public waitUI ( ) { return new Promise ( resolve => setTimeout ( resolve , this . pollTimeout * 1000 ) ) ; } public get webclient ( ) : WebClient { if ( ! this . spectron ) { throw new Error ( 'Application not started' ) ; } return this . spectron . client ; } private async checkWindowReady ( ) { await this . webclient . waitUntilWindowLoaded ( 60000 ) ; const count = await this . webclient . getWindowCount ( ) ; if ( count > 1 ) { for ( let i = 0 ; i < count ; i ++ ) { await this . webclient . windowByIndex ( i ) ; const title = await this . webclient . getTitle ( ) ; if ( process . platform === 'win32' && title !== '' && /Visual Studio Code/ . test ( title ) ) { break ; } else if ( /bootstrap\/index\.html/ . test ( await this . webclient . getUrl ( ) ) ) { break ; } } } else { await this . webclient . windowByIndex ( 0 ) ; } await this . waitFor ( this . spectron . client . getHTML , '.monaco-workbench' ) ; } private getKeybindingPlatform ( ) { switch ( process . platform ) { case 'darwin' : return 'osx' ; case 'win32' : return 'win' ; default : return process . platform ; } } private retrieveKeybindings ( ) { fs . readFile ( path . join ( process . cwd ( ) , 'test_data' , `keybindings.${ this . getKeybindingPlatform ( ) }.json` ) , 'utf8' , ( err , data ) => { if ( err ) { throw err ; } try { this . keybindings = JSON . parse ( data ) ; } catch ( e ) { throw new Error ( `Error parsing keybindings JSON: ${ e }` ) ; } } ) ; } private callClientAPI ( func , args ) { let trial = 1 ; return new Promise ( async ( res , rej ) => { while ( true ) { if ( trial > this . pollTrials ) { await this . screenshot . capture ( ) ; rej ( `Could not retrieve the element in ${ this . testRetry * this . pollTrials * this . pollTimeout } seconds. (${ JSON . stringify ( args ) })` ) ; break ; } let result ; try { result = await func . call ( this . client , args , false ) ; } catch ( e ) { console . log ( ` Attempt #${ trial }: ${ args } :::: ${ e }` ) ; await this . screenshot . capture ( ) ; } if ( result && result !== '' ) { await this . screenshot . capture ( ) ; res ( result ) ; break ; } await this . wait ( ) ; trial ++ ; } } ) ; } public command ( command , capture ? ) { const binding = this . keybindings . find ( x => x [ 'command' ] === command ) ; if ( ! binding ) { return Promise . reject ( `Key binding for ${ command } was not found.` ) ; } const keyBindings = binding . key ; const keysToPress = [ ] ; const chords = keyBindings . split ( ' ' ) ; chords . forEach ( chord => { const keys = chord . split ( '+' ) ; keys . forEach ( key => keysToPress . push ( this . transliterate ( key ) ) ) ; keysToPress . push ( 'NULL' ) ; } ) ; return this . client . keys ( keysToPress , capture ) ; } private transliterate ( key ) { switch ( key ) { case 'ctrl' : return 'Control' ; case 'cmd' : return 'Meta' ; default : return key . length === 1 ? key : key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) ; } } public static removeWebdriverDeprecationWarning ( ) { see webdriverio see https const wdioDeprecationWarning = /^WARNING: the "\w+" command will be (deprecated|depcrecated) soon..*/ ; const warn = console . warn ; console . warn = function suppressWebdriverWarnings ( message ) { if ( wdioDeprecationWarning . test ( message ) ) { return ; } warn . apply ( console , Array . from ( arguments ) as [ any ? , ... any [ ] ] ) ; } ; } } SpectronApplication . removeWebdriverDeprecationWarning ( ) ;