import Element , { ElementOrElementId } from './Element' ; import Server from './Server' ; import findDisplayed from './lib/findDisplayed' ; import { Task , CancellablePromise , partial } from '@theintern/common' ; import statusCodes from './lib/statusCodes' ; import Locator , { toW3cLocator , Strategy } from './lib/Locator' ; import { forCommand as utilForCommand , manualFindByLinkText , sleep , toExecuteString , } from './lib/util' ; import waitForDeleted from './lib/waitForDeleted' ; import { Capabilities , Geolocation , LogEntry , WebDriverCookie , WebDriverResponse , } from './interfaces' ; /**
 * A Session represents a connection to a remote environment that can be driven
 * programmatically.
 */ export default class Session extends Locator < CancellablePromise < Element > , CancellablePromise < Element [ ] > , CancellablePromise < void > > { private _sessionId : string ; private _server : Server ; private _capabilities : Capabilities ; private _closedWindows : any = null ; private _timeouts : { [ key ] : CancellablePromise < number > ; } = { } ; private _movedToElement = false ; private _lastMousePosition : any = null ; private _lastAltitude : any = null ; private _nextRequest : CancellablePromise < any > | undefined ; param sessionId param server param capabilities constructor ( sessionId , server , capabilities ) { super ( ) ; this . _sessionId = sessionId ; this . _server = server ; this . _capabilities = capabilities ; this . _closedWindows = { } ; this . _timeouts = { script : Task . resolve ( 0 ) , implicit : Task . resolve ( 0 ) , 'page load' : Task . resolve ( Infinity ) , } ; } /**
     * Information about the available features and bugs in the remote
     * environment.
     */ get capabilities ( ) { return this . _capabilities ; } /**
     * The current session ID.
     */ get sessionId ( ) { return this . _sessionId ; } /**
     * The Server that the session runs on.
     */ get server ( ) { return this . _server ; } /**
     * Delegates the HTTP request for a method to the underlying
     * [[Server.Server]] object.
     */ private _delegateToServer < T > ( method , path , requestData , pathParts ? ) { path = 'session/' + this . _sessionId + ( path ? '/' + path : '' ) ; if ( method === 'post' && ! requestData && this . capabilities . brokenEmptyPost ) { requestData = { } ; } let cancelled = false ; return new Task < T > ( ( resolve ) => { let thisRequest ; const clearNextRequest = ( ) => { if ( this . _nextRequest === thisRequest ) { this . _nextRequest = undefined ; } } ; const runRequest = ( ) => { if ( cancelled ) { clearNextRequest ( ) ; return ; } const response = this . _server [ method ] < WebDriverResponse > ( path , requestData , pathParts ) . then ( ( response ) => response . value ) ; const safePromise = response . catch ( ( _error ) => { } ) ; safePromise . then ( clearNextRequest ) ; resolve ( response ) ; return safePromise ; } ; if ( this . _nextRequest ) { thisRequest = this . _nextRequest = this . _nextRequest . finally ( runRequest ) ; } else { thisRequest = this . _nextRequest = runRequest ( ) ; } } , ( ) => ( cancelled = true ) ) ; } serverGet < T > ( path , requestData ? , pathParts ? ) { return this . _delegateToServer < T > ( 'get' , path , requestData , pathParts ) ; } serverPost < T > ( path , requestData ? , pathParts ? ) { return this . _delegateToServer < T > ( 'post' , path , requestData , pathParts ) ; } serverDelete < T > ( path , requestData ? , pathParts ? ) { return this . _delegateToServer < T > ( 'delete' , path , requestData , pathParts ) ; } param type returns getTimeout ( type ) { if ( this . capabilities . supportsGetTimeouts ) { return this . serverGet < WebDriverTimeouts > ( 'timeouts' ) . then ( ( timeouts ) => type === 'page load' ? timeouts . pageLoad : timeouts [ type ] ) ; } else { return this . _timeouts [ type ] ; } } param type param ms setTimeout ( type , ms ) { if ( ms === Infinity ) { ms = Math . pow ( 2 , 23 ) - 1 ; } if ( this . capabilities . brokenZeroTimeout && ms === 0 ) { ms = 1 ; } let data = this . capabilities . usesWebDriverTimeouts ? { [ type === 'page load' ? 'pageLoad' : type ] : ms , } : { type , ms , } ; const promise = this . serverPost < void > ( 'timeouts' , data ) . catch ( ( error ) => { if ( error . name === 'UnknownCommand' ) { if ( type === 'script' ) { return this . serverPost < void > ( 'timeouts/async_script' , { ms : ms , } ) ; } else if ( type === 'implicit' ) { return this . serverPost < void > ( 'timeouts/implicit_wait' , { ms : ms , } ) ; } } else if ( ! this . capabilities . usesWebDriverTimeouts && ( /Missing 'type' parameter/ . test ( error . message ) || /Unknown timeout type/ . test ( error . message ) || /Invalid timeout type specified/ . test ( error . message ) ) ) { this . capabilities . usesWebDriverTimeouts = true ; return this . setTimeout ( type , ms ) ; } throw error ; } ) ; this . _timeouts [ type ] = promise . then ( ( ) => ms ) . catch ( ( ) => 0 ) ; return promise ; } returns getCurrentWindowHandle ( ) { const endpoint = this . capabilities . usesWebDriverWindowHandleCommands ? 'window' : 'window_handle' ; return this . serverGet < string > ( endpoint ) . then ( ( handle ) => { if ( this . capabilities . brokenDeleteWindow && this . _closedWindows [ handle ] ) { const error = new Error ( ) ; error . status = '23' ; error . name = statusCodes [ error . status ] [ 0 ] ; error . message = statusCodes [ error . status ] [ 1 ] ; throw error ; } return handle ; } ) . catch ( ( error ) => { if ( /^Unknown/ . test ( error . name ) && ! this . capabilities . usesWebDriverWindowHandleCommands ) { this . capabilities . usesWebDriverWindowHandleCommands = true ; return this . getCurrentWindowHandle ( ) ; } throw error ; } ) ; } /**
     * Gets a list of identifiers for all currently open windows.
     */ getAllWindowHandles ( ) { const endpoint = this . capabilities . usesWebDriverWindowHandleCommands ? 'window/handles' : 'window_handles' ; return this . serverGet < string [ ] > ( endpoint ) . then ( ( handles ) => { if ( this . capabilities . brokenDeleteWindow ) { return handles . filter ( ( handle ) => { return ! this . _closedWindows [ handle ] ; } ) ; } return handles ; } ) . catch ( ( error ) => { if ( error . name === 'UnknownCommand' && ! this . capabilities . usesWebDriverWindowHandleCommands ) { this . capabilities . usesWebDriverWindowHandleCommands = true ; return this . getAllWindowHandles ( ) ; } throw error ; } ) ; } /**
     * Gets the URL that is loaded in the focused window/frame.
     */ getCurrentUrl ( ) { return this . serverGet < string > ( 'url' ) ; } /**
     * Navigates the focused window/frame to a new URL.
     */ get ( url ) { this . _movedToElement = false ; if ( this . capabilities . brokenMouseEvents ) { this . _lastMousePosition = { x : 0 , y : 0 } ; } return this . serverPost < void > ( 'url' , { url : url } ) ; } /**
     * Navigates the focused window/frame forward one page using the browser’s
     * navigation history.
     */ goForward ( ) { return this . serverPost < void > ( 'forward' ) ; } /**
     * Navigates the focused window/frame back one page using the browser’s
     * navigation history.
     */ goBack ( ) { return this . serverPost < void > ( 'back' ) ; } /**
     * Reloads the current browser window/frame.
     */ refresh ( ) { if ( this . capabilities . brokenRefresh ) { return this . execute < void > ( 'location.reload();' ) ; } return this . serverPost < void > ( 'refresh' ) ; } param script param args returns execute < T > ( script , args ? ) { if ( typeof args !== 'undefined' && ! Array . isArray ( args ) ) { throw new Error ( 'Arguments passed to execute must be an array' ) ; } const endpoint = this . capabilities . usesWebDriverExecuteSync ? 'execute/sync' : 'execute' ; let result = this . serverPost < T > ( endpoint , { script : toExecuteString ( script ) , args : args || [ ] , } ) . then ( ( value ) => convertToElements ( this , value ) , fixExecuteError ) . catch ( ( error ) => { if ( error . detail . error === 'unknown command' && ! this . capabilities . usesWebDriverExecuteSync ) { this . capabilities . usesWebDriverExecuteSync = true ; return this . execute ( script , args ) ; } throw error ; } ) ; if ( this . capabilities . brokenExecuteUndefinedReturn ) { result = result . then ( ( value ) => ( value == null ? null : value ) ) ; } return result ; } param script param args returns executeAsync < T > ( script , args ? ) { if ( typeof args !== 'undefined' && ! Array . isArray ( args ) ) { throw new Error ( 'Arguments passed to executeAsync must be an array' ) ; } const endpoint = this . capabilities . usesWebDriverExecuteAsync ? 'execute/async' : 'execute_async' ; return this . serverPost < T > ( endpoint , { script : toExecuteString ( script ) , args : args || [ ] , } ) . then ( partial ( convertToElements , this ) , fixExecuteError ) . catch ( ( error ) => { if ( error . detail . error === 'unknown command' && ! this . capabilities . usesWebDriverExecuteAsync ) { this . capabilities . usesWebDriverExecuteAsync = true ; return this . executeAsync < T > ( script , args ) ; } if ( error . name === 'Timeout' ) { error . name = 'ScriptTimeout' ; } throw error ; } ) ; } returns takeScreenshot ( ) { return this . serverGet < string > ( 'screenshot' ) . then ( ( data ) => Buffer . from ( data , 'base64' ) ) ; } /**
     * Gets a list of input method editor engines available to the remote
     * environment. As of April 2014, no known remote environments support IME
     * functions.
     */ getAvailableImeEngines ( ) { return this . serverGet < string [ ] > ( 'ime/available_engines' ) ; } /**
     * Gets the currently active input method editor for the remote environment.
     * As of April 2014, no known remote environments support IME functions.
     */ getActiveImeEngine ( ) { return this . serverGet < string > ( 'ime/active_engine' ) ; } /**
     * Returns whether or not an input method editor is currently active in the
     * remote environment. As of April 2014, no known remote environments
     * support IME functions.
     */ isImeActivated ( ) { return this . serverGet < boolean > ( 'ime/activated' ) ; } /**
     * Deactivates any active input method editor in the remote environment.
     * As of April 2014, no known remote environments support IME functions.
     */ deactivateIme ( ) { return this . serverPost < void > ( 'ime/deactivate' ) ; } param engine activateIme ( engine ) { return this . serverPost < void > ( 'ime/activate' , { engine : engine } ) ; } param id switchToFrame ( id ) { if ( this . capabilities . usesWebDriverFrameId && typeof id === 'string' ) { return this . findById ( id ) . then ( ( element ) => this . serverPost < void > ( 'frame' , { id : element } ) ) ; } return this . serverPost < void > ( 'frame' , { id : id } ) . catch ( ( error ) => { if ( this . capabilities . usesWebDriverFrameId == null && ( error . name === 'InvalidArgument' || error . name === 'NoSuchFrame' || /any variant of untagged/ . test ( error . detail . message ) || /invalid argument/ . test ( error . detail . message ) ) ) { this . capabilities . usesWebDriverFrameId = true ; return this . switchToFrame ( id ) ; } throw error ; } ) ; } param handle switchToWindow ( handle ) { let data = { name : handle } ; if ( this . capabilities . usesHandleParameter ) { data = { handle } ; } return this . serverPost < void > ( 'window' , data ) ; } /**
     * Switches the currently focused frame to the parent of the currently
     * focused frame.
     */ switchToParentFrame ( ) { if ( this . capabilities . brokenParentFrameSwitch ) { return this . execute < Element > ( 'return window.parent.frameElement;' ) . then ( ( parent ) => { return this . switchToFrame ( parent || null ) ; } ) ; } else { return this . serverPost < void > ( 'frame/parent' ) . catch ( ( error ) => { if ( this . capabilities . scriptedParentFrameCrashesBrowser ) { throw error ; } this . capabilities . brokenParentFrameSwitch = true ; return this . switchToParentFrame ( ) ; } ) ; } } /**
     * Closes the currently focused window. In most environments, after the
     * window has been closed, it is necessary to explicitly switch to whatever
     * window is now focused.
     */ closeCurrentWindow ( ) { const self = this ; function manualClose ( ) { return self . getCurrentWindowHandle ( ) . then ( function ( handle ) { return self . execute ( 'window.close();' ) . then ( function ( ) { self . _closedWindows [ handle ] = true ; } ) ; } ) ; } if ( this . capabilities . brokenDeleteWindow ) { return manualClose ( ) ; } return this . serverDelete < void > ( 'window' ) . catch ( ( error ) => { if ( error . name === 'UnknownCommand' && ! this . capabilities . brokenDeleteWindow ) { this . capabilities . brokenDeleteWindow = true ; return manualClose ( ) ; } throw error ; } ) ; } param windowHandle param width param height setWindowSize ( width , height ) ; setWindowSize ( windowHandle , width , height ) ; setWindowSize ( ... args ) { let [ windowHandle , width , height ] = args ; if ( typeof height === 'undefined' ) { height = width ; width = windowHandle ; windowHandle = null ; } const data = { width , height } ; if ( this . capabilities . usesWebDriverWindowCommands ) { const setWindowSize = ( ) => this . getWindowPosition ( ) . then ( ( position ) => this . setWindowRect ( { x : position . x , y : position . y , width : data . width , height : data . height , } ) ) ; if ( windowHandle == null ) { return setWindowSize ( ) ; } else { let error ; return this . getCurrentWindowHandle ( ) . then ( ( originalHandle ) => { return this . switchToWindow ( windowHandle ) . then ( ( ) => setWindowSize ( ) ) . catch ( ( _error ) => { error = _error ; } ) . then ( ( ) => this . switchToWindow ( originalHandle ) ) . then ( ( ) => { if ( error ) { throw error ; } } ) ; } ) ; } } else { if ( windowHandle == null ) { windowHandle = 'current' ; } return this . serverPost < void > ( 'window/$0/size' , { width , height } , [ windowHandle , ] ) ; } } param windowHandle returns getWindowSize ( windowHandle ? ) { if ( this . capabilities . usesWebDriverWindowCommands ) { const getWindowSize = ( ) => this . getWindowRect ( ) . then ( ( rect ) => ( { width : rect . width , height : rect . height , } ) ) ; if ( windowHandle == null ) { return getWindowSize ( ) ; } else { let error ; let size ; return this . getCurrentWindowHandle ( ) . then ( ( originalHandle ) => { return this . switchToWindow ( windowHandle ! ) . then ( ( ) => getWindowSize ( ) ) . then ( ( _size ) => { size = _size ; } , ( _error ) => { error = _error ; } ) . then ( ( ) => this . switchToWindow ( originalHandle ) ) . then ( ( ) => { if ( error ) { throw error ; } return size ; } ) ; } ) ; } } else { if ( windowHandle == null ) { windowHandle = 'current' ; } return this . serverGet < { width : number ; height : number ; } > ( 'window/$0/size' , null , [ windowHandle ] ) ; } } /**
     * Return the current window's rectangle (size and position).
     */ getWindowRect ( ) { return this . serverGet < { width : number ; height : number ; x : number ; y : number ; } > ( 'window/rect' ) ; } param rect setWindowRect ( rect ) { return this . serverPost < void > ( 'window/rect' , rect ) ; } param windowHandle param x param y setWindowPosition ( x , y ) ; setWindowPosition ( windowHandle , x , y ) ; setWindowPosition ( ... args ) { let [ windowHandle , x , y ] = args ; if ( typeof y === 'undefined' ) { y = x ; x = windowHandle ; windowHandle = null ; } if ( this . capabilities . usesWebDriverWindowCommands ) { return this . getWindowSize ( ) . then ( ( size ) => { const data = { x , y , width : size . width , height : size . height } ; if ( windowHandle == null ) { return this . setWindowRect ( data ) ; } else { let error ; return this . getCurrentWindowHandle ( ) . then ( ( originalHandle ) => { if ( originalHandle === windowHandle ) { this . setWindowRect ( data ) ; } else { return this . switchToWindow ( windowHandle ) . then ( ( ) => this . setWindowRect ( data ) ) . catch ( ( _error ) => { error = _error ; } ) . then ( ( ) => this . switchToWindow ( originalHandle ) ) . then ( ( ) => { if ( error ) { throw error ; } } ) ; } } ) ; } } ) ; } else { if ( windowHandle == null ) { windowHandle = 'current' ; } return this . serverPost < void > ( 'window/$0/position' , { x , y } , [ windowHandle , ] ) ; } } param windowHandle returns getWindowPosition ( windowHandle ? ) { if ( this . capabilities . usesWebDriverWindowCommands ) { const getWindowPosition = ( ) => this . getWindowRect ( ) . then ( ( { x , y } ) => { return { x , y } ; } ) ; if ( windowHandle == null ) { return getWindowPosition ( ) ; } else { let error ; let position ; return this . getCurrentWindowHandle ( ) . then ( ( originalHandle ) => { return this . switchToWindow ( windowHandle ! ) . then ( ( ) => getWindowPosition ( ) ) . then ( ( _position ) => { position = _position ; } , ( _error ) => { error = _error ; } ) . then ( ( ) => this . switchToWindow ( originalHandle ) ) . then ( ( ) => { if ( error ) { throw error ; } return position ; } ) ; } ) ; } } else { if ( typeof windowHandle === 'undefined' ) { windowHandle = 'current' ; } return this . serverGet < { x : number ; y : number ; } > ( 'window/$0/position' , null , [ windowHandle ] ) . then ( ( position ) => { return { x : position . x , y : position . y , } ; } ) ; } } param windowHandle maximizeWindow ( windowHandle ? ) { if ( this . capabilities . usesWebDriverWindowCommands ) { const maximizeWindow = ( ) => this . serverPost < void > ( 'window/maximize' , { } ) ; if ( windowHandle == null ) { return maximizeWindow ( ) ; } else { let error ; return this . getCurrentWindowHandle ( ) . then ( ( originalHandle ) => { return this . switchToWindow ( windowHandle ! ) . then ( ( ) => maximizeWindow ( ) ) . catch ( ( _error ) => { error = _error ; } ) . then ( ( ) => this . switchToWindow ( originalHandle ) ) . then ( ( ) => { if ( error ) { throw error ; } } ) ; } ) ; } } else { if ( typeof windowHandle === 'undefined' ) { windowHandle = 'current' ; } return this . serverPost < void > ( 'window/$0/maximize' , null , [ windowHandle ] ) ; } } /**
     * Gets all cookies set on the current page.
     */ getCookies ( ) { return this . serverGet < WebDriverCookie [ ] > ( 'cookie' ) . then ( function ( cookies ) { return ( cookies || [ ] ) . map ( function ( badCookie ) { let cookie = { } ; for ( let key in badCookie ) { if ( key === 'name' || key === 'value' || key === 'path' || key === 'domain' || key === 'secure' || key === 'httpOnly' || key === 'expiry' ) { cookie [ key ] = ( < any > badCookie ) [ key ] ; } } if ( typeof cookie . expiry === 'number' ) { cookie . expiry = new Date ( cookie . expiry * 1000 ) ; } return cookie ; } ) ; } ) ; } /**
     * Sets a cookie on the current page.
     */ setCookie ( cookie ) { if ( typeof cookie . expiry === 'string' ) { cookie . expiry = new Date ( cookie . expiry ) ; } if ( cookie . expiry instanceof Date ) { cookie . expiry = cookie . expiry . valueOf ( ) / 1000 ; } const self = this ; return this . serverPost < void > ( 'cookie' , { cookie : cookie , } ) . catch ( ( error ) => { if ( error . name === 'UnknownCommand' ) { if ( /[^A-Za-z0-9!#$%&'*+.^_`|~-]/ . test ( cookie . name ) ) { error = new Error ( ) ; error . status = '25' ; error . name = statusCodes [ error . status ] [ 0 ] ; error . message = 'Invalid cookie name' ; throw error ; } if ( /[^\u0021\u0023-\u002b\u002d-\u003a\u003c-\u005b\u005d-\u007e]/ . test ( cookie . value ) ) { error = new Error ( ) ; error . status = '25' ; error . name = statusCodes [ error . status ] [ 0 ] ; error . message = 'Invalid cookie value' ; throw error ; } const cookieToSet = [ cookie . name + '=' + cookie . value ] ; pushCookieProperties ( cookieToSet , cookie ) ; return self . execute < void > ( function ( cookie ) { document . cookie = cookie ; } , [ cookieToSet . join ( ';' ) ] ) ; } throw error ; } ) ; } /**
     * Clears all cookies for the current page.
     */ clearCookies ( ) { if ( this . capabilities . brokenDeleteCookie ) { return this . getCookies ( ) . then ( ( cookies ) => { return cookies . reduce ( ( promise , cookie ) => { const expiredCookie = [ `${ cookie . name }=` , 'expires=Thu, 01 Jan 1970 00:00:00 GMT' , ] ; pushCookieProperties ( expiredCookie , cookie ) ; return promise . then ( ( ) => { return this . execute < void > ( function ( expiredCookie ) { document . cookie = `${ expiredCookie }; domain=${ encodeURIComponent ( document . domain ) }; path=/` ; } , [ expiredCookie . join ( ';' ) ] ) ; } ) ; } , Task . resolve ( ) ) ; } ) ; } return this . serverDelete < void > ( 'cookie' ) ; } param name deleteCookie ( name ) { if ( this . capabilities . brokenDeleteCookie ) { return this . getCookies ( ) . then ( ( cookies ) => { let cookie ; if ( cookies . some ( ( value ) => { if ( value . name === name ) { cookie = value ; return true ; } return false ; } ) ) { const expiredCookie = [ `${ cookie . name }=` , 'expires=Thu, 01 Jan 1970 00:00:00 GMT' , ] ; pushCookieProperties ( expiredCookie , cookie ) ; return this . execute < void > ( function ( expiredCookie ) { document . cookie = `${ expiredCookie }; domain=${ encodeURIComponent ( document . domain ) }; path=/` ; } , [ expiredCookie . join ( ';' ) ] ) ; } } ) ; } return this . serverDelete < void > ( 'cookie/$0' , null , [ name ] ) ; } /**
     * Gets the HTML loaded in the focused window/frame. This markup is
     * serialised by the remote environment so may not exactly match the HTML
     * provided by the Web server.
     */ getPageSource ( ) { if ( this . capabilities . brokenPageSource ) { return this . execute < string > ( function ( ) { return document . documentElement ! . outerHTML ; } ) ; } else { return this . serverGet < string > ( 'source' ) ; } } /**
     * Gets the title of the top-level browsing context of the current window
     * or tab.
     */ getPageTitle ( ) { return this . serverGet < string > ( 'title' ) ; } param using param value find ( using , value ) { if ( this . capabilities . usesWebDriverLocators ) { const locator = toW3cLocator ( using , value ) ; using = locator . using ; value = locator . value ; } if ( using . indexOf ( 'link text' ) !== - 1 && ( this . capabilities . brokenWhitespaceNormalization || this . capabilities . brokenLinkTextLocator ) ) { return this . execute < Element > ( manualFindByLinkText , [ using , value ] ) . then ( ( element ) => { if ( ! element ) { const error = new Error ( ) ; error . name = 'NoSuchElement' ; throw error ; } return new Element ( element , this ) ; } ) ; } return this . serverPost < ElementOrElementId > ( 'element' , { using : using , value : value , } ) . then ( ( element ) => { return new Element ( element , this ) ; } , ( error ) => { if ( ! this . capabilities . usesWebDriverLocators && /search strategy: 'id'/ . test ( error . message ) ) { this . capabilities . usesWebDriverLocators = true ; return this . find ( using , value ) ; } throw error ; } ) ; } param using param value findAll ( using , value ) { if ( this . capabilities . usesWebDriverLocators ) { const locator = toW3cLocator ( using , value ) ; using = locator . using ; value = locator . value ; } if ( using . indexOf ( 'link text' ) !== - 1 && ( this . capabilities . brokenWhitespaceNormalization || this . capabilities . brokenLinkTextLocator ) ) { return this . execute < Element [ ] > ( manualFindByLinkText , [ using , value , true , ] ) . then ( ( elements ) => { return elements . map ( ( element ) => { return new Element ( element , this ) ; } ) ; } ) ; } return this . serverPost < any [ ] > ( 'elements' , { using : using , value : value , } ) . then ( ( elements ) => { return elements . map ( ( element ) => { return new Element ( element , this ) ; } ) ; } , ( error ) => { if ( ! this . capabilities . usesWebDriverLocators && /search strategy: 'id'/ . test ( error . message ) ) { this . capabilities . usesWebDriverLocators = true ; return this . findAll ( using , value ) ; } throw error ; } ) ; } /**
     * Gets the currently focused element from the focused window/frame.
     */ @ forCommand ( { createsContext : true } ) getActiveElement ( ) { const getDocumentActiveElement = ( ) => { return this . execute < Element > ( 'return document.activeElement;' ) ; } ; if ( this . capabilities . brokenActiveElement ) { return getDocumentActiveElement ( ) ; } else { let task ; if ( this . capabilities . usesWebDriverActiveElement ) { task = this . serverGet < ElementOrElementId > ( 'element/active' ) ; } else { task = this . serverPost < ElementOrElementId > ( 'element/active' ) ; } return task . then ( ( element ) => { if ( element ) { return new Element ( element , this ) ; } else { return getDocumentActiveElement ( ) ; } } , ( error ) => { if ( error . name === 'UnknownMethod' && ! this . capabilities . usesWebDriverActiveElement ) { this . capabilities . usesWebDriverActiveElement = true ; return this . getActiveElement ( ) ; } throw error ; } ) ; } } param keys pressKeys ( keys ) { if ( ! Array . isArray ( keys ) ) { keys = [ keys ] ; } if ( this . capabilities . brokenSendKeys || this . capabilities . noKeysCommand ) { return this . execute < void > ( simulateKeys , [ keys ] ) ; } return this . serverPost < void > ( 'keys' , { value : keys , } ) ; } returns getOrientation ( ) { return this . serverGet < 'portrait' | 'landscape' > ( 'orientation' ) . then ( function ( orientation ) { return orientation . toLowerCase ( ) ; } ) ; } param orientation setOrientation ( orientation ) { orientation = orientation . toUpperCase ( ) ; return this . serverPost < void > ( 'orientation' , { orientation : orientation , } ) ; } /**
     * Gets the text displayed in the currently active alert pop-up.
     */ getAlertText ( ) { return this . serverGet < string > ( 'alert_text' ) ; } param text typeInPrompt ( text ) { if ( Array . isArray ( text ) ) { text = text . join ( '' ) ; } return this . serverPost < void > ( 'alert_text' , { text : text , } ) ; } /**
     * Accepts an alert, prompt, or confirmation pop-up. Equivalent to clicking
     * the 'OK' button.
     */ acceptAlert ( ) { return this . serverPost < void > ( 'accept_alert' ) ; } /**
     * Dismisses an alert, prompt, or confirmation pop-up. Equivalent to
     * clicking the 'OK' button of an alert pop-up or the 'Cancel' button of a
     * prompt or confirmation pop-up.
     */ dismissAlert ( ) { return this . serverPost < void > ( 'dismiss_alert' ) ; } param element param xOffset param yOffset moveMouseTo ( ) ; moveMouseTo ( xOffset ? , yOffset ? ) ; moveMouseTo ( element ? , xOffset ? , yOffset ? ) ; @ forCommand ( { usesElement : true } ) moveMouseTo ( ... args ) { let [ element , xOffset , yOffset ] = args ; if ( typeof yOffset === 'undefined' && typeof xOffset !== 'undefined' ) { yOffset = xOffset ; xOffset = element ; element = null ; } if ( this . capabilities . brokenMouseEvents ) { return this . execute < void > ( simulateMouse , [ { action : 'mousemove' , position : this . _lastMousePosition , element : element , xOffset : xOffset , yOffset : yOffset , } , ] ) . then ( ( newPosition ) => { this . _lastMousePosition = newPosition ; } ) ; } if ( element ) { element = element . elementId ; } else if ( ! this . _movedToElement ) { if ( this . capabilities . brokenHtmlMouseMove ) { return this . execute < Element > ( 'return document.body;' ) . then ( ( element ) => { return element . getPosition ( ) . then ( ( position ) => { return this . moveMouseTo ( element , xOffset - position . x , yOffset - position . y ) ; } ) ; } ) ; } else { return this . execute < Element > ( 'return document.body;' ) . then ( ( element ) => { return this . moveMouseTo ( element , xOffset , yOffset ) ; } ) ; } } const data = { } ; if ( element ) { data . element = element ; } if ( xOffset != null ) { data . xoffset = xOffset ; } if ( yOffset != null ) { data . yoffset = yOffset ; } return this . serverPost < void > ( 'moveto' , data ) . then ( ( ) => { this . _movedToElement = true ; } ) ; } param button clickMouseButton ( button ? ) { if ( this . capabilities . brokenMouseEvents ) { return this . execute < void > ( simulateMouse , [ { action : 'click' , button : button , position : this . _lastMousePosition , } , ] ) ; } return this . serverPost < void > ( 'click' , { button : button , } ) . then ( ( ) => { if ( this . capabilities . touchEnabled ) { return sleep ( 300 ) ; } } ) ; } param button pressMouseButton ( button ? ) { if ( this . capabilities . brokenMouseEvents ) { return this . execute < void > ( simulateMouse , [ { action : 'mousedown' , button : button , position : this . _lastMousePosition , } , ] ) ; } return this . serverPost < void > ( 'buttondown' , { button : button , } ) ; } param button releaseMouseButton ( button ? ) { if ( this . capabilities . brokenMouseEvents ) { return this . execute < void > ( simulateMouse , [ { action : 'mouseup' , button : button , position : this . _lastMousePosition , } , ] ) ; } return this . serverPost < void > ( 'buttonup' , { button : button , } ) ; } /**
     * Double-clicks the primary mouse button.
     */ doubleClick ( ) { if ( this . capabilities . brokenMouseEvents ) { return this . execute < void > ( simulateMouse , [ { action : 'dblclick' , button : 0 , position : this . _lastMousePosition , } , ] ) ; } if ( this . capabilities . brokenDoubleClick ) { return this . pressMouseButton ( ) . then ( ( ) => { return this . releaseMouseButton ( ) ; } ) . then ( ( ) => this . serverPost < void > ( 'doubleclick' ) ) ; } return this . serverPost < void > ( 'doubleclick' ) . catch ( ( ) => { if ( this . capabilities . brokenDoubleClick == null ) { this . capabilities . brokenDoubleClick = true ; return this . doubleClick ( ) ; } } ) ; } param element @ forCommand ( { usesElement : true } ) tap ( element ) { return this . serverPost < void > ( 'touch/click' , { element : element . elementId , } ) ; } param x param y pressFinger ( x , y ) { return this . serverPost < void > ( 'touch/down' , { x : x , y : y } ) ; } param x param y releaseFinger ( x , y ) { return this . serverPost < void > ( 'touch/up' , { x : x , y : y } ) ; } param x param y moveFinger ( x , y ) { return this . serverPost < void > ( 'touch/move' , { x : x , y : y } ) ; } param element param xOffset param yOffset touchScroll ( xOffset , yOffset ) ; touchScroll ( element ? , xOffset ? , yOffset ? ) ; @ forCommand ( { usesElement : true } ) touchScroll ( ... args ) { let [ element , xOffset , yOffset ] = args ; if ( typeof yOffset === 'undefined' && typeof xOffset !== 'undefined' ) { yOffset = xOffset ; xOffset = element ; element = undefined ; } if ( this . capabilities . brokenTouchScroll ) { return this . execute < void > ( function ( element , x , y ) { const rect = { left : window . scrollX , top : window . scrollY } ; if ( element ) { const bbox = element . getBoundingClientRect ( ) ; rect . left += bbox . left ; rect . top += bbox . top ; } window . scrollTo ( rect . left + x , rect . top + y ) ; } , [ element , xOffset , yOffset ] ) ; } if ( element ) { element = element . elementId ; } return this . serverPost < void > ( 'touch/scroll' , { element : element , xoffset : xOffset , yoffset : yOffset , } ) ; } param element @ forCommand ( { usesElement : true } ) doubleTap ( element ? ) { const elementId = element && element . elementId ; return this . serverPost < void > ( 'touch/doubleclick' , { element : elementId , } ) ; } param element @ forCommand ( { usesElement : true } ) longTap ( element ? ) { const elementId = element && element . elementId ; return this . serverPost < void > ( 'touch/longclick' , { element : elementId } ) ; } param element param xOffset param yOffset param speed flickFinger ( element , xOffset , yOffset , speed ? ) ; flickFinger ( xOffset , yOffset , speed ? ) ; @ forCommand ( { usesElement : true } ) flickFinger ( ... args ) { let [ element , xOffset , yOffset , speed ] = args ; if ( typeof speed === 'undefined' && typeof yOffset === 'undefined' && typeof xOffset !== 'undefined' ) { return this . serverPost < void > ( 'touch/flick' , { xspeed : element , yspeed : xOffset , } ) ; } return this . serverPost < void > ( 'touch/flick' , { element : element . elementId , xoffset : xOffset , yoffset : yOffset , speed : speed , } ) ; } returns getGeolocation ( ) { return this . serverGet < Geolocation > ( 'location' ) . then ( ( location ) => { if ( location . altitude === 0 && this . _lastAltitude !== location . altitude ) { location . altitude = undefined ; } return location ; } ) ; } param location setGeolocation ( location ) { if ( location . altitude !== undefined ) { this . _lastAltitude = location . altitude ; } return this . serverPost < void > ( 'location' , { location } ) ; } param type returns getLogsFor ( type ) { return this . serverPost < string [ ] | LogEntry [ ] > ( 'log' , { type : type , } ) . then ( function ( logs ) { if ( ! logs ) { return logs ; } if ( isStringArray ( logs ) ) { return logs . map ( ( log ) => { const logData = /\[([^\]]+)\]\s*\[([^\]]+)\]\s*(.*)/ . exec ( log ) ; let entry ; if ( logData ) { entry = { timestamp : Date . parse ( logData [ 1 ] ) / 1000 , level : logData [ 2 ] , message : logData [ 3 ] , } ; } else { entry = { timestamp : NaN , level : 'INFO' , message : log , } ; } return entry ; } ) ; } else { return logs ; } } ) ; } /**
     * Gets the types of logs that are currently available for retrieval from
     * the remote environment.
     */ getAvailableLogTypes ( ) { if ( this . capabilities . fixedLogTypes ) { return Task . resolve < string [ ] > ( this . capabilities . fixedLogTypes ) ; } return this . serverGet < string [ ] > ( 'log/types' ) ; } returns getApplicationCacheStatus ( ) { return this . serverGet < number > ( 'application_cache/status' ) ; } /**
     * Terminates the session. No more commands will be accepted by the remote
     * after this point.
     */ quit ( ) { return this . _server . deleteSession ( this . _sessionId ) ; } /**
     * Gets the list of keys set in local storage for the focused window/frame.
     */ getLocalStorageKeys ( ) { return this . serverGet < string [ ] > ( 'local_storage' ) ; } param key param value setLocalStorageItem ( key , value ) { return this . serverPost < void > ( 'local_storage' , { key , value } ) ; } /**
     * Clears all data in local storage for the focused window/frame.
     */ clearLocalStorage ( ) { return this . serverDelete < void > ( 'local_storage' ) ; } param key getLocalStorageItem ( key ) { return this . serverGet < string > ( 'local_storage/key/$0' , null , [ key ] ) ; } param key deleteLocalStorageItem ( key ) { return this . serverDelete < void > ( 'local_storage/key/$0' , null , [ key ] ) ; } /**
     * Gets the number of keys set in local storage for the focused
     * window/frame.
     */ getLocalStorageLength ( ) { return this . serverGet < number > ( 'local_storage/size' ) ; } /**
     * Gets the list of keys set in session storage for the focused
     * window/frame.
     */ getSessionStorageKeys ( ) { return this . serverGet < string [ ] > ( 'session_storage' ) ; } param key param value setSessionStorageItem ( key , value ) { return this . serverPost < void > ( 'session_storage' , { key , value } ) ; } /**
     * Clears all data in session storage for the focused window/frame.
     */ clearSessionStorage ( ) { return this . serverDelete < void > ( 'session_storage' ) ; } param key getSessionStorageItem ( key ) { return this . serverGet < string > ( 'session_storage/key/$0' , null , [ key ] ) ; } param key deleteSessionStorageItem ( key ) { return this . serverDelete < void > ( 'session_storage/key/$0' , null , [ key ] ) ; } /**
     * Gets the number of keys set in session storage for the focused
     * window/frame.
     */ getSessionStorageLength ( ) { return this . serverGet < number > ( 'session_storage/size' ) ; } since param using param value findDisplayed ( using , value ) { return findDisplayed ( this , this , using , value ) ; } param using param value waitForDeleted ( using , value ) { return waitForDeleted ( this , this , using , value ) ; } /**
     * Gets the timeout for [[Session.Session.executeAsync]] calls.
     */ getExecuteAsyncTimeout ( ) { return this . getTimeout ( 'script' ) ; } param ms setExecuteAsyncTimeout ( ms ) { return this . setTimeout ( 'script' , ms ) ; } /**
     * Gets the timeout for [[Session.Session.find]] calls.
     */ getFindTimeout ( ) { return this . getTimeout ( 'implicit' ) ; } param ms setFindTimeout ( ms ) { return this . setTimeout ( 'implicit' , ms ) ; } /**
     * Gets the timeout for [[Session.Session.get]] calls.
     */ getPageLoadTimeout ( ) { return this . getTimeout ( 'page load' ) ; } param ms setPageLoadTimeout ( ms ) { return this . setTimeout ( 'page load' , ms ) ; } } export interface WebDriverTimeouts { script : number ; pageLoad : number ; implicit : number ; } interface SessionError extends Error { status ? : keyof typeof statusCodes ; } type Timeout = 'script' | 'implicit' | 'page load' ; /**
 * Decorator for the [[util.forCommand]] method
 */ function forCommand ( properties ) { return function ( target , property , descriptor ) { const fn = < Function > target [ property ] ; descriptor . value = utilForCommand ( fn , properties ) ; } ; } param session param value returns function convertToElements ( session , value ) { function convert ( value ) { if ( Array . isArray ( value ) ) { value = value . map ( convert ) ; } else if ( typeof value === 'object' && value !== null ) { if ( value . ELEMENT || value [ 'element-6066-11e4-a52e-4f735466cecf' ] ) { value = new Element ( value , session ) ; } else { for ( let k in value ) { value [ k ] = convert ( value [ k ] ) ; } } } return value ; } return convert ( value ) ; } /**
 * As of Selenium 2.40.0 (March 2014), all drivers incorrectly transmit an
 * UnknownError instead of a JavaScriptError when user code fails to execute
 * correctly. This method corrects this status code, under the assumption that
 * drivers will follow the spec in future.
 */ function fixExecuteError ( error ) { if ( error . name === 'UnknownError' ) { error . status = '17' ; error . name = statusCodes [ error . status ] [ 0 ] ; } throw error ; } /**
 * HTTP cookies are transmitted as semicolon-delimited strings, with a
 * `key=value` pair giving the cookie’s name and value, then additional
 * information about the cookie (expiry, path, domain, etc.) as additional k-v
 * pairs. This method takes an Array describing the parts of a cookie
 * (`target`), and a hash map containing the additional information (`source`),
 * and pushes the properties from the source object onto the target array as
 * properly escaped key-value strings.
 */ function pushCookieProperties ( target , source ) { Object . keys ( source ) . forEach ( function ( key ) { let value = source [ key ] ; if ( key === 'name' || key === 'value' || ( key === 'domain' && value === 'http' ) ) { return ; } if ( typeof value === 'boolean' ) { value && target . push ( key ) ; } else if ( key === 'expiry' ) { if ( typeof value === 'number' ) { value = new Date ( value * 1000 ) ; } if ( value instanceof Date ) { value = value . toUTCString ( ) ; } target . push ( 'expires=' + encodeURIComponent ( value ) ) ; } else { target . push ( key + '=' + encodeURIComponent ( value ) ) ; } } ) ; } param keys function simulateKeys ( keys ) { const target = < any > document . activeElement ; function dispatch ( kwArgs ) { let event ; if ( typeof KeyboardEvent === 'function' ) { event = new KeyboardEvent ( kwArgs . type , { bubbles : true , cancelable : kwArgs . cancelable || false , view : window , key : kwArgs . key || '' , location : 3 , } ) ; } else { event = document . createEvent ( 'KeyboardEvent' ) ; ( event as any ) . initKeyboardEvent ( kwArgs . type , true , kwArgs . cancelable || false , window , kwArgs . key || '' , 3 , '' , < any > 0 , '' ) ; } return target . dispatchEvent ( event ) ; } function dispatchInput ( ) { let event ; if ( typeof Event === 'function' ) { event = new Event ( 'input' , { bubbles : true , cancelable : false } ) ; } else { event = document . createEvent ( 'Event' ) ; event . initEvent ( 'input' , true , false ) ; } return target . dispatchEvent ( event ) ; } keys = ( < string [ ] > [ ] ) . concat ( ... keys . map ( ( keys ) => keys . split ( '' ) ) ) ; for ( let i = 0 , j = keys . length ; i < j ; ++ i ) { const key = keys [ i ] ; let performDefault = true ; performDefault = dispatch ( { type : 'keydown' , cancelable : true , key : key , } ) ; performDefault = performDefault && dispatch ( { type : 'keypress' , cancelable : true , key : key } ) ; if ( performDefault ) { if ( 'value' in target ) { target . value = target . value . slice ( 0 , target . selectionStart ) + key + target . value . slice ( target . selectionEnd ) ; dispatchInput ( ) ; } else if ( target . isContentEditable ) { let node = document . createTextNode ( key ) ; let selection = window . getSelection ( ) ! ; let range = selection . getRangeAt ( 0 ) ; range . deleteContents ( ) ; range . insertNode ( node ) ; range . setStartAfter ( node ) ; range . setEndAfter ( node ) ; selection . removeAllRanges ( ) ; selection . addRange ( range ) ; } } dispatch ( { type : 'keyup' , cancelable : true , key : key } ) ; } } param kwArgs function simulateMouse ( kwArgs ) { let position = kwArgs . position ; function dispatch ( kwArgs ) { let event ; if ( typeof MouseEvent === 'function' ) { event = new MouseEvent ( kwArgs . type , { bubbles : 'bubbles' in kwArgs ? kwArgs . bubbles : true , cancelable : kwArgs . cancelable || false , view : window , detail : kwArgs . detail || 0 , screenX : window . screenX + position . x , screenY : window . screenY + position . y , clientX : position . x , clientY : position . y , ctrlKey : kwArgs . ctrlKey || false , shiftKey : kwArgs . shiftKey || false , altKey : kwArgs . altKey || false , metaKey : kwArgs . metaKey || false , button : kwArgs . button || 0 , relatedTarget : kwArgs . relatedTarget , } ) ; } else { event = document . createEvent ( 'MouseEvents' ) ; event . initMouseEvent ( kwArgs . type , kwArgs . bubbles || true , kwArgs . cancelable || false , window , kwArgs . detail || 0 , window . screenX + position . x , window . screenY + position . y , position . x , position . y , kwArgs . ctrlKey || false , kwArgs . altKey || false , kwArgs . shiftKey || false , kwArgs . metaKey || false , kwArgs . button || 0 , kwArgs . relatedTarget || null ) ; } return kwArgs . target . dispatchEvent ( event ) ; } function click ( target , button , detail ) { if ( ! down ( target , button ) ) { return false ; } if ( ! up ( target , button ) ) { return false ; } return dispatch ( { button : button , cancelable : true , detail : detail , target : target , type : 'click' , } ) ; } function down ( target , button ) { return dispatch ( { button : button , cancelable : true , target : target , type : 'mousedown' , } ) ; } function up ( target , button ) { return dispatch ( { button : button , cancelable : true , target : target , type : 'mouseup' , } ) ; } function move ( currentElement , newElement , xOffset , yOffset ) { if ( newElement ) { const bbox = newElement . getBoundingClientRect ( ) ; if ( xOffset == null ) { xOffset = ( bbox . right - bbox . left ) * 0.5 ; } if ( yOffset == null ) { yOffset = ( bbox . bottom - bbox . top ) * 0.5 ; } position = { x : bbox . left + xOffset , y : bbox . top + yOffset } ; } else { position . x += xOffset || 0 ; position . y += yOffset || 0 ; newElement = < HTMLElement > ( document . elementFromPoint ( position . x , position . y ) ) ; } if ( currentElement !== newElement ) { dispatch ( { type : 'mouseout' , target : currentElement , relatedTarget : newElement , } ) ; dispatch ( { type : 'mouseleave' , target : currentElement , relatedTarget : newElement , bubbles : false , } ) ; dispatch ( { type : 'mouseenter' , target : newElement , relatedTarget : currentElement , bubbles : false , } ) ; dispatch ( { type : 'mouseover' , target : newElement , relatedTarget : currentElement , } ) ; } dispatch ( { type : 'mousemove' , target : newElement , bubbles : true } ) ; return position ; } const target = < HTMLElement > document . elementFromPoint ( position . x , position . y ) ; if ( kwArgs . action === 'mousemove' ) { return move ( target , kwArgs . element , kwArgs . xOffset , kwArgs . yOffset ) ; } else if ( kwArgs . action === 'mousedown' ) { return down ( target , kwArgs . button ) ; } else if ( kwArgs . action === 'mouseup' ) { return up ( target , kwArgs . button ) ; } else if ( kwArgs . action === 'click' ) { return click ( target , kwArgs . button , 0 ) ; } else if ( kwArgs . action === 'dblclick' ) { if ( ! click ( target , kwArgs . button , 0 ) ) { return false ; } if ( ! click ( target , kwArgs . button , 1 ) ) { return false ; } return dispatch ( { type : 'dblclick' , target : target , button : kwArgs . button , detail : 2 , cancelable : true , } ) ; } } function isStringArray ( value ) { return Array . isArray ( value ) && typeof value [ 0 ] === 'string' ; }