Skip to content

Commit

Permalink
Change interface to stem from wrapped driver.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaridmargolin committed Jan 10, 2017
1 parent 594bd47 commit cac5f25
Show file tree
Hide file tree
Showing 12 changed files with 618 additions and 747 deletions.
7 changes: 3 additions & 4 deletions README.md
Expand Up @@ -24,14 +24,13 @@ The goal of **wd-elements** is to provide a sane and consistent environment to a

```js
const webdriver = require('selenium-webdriver')
const WDE = require('wd-elements')(webdriver)
const WDE = require('wd-elements')

const driver = new webdriver.Builder().forBrowser('chrome').build();
const page = WDE.Page.create(driver)
const driver = WDE(new webdriver.Builder().forBrowser('chrome').build())

// for the most part WDElements behave like native selenium WebElements
// the true power comes from primitives and extensibility (see advanced usage)
page.find('#app')
driver.find('#app')
.then((app) => app.find('h1'))
.then((h1) => h1.getText())
```
Expand Down
82 changes: 82 additions & 0 deletions lib/driver.js
@@ -0,0 +1,82 @@
'use strict'

/* -----------------------------------------------------------------------------
* dependencies
* -------------------------------------------------------------------------- */

// 3rd party
const wd = require('selenium-webdriver')

// lib
const Finder = require('./finder')

/* -----------------------------------------------------------------------------
* private
* -------------------------------------------------------------------------- */

const elementWaitMethods = new Set([
'elementIsEnabled',
'elementIsDisabled',
'elementIsSelected',
'elementIsNotSelected',
'elementIsVisible',
'elementIsNotVisible',
'stalenessOf',
'elementTextContains',
'elementTextIs',
'elementTextMatches',
'elementLocated'
])

/* -----------------------------------------------------------------------------
* El
* -------------------------------------------------------------------------- */

module.exports = class Driver extends Finder {

get elementWaitMethods () {
return elementWaitMethods
}

constructor (driver) {
super(driver)
this._driver = driver

return this.proxyTo('_driver')
}

/* ---------------------------------------------------------------------------
* actions
* ------------------------------------------------------------------------ */

refresh () {
return this._driver.navigate().refresh()
}

/* ---------------------------------------------------------------------------
* wait
* ------------------------------------------------------------------------ */

wait () {
return this._driver.wait.apply(this._driver, arguments).then((result) => {
return result instanceof wd.WebElement
? this._createFromEl(result)
: result
})
}

waitUntil (name, ...args) {
// if a wd el is passed -> parse it into a raw WebElement
if (args[0] && args[0]._el) {
args[0] = args[0]._el
}

if (!this.elementWaitMethods.has(name) || args[0] instanceof wd.WebElement) {
return this.wait(wd.until[name](...args))
}

return this.find(args[0])
.then((el) => this.waitUntil(name, ...[el].concat(args.slice(1))))
}

}
139 changes: 139 additions & 0 deletions lib/finder.js
@@ -0,0 +1,139 @@
'use strict'

/* -----------------------------------------------------------------------------
* dependencies
* -------------------------------------------------------------------------- */

// 3rd party
const _ = require('lodash')
const Promise = require('bluebird')

/* -----------------------------------------------------------------------------
* El
* -------------------------------------------------------------------------- */

module.exports = class Finder {

static lookupUsing (primitives) {
this.primitives = primitives
}

static createUsing (driver) {
this.driver = driver
}

get children () {
return {}
}

constructor (root) {
this.root = root
this.primitives = this.constructor.primitives
this.driver = this.constructor.driver
this.definitions = _.mapValues(this.children, this._parseChild)

// alias
this.find = this.findElement
this.findAll = this.findElements
}

proxyTo (key) {
return new Proxy(this, {
get: (target, name) => name in target ? target[name] : target[key][name]
})
}

/* ---------------------------------------------------------------------------
* find methods
* ------------------------------------------------------------------------ */

findElement (_by, _options) {
const { by, options } = this._normalizeFindArgs(_by, _options)

return this.root.findElement(this._normalizeBy(by))
.then((el) => this._createFromEl(el, options))
}

findElements (_by, _options) {
const { by, options } = this._normalizeFindArgs(_by, _options)

return this.root.findElements(this._normalizeBy(by))
.then((els) => Promise.map(els, (el) => this._createFromEl(el, options)))
}

findLast (by, options) {
return this.findElements(by, options)
.then((els) => _.last(els))
}

findNth (by, n, options) {
return this.findElements(by, options)
.then((els) => els[n])
}

/* ---------------------------------------------------------------------------
* utils
* ------------------------------------------------------------------------ */

_normalizeFindArgs (by, options) {
if (_.isString(by) && by.startsWith('@child.') && this.definitions) {
return this._normalizeFromDefinition(by, options)
}

return {
by: this._normalizeBy(by),
options: options
}
}

_normalizeFromDefinition (by, options) {
const definition = this.definitions[by.split('@child.')[1]]

if (_.isUndefined(definition)) {
throw new Error(`No child found by the name: ${by}`)
}

return {
by: this._normalizeBy(definition.by),
options: _.assign({}, _.omit(definition, 'by'), options)
}
}

_normalizeBy (by) {
if (_.isFunction(by) || _.isObject(by)) {
return by
} else if (_.isString(by)) {
return { css: by }
} else {
throw new Error(`Unreconized lookup value`)
}
}

_createFromEl (el, options = {}) {
if (options.Class) {
return Promise.resolve(new options.Class(el, options))
}

return this.primitives.findByEl(el)
.then((Class) => new Class(el, options))
}

_parseChild (raw) {
let definition

if (_.isObject(raw)) {
definition = raw
} else if (_.isString(raw)) {
definition = { by: raw }
}

if (!definition) {
throw new Error(`Definition type not recognized`)
} else if (!definition.by) {
throw new Error(`Definition must contain lookup property`)
}

return definition
}

}
52 changes: 31 additions & 21 deletions lib/index.js
Expand Up @@ -6,36 +6,46 @@

// 3rd party
const _ = require('lodash')
const wd = require('selenium-webdriver')

// lib
const Primitives = require('./primitives')
const El = require('./primitives/el')
const Finder = require('./finder')
const Driver = require('./driver')

/* -----------------------------------------------------------------------------
* WDElements
* -------------------------------------------------------------------------- */

// add all of our base internal primitives
const primitives = new Primitives()
primitives.load('./primitives')

// config method - lets you set the webdriver module to use - If not specified,
// will utilize module version packaged with WDElement (using mismatched
// versions may have undesired side effects). Setting the driver is recommended
module.exports = function (wd) {
El.configure(wd || require('selenium-webdriver'), primitives)
primitives.load('./primitives')

// Return proxy of WDElemts that implements a dynamic getter to return
// an interface to interact with primitives
return new Proxy({}, {
get: function (target, name) {
if (_.isUndefined(primitives[name])) {
return primitives.get(name)
}

return _.isFunction(primitives[name])
? primitives[name].bind(primitives)
: primitives[name]
Finder.lookupUsing(primitives)

// Return proxy of WDElemts that implements a dynamic getter to return
// an interface to interact with primitives
module.exports = new Proxy(function (_driver) {
const isWrapper = _driver instanceof Driver
const isSupported = _driver instanceof wd.WebDriver || isWrapper

if (!isSupported) {
throw new Error(`The specified driver is using an unsupported "selenium-webdriver" version.`)
}

// protect against wrapping driver multiple times
const driver = isWrapper ? _driver : new Driver(_driver)

Finder.createUsing(driver)
return driver
}, {
get: function (target, name) {
if (_.isUndefined(primitives[name])) {
return primitives.get(name)
}
})
}

return _.isFunction(primitives[name])
? primitives[name].bind(primitives)
: primitives[name]
}
})

0 comments on commit cac5f25

Please sign in to comment.