import { Agent as HttpAgent } from 'http' ; import { Agent as HttpsAgent } from 'https' ; import keys from './keys' ; import { Task , CancellablePromise , request , RequestOptions , RequestMethod , Response } from '@theintern/common' ; import Session , { WebDriverTimeouts } from './Session' ; import Element from './Element' ; import statusCodes from './lib/statusCodes' ; import { format , parse , resolve , Url } from 'url' ; import { sleep , trimStack } from './lib/util' ; import { Capabilities , LeadfootURL , LeadfootError } from './interfaces' ; export default class Server { url : string ; requestOptions : RequestOptions ; /**
     * An alternative session constructor. Defaults to the standard [[Session]]
     * constructor if one is not provided.
     */ sessionConstructor = Session ; /**
     * Whether or not to detect and/or correct environment capabilities when
     * creating a new Server. If the value is "no-detect", capabilities will be
     * updated with already-known features and defects based on the platform, but
     * no tests will be run.
     */ fixSessionCapabilities : boolean | 'no-detect' = true ; private _httpAgent = new HttpAgent ( { keepAlive : true , keepAliveMsecs : 30000 } ) ; private _httpsAgent = new HttpsAgent ( { keepAlive : true , keepAliveMsecs : 30000 } ) ; param url param options constructor ( url , options ? ) { if ( typeof url === 'object' ) { url = < URL > { ... url } ; if ( url . username || url . password || url . accessKey ) { url . auth = encodeURIComponent ( url . username || '' ) + ':' + encodeURIComponent ( url . password || url . accessKey || '' ) ; } } this . url = format ( < Url > url ) . replace ( /\/*$/ , '/' ) ; this . requestOptions = options || { } ; } param method param path param requestData param pathParts private _sendRequest < T > ( method , path , requestData , pathParts ? ) { const url = this . url + path . replace ( /\$(\d)/ , function ( _ , index ) { return encodeURIComponent ( pathParts ! [ index ] ) ; } ) ; const defaultRequestHeaders = { Accept : 'application/json,text/plain;q=0.9' } ; const headers = { ... defaultRequestHeaders } ; const kwArgs = { ... this . requestOptions , followRedirects : false , handleAs : 'text' , httpAgent : this . _httpAgent , httpsAgent : this . _httpsAgent , headers , method } as RequestOptions ; if ( requestData ) { kwArgs . data = JSON . stringify ( requestData ) ; headers [ 'Content-Type' ] = 'application/json;charset=UTF-8' ; headers [ 'Content-Length' ] = String ( Buffer . byteLength ( kwArgs . data , 'utf8' ) ) ; } else { headers [ 'Content-Length' ] = '0' ; } const trace = { } ; Error . captureStackTrace ( trace , this . _sendRequest ) ; return new Task ( ( resolve , reject ) => { request ( url , kwArgs ) . then ( resolve , reject ) . finally ( ( ) => { const error = new Error ( 'Canceled' ) ; error . name = 'CancelError' ; reject ( error ) ; } ) ; } ) . then ( function handleResponse ( response ) : ResponseData | CancellablePromise < ResponseData > { if ( response . status >= 300 && response . status < 400 && response . headers . get ( 'Location' ) ) { let redirectUrl = response . headers . get ( 'Location' ) ! ; if ( ! /^\w+:/ . test ( redirectUrl ) ) { redirectUrl = resolve ( url , redirectUrl ) ; } return request ( redirectUrl , { headers : defaultRequestHeaders } ) . then ( handleResponse ) ; } return response . text ( ) . then ( data => { return { response , data } ; } ) ; } ) . then ( ( responseData ) => { const response = responseData . response ; const responseType = response . headers . get ( 'Content-Type' ) ; let data ; if ( responseType && responseType . indexOf ( 'application/json' ) === 0 && responseData . data ) { data = JSON . parse ( responseData . data ) ; } if ( response . status === 204 ) { data = { status : 0 , sessionId : null , value : null } ; } else if ( response . status >= 400 || ( data && data . status > 0 ) ) { const error = new Error ( ) ; if ( ! data ) { data = { value : { message : responseData . data } } ; } else if ( ! data . value && 'message' in data ) { data = { status : response . status === 404 || response . status === 501 || data . message . indexOf ( 'cannot find command' ) > - 1 ? 9 : 13 , value : data } ; } if ( ! data . status ) { data . status = response . status === 404 || response . status === 501 ? 9 : 13 ; } if ( data . status === 404 || data . status === 501 ) { data . status = 9 ; } if ( response . status === 501 && data . status === 13 ) { data . status = 9 ; } if ( response . status === 500 && data . value && data . value . message === 'Invalid Command' ) { data . status = 9 ; } if ( response . status === 422 && data . value && data . value . message === 'Invalid Command' ) { data . status = 9 ; } if ( data . status === 13 && data . value && data . value . class && ( data . value . class . indexOf ( 'UnsupportedOperationException' ) > - 1 || data . value . class . indexOf ( 'UnsupportedCommandException' ) > - 1 ) ) { data . status = 9 ; } if ( response . status === 500 && data . value && data . value . message && ( data . value . message . indexOf ( 'Command not found' ) > - 1 || data . value . message . indexOf ( 'Unknown command' ) > - 1 ) ) { data . status = 9 ; } if ( response . status === 405 && data . value && data . value . message && data . value . message . indexOf ( 'Invalid Command Method' ) > - 1 ) { data . status = 9 ; } const statusCode = statusCodes [ < keyof typeof statusCodes > data . status ] ; if ( statusCode ) { const [ name , message ] = statusCode ; if ( name && message ) { error . name = name ; error . message = message ; } } if ( data . value && data . value . message ) { error . message = data . value . message ; } if ( data . value && data . value . screen ) { data . value . screen = Buffer . from ( data . value . screen , 'base64' ) ; } error . status = data . status ; error . detail = data . value ; error . name = getErrorName ( error ) ; error . request = { url : url , method : method , data : requestData } ; error . response = response ; const sanitizedUrl = ( function ( ) { const parsedUrl = parse ( url ) ; if ( parsedUrl . auth ) { parsedUrl . auth = '(redacted)' ; } return format ( parsedUrl ) ; } ) ( ) ; error . message = `[${ method } ${ sanitizedUrl }` + ( requestData ? ` / ${ JSON . stringify ( requestData ) }` : '' ) + `] ${ error . message }` ; error . stack = error . message + trimStack ( trace . stack ) ; throw error ; } return data ; } ) . catch ( function ( error ) { error . stack = error . message + trimStack ( trace . stack ) ; throw error ; } ) ; } get < T > ( path , requestData ? , pathParts ? ) { return this . _sendRequest < T > ( 'GET' , path , requestData , pathParts ) ; } post < T > ( path , requestData ? , pathParts ? ) { return this . _sendRequest < T > ( 'POST' , path , requestData , pathParts ) ; } delete < T > ( path , requestData ? , pathParts ? ) { return this . _sendRequest < T > ( 'DELETE' , path , requestData , pathParts ) ; } returns getStatus ( ) { return this . get ( 'status' ) . then ( returnValue ) ; } param desiredCapabilities param requiredCapabilities createSession < S extends Session = Session > ( desiredCapabilities , requiredCapabilities ? ) { let fixSessionCapabilities = this . fixSessionCapabilities ; if ( desiredCapabilities . fixSessionCapabilities != null ) { fixSessionCapabilities = desiredCapabilities . fixSessionCapabilities ; desiredCapabilities = { ... desiredCapabilities } ; desiredCapabilities . fixSessionCapabilities = undefined ; } return this . post < any > ( 'session' , { desiredCapabilities , requiredCapabilities } ) . then ( response => { let responseData ; let sessionId ; if ( response . value . sessionId && response . value . capabilities ) { responseData = response . value . capabilities ; sessionId = response . value . sessionId ; } else if ( response . value . value && response . value . sessionId ) { responseData = response . value . value ; sessionId = response . value . sessionId ; } else { responseData = response . value ; sessionId = response . sessionId ; } const session = new this . sessionConstructor ( sessionId , this , responseData ) ; const userKeys = Object . keys ( desiredCapabilities ) . filter ( key => ! ( key in session . capabilities ) ) ; for ( const key of userKeys ) { session . capabilities [ key ] = desiredCapabilities [ key ] ; } if ( fixSessionCapabilities ) { return this . _fillCapabilities ( < S > session , fixSessionCapabilities !== 'no-detect' ) . catch ( error => session . quit ( ) . finally ( ( ) => { throw error ; } ) ) . then ( ( ) => < S > session ) ; } else { return < S > session ; } } ) ; } /**
     * Fill in known capabilities/defects and optionally run tests to detect
     * more
     */ private _fillCapabilities < S extends Session > ( session , detectCapabilities = true ) { Object . assign ( session . capabilities , this . _getKnownCapabilities ( session ) ) ; return ( detectCapabilities ? this . _detectCapabilities ( session ) : Task . resolve ( session ) ) . then ( ( ) => { Object . defineProperty ( session . capabilities , '_filled' , { value : true , configurable : true } ) ; return session ; } ) ; } /**
     * Return capabilities and defects that don't require running tests
     */ private _getKnownCapabilities ( session ) { const capabilities = session . capabilities ; const updates = { } ; if ( isSafari ( session . capabilities ) && ! session . capabilities . browserVersion ) { const { version } = session . capabilities ; const versionNum = parseFloat ( version ! ) ; if ( versionNum > 12000 && versionNum < 13000 ) { session . capabilities . browserVersion = '10' ; } else if ( versionNum > 13000 ) { session . capabilities . browserVersion = '11' ; } } if ( capabilities . platform && ! capabilities . platformName ) { capabilities . platformName = capabilities . platform ; } if ( capabilities . version && ! capabilities . browserVersion ) { capabilities . browserVersion = capabilities . version ; } if ( isSafari ( capabilities ) ) { if ( isMac ( capabilities ) ) { if ( isValidVersion ( capabilities , 0 , 11 ) ) { Object . assign ( updates , { nativeEvents : false , rotatable : false , locationContextEnabled : false , webStorageEnabled : false , applicationCacheEnabled : false , supportsNavigationDataUris : true , supportsCssTransforms : true , supportsExecuteAsync : true , mouseEnabled : true , touchEnabled : false , dynamicViewport : true , shortcutKey : keys . COMMAND , returnsFromClickImmediately : false , brokenDeleteCookie : false , brokenExecuteElementReturn : false , brokenExecuteUndefinedReturn : false , brokenElementDisplayedOpacity : false , brokenElementDisplayedOffscreen : false , brokenSubmitElement : true , brokenWindowSwitch : true , brokenDoubleClick : false , brokenCssTransformedSize : true , fixedLogTypes : false as false , brokenHtmlTagName : false , brokenNullGetSpecAttribute : false } ) ; } if ( isValidVersion ( capabilities , 0 , 10 ) ) { Object . assign ( updates , { remoteFiles : false , brokenActiveElement : true , brokenExecuteForNonHttpUrl : true , brokenMouseEvents : true , brokenNavigation : true , brokenOptionSelect : false , brokenSendKeys : true , brokenWindowPosition : true , brokenWindowSize : true , brokenCookies : true } ) ; } if ( isValidVersion ( capabilities , 10 , 12 ) ) { Object . assign ( updates , { brokenLinkTextLocator : true , brokenOptionSelect : true , brokenWhitespaceNormalization : true , brokenMouseEvents : true , brokenWindowClose : true , usesWebDriverActiveElement : true } ) ; } if ( isValidVersion ( capabilities , 12 , 13 ) ) { Object . assign ( updates , { usesWebDriverElementAttribute : true , brokenDeleteWindow : true } ) ; } if ( isValidVersion ( capabilities , 13 , Infinity ) ) { Object . assign ( updates , { brokenClick : true , brokenMouseEvents : true , brokenOptionSelect : true , brokenWindowClose : true } ) ; } } if ( isIos ( capabilities ) ) { updates . brokenWindowSwitch = true ; } return updates ; } if ( isFirefox ( capabilities ) ) { if ( isValidVersion ( capabilities , 49 , Infinity ) ) { updates . noKeysCommand = true ; updates . usesWebDriverLocators = true ; updates . usesFlatKeysArray = true ; updates . brokenEmptyPost = true ; updates . brokenMouseEvents = true ; updates . fixedLogTypes = [ ] ; } if ( isValidVersion ( capabilities , 49 , 53 ) ) { updates . brokenWindowSwitch = true ; } if ( capabilities . mouseEnabled == null && isValidVersion ( capabilities , 49 , Infinity ) && isMac ( capabilities ) ) { updates . mouseEnabled = true ; } } if ( isMsEdge ( capabilities ) ) { updates . returnsFromClickImmediately = true ; if ( isValidVersion ( capabilities , 0 , 44.17763 ) ) { updates . brokenClick = true ; } updates . remoteFiles = false ; if ( isValidVersion ( capabilities , 25.10586 ) ) { updates . brokenWindowClose = true ; } if ( isValidVersion ( capabilities , 38.14366 ) ) { updates . brokenFileSendKeys = true ; } if ( isValidVersion ( capabilities , 37.14316 ) && ! ( 'handlesAlerts' in capabilities ) ) { updates . handlesAlerts = true ; } if ( isValidVersion ( capabilities , 44.17763 ) ) { if ( capabilities . usesWebDriverExecuteSync == null ) { updates . usesWebDriverExecuteSync = true ; } if ( capabilities . usesWebDriverExecuteAsync == null ) { updates . usesWebDriverExecuteAsync = true ; } } if ( isValidVersion ( capabilities , 0 , 43 ) ) { updates . brokenDeleteCookie = true ; } } if ( isInternetExplorer ( capabilities ) ) { updates . supportsNavigationDataUris = false ; if ( isValidVersion ( capabilities , 10 , Infinity ) ) { updates . brokenParentFrameSwitch = true ; } if ( isValidVersion ( capabilities , 11 ) ) { updates . takesScreenshot = true ; updates . brokenSubmitElement = true ; updates . brokenWindowSwitch = false ; updates . brokenParentFrameSwitch = true ; } if ( isValidVersion ( capabilities , 11 , Infinity ) ) { updates . brokenOptionSelect = false ; updates . supportsGetTimeouts = false ; updates . brokenZeroTimeout = true ; } if ( isValidVersion ( capabilities , 9 ) ) { updates . supportsCssTransforms = true ; } updates . scriptedParentFrameCrashesBrowser = isValidVersion ( capabilities , 0 , 9 ) ; } if ( capabilities . hasTouchScreen === false ) { updates . touchEnabled = false ; } updates . shortcutKey = ( function ( ) { if ( isIos ( capabilities ) ) { return null ; } if ( isMac ( capabilities ) ) { return keys . COMMAND ; } return keys . CONTROL ; } ) ( ) ; if ( isAndroid ( capabilities ) && isAndroidEmulator ( capabilities ) ) { updates . brokenParentFrameSwitch = true ; } return updates ; } /**
     * Run tests to detect capabilities/defects
     */ private _detectCapabilities ( session ) { const capabilities = session . capabilities ; const supported = ( ) => true ; const unsupported = ( ) => false ; const maybeSupported = ( error ) => { if ( error . name === 'UnknownCommand' ) { return false ; } if ( /\bunimplemented command\b/ . test ( error . message ) ) { return false ; } if ( /The command .* not found/ . test ( error . message ) ) { return false ; } if ( /HTTP method not allowed/ . test ( error . message ) ) { return false ; } return true ; } ; const broken = supported ; const works = unsupported ; /**
         * Adds the capabilities listed in the `testedCapabilities` object to
         * the hash of capabilities for the current session. If a tested
         * capability value is a function, it is assumed that it still needs to
         * be executed serially in order to resolve the correct value of that
         * particular capability.
         */ const addCapabilities = ( testedCapabilities ) : CancellablePromise < void > => Object . keys ( testedCapabilities ) . reduce ( ( previous , key ) => previous . then ( ( ) => { const value = testedCapabilities [ key ] ; const task = typeof value === 'function' ? value ( ) : Task . resolve ( value ) ; return task . then ( ( value ) => { capabilities [ key ] = value ; } ) ; } ) , Task . resolve ( ) ) ; const get = ( page ) => { if ( capabilities . supportsNavigationDataUris !== false ) { return session . get ( 'data:text/html;charset=utf-8,' + encodeURIComponent ( page ) ) ; } if ( isInternetExplorer ( capabilities ) || isMsEdge ( capabilities ) ) { let initialUrl = 'about:blank' ; if ( isInternetExplorer ( capabilities ) && capabilities [ 'se:ieOptions' ] ) { initialUrl = capabilities [ 'se:ieOptions' ] . initialBrowserUrl ; } else if ( capabilities . initialBrowserUrl ) { initialUrl = capabilities . initialBrowserUrl ; } return session . get ( initialUrl ) . then ( function ( ) { return session . execute < void > ( 'document.body.innerHTML = arguments[0];' , [ page . replace ( '<!DOCTYPE html>' , 'x' ) ] ) ; } ) ; } return session . get ( 'about:blank' ) . then ( function ( ) { return session . execute < void > ( 'document.write(arguments[0]);' , [ page ] ) ; } ) ; } ; const discoverServerFeatures = ( ) => { const testedCapabilities = { } ; if ( capabilities . remoteFiles == null ) { testedCapabilities . remoteFiles = ( ) => session . serverPost < string > ( 'file' , { file : 'UEsDBAoAAAAAAD0etkYAAAAAAAAAAAAA' + 'AAAIABwAdGVzdC50eHRVVAkAA2WnXlVl' + 'p15VdXgLAAEE8gMAAATyAwAAUEsBAh4D' + 'CgAAAAAAPR62RgAAAAAAAAAAAAAAAAgA' + 'GAAAAAAAAAAAAKSBAAAAAHRlc3QudHh0' + 'VVQFAANlp15VdXgLAAEE8gMAAATyAwAA' + 'UEsFBgAAAAABAAEATgAAAEIAAAAAAA==' } ) . then ( filename => filename && filename . indexOf ( 'test.txt' ) > - 1 ) . catch ( unsupported ) ; } if ( capabilities . supportsSessionCommand == null ) { testedCapabilities . supportsSessionCommands = ( ) => this . get ( 'session/$0' , undefined , [ session . sessionId ] ) . then ( supported , unsupported ) ; } if ( capabilities . supportsGetTimeouts == null ) { testedCapabilities . supportsGetTimeouts = ( ) => { return session . serverGet ( 'timeouts' ) . then ( supported , unsupported ) ; } ; } if ( capabilities . usesWebDriverTimeouts == null ) { testedCapabilities . usesWebDriverTimeouts = ( ) => { return ( session . serverPost < void > ( 'timeouts' , { implicit : 1234 } ) . then ( ( ) => { if ( isInternetExplorer ( capabilities ) ) { return supported ( ) ; } return session . serverGet < WebDriverTimeouts > ( 'timeouts' ) . then ( timeouts => timeouts . implicit === 1234 ) . catch ( unsupported ) ; } , unsupported ) ) ; } ; } if ( capabilities . usesWebDriverWindowCommands == null ) { testedCapabilities . usesWebDriverWindowCommands = ( ) => session . serverGet ( 'window/rect' ) . then ( supported , unsupported ) ; } if ( capabilities . usesHandleParameter == null ) { testedCapabilities . usesHandleParameter = ( ) => session . switchToWindow ( 'current' ) . then ( unsupported , error => error . name === 'InvalidArgument' || /missing .*handle/i . test ( error . message ) ) ; } if ( capabilities . brokenSessionList == null ) { testedCapabilities . brokenSessionList = ( ) => this . getSessions ( ) . then ( works , broken ) ; } if ( capabilities . returnsFromClickImmediately == null ) { testedCapabilities . returnsFromClickImmediately = ( ) => { function assertSelected ( expected ) { return function ( actual ) { if ( expected !== actual ) { throw new Error ( 'unexpected selection state' ) ; } } ; } return get ( '<!DOCTYPE html><input type="checkbox" id="c">' ) . then ( ( ) => session . findById ( 'c' ) ) . then ( element => element . click ( ) . then ( ( ) => element . isSelected ( ) ) . then ( assertSelected ( true ) ) . then ( ( ) => element . click ( ) . then ( ( ) => element . isSelected ( ) ) ) . then ( assertSelected ( false ) ) . then ( ( ) => element . click ( ) . then ( ( ) => element . isSelected ( ) ) ) . then ( assertSelected ( true ) ) ) . then ( works , broken ) ; } ; } if ( capabilities . noKeysCommand == null ) { testedCapabilities . noKeysCommand = ( ) => session . serverPost ( 'keys' , { value : [ 'a' ] } ) . then ( ( ) => false , ( ) => true ) ; } if ( capabilities . noElementDisplayed == null ) { testedCapabilities . noElementDisplayed = ( ) => session . findByCssSelector ( 'html' ) . then ( element => element . isDisplayed ( ) ) . then ( ( ) => false , ( ) => true ) ; } return Task . all ( Object . keys ( testedCapabilities ) . map ( key => testedCapabilities [ key ] ) ) . then ( ( ) => testedCapabilities ) ; } ; const discoverFeatures = ( ) => { const testedCapabilities = { } ; if ( isSafari ( capabilities , 0 , 10 ) && isMac ( capabilities ) ) { return Task . resolve ( { } ) ; } if ( capabilities . rotatable == null ) { testedCapabilities . rotatable = ( ) => session . getOrientation ( ) . then ( supported , unsupported ) ; } if ( capabilities . locationContextEnabled ) { testedCapabilities . locationContextEnabled = ( ) => session . getGeolocation ( ) . then ( supported , function ( error ) { if ( error . message . indexOf ( 'not mapped : GET_LOCATION' ) !== - 1 ) { return false ; } if ( error . message . indexOf ( 'Location must be set' ) !== - 1 ) { return session . setGeolocation ( { latitude : 12.1 , longitude : - 22.33 , altitude : 1000.2 } ) . then ( ( ) => session . getGeolocation ( ) ) . then ( supported , unsupported ) ; } return false ; } ) ; } if ( capabilities . webStorageEnabled ) { testedCapabilities . webStorageEnabled = ( ) => session . getLocalStorageLength ( ) . then ( supported , maybeSupported ) ; } if ( capabilities . applicationCacheEnabled ) { testedCapabilities . applicationCacheEnabled = ( ) => session . getApplicationCacheStatus ( ) . then ( supported , maybeSupported ) ; } if ( capabilities . takesScreenshot == null ) { testedCapabilities . takesScreenshot = ( ) => session . takeScreenshot ( ) . then ( supported , unsupported ) ; } if ( capabilities . supportsExecuteAsync == null ) { testedCapabilities . supportsExecuteAsync = ( ) => session . executeAsync ( 'arguments[0](true);' ) . catch ( unsupported ) ; } if ( capabilities . mouseEnabled == null && ! ( isFirefox ( capabilities , 49 , Infinity ) && isMac ( capabilities ) ) ) { testedCapabilities . mouseEnabled = ( ) => get ( '<!DOCTYPE html><button id="clicker">Click me</button>' ) . then ( ( ) => session . findById ( 'clicker' ) ) . then ( button => button . click ( ) . then ( supported , maybeSupported ) ) . catch ( unsupported ) ; } if ( capabilities . touchEnabled == null ) { testedCapabilities . touchEnabled = ( ) => get ( '<!DOCTYPE html><button id="clicker">Click me</button>' ) . then ( ( ) => session . findById ( 'clicker' ) ) . then ( button => session . doubleTap ( button ) . then ( supported , maybeSupported ) ) . catch ( unsupported ) ; } if ( capabilities . dynamicViewport == null ) { testedCapabilities . dynamicViewport = ( ) => session . getWindowSize ( ) . then ( originalSize => session . setWindowSize ( originalSize . width - 2 , originalSize . height - 2 ) ) . then ( supported , unsupported ) ; } if ( capabilities . supportsNavigationDataUris == null ) { testedCapabilities . supportsNavigationDataUris = ( ) => get ( '<!DOCTYPE html><title>a</title>' ) . then ( ( ) => session . getPageTitle ( ) ) . then ( pageTitle => pageTitle === 'a' ) . catch ( unsupported ) ; } if ( capabilities . supportsCssTransforms == null ) { testedCapabilities . supportsCssTransforms = ( ) => get ( '<!DOCTYPE html><style>' + '#a{width:8px;height:8px;-ms-transform:scale(0.5);' + '-moz-transform:scale(0.5);-webkit-transform:scale(0.5);' + 'transform:scale(0.5);}</style><div id="a"></div>' ) . then ( ( ) => session . execute ( ( ) => { const bbox = document . getElementById ( 'a' ) ! . getBoundingClientRect ( ) ; return bbox . right - bbox . left === 4 ; } ) ) . catch ( unsupported ) ; } if ( capabilities . usesWebDriverMoveBase == null ) { testedCapabilities . usesWebDriverMoveBase = ( ) => { return get ( `<!DOCTYPE html><html><body style="width:100%;height:100px;">
              <script>
                var events = [];
                document.onmousemove = function (event) {
                  events.push({
                    x: event.clientX,
                    y: event.clientY
                  });
                };
              </script>
            </body></html>
            ` ) . then ( ( ) => session . moveMouseTo ( 100 , 50 ) ) . then ( ( ) => session . execute < { x : number ; y : number ; } [ ] > ( 'return window.events' ) ) . then ( events => { if ( ! events ) { return undefined ; } const event = events [ 0 ] ; return event . x !== 100 ; } ) ; } ; } return Task . all ( Object . keys ( testedCapabilities ) . map ( key => testedCapabilities [ key ] ) ) . then ( ( ) => testedCapabilities ) ; } ; const discoverDefects = ( ) : CancellablePromise < Capabilities > => { const testedCapabilities = { } ; if ( isSafari ( capabilities , 0 , 10 ) && isMac ( capabilities ) ) { return Task . resolve ( { } ) ; } if ( capabilities . brokenActiveElement == null ) { testedCapabilities . brokenActiveElement = ( ) => session . getActiveElement ( ) . then ( works , error => error . name === 'UnknownCommand' ) ; } if ( capabilities . brokenDeleteCookie == null ) { if ( capabilities . browserName === 'selendroid' ) { testedCapabilities . brokenDeleteCookie = ( ) => session . get ( 'about:blank' ) . then ( ( ) => session . clearCookies ( ) ) . then ( ( ) => session . setCookie ( { name : 'foo' , value : 'foo' } ) ) . then ( ( ) => session . deleteCookie ( 'foo' ) ) . then ( ( ) => session . getCookies ( ) ) . then ( cookies => cookies . length > 0 ) . catch ( ( ) => true ) . then ( isBroken => session . clearCookies ( ) . then ( ( ) => isBroken , ( ) => isBroken ) ) ; } else { testedCapabilities . brokenDeleteCookie = ( ) => session . get ( 'about:blank' ) . then ( ( ) => session . clearCookies ( ) ) . then ( works , broken ) ; } } if ( capabilities . brokenHtmlTagName == null ) { testedCapabilities . brokenHtmlTagName = ( ) => session . findByTagName ( 'html' ) . then ( element => element . getTagName ( ) ) . then ( tagName => tagName !== 'html' ) . catch ( broken ) ; } if ( capabilities . brokenNullGetSpecAttribute == null ) { testedCapabilities . brokenNullGetSpecAttribute = ( ) => session . findByTagName ( 'html' ) . then ( element => element . getSpecAttribute ( 'nonexisting' ) ) . then ( value => value !== null ) . catch ( broken ) ; } if ( capabilities . brokenElementSerialization == null ) { testedCapabilities . brokenElementSerialization = ( ) => get ( '<!DOCTYPE html><div id="a"></div>' ) . then ( ( ) => session . findById ( 'a' ) ) . then ( element => session . execute ( ( element ) => element . getAttribute ( 'id' ) , [ element ] ) ) . then ( attribute => attribute !== 'a' ) . catch ( broken ) ; } if ( capabilities . brokenExecuteUndefinedReturn == null ) { testedCapabilities . brokenExecuteUndefinedReturn = ( ) => session . execute ( 'return undefined;' ) . then ( value => value !== null , broken ) ; } if ( capabilities . brokenExecuteElementReturn == null ) { testedCapabilities . brokenExecuteElementReturn = ( ) => get ( '<!DOCTYPE html><div id="a"></div>' ) . then ( ( ) => session . execute < Element > ( 'return document.getElementById("a");' ) ) . then ( element => element && element . getTagName ( ) ) . then ( works , broken ) ; } if ( capabilities . brokenElementDisplayedOpacity == null ) { testedCapabilities . brokenElementDisplayedOpacity = ( ) => get ( '<!DOCTYPE html><div id="a" style="opacity: .1;">a</div>' ) . then ( ( ) => session . execute ( 'var o = document.getElementById("a").style.opacity; return o && o.charAt(0) === "0";' ) ) . then ( supportsOpacity => { if ( ! supportsOpacity ) { return works ( ) ; } else { return session . execute ( 'document.getElementById("a").style.opacity = "0";' ) . then ( ( ) => session . findById ( 'a' ) ) . then ( element => element . isDisplayed ( ) ) ; } } ) . catch ( broken ) ; } if ( capabilities . brokenElementDisplayedOffscreen == null ) { testedCapabilities . brokenElementDisplayedOffscreen = ( ) => { const pageText = '<!DOCTYPE html><div id="a" style="left: 0; position: absolute; top: -1000px;">a</div>' ; return get ( pageText ) . then ( ( ) => session . findById ( 'a' ) ) . then ( element => element . isDisplayed ( ) ) . catch ( broken ) ; } ; } if ( capabilities . brokenVisibleText == null ) { testedCapabilities . brokenVisibleText = ( ) => get ( '<!DOCTYPE html><div id="d">This is<span style="display:none"> really</span> great</div>' ) . then ( ( ) => session . findById ( 'd' ) . then ( element => element . getVisibleText ( ) ) . then ( text => { if ( text !== 'This is great' ) { throw new Error ( 'Incorrect text' ) ; } } ) ) . then ( works , broken ) ; } if ( capabilities . brokenWhitespaceNormalization == null ) { testedCapabilities . brokenWhitespaceNormalization = ( ) => get ( '<!DOCTYPE html><div id="d">This is\n<br>a test\n</div>' ) . then ( ( ) => session . findById ( 'd' ) . then ( element => element . getVisibleText ( ) ) . then ( text => { if ( /\r\n/ . test ( text ) || /\s+$/ . test ( text ) ) { throw new Error ( 'invalid whitespace' ) ; } } ) ) . then ( works , broken ) ; } if ( capabilities . brokenLinkTextLocator == null ) { testedCapabilities . brokenLinkTextLocator = ( ) => get ( '<!DOCTYPE html><a id="d">What a cute<span style="display:none">, ' + 'yellow</span> backpack</a><a id="e">What a cute, yellow backpack</a>' ) . then ( ( ) => session . findByLinkText ( 'What a cute, yellow backpack' ) . then ( element => element . getAttribute ( 'id' ) ) . then ( attr => { if ( attr !== 'e' ) { throw new Error ( 'incorrect link was found' ) ; } } ) ) . then ( works , broken ) ; } if ( capabilities . brokenComputedStyles == null ) { testedCapabilities . brokenComputedStyles = ( ) => { const pageText = '<!DOCTYPE html><style>a { background: purple }</style><a id="a1">foo</a>' ; return get ( pageText ) . then ( ( ) => session . findById ( 'a1' ) ) . then ( element => element . getComputedStyle ( 'background-color' ) ) . then ( value => { if ( ! value ) { throw new Error ( 'empty style' ) ; } } ) . then ( works , broken ) ; } ; } if ( capabilities . brokenOptionSelect == null ) { testedCapabilities . brokenOptionSelect = ( ) => get ( '<!DOCTYPE html><select id="d"><option id="o1" value="foo">foo</option>' + '<option id="o2" value="bar" selected>bar</option></select>' ) . then ( ( ) => session . findById ( 'd' ) ) . then ( element => element . click ( ) ) . then ( ( ) => session . findById ( 'o1' ) ) . then ( element => element . click ( ) ) . then ( works , broken ) ; } if ( capabilities . brokenPageSource == null ) { testedCapabilities . brokenPageSource = ( ) => session . getPageSource ( ) . then ( works , broken ) ; } if ( capabilities . brokenSubmitElement == null ) { testedCapabilities . brokenSubmitElement = ( ) => get ( '<!DOCTYPE html><form method="get" action="about:blank">' + '<input id="a" type="submit" name="a" value="a"></form>' ) . then ( ( ) => session . findById ( 'a' ) ) . then ( element => element . submit ( ) ) . then ( ( ) => session . getCurrentUrl ( ) ) . then ( url => url . indexOf ( 'a=a' ) === - 1 ) . catch ( broken ) ; } if ( capabilities . brokenWindowSize == null ) { testedCapabilities . brokenWindowSize = ( ) => session . getWindowSize ( ) . then ( works , broken ) ; } if ( capabilities . fixedLogTypes == null ) { testedCapabilities . fixedLogTypes = ( ) => session . getAvailableLogTypes ( ) . then ( unsupported , ( error ) => { if ( capabilities . browserName === 'selendroid' && error . response ! . text . length === 0 ) { return [ 'logcat' ] ; } return [ ] ; } ) ; } if ( capabilities . brokenZeroTimeout == null ) { testedCapabilities . brokenZeroTimeout = ( ) => session . setTimeout ( 'implicit' , 0 ) . then ( works , broken ) ; } if ( capabilities . brokenWindowSwitch == null ) { testedCapabilities . brokenWindowSwitch = ( ) => session . getCurrentWindowHandle ( ) . then ( handle => session . switchToWindow ( handle ) ) . then ( works , broken ) ; } if ( capabilities . brokenParentFrameSwitch == null ) { testedCapabilities . brokenParentFrameSwitch = ( ) => session . serverPost ( 'frame/parent' ) . then ( works , broken ) ; } const scrollTestUrl = '<!DOCTYPE html><div id="a" style="margin: 3000px;"></div>' ; if ( capabilities . brokenElementPosition == null ) { testedCapabilities . brokenElementPosition = ( ) => get ( scrollTestUrl ) . then ( ( ) => session . findById ( 'a' ) ) . then ( element => element . getPosition ( ) ) . then ( position => position . x !== 3000 || position . y !== 3000 ) . catch ( broken ) ; } if ( capabilities . brokenRefresh == null ) { testedCapabilities . brokenRefresh = ( ) => session . get ( 'about:blank?1' ) . then ( ( ) => { let timer ; let refresh ; return new Task ( resolve => { let settled = false ; refresh = session . refresh ( ) . then ( ( ) => { settled = true ; clearTimeout ( timer ) ; resolve ( false ) ; } , ( ) => { settled = true ; clearTimeout ( timer ) ; resolve ( true ) ; } ) . finally ( ( ) => { if ( ! settled ) { resolve ( true ) ; } } ) ; timer = setTimeout ( function ( ) { refresh . cancel ( ) ; } , 2000 ) ; } , ( ) => { clearTimeout ( timer ) ; refresh . cancel ( ) ; } ) ; } ) . catch ( broken ) ; } if ( capabilities . brokenMouseEvents == null && capabilities . mouseEnabled ) { testedCapabilities . brokenMouseEvents = ( ) => get ( '<!DOCTYPE html><div id="foo">foo</div>' + '<script>window.counter = 0; var d = document; d.onmousemove = function () { window.counter++; };</script>' ) . then ( ( ) => session . findById ( 'foo' ) ) . then ( element => session . moveMouseTo ( element , 20 , 20 ) ) . then ( ( ) => sleep ( 100 ) ) . then ( ( ) => session . execute < number > ( 'return window.counter;' ) ) . then ( counter => ( counter > 0 ? works ( ) : broken ( ) ) , broken ) ; if ( capabilities . brokenHtmlMouseMove == null ) { testedCapabilities . brokenHtmlMouseMove = ( ) => get ( '<!DOCTYPE html><html></html>' ) . then ( ( ) => session . findByTagName ( 'html' ) . then ( element => session . moveMouseTo ( element , 0 , 0 ) ) ) . then ( works , broken ) ; } } if ( capabilities . brokenDoubleClick == null ) { testedCapabilities . brokenDoubleClick = function retry ( ) : CancellablePromise < any > { if ( capabilities . browserName === 'internet explorer' && capabilities . browserVersion === '9' ) { return Task . resolve ( false ) ; } return get ( '<!DOCTYPE html><html><body><button id="clicker">Clicker</button><script>' + 'window.counter = 0; var d = document; d.onclick = ' + 'd.onmousedown = d.onmouseup = function () { window.counter++; };' + '</script></body></html>' ) . then ( ( ) => session . findById ( 'clicker' ) ) . then ( element => session . moveMouseTo ( element ) ) . then ( ( ) => sleep ( 100 ) ) . then ( ( ) => session . doubleClick ( ) ) . then ( ( ) => session . execute ( 'return window.counter;' ) ) . then ( counter => { if ( counter === 0 ) { return retry ( ) ; } return counter !== 6 ; } ) . catch ( broken ) ; } ; } if ( capabilities . touchEnabled ) { if ( capabilities . brokenLongTap == null ) { testedCapabilities . brokenLongTap = ( ) => session . findByTagName ( 'body' ) . then ( element => session . longTap ( element ) ) . then ( works , broken ) ; if ( capabilities . brokenMoveFinger == null ) { testedCapabilities . brokenMoveFinger = ( ) => session . pressFinger ( 0 , 0 ) . then ( works , error => error . name === 'UnknownCommand' || error . message . indexOf ( 'need to specify the JS' ) > - 1 ) ; } if ( capabilities . brokenTouchScroll == null ) { testedCapabilities . brokenTouchScroll = ( ) => get ( scrollTestUrl ) . then ( ( ) => session . touchScroll ( 0 , 20 ) ) . then ( ( ) => session . execute ( 'return window.scrollY !== 20;' ) ) . then ( isBroken => { if ( isBroken ) { return true ; } return session . findById ( 'a' ) . then ( element => session . touchScroll ( element , 0 , 0 ) ) . then ( ( ) => session . execute ( 'return window.scrollY !== 3000;' ) ) ; } ) . catch ( broken ) ; } } if ( capabilities . brokenFlickFinger == null ) { testedCapabilities . brokenFlickFinger = ( ) => get ( scrollTestUrl ) . then ( ( ) => session . flickFinger ( 0 , 400 ) ) . then ( ( ) => session . execute ( 'return window.scrollY === 0;' ) ) . catch ( broken ) ; } } if ( capabilities . supportsCssTransforms && capabilities . brokenCssTransformedSize == null ) { testedCapabilities . brokenCssTransformedSize = ( ) => get ( '<!DOCTYPE html><style>#a{width:8px;height:8px;' + '-ms-transform:scale(0.5);-moz-transform:scale(0.5);' + '-webkit-transform:scale(0.5);transform:scale(0.5);}' + '</style><div id="a"></div>' ) . then ( ( ) => session . execute < Element > ( 'return document.getElementById("a");' ) . then ( element => element . getSize ( ) ) . then ( dimensions => dimensions . width !== 4 || dimensions . height !== 4 ) ) . catch ( broken ) ; } if ( ! isMsEdge ( capabilities ) ) { if ( capabilities . brokenElementEnabled == null ) { testedCapabilities . brokenElementEnabled = ( ) => get ( '<!DOCTYPE html><input id="dis" type="text" disabled>' ) . then ( ( ) => session . execute < Element > ( 'return document.getElementById("dis");' ) . then ( element => element . isEnabled ( ) ) . then ( isEnabled => ( isEnabled ? broken ( ) : works ( ) ) ) . catch ( broken ) ) ; } } return Task . all ( Object . keys ( testedCapabilities ) . map ( key => testedCapabilities [ key ] ) ) . then ( ( ) => testedCapabilities ) ; } ; if ( capabilities . _filled ) { return Task . resolve ( session ) ; } const promise = isFirefox ( capabilities , 49 , Infinity ) ? Task . resolve ( session ) : session . get ( 'about:blank' ) ; return promise . then ( discoverServerFeatures ) . then ( addCapabilities ) . then ( discoverFeatures ) . then ( addCapabilities ) . then ( ( ) => session . get ( 'about:blank' ) ) . then ( discoverDefects ) . then ( addCapabilities ) . then ( ( ) => session . get ( 'about:blank' ) ) . then ( ( ) => session ) ; } /**
     * Gets a list of all currently active remote control sessions on this
     * server.
     */ getSessions ( ) { return this . get ( 'sessions' ) . then ( function ( sessions ) { if ( sessions && ! Array . isArray ( sessions ) ) { sessions = returnValue ( sessions ) ; } sessions . forEach ( function ( session ) { if ( session . sessionId && ! session . id ) { session . id = session . sessionId ; } } ) ; return sessions ; } ) ; } /**
     * Gets information on the capabilities of a given session from the server.
     * The list of capabilities returned by this command will not include any
     * of the extra session capabilities detected by Leadfoot and may be
     * inaccurate.
     */ getSessionCapabilities ( sessionId ) { return this . get ( 'session/$0' , undefined , [ sessionId ] ) . then ( returnValue ) ; } /**
     * Terminates a session on the server.
     */ deleteSession ( sessionId ) { return this . delete < void > ( 'session/$0' , undefined , [ sessionId ] ) . then ( noop ) ; } } function getErrorName ( error ) { if ( ( error . name !== 'Error' && error . name !== 'UnknownError' ) || ! error . detail || ! error . detail . error ) { return error . name ; } const desc = error . detail . error ; if ( desc === 'javascript error' ) { return 'JavaScriptError' ; } return desc . split ( ' ' ) . map ( word => `${ word [ 0 ] . toUpperCase ( ) }${ word . slice ( 1 ) }` ) . join ( '' ) ; } function isAndroid ( capabilities ) { const { browserName = '' } = capabilities ; return browserName . toLowerCase ( ) === 'android' ; } function isAndroidEmulator ( capabilities ) { const { deviceName = '' } = capabilities ; return deviceName . toLowerCase ( ) === 'android emulator' ; } function isIos ( capabilities ) { const { platform = '' , platformName = '' } = capabilities ; return ( platformName || platform ) . toLowerCase ( ) === 'ios' ; } function isMac ( capabilities ) { const { platform = '' , platformName = '' } = capabilities ; const _platform = platform || platformName ; return ( ( /mac(os)?/i . test ( _platform ) || /darwin/i . test ( _platform ) ) && _platform . toLowerCase ( ) !== 'ios' ) ; } export function isMsEdge ( capabilities , minOrExactVersion ? , maxVersion ? ) { const { browserName = '' } = capabilities ; if ( browserName . toLowerCase ( ) !== 'microsoftedge' ) { return false ; } return isValidVersion ( capabilities , minOrExactVersion , maxVersion ) ; } export function isInternetExplorer ( capabilities , minOrExactVersion ? , maxVersion ? ) { const { browserName = '' } = capabilities ; if ( browserName . toLowerCase ( ) !== 'internet explorer' ) { return false ; } return isValidVersion ( capabilities , minOrExactVersion , maxVersion ) ; } export function isSafari ( capabilities , minOrExactVersion ? , maxVersion ? ) { const { browserName = '' } = capabilities ; if ( browserName . toLowerCase ( ) !== 'safari' ) { return false ; } return isValidVersion ( capabilities , minOrExactVersion , maxVersion ) ; } export function isFirefox ( capabilities , minOrExactVersion ? , maxVersion ? ) { const { browserName = '' } = capabilities ; if ( browserName . toLowerCase ( ) !== 'firefox' ) { return false ; } return isValidVersion ( capabilities , minOrExactVersion , maxVersion ) ; } /**
 * Check if a browserVersion is between a min (inclusive) and a max
 * (exclusive). If only one version is specified, it is treated as an exact
 * match.
 */ function isValidVersion ( capabilities , minOrExactVersion ? , maxVersion ? ) { if ( minOrExactVersion != null ) { const version = parseFloat ( ( capabilities . browserVersion || capabilities . version ) ! ) ; if ( maxVersion != null ) { if ( version < minOrExactVersion ) { return false ; } if ( version >= maxVersion ) { return false ; } } else if ( version !== minOrExactVersion ) { return false ; } } return true ; } function noop ( ) { } param response returns function returnValue ( response ) { return response . value ; } interface ResponseData { response : Response ; data : any ; }