Check an ES6 API implementation: can you see anything terrible?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
1
down vote

favorite












I have just finished writing an API that implements the FULL w3c protocol for webdrivers.



This is my first attempt to write fully ES6 code, along with async/await. I removed some of the functions here as they were very similar.



Questions:



  • Did I make any ES6 disaster?

  • Can you see anything clearly terrible in the code?

  • I have an inconsistency.. I have static get Using () return USING and static get KEY () return KEY . They are constants added to a class. Should I have KEY or Key? USING or Using?

  • Anything I should look at/improve?

I am all ears... thank you!



var request = require('request-promise-native')
const spawn = require('child_process')
var DO = require('deepobject')
const getPort = require('get-port')
var consolelog = require('debug')('webdriver')

const KEY = require('./KEY.js')

const USING =
CSS: 'css selector',
LINK_TEXT: 'link text',
PARTIAL_LINK_TEXT: 'partial link text',
TAG_NAME: 'tag name',
XPATH: 'xpath'


function isObject (p) return typeof p === 'object' && p !== null && !Array.isArray(p)

function checkRes (res)
if (!isObject(res)) throw new Error('Unexpected non-object received from webdriver')
if (typeof res.value === 'undefined') throw new Error('Missing `value` from object returned by webdriver')
return res


function exec (command, commandOptions) , 'ignore'
)

proc.on('error', (err) =>
consolelog(`Could not run $command:`, err)
throw new Error(`Error running the webdriver '$command'`)
)

proc.unref()
process.once('exit', onProcessExit)

let result = new Promise(resolve =>
proc.once('exit', (code, signal) =>
consolelog(`Process $command has exited! Code and signal:`, code, signal)
proc = null
process.removeListener('exit', onProcessExit)
resolve( code, signal )
)
)
return result, killCommand

function onProcessExit ()
consolelog(`Process closed, killing $command`, killCommand)
killCommand('SIGTERM')


function killCommand (signal)
consolelog(`killCommand() called! sending $signal to $command`)
process.removeListener('exit', onProcessExit)
if (proc)
consolelog(`Sending $signal to $command`)
proc.kill(signal)
proc = null




function sleep (ms)
return new Promise(resolve => setTimeout(resolve, ms))


class Browser
constructor (alwaysMatch = , firstMatch = , root = )
// Sanity checks. Things can go pretty bad if these are wrong
if (!isObject(alwaysMatch))
throw new Error('alwaysMatch must be an object')

if (!Array.isArray(firstMatch))
throw new Error('firstmatch parameter must be an array')

if (!isObject(root))
throw new Error('root options must be an object')


this.sessionParameters =
capabilities:
alwaysMatch: alwaysMatch,
firstMatch: firstMatch


// Copy over whatever is specified in `root`
for (var k in root)
if (root.hasOwnProperty(k)) this.sessionParameters[ k ] = root[k]


// Give it a nice, lowercase name
this.name = 'browser'

setAlwaysMatchKey (name, value, force = false)
if (force

addFirstMatch (name, value, force = false)

setRootKey (name, value, force = false)
if (force

getSessionParameters ()
return this.sessionParameters


// Options: port, args, env, stdio
async run (options)



class Chrome extends Browser // eslint-disable-line no-unused-vars
constructor (alwaysMatch = , firstMatch = , root = , specific = )
super(...arguments)

// Give it a nice, lowercase name
this.name = 'chrome'

this.setAlwaysMatchKey('chromeOptions.w3c', true, true)

this.setAlwaysMatchKey('browserName', 'chrome')

for (var k in specific)
if (specific.hasOwnProperty(k))
this.alwaysMatch.chromeOptions[ k ] = specific[ k ]




run (options)
var executable = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'
options.args.push('--port=' + options.port)
return exec(executable, options)



class Firefox extends Browser // eslint-disable-line no-unused-vars
constructor (alwaysMatch = , firstMatch = , root = , specific = )
super(...arguments)

this.name = 'firefox'
this.setAlwaysMatchKey('moz:firefoxOptions', , true)
this.setAlwaysMatchKey('browserName', 'firefox')

for (var k in specific)
if (specific.hasOwnProperty(k))
this.alwaysMatch['moz:firefoxOptions'][ k ] = specific[ k ]




run (options)
var executable = process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver'
options.args.push('--port=' + options.port)
return exec(executable, options)



class Selenium extends Browser // eslint-disable-line no-unused-vars


class InputDevice
constructor (id)
this.id = id



class Keyboard extends InputDevice
constructor (id)
super(id)
this.type = 'key'


static get UP ()
return 'keyUp'


static get DOWN ()
return 'keyDown'


tickMethods ()
return
Up: (value) =>
return
type: 'keyUp',
value

,

Down: (value) =>
return
type: 'keyDown',
value






class Pointer extends InputDevice
constructor (id, pointerType)
super(id)
this.pointerType = pointerType
this.type = 'pointer'


static get Type ()
return
MOUSE: 'mouse',
PEN: 'pen',
TOUCH: 'touch'



static get Origin ()
return
VIEWPORT: 'viewport',
POINTER: 'pointer'



tickMethods ()
return
Move: (args) =>
var origin
if (!args.origin)
origin = Pointer.Origin.VIEWPORT
else
if (args.origin instanceof Element)
origin =
'element-6066-11e4-a52e-4f735466cecf': args.origin.id,
ELEMENT: args.origin.id

else
origin = args.origin
if (origin !== Pointer.Origin.VIEWPORT &&
origin !== Pointer.Origin.POINTER)
throw new Error('When using move(), origin must be an element, Pointer.Origin.VIEWPORT or Pointer.Origin.POINTER')
else




return 0,
origin,
x: args.x,
y: args.y

,
Down: (button = 0) =>
return
type: 'pointerDown',
button

,
Up: (button = 0) =>
return
type: 'pointerUp',
button

,
Cancel: () =>
return
type: 'pointerCancel'






class Actions // eslint-disable-line no-unused-vars
constructor (...devices)
var self = this
this.actions =

this._compiled = false
this.compiledActions =

if (Object.keys(devices).length)
this.devices = devices
else
this.devices = [
new Pointer('mouse', Pointer.Type.MOUSE),
new Keyboard('keyboard')
]


this._tickSetters =
get tick ()
return self.tick
,
compile: self.compile.bind(self)

this.devices.forEach((device) =>
var deviceTickMethods = device.tickMethods()
Object.keys(deviceTickMethods).forEach((k) =>
this._tickSetters[device.id + k] = function (...args)
if (!self._currentAction[device.id].virgin)
throw new Error(`Action for device $device.id already defined ($device.id + k) for this tick`)

var res = deviceTickMethods[k].apply(device, args)
self._currentAction[device.id] = res
return self._tickSetters

this._tickSetters['pause'] = function (duration = 0)
self._currentAction[device.id] = type: 'pause', duration
return self._tickSetters

)
)


static get KEY () return KEY

compile ()
if (this._compiled) return
this.compiledActions =

this.devices.forEach((device) =>
var deviceActions = actions:
deviceActions.type = device.type
deviceActions.id = device.id
if (device.type === 'pointer')
deviceActions.parameters = pointerType: device.pointerType


this.actions.forEach((action) =>
deviceActions.actions.push(action[ device.id ])
)
this.compiledActions.push(deviceActions)
)


_setAction (deviceId, action)
this._currentAction[ deviceId ] = action


get tick ()
this._compiled = false

var action =
this.devices.forEach((device) =>
action[ device.id ] = type: 'pause', duration: 0, virgin: true
)
this.actions.push(action)

this._currentAction = action

return this._tickSetters



const FindHelpersMixin = (superClass) => class extends superClass
findElementCss (value)
return this.findElement(Driver.Using.CSS, value)


findElementLinkText (value)
return this.findElement(Driver.Using.LINK_TEXT, value)


findElementPartialLinkText (value)
return this.findElement(Driver.Using.PARTIAL_LINK_TEXT, value)


findElementTagName (value)
return this.findElement(Driver.Using.TAG_NAME, value)


findElementXpath (value)
return this.findElement(Driver.Using.XPATH, value)


findElementsCss (value)
return this.findElements(Driver.Using.CSS, value)


findElementsLinkText (value)
return this.findElements(Driver.Using.LINK_TEXT, value)


findElementsPartialLinkText (value)
return this.findElements(Driver.Using.PARTIAL_LINK_TEXT, value)


findElementsTagName (value)
return this.findElements(Driver.Using.TAG_NAME, value)


findElementsXpath (value)
return this.findElements(Driver.Using.XPATH, value)



var Element = class
constructor (driver, elObject)
var value

this.driver = driver

if (isObject(elObject) && elObject.value) value = elObject.value
else value = elObject

// Sets the ID. Having `element-XXX` and `ELEMENT` as keys to the object means
// that it can be used by switchToFrame()
var idx = 'element-6066-11e4-a52e-4f735466cecf'
this.id = this.ELEMENT = this[idx] = value[idx]

// No ID could be find
if (!this.id) throw new Error('Could not get element ID from element object')


waitFor (timeout = 0, pollInterval = 0)

static get KEY () return KEY

async findElement (using, value)
var el = await this._execute('post', `/element/$this.id/element`, using, value)
return new Element(this.driver, el)


async findElements (using, value)
var els = await this._execute('post', `/element/$this.id/elements`, using, value)
if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
return els.map((v) => new Element(this.driver, v))


async isSelected ()
return !!(await this._execute('get', `/element/$this.id/selected`))


getRect ()
return this._execute('get', `/element/$this.id/rect`)


// REMOVED: A FEW MORE, SIMILAR TO getRect() (one liners)

async isEnabled ()
return !!(await this._execute('get', `/element/$this.id/enabled`))



async sendKeys (text)
var value = text.split('')
await this._execute('post', `/element/$this.id/value`, text, value )
return this


async takeScreenshot (scroll = true)
var data = await this._execute('get', `/element/$this.id/screenshot`, scroll )
return Buffer.from(data, 'base64')




var Driver = class
constructor (browser, options = ) '127.0.0.1'
this._spawn = typeof options.spawn !== 'undefined' ? !!options.spawn : true
this._webDriverRunning = !this._spawn

// Parameters passed onto child process
this._port = options.port
this._env = isObject(options.env) ? options.env : process.env
this._stdio = options.stdio

waitFor (timeout = 0, pollInterval = 0) this._defaultPollTimeout
pollInterval = pollInterval

_ready ()
return !!(this._sessionId && this._webDriverRunning)


async startWebDriver ()
// If it's already connected, nothing to do
if (this._webDriverRunning) return

// If spawning is required, do so
if (this._spawn)
// No port: find a free port
if (!this._port)
this._port = await getPort( host: this._hostname )


// Options: port, args, env, stdio
var res = this._browser.run(
port: this._port,
args: this._args,
env: this._env,
stdio: this._stdio
)
this._killCommand = res.killCommand
this._commandResult = res.result


if (!this.port) this.port = '4444'

this._urlBase = `http://$this._hostname:$this._port/session`

var success = false
for (var i = 0; i < 10; i++)
if (i > 5)
consolelog(`Attempt n. $i to connect to $this._hostname, port $this._port... `)

try
await this.status()
success = true
break
catch (e)
await this.sleep(1000)


if (!success)
throw new Error(`Could not connect to the driver`)


this._webDriverRunning = true


stopWebDriver (signal = 'SIGTERM')
if (this._killCommand)
this._killCommand(signal)

this.killWebDriver('SIGTERM')
this._webDriverRunning = false



inspect ()
return `Driver ip: $this.Name, port: $this._port `


async newSession ()
try catch (e)
this._sessionId = null
this._sessionData =
this._urlBase = `http://$this._hostname:$this._port/session`
throw (e)



async deleteSession ()
try
var value = await this._execute('delete', '')
this._sessionId = null
this._sessionData =
this._urlBase = `http://$this._hostname:$this._port/session`
return value
catch (e)
throw (e)



async status ()
var _urlBase = `http://$this._hostname:$this._port`
var res = await request.get( url: `$_urlBase/status`, json: true )
return checkRes(res).value


async findElement (using, value)
var el = await this._execute('post', '/element', using, value)
return new Element(this, el)


async findElements (using, value)
var els = await this._execute('post', '/elements', using, value)
if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
return els.map((v) => new Element(this, v))


async performActions (actions)
actions.compile()
await this._execute('post', '/actions', actions: actions.compiledActions )
return this


async releaseActions ()
await this._execute('delete', '/actions')
return this


async sleep (ms)
return sleep(ms)


async _execute (method, command, params)

static get Using () return USING

getTimeouts ()
return this._execute('get', '/timeouts')


async navigateTo (url)
await this._execute('post', '/url', url )
return this



// Mixin the find helpers with Driver and Element
Driver = FindHelpersMixin(Driver)
Element = FindHelpersMixin(Element)






share|improve this question

























    up vote
    1
    down vote

    favorite












    I have just finished writing an API that implements the FULL w3c protocol for webdrivers.



    This is my first attempt to write fully ES6 code, along with async/await. I removed some of the functions here as they were very similar.



    Questions:



    • Did I make any ES6 disaster?

    • Can you see anything clearly terrible in the code?

    • I have an inconsistency.. I have static get Using () return USING and static get KEY () return KEY . They are constants added to a class. Should I have KEY or Key? USING or Using?

    • Anything I should look at/improve?

    I am all ears... thank you!



    var request = require('request-promise-native')
    const spawn = require('child_process')
    var DO = require('deepobject')
    const getPort = require('get-port')
    var consolelog = require('debug')('webdriver')

    const KEY = require('./KEY.js')

    const USING =
    CSS: 'css selector',
    LINK_TEXT: 'link text',
    PARTIAL_LINK_TEXT: 'partial link text',
    TAG_NAME: 'tag name',
    XPATH: 'xpath'


    function isObject (p) return typeof p === 'object' && p !== null && !Array.isArray(p)

    function checkRes (res)
    if (!isObject(res)) throw new Error('Unexpected non-object received from webdriver')
    if (typeof res.value === 'undefined') throw new Error('Missing `value` from object returned by webdriver')
    return res


    function exec (command, commandOptions) , 'ignore'
    )

    proc.on('error', (err) =>
    consolelog(`Could not run $command:`, err)
    throw new Error(`Error running the webdriver '$command'`)
    )

    proc.unref()
    process.once('exit', onProcessExit)

    let result = new Promise(resolve =>
    proc.once('exit', (code, signal) =>
    consolelog(`Process $command has exited! Code and signal:`, code, signal)
    proc = null
    process.removeListener('exit', onProcessExit)
    resolve( code, signal )
    )
    )
    return result, killCommand

    function onProcessExit ()
    consolelog(`Process closed, killing $command`, killCommand)
    killCommand('SIGTERM')


    function killCommand (signal)
    consolelog(`killCommand() called! sending $signal to $command`)
    process.removeListener('exit', onProcessExit)
    if (proc)
    consolelog(`Sending $signal to $command`)
    proc.kill(signal)
    proc = null




    function sleep (ms)
    return new Promise(resolve => setTimeout(resolve, ms))


    class Browser
    constructor (alwaysMatch = , firstMatch = , root = )
    // Sanity checks. Things can go pretty bad if these are wrong
    if (!isObject(alwaysMatch))
    throw new Error('alwaysMatch must be an object')

    if (!Array.isArray(firstMatch))
    throw new Error('firstmatch parameter must be an array')

    if (!isObject(root))
    throw new Error('root options must be an object')


    this.sessionParameters =
    capabilities:
    alwaysMatch: alwaysMatch,
    firstMatch: firstMatch


    // Copy over whatever is specified in `root`
    for (var k in root)
    if (root.hasOwnProperty(k)) this.sessionParameters[ k ] = root[k]


    // Give it a nice, lowercase name
    this.name = 'browser'

    setAlwaysMatchKey (name, value, force = false)
    if (force

    addFirstMatch (name, value, force = false)

    setRootKey (name, value, force = false)
    if (force

    getSessionParameters ()
    return this.sessionParameters


    // Options: port, args, env, stdio
    async run (options)



    class Chrome extends Browser // eslint-disable-line no-unused-vars
    constructor (alwaysMatch = , firstMatch = , root = , specific = )
    super(...arguments)

    // Give it a nice, lowercase name
    this.name = 'chrome'

    this.setAlwaysMatchKey('chromeOptions.w3c', true, true)

    this.setAlwaysMatchKey('browserName', 'chrome')

    for (var k in specific)
    if (specific.hasOwnProperty(k))
    this.alwaysMatch.chromeOptions[ k ] = specific[ k ]




    run (options)
    var executable = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'
    options.args.push('--port=' + options.port)
    return exec(executable, options)



    class Firefox extends Browser // eslint-disable-line no-unused-vars
    constructor (alwaysMatch = , firstMatch = , root = , specific = )
    super(...arguments)

    this.name = 'firefox'
    this.setAlwaysMatchKey('moz:firefoxOptions', , true)
    this.setAlwaysMatchKey('browserName', 'firefox')

    for (var k in specific)
    if (specific.hasOwnProperty(k))
    this.alwaysMatch['moz:firefoxOptions'][ k ] = specific[ k ]




    run (options)
    var executable = process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver'
    options.args.push('--port=' + options.port)
    return exec(executable, options)



    class Selenium extends Browser // eslint-disable-line no-unused-vars


    class InputDevice
    constructor (id)
    this.id = id



    class Keyboard extends InputDevice
    constructor (id)
    super(id)
    this.type = 'key'


    static get UP ()
    return 'keyUp'


    static get DOWN ()
    return 'keyDown'


    tickMethods ()
    return
    Up: (value) =>
    return
    type: 'keyUp',
    value

    ,

    Down: (value) =>
    return
    type: 'keyDown',
    value






    class Pointer extends InputDevice
    constructor (id, pointerType)
    super(id)
    this.pointerType = pointerType
    this.type = 'pointer'


    static get Type ()
    return
    MOUSE: 'mouse',
    PEN: 'pen',
    TOUCH: 'touch'



    static get Origin ()
    return
    VIEWPORT: 'viewport',
    POINTER: 'pointer'



    tickMethods ()
    return
    Move: (args) =>
    var origin
    if (!args.origin)
    origin = Pointer.Origin.VIEWPORT
    else
    if (args.origin instanceof Element)
    origin =
    'element-6066-11e4-a52e-4f735466cecf': args.origin.id,
    ELEMENT: args.origin.id

    else
    origin = args.origin
    if (origin !== Pointer.Origin.VIEWPORT &&
    origin !== Pointer.Origin.POINTER)
    throw new Error('When using move(), origin must be an element, Pointer.Origin.VIEWPORT or Pointer.Origin.POINTER')
    else




    return 0,
    origin,
    x: args.x,
    y: args.y

    ,
    Down: (button = 0) =>
    return
    type: 'pointerDown',
    button

    ,
    Up: (button = 0) =>
    return
    type: 'pointerUp',
    button

    ,
    Cancel: () =>
    return
    type: 'pointerCancel'






    class Actions // eslint-disable-line no-unused-vars
    constructor (...devices)
    var self = this
    this.actions =

    this._compiled = false
    this.compiledActions =

    if (Object.keys(devices).length)
    this.devices = devices
    else
    this.devices = [
    new Pointer('mouse', Pointer.Type.MOUSE),
    new Keyboard('keyboard')
    ]


    this._tickSetters =
    get tick ()
    return self.tick
    ,
    compile: self.compile.bind(self)

    this.devices.forEach((device) =>
    var deviceTickMethods = device.tickMethods()
    Object.keys(deviceTickMethods).forEach((k) =>
    this._tickSetters[device.id + k] = function (...args)
    if (!self._currentAction[device.id].virgin)
    throw new Error(`Action for device $device.id already defined ($device.id + k) for this tick`)

    var res = deviceTickMethods[k].apply(device, args)
    self._currentAction[device.id] = res
    return self._tickSetters

    this._tickSetters['pause'] = function (duration = 0)
    self._currentAction[device.id] = type: 'pause', duration
    return self._tickSetters

    )
    )


    static get KEY () return KEY

    compile ()
    if (this._compiled) return
    this.compiledActions =

    this.devices.forEach((device) =>
    var deviceActions = actions:
    deviceActions.type = device.type
    deviceActions.id = device.id
    if (device.type === 'pointer')
    deviceActions.parameters = pointerType: device.pointerType


    this.actions.forEach((action) =>
    deviceActions.actions.push(action[ device.id ])
    )
    this.compiledActions.push(deviceActions)
    )


    _setAction (deviceId, action)
    this._currentAction[ deviceId ] = action


    get tick ()
    this._compiled = false

    var action =
    this.devices.forEach((device) =>
    action[ device.id ] = type: 'pause', duration: 0, virgin: true
    )
    this.actions.push(action)

    this._currentAction = action

    return this._tickSetters



    const FindHelpersMixin = (superClass) => class extends superClass
    findElementCss (value)
    return this.findElement(Driver.Using.CSS, value)


    findElementLinkText (value)
    return this.findElement(Driver.Using.LINK_TEXT, value)


    findElementPartialLinkText (value)
    return this.findElement(Driver.Using.PARTIAL_LINK_TEXT, value)


    findElementTagName (value)
    return this.findElement(Driver.Using.TAG_NAME, value)


    findElementXpath (value)
    return this.findElement(Driver.Using.XPATH, value)


    findElementsCss (value)
    return this.findElements(Driver.Using.CSS, value)


    findElementsLinkText (value)
    return this.findElements(Driver.Using.LINK_TEXT, value)


    findElementsPartialLinkText (value)
    return this.findElements(Driver.Using.PARTIAL_LINK_TEXT, value)


    findElementsTagName (value)
    return this.findElements(Driver.Using.TAG_NAME, value)


    findElementsXpath (value)
    return this.findElements(Driver.Using.XPATH, value)



    var Element = class
    constructor (driver, elObject)
    var value

    this.driver = driver

    if (isObject(elObject) && elObject.value) value = elObject.value
    else value = elObject

    // Sets the ID. Having `element-XXX` and `ELEMENT` as keys to the object means
    // that it can be used by switchToFrame()
    var idx = 'element-6066-11e4-a52e-4f735466cecf'
    this.id = this.ELEMENT = this[idx] = value[idx]

    // No ID could be find
    if (!this.id) throw new Error('Could not get element ID from element object')


    waitFor (timeout = 0, pollInterval = 0)

    static get KEY () return KEY

    async findElement (using, value)
    var el = await this._execute('post', `/element/$this.id/element`, using, value)
    return new Element(this.driver, el)


    async findElements (using, value)
    var els = await this._execute('post', `/element/$this.id/elements`, using, value)
    if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
    return els.map((v) => new Element(this.driver, v))


    async isSelected ()
    return !!(await this._execute('get', `/element/$this.id/selected`))


    getRect ()
    return this._execute('get', `/element/$this.id/rect`)


    // REMOVED: A FEW MORE, SIMILAR TO getRect() (one liners)

    async isEnabled ()
    return !!(await this._execute('get', `/element/$this.id/enabled`))



    async sendKeys (text)
    var value = text.split('')
    await this._execute('post', `/element/$this.id/value`, text, value )
    return this


    async takeScreenshot (scroll = true)
    var data = await this._execute('get', `/element/$this.id/screenshot`, scroll )
    return Buffer.from(data, 'base64')




    var Driver = class
    constructor (browser, options = ) '127.0.0.1'
    this._spawn = typeof options.spawn !== 'undefined' ? !!options.spawn : true
    this._webDriverRunning = !this._spawn

    // Parameters passed onto child process
    this._port = options.port
    this._env = isObject(options.env) ? options.env : process.env
    this._stdio = options.stdio

    waitFor (timeout = 0, pollInterval = 0) this._defaultPollTimeout
    pollInterval = pollInterval

    _ready ()
    return !!(this._sessionId && this._webDriverRunning)


    async startWebDriver ()
    // If it's already connected, nothing to do
    if (this._webDriverRunning) return

    // If spawning is required, do so
    if (this._spawn)
    // No port: find a free port
    if (!this._port)
    this._port = await getPort( host: this._hostname )


    // Options: port, args, env, stdio
    var res = this._browser.run(
    port: this._port,
    args: this._args,
    env: this._env,
    stdio: this._stdio
    )
    this._killCommand = res.killCommand
    this._commandResult = res.result


    if (!this.port) this.port = '4444'

    this._urlBase = `http://$this._hostname:$this._port/session`

    var success = false
    for (var i = 0; i < 10; i++)
    if (i > 5)
    consolelog(`Attempt n. $i to connect to $this._hostname, port $this._port... `)

    try
    await this.status()
    success = true
    break
    catch (e)
    await this.sleep(1000)


    if (!success)
    throw new Error(`Could not connect to the driver`)


    this._webDriverRunning = true


    stopWebDriver (signal = 'SIGTERM')
    if (this._killCommand)
    this._killCommand(signal)

    this.killWebDriver('SIGTERM')
    this._webDriverRunning = false



    inspect ()
    return `Driver ip: $this.Name, port: $this._port `


    async newSession ()
    try catch (e)
    this._sessionId = null
    this._sessionData =
    this._urlBase = `http://$this._hostname:$this._port/session`
    throw (e)



    async deleteSession ()
    try
    var value = await this._execute('delete', '')
    this._sessionId = null
    this._sessionData =
    this._urlBase = `http://$this._hostname:$this._port/session`
    return value
    catch (e)
    throw (e)



    async status ()
    var _urlBase = `http://$this._hostname:$this._port`
    var res = await request.get( url: `$_urlBase/status`, json: true )
    return checkRes(res).value


    async findElement (using, value)
    var el = await this._execute('post', '/element', using, value)
    return new Element(this, el)


    async findElements (using, value)
    var els = await this._execute('post', '/elements', using, value)
    if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
    return els.map((v) => new Element(this, v))


    async performActions (actions)
    actions.compile()
    await this._execute('post', '/actions', actions: actions.compiledActions )
    return this


    async releaseActions ()
    await this._execute('delete', '/actions')
    return this


    async sleep (ms)
    return sleep(ms)


    async _execute (method, command, params)

    static get Using () return USING

    getTimeouts ()
    return this._execute('get', '/timeouts')


    async navigateTo (url)
    await this._execute('post', '/url', url )
    return this



    // Mixin the find helpers with Driver and Element
    Driver = FindHelpersMixin(Driver)
    Element = FindHelpersMixin(Element)






    share|improve this question





















      up vote
      1
      down vote

      favorite









      up vote
      1
      down vote

      favorite











      I have just finished writing an API that implements the FULL w3c protocol for webdrivers.



      This is my first attempt to write fully ES6 code, along with async/await. I removed some of the functions here as they were very similar.



      Questions:



      • Did I make any ES6 disaster?

      • Can you see anything clearly terrible in the code?

      • I have an inconsistency.. I have static get Using () return USING and static get KEY () return KEY . They are constants added to a class. Should I have KEY or Key? USING or Using?

      • Anything I should look at/improve?

      I am all ears... thank you!



      var request = require('request-promise-native')
      const spawn = require('child_process')
      var DO = require('deepobject')
      const getPort = require('get-port')
      var consolelog = require('debug')('webdriver')

      const KEY = require('./KEY.js')

      const USING =
      CSS: 'css selector',
      LINK_TEXT: 'link text',
      PARTIAL_LINK_TEXT: 'partial link text',
      TAG_NAME: 'tag name',
      XPATH: 'xpath'


      function isObject (p) return typeof p === 'object' && p !== null && !Array.isArray(p)

      function checkRes (res)
      if (!isObject(res)) throw new Error('Unexpected non-object received from webdriver')
      if (typeof res.value === 'undefined') throw new Error('Missing `value` from object returned by webdriver')
      return res


      function exec (command, commandOptions) , 'ignore'
      )

      proc.on('error', (err) =>
      consolelog(`Could not run $command:`, err)
      throw new Error(`Error running the webdriver '$command'`)
      )

      proc.unref()
      process.once('exit', onProcessExit)

      let result = new Promise(resolve =>
      proc.once('exit', (code, signal) =>
      consolelog(`Process $command has exited! Code and signal:`, code, signal)
      proc = null
      process.removeListener('exit', onProcessExit)
      resolve( code, signal )
      )
      )
      return result, killCommand

      function onProcessExit ()
      consolelog(`Process closed, killing $command`, killCommand)
      killCommand('SIGTERM')


      function killCommand (signal)
      consolelog(`killCommand() called! sending $signal to $command`)
      process.removeListener('exit', onProcessExit)
      if (proc)
      consolelog(`Sending $signal to $command`)
      proc.kill(signal)
      proc = null




      function sleep (ms)
      return new Promise(resolve => setTimeout(resolve, ms))


      class Browser
      constructor (alwaysMatch = , firstMatch = , root = )
      // Sanity checks. Things can go pretty bad if these are wrong
      if (!isObject(alwaysMatch))
      throw new Error('alwaysMatch must be an object')

      if (!Array.isArray(firstMatch))
      throw new Error('firstmatch parameter must be an array')

      if (!isObject(root))
      throw new Error('root options must be an object')


      this.sessionParameters =
      capabilities:
      alwaysMatch: alwaysMatch,
      firstMatch: firstMatch


      // Copy over whatever is specified in `root`
      for (var k in root)
      if (root.hasOwnProperty(k)) this.sessionParameters[ k ] = root[k]


      // Give it a nice, lowercase name
      this.name = 'browser'

      setAlwaysMatchKey (name, value, force = false)
      if (force

      addFirstMatch (name, value, force = false)

      setRootKey (name, value, force = false)
      if (force

      getSessionParameters ()
      return this.sessionParameters


      // Options: port, args, env, stdio
      async run (options)



      class Chrome extends Browser // eslint-disable-line no-unused-vars
      constructor (alwaysMatch = , firstMatch = , root = , specific = )
      super(...arguments)

      // Give it a nice, lowercase name
      this.name = 'chrome'

      this.setAlwaysMatchKey('chromeOptions.w3c', true, true)

      this.setAlwaysMatchKey('browserName', 'chrome')

      for (var k in specific)
      if (specific.hasOwnProperty(k))
      this.alwaysMatch.chromeOptions[ k ] = specific[ k ]




      run (options)
      var executable = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'
      options.args.push('--port=' + options.port)
      return exec(executable, options)



      class Firefox extends Browser // eslint-disable-line no-unused-vars
      constructor (alwaysMatch = , firstMatch = , root = , specific = )
      super(...arguments)

      this.name = 'firefox'
      this.setAlwaysMatchKey('moz:firefoxOptions', , true)
      this.setAlwaysMatchKey('browserName', 'firefox')

      for (var k in specific)
      if (specific.hasOwnProperty(k))
      this.alwaysMatch['moz:firefoxOptions'][ k ] = specific[ k ]




      run (options)
      var executable = process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver'
      options.args.push('--port=' + options.port)
      return exec(executable, options)



      class Selenium extends Browser // eslint-disable-line no-unused-vars


      class InputDevice
      constructor (id)
      this.id = id



      class Keyboard extends InputDevice
      constructor (id)
      super(id)
      this.type = 'key'


      static get UP ()
      return 'keyUp'


      static get DOWN ()
      return 'keyDown'


      tickMethods ()
      return
      Up: (value) =>
      return
      type: 'keyUp',
      value

      ,

      Down: (value) =>
      return
      type: 'keyDown',
      value






      class Pointer extends InputDevice
      constructor (id, pointerType)
      super(id)
      this.pointerType = pointerType
      this.type = 'pointer'


      static get Type ()
      return
      MOUSE: 'mouse',
      PEN: 'pen',
      TOUCH: 'touch'



      static get Origin ()
      return
      VIEWPORT: 'viewport',
      POINTER: 'pointer'



      tickMethods ()
      return
      Move: (args) =>
      var origin
      if (!args.origin)
      origin = Pointer.Origin.VIEWPORT
      else
      if (args.origin instanceof Element)
      origin =
      'element-6066-11e4-a52e-4f735466cecf': args.origin.id,
      ELEMENT: args.origin.id

      else
      origin = args.origin
      if (origin !== Pointer.Origin.VIEWPORT &&
      origin !== Pointer.Origin.POINTER)
      throw new Error('When using move(), origin must be an element, Pointer.Origin.VIEWPORT or Pointer.Origin.POINTER')
      else




      return 0,
      origin,
      x: args.x,
      y: args.y

      ,
      Down: (button = 0) =>
      return
      type: 'pointerDown',
      button

      ,
      Up: (button = 0) =>
      return
      type: 'pointerUp',
      button

      ,
      Cancel: () =>
      return
      type: 'pointerCancel'






      class Actions // eslint-disable-line no-unused-vars
      constructor (...devices)
      var self = this
      this.actions =

      this._compiled = false
      this.compiledActions =

      if (Object.keys(devices).length)
      this.devices = devices
      else
      this.devices = [
      new Pointer('mouse', Pointer.Type.MOUSE),
      new Keyboard('keyboard')
      ]


      this._tickSetters =
      get tick ()
      return self.tick
      ,
      compile: self.compile.bind(self)

      this.devices.forEach((device) =>
      var deviceTickMethods = device.tickMethods()
      Object.keys(deviceTickMethods).forEach((k) =>
      this._tickSetters[device.id + k] = function (...args)
      if (!self._currentAction[device.id].virgin)
      throw new Error(`Action for device $device.id already defined ($device.id + k) for this tick`)

      var res = deviceTickMethods[k].apply(device, args)
      self._currentAction[device.id] = res
      return self._tickSetters

      this._tickSetters['pause'] = function (duration = 0)
      self._currentAction[device.id] = type: 'pause', duration
      return self._tickSetters

      )
      )


      static get KEY () return KEY

      compile ()
      if (this._compiled) return
      this.compiledActions =

      this.devices.forEach((device) =>
      var deviceActions = actions:
      deviceActions.type = device.type
      deviceActions.id = device.id
      if (device.type === 'pointer')
      deviceActions.parameters = pointerType: device.pointerType


      this.actions.forEach((action) =>
      deviceActions.actions.push(action[ device.id ])
      )
      this.compiledActions.push(deviceActions)
      )


      _setAction (deviceId, action)
      this._currentAction[ deviceId ] = action


      get tick ()
      this._compiled = false

      var action =
      this.devices.forEach((device) =>
      action[ device.id ] = type: 'pause', duration: 0, virgin: true
      )
      this.actions.push(action)

      this._currentAction = action

      return this._tickSetters



      const FindHelpersMixin = (superClass) => class extends superClass
      findElementCss (value)
      return this.findElement(Driver.Using.CSS, value)


      findElementLinkText (value)
      return this.findElement(Driver.Using.LINK_TEXT, value)


      findElementPartialLinkText (value)
      return this.findElement(Driver.Using.PARTIAL_LINK_TEXT, value)


      findElementTagName (value)
      return this.findElement(Driver.Using.TAG_NAME, value)


      findElementXpath (value)
      return this.findElement(Driver.Using.XPATH, value)


      findElementsCss (value)
      return this.findElements(Driver.Using.CSS, value)


      findElementsLinkText (value)
      return this.findElements(Driver.Using.LINK_TEXT, value)


      findElementsPartialLinkText (value)
      return this.findElements(Driver.Using.PARTIAL_LINK_TEXT, value)


      findElementsTagName (value)
      return this.findElements(Driver.Using.TAG_NAME, value)


      findElementsXpath (value)
      return this.findElements(Driver.Using.XPATH, value)



      var Element = class
      constructor (driver, elObject)
      var value

      this.driver = driver

      if (isObject(elObject) && elObject.value) value = elObject.value
      else value = elObject

      // Sets the ID. Having `element-XXX` and `ELEMENT` as keys to the object means
      // that it can be used by switchToFrame()
      var idx = 'element-6066-11e4-a52e-4f735466cecf'
      this.id = this.ELEMENT = this[idx] = value[idx]

      // No ID could be find
      if (!this.id) throw new Error('Could not get element ID from element object')


      waitFor (timeout = 0, pollInterval = 0)

      static get KEY () return KEY

      async findElement (using, value)
      var el = await this._execute('post', `/element/$this.id/element`, using, value)
      return new Element(this.driver, el)


      async findElements (using, value)
      var els = await this._execute('post', `/element/$this.id/elements`, using, value)
      if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
      return els.map((v) => new Element(this.driver, v))


      async isSelected ()
      return !!(await this._execute('get', `/element/$this.id/selected`))


      getRect ()
      return this._execute('get', `/element/$this.id/rect`)


      // REMOVED: A FEW MORE, SIMILAR TO getRect() (one liners)

      async isEnabled ()
      return !!(await this._execute('get', `/element/$this.id/enabled`))



      async sendKeys (text)
      var value = text.split('')
      await this._execute('post', `/element/$this.id/value`, text, value )
      return this


      async takeScreenshot (scroll = true)
      var data = await this._execute('get', `/element/$this.id/screenshot`, scroll )
      return Buffer.from(data, 'base64')




      var Driver = class
      constructor (browser, options = ) '127.0.0.1'
      this._spawn = typeof options.spawn !== 'undefined' ? !!options.spawn : true
      this._webDriverRunning = !this._spawn

      // Parameters passed onto child process
      this._port = options.port
      this._env = isObject(options.env) ? options.env : process.env
      this._stdio = options.stdio

      waitFor (timeout = 0, pollInterval = 0) this._defaultPollTimeout
      pollInterval = pollInterval

      _ready ()
      return !!(this._sessionId && this._webDriverRunning)


      async startWebDriver ()
      // If it's already connected, nothing to do
      if (this._webDriverRunning) return

      // If spawning is required, do so
      if (this._spawn)
      // No port: find a free port
      if (!this._port)
      this._port = await getPort( host: this._hostname )


      // Options: port, args, env, stdio
      var res = this._browser.run(
      port: this._port,
      args: this._args,
      env: this._env,
      stdio: this._stdio
      )
      this._killCommand = res.killCommand
      this._commandResult = res.result


      if (!this.port) this.port = '4444'

      this._urlBase = `http://$this._hostname:$this._port/session`

      var success = false
      for (var i = 0; i < 10; i++)
      if (i > 5)
      consolelog(`Attempt n. $i to connect to $this._hostname, port $this._port... `)

      try
      await this.status()
      success = true
      break
      catch (e)
      await this.sleep(1000)


      if (!success)
      throw new Error(`Could not connect to the driver`)


      this._webDriverRunning = true


      stopWebDriver (signal = 'SIGTERM')
      if (this._killCommand)
      this._killCommand(signal)

      this.killWebDriver('SIGTERM')
      this._webDriverRunning = false



      inspect ()
      return `Driver ip: $this.Name, port: $this._port `


      async newSession ()
      try catch (e)
      this._sessionId = null
      this._sessionData =
      this._urlBase = `http://$this._hostname:$this._port/session`
      throw (e)



      async deleteSession ()
      try
      var value = await this._execute('delete', '')
      this._sessionId = null
      this._sessionData =
      this._urlBase = `http://$this._hostname:$this._port/session`
      return value
      catch (e)
      throw (e)



      async status ()
      var _urlBase = `http://$this._hostname:$this._port`
      var res = await request.get( url: `$_urlBase/status`, json: true )
      return checkRes(res).value


      async findElement (using, value)
      var el = await this._execute('post', '/element', using, value)
      return new Element(this, el)


      async findElements (using, value)
      var els = await this._execute('post', '/elements', using, value)
      if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
      return els.map((v) => new Element(this, v))


      async performActions (actions)
      actions.compile()
      await this._execute('post', '/actions', actions: actions.compiledActions )
      return this


      async releaseActions ()
      await this._execute('delete', '/actions')
      return this


      async sleep (ms)
      return sleep(ms)


      async _execute (method, command, params)

      static get Using () return USING

      getTimeouts ()
      return this._execute('get', '/timeouts')


      async navigateTo (url)
      await this._execute('post', '/url', url )
      return this



      // Mixin the find helpers with Driver and Element
      Driver = FindHelpersMixin(Driver)
      Element = FindHelpersMixin(Element)






      share|improve this question











      I have just finished writing an API that implements the FULL w3c protocol for webdrivers.



      This is my first attempt to write fully ES6 code, along with async/await. I removed some of the functions here as they were very similar.



      Questions:



      • Did I make any ES6 disaster?

      • Can you see anything clearly terrible in the code?

      • I have an inconsistency.. I have static get Using () return USING and static get KEY () return KEY . They are constants added to a class. Should I have KEY or Key? USING or Using?

      • Anything I should look at/improve?

      I am all ears... thank you!



      var request = require('request-promise-native')
      const spawn = require('child_process')
      var DO = require('deepobject')
      const getPort = require('get-port')
      var consolelog = require('debug')('webdriver')

      const KEY = require('./KEY.js')

      const USING =
      CSS: 'css selector',
      LINK_TEXT: 'link text',
      PARTIAL_LINK_TEXT: 'partial link text',
      TAG_NAME: 'tag name',
      XPATH: 'xpath'


      function isObject (p) return typeof p === 'object' && p !== null && !Array.isArray(p)

      function checkRes (res)
      if (!isObject(res)) throw new Error('Unexpected non-object received from webdriver')
      if (typeof res.value === 'undefined') throw new Error('Missing `value` from object returned by webdriver')
      return res


      function exec (command, commandOptions) , 'ignore'
      )

      proc.on('error', (err) =>
      consolelog(`Could not run $command:`, err)
      throw new Error(`Error running the webdriver '$command'`)
      )

      proc.unref()
      process.once('exit', onProcessExit)

      let result = new Promise(resolve =>
      proc.once('exit', (code, signal) =>
      consolelog(`Process $command has exited! Code and signal:`, code, signal)
      proc = null
      process.removeListener('exit', onProcessExit)
      resolve( code, signal )
      )
      )
      return result, killCommand

      function onProcessExit ()
      consolelog(`Process closed, killing $command`, killCommand)
      killCommand('SIGTERM')


      function killCommand (signal)
      consolelog(`killCommand() called! sending $signal to $command`)
      process.removeListener('exit', onProcessExit)
      if (proc)
      consolelog(`Sending $signal to $command`)
      proc.kill(signal)
      proc = null




      function sleep (ms)
      return new Promise(resolve => setTimeout(resolve, ms))


      class Browser
      constructor (alwaysMatch = , firstMatch = , root = )
      // Sanity checks. Things can go pretty bad if these are wrong
      if (!isObject(alwaysMatch))
      throw new Error('alwaysMatch must be an object')

      if (!Array.isArray(firstMatch))
      throw new Error('firstmatch parameter must be an array')

      if (!isObject(root))
      throw new Error('root options must be an object')


      this.sessionParameters =
      capabilities:
      alwaysMatch: alwaysMatch,
      firstMatch: firstMatch


      // Copy over whatever is specified in `root`
      for (var k in root)
      if (root.hasOwnProperty(k)) this.sessionParameters[ k ] = root[k]


      // Give it a nice, lowercase name
      this.name = 'browser'

      setAlwaysMatchKey (name, value, force = false)
      if (force

      addFirstMatch (name, value, force = false)

      setRootKey (name, value, force = false)
      if (force

      getSessionParameters ()
      return this.sessionParameters


      // Options: port, args, env, stdio
      async run (options)



      class Chrome extends Browser // eslint-disable-line no-unused-vars
      constructor (alwaysMatch = , firstMatch = , root = , specific = )
      super(...arguments)

      // Give it a nice, lowercase name
      this.name = 'chrome'

      this.setAlwaysMatchKey('chromeOptions.w3c', true, true)

      this.setAlwaysMatchKey('browserName', 'chrome')

      for (var k in specific)
      if (specific.hasOwnProperty(k))
      this.alwaysMatch.chromeOptions[ k ] = specific[ k ]




      run (options)
      var executable = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'
      options.args.push('--port=' + options.port)
      return exec(executable, options)



      class Firefox extends Browser // eslint-disable-line no-unused-vars
      constructor (alwaysMatch = , firstMatch = , root = , specific = )
      super(...arguments)

      this.name = 'firefox'
      this.setAlwaysMatchKey('moz:firefoxOptions', , true)
      this.setAlwaysMatchKey('browserName', 'firefox')

      for (var k in specific)
      if (specific.hasOwnProperty(k))
      this.alwaysMatch['moz:firefoxOptions'][ k ] = specific[ k ]




      run (options)
      var executable = process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver'
      options.args.push('--port=' + options.port)
      return exec(executable, options)



      class Selenium extends Browser // eslint-disable-line no-unused-vars


      class InputDevice
      constructor (id)
      this.id = id



      class Keyboard extends InputDevice
      constructor (id)
      super(id)
      this.type = 'key'


      static get UP ()
      return 'keyUp'


      static get DOWN ()
      return 'keyDown'


      tickMethods ()
      return
      Up: (value) =>
      return
      type: 'keyUp',
      value

      ,

      Down: (value) =>
      return
      type: 'keyDown',
      value






      class Pointer extends InputDevice
      constructor (id, pointerType)
      super(id)
      this.pointerType = pointerType
      this.type = 'pointer'


      static get Type ()
      return
      MOUSE: 'mouse',
      PEN: 'pen',
      TOUCH: 'touch'



      static get Origin ()
      return
      VIEWPORT: 'viewport',
      POINTER: 'pointer'



      tickMethods ()
      return
      Move: (args) =>
      var origin
      if (!args.origin)
      origin = Pointer.Origin.VIEWPORT
      else
      if (args.origin instanceof Element)
      origin =
      'element-6066-11e4-a52e-4f735466cecf': args.origin.id,
      ELEMENT: args.origin.id

      else
      origin = args.origin
      if (origin !== Pointer.Origin.VIEWPORT &&
      origin !== Pointer.Origin.POINTER)
      throw new Error('When using move(), origin must be an element, Pointer.Origin.VIEWPORT or Pointer.Origin.POINTER')
      else




      return 0,
      origin,
      x: args.x,
      y: args.y

      ,
      Down: (button = 0) =>
      return
      type: 'pointerDown',
      button

      ,
      Up: (button = 0) =>
      return
      type: 'pointerUp',
      button

      ,
      Cancel: () =>
      return
      type: 'pointerCancel'






      class Actions // eslint-disable-line no-unused-vars
      constructor (...devices)
      var self = this
      this.actions =

      this._compiled = false
      this.compiledActions =

      if (Object.keys(devices).length)
      this.devices = devices
      else
      this.devices = [
      new Pointer('mouse', Pointer.Type.MOUSE),
      new Keyboard('keyboard')
      ]


      this._tickSetters =
      get tick ()
      return self.tick
      ,
      compile: self.compile.bind(self)

      this.devices.forEach((device) =>
      var deviceTickMethods = device.tickMethods()
      Object.keys(deviceTickMethods).forEach((k) =>
      this._tickSetters[device.id + k] = function (...args)
      if (!self._currentAction[device.id].virgin)
      throw new Error(`Action for device $device.id already defined ($device.id + k) for this tick`)

      var res = deviceTickMethods[k].apply(device, args)
      self._currentAction[device.id] = res
      return self._tickSetters

      this._tickSetters['pause'] = function (duration = 0)
      self._currentAction[device.id] = type: 'pause', duration
      return self._tickSetters

      )
      )


      static get KEY () return KEY

      compile ()
      if (this._compiled) return
      this.compiledActions =

      this.devices.forEach((device) =>
      var deviceActions = actions:
      deviceActions.type = device.type
      deviceActions.id = device.id
      if (device.type === 'pointer')
      deviceActions.parameters = pointerType: device.pointerType


      this.actions.forEach((action) =>
      deviceActions.actions.push(action[ device.id ])
      )
      this.compiledActions.push(deviceActions)
      )


      _setAction (deviceId, action)
      this._currentAction[ deviceId ] = action


      get tick ()
      this._compiled = false

      var action =
      this.devices.forEach((device) =>
      action[ device.id ] = type: 'pause', duration: 0, virgin: true
      )
      this.actions.push(action)

      this._currentAction = action

      return this._tickSetters



      const FindHelpersMixin = (superClass) => class extends superClass
      findElementCss (value)
      return this.findElement(Driver.Using.CSS, value)


      findElementLinkText (value)
      return this.findElement(Driver.Using.LINK_TEXT, value)


      findElementPartialLinkText (value)
      return this.findElement(Driver.Using.PARTIAL_LINK_TEXT, value)


      findElementTagName (value)
      return this.findElement(Driver.Using.TAG_NAME, value)


      findElementXpath (value)
      return this.findElement(Driver.Using.XPATH, value)


      findElementsCss (value)
      return this.findElements(Driver.Using.CSS, value)


      findElementsLinkText (value)
      return this.findElements(Driver.Using.LINK_TEXT, value)


      findElementsPartialLinkText (value)
      return this.findElements(Driver.Using.PARTIAL_LINK_TEXT, value)


      findElementsTagName (value)
      return this.findElements(Driver.Using.TAG_NAME, value)


      findElementsXpath (value)
      return this.findElements(Driver.Using.XPATH, value)



      var Element = class
      constructor (driver, elObject)
      var value

      this.driver = driver

      if (isObject(elObject) && elObject.value) value = elObject.value
      else value = elObject

      // Sets the ID. Having `element-XXX` and `ELEMENT` as keys to the object means
      // that it can be used by switchToFrame()
      var idx = 'element-6066-11e4-a52e-4f735466cecf'
      this.id = this.ELEMENT = this[idx] = value[idx]

      // No ID could be find
      if (!this.id) throw new Error('Could not get element ID from element object')


      waitFor (timeout = 0, pollInterval = 0)

      static get KEY () return KEY

      async findElement (using, value)
      var el = await this._execute('post', `/element/$this.id/element`, using, value)
      return new Element(this.driver, el)


      async findElements (using, value)
      var els = await this._execute('post', `/element/$this.id/elements`, using, value)
      if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
      return els.map((v) => new Element(this.driver, v))


      async isSelected ()
      return !!(await this._execute('get', `/element/$this.id/selected`))


      getRect ()
      return this._execute('get', `/element/$this.id/rect`)


      // REMOVED: A FEW MORE, SIMILAR TO getRect() (one liners)

      async isEnabled ()
      return !!(await this._execute('get', `/element/$this.id/enabled`))



      async sendKeys (text)
      var value = text.split('')
      await this._execute('post', `/element/$this.id/value`, text, value )
      return this


      async takeScreenshot (scroll = true)
      var data = await this._execute('get', `/element/$this.id/screenshot`, scroll )
      return Buffer.from(data, 'base64')




      var Driver = class
      constructor (browser, options = ) '127.0.0.1'
      this._spawn = typeof options.spawn !== 'undefined' ? !!options.spawn : true
      this._webDriverRunning = !this._spawn

      // Parameters passed onto child process
      this._port = options.port
      this._env = isObject(options.env) ? options.env : process.env
      this._stdio = options.stdio

      waitFor (timeout = 0, pollInterval = 0) this._defaultPollTimeout
      pollInterval = pollInterval

      _ready ()
      return !!(this._sessionId && this._webDriverRunning)


      async startWebDriver ()
      // If it's already connected, nothing to do
      if (this._webDriverRunning) return

      // If spawning is required, do so
      if (this._spawn)
      // No port: find a free port
      if (!this._port)
      this._port = await getPort( host: this._hostname )


      // Options: port, args, env, stdio
      var res = this._browser.run(
      port: this._port,
      args: this._args,
      env: this._env,
      stdio: this._stdio
      )
      this._killCommand = res.killCommand
      this._commandResult = res.result


      if (!this.port) this.port = '4444'

      this._urlBase = `http://$this._hostname:$this._port/session`

      var success = false
      for (var i = 0; i < 10; i++)
      if (i > 5)
      consolelog(`Attempt n. $i to connect to $this._hostname, port $this._port... `)

      try
      await this.status()
      success = true
      break
      catch (e)
      await this.sleep(1000)


      if (!success)
      throw new Error(`Could not connect to the driver`)


      this._webDriverRunning = true


      stopWebDriver (signal = 'SIGTERM')
      if (this._killCommand)
      this._killCommand(signal)

      this.killWebDriver('SIGTERM')
      this._webDriverRunning = false



      inspect ()
      return `Driver ip: $this.Name, port: $this._port `


      async newSession ()
      try catch (e)
      this._sessionId = null
      this._sessionData =
      this._urlBase = `http://$this._hostname:$this._port/session`
      throw (e)



      async deleteSession ()
      try
      var value = await this._execute('delete', '')
      this._sessionId = null
      this._sessionData =
      this._urlBase = `http://$this._hostname:$this._port/session`
      return value
      catch (e)
      throw (e)



      async status ()
      var _urlBase = `http://$this._hostname:$this._port`
      var res = await request.get( url: `$_urlBase/status`, json: true )
      return checkRes(res).value


      async findElement (using, value)
      var el = await this._execute('post', '/element', using, value)
      return new Element(this, el)


      async findElements (using, value)
      var els = await this._execute('post', '/elements', using, value)
      if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
      return els.map((v) => new Element(this, v))


      async performActions (actions)
      actions.compile()
      await this._execute('post', '/actions', actions: actions.compiledActions )
      return this


      async releaseActions ()
      await this._execute('delete', '/actions')
      return this


      async sleep (ms)
      return sleep(ms)


      async _execute (method, command, params)

      static get Using () return USING

      getTimeouts ()
      return this._execute('get', '/timeouts')


      async navigateTo (url)
      await this._execute('post', '/url', url )
      return this



      // Mixin the find helpers with Driver and Element
      Driver = FindHelpersMixin(Driver)
      Element = FindHelpersMixin(Element)








      share|improve this question










      share|improve this question




      share|improve this question









      asked Jan 28 at 16:02









      Merc

      22219




      22219

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186203%2fcheck-an-es6-api-implementation-can-you-see-anything-terrible%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186203%2fcheck-an-es6-api-implementation-can-you-see-anything-terrible%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Python Lists

          Aion

          JavaScript Array Iteration Methods