diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fc9bb6c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +1.0.3 +----- +* Initial release diff --git a/lib/assert/element.js b/lib/assert/element.js index 3b763f6..4f412c9 100644 --- a/lib/assert/element.js +++ b/lib/assert/element.js @@ -9,11 +9,6 @@ function isTextOrRegexp(textOrRegExp) { return _.isString(textOrRegExp) || _.isRegExp(textOrRegExp); } -exports._getElementWithProperty = function _getElementWithProperty(selector, property) { - var element = this._getElement(selector); - return [ element, element.get(property) ]; -}; - exports._getElement = function _getElement(selector) { var elements = this.driver.getElements(selector); var count = elements.length; @@ -76,15 +71,16 @@ exports.elementHasText = function elementHasText(doc, selector, textOrRegExp) { assert.hasType('elementHasText(selector, textOrRegExp) - requires selector', String, selector); assert.truthy('elementHasText(selector, textOrRegExp) - requires textOrRegExp', isTextOrRegexp(textOrRegExp)); - var result = this._getElementWithProperty(selector, 'text'); + var element = this._getElement(selector); + var actualText = element.get('text'); if (textOrRegExp === '') { - assert.equal(textOrRegExp, result[1]); + assert.equal(textOrRegExp, actualText); } else { - assert.include(doc, textOrRegExp, result[1]); + assert.include(doc, textOrRegExp, actualText); } - return result[0]; + return element; }; exports.elementLacksText = function elementLacksText(doc, selector, textOrRegExp) { @@ -99,10 +95,11 @@ exports.elementLacksText = function elementLacksText(doc, selector, textOrRegExp assert.hasType('elementLacksText(selector, textOrRegExp) - requires selector', String, selector); assert.truthy('elementLacksText(selector, textOrRegExp) - requires textOrRegExp', isTextOrRegexp(textOrRegExp)); - var result = this._getElementWithProperty(selector, 'text'); + var element = this._getElement(selector); + var actualText = element.get('text'); - assert.notInclude(doc, textOrRegExp, result[1]); - return result[0]; + assert.notInclude(doc, textOrRegExp, actualText); + return element; }; exports.elementHasValue = function elementHasValue(doc, selector, textOrRegExp) { @@ -117,15 +114,16 @@ exports.elementHasValue = function elementHasValue(doc, selector, textOrRegExp) assert.hasType('elementHasValue(selector, textOrRegExp) - requires selector', String, selector); assert.truthy('elementHasValue(selector, textOrRegExp) - requires textOrRegExp', isTextOrRegexp(textOrRegExp)); - var result = this._getElementWithProperty(selector, 'value'); + var element = this._getElement(selector); + var actualValue = element.get('value'); if (textOrRegExp === '') { - assert.equal(textOrRegExp, result[1]); + assert.equal(textOrRegExp, actualValue); } else { - assert.include(doc, textOrRegExp, result[1]); + assert.include(doc, textOrRegExp, actualValue); } - return result[0]; + return element; }; exports.elementLacksValue = function elementLacksValue(doc, selector, textOrRegExp) { @@ -140,10 +138,11 @@ exports.elementLacksValue = function elementLacksValue(doc, selector, textOrRegE assert.hasType('elementLacksValue(selector, textOrRegExp) - requires selector', String, selector); assert.truthy('elementLacksValue(selector, textOrRegExp) - requires textOrRegExp', isTextOrRegexp(textOrRegExp)); - var result = this._getElementWithProperty(selector, 'value'); + var element = this._getElement(selector); + var actualValue = element.get('value'); - assert.notInclude(doc, textOrRegExp, result[1]); - return result[0]; + assert.notInclude(doc, textOrRegExp, actualValue); + return element; }; exports.elementIsVisible = function elementIsVisible(selector) { diff --git a/lib/assert/imgLoaded_client.js b/lib/assert/imgLoaded_client.js index fab7a04..35823ee 100644 --- a/lib/assert/imgLoaded_client.js +++ b/lib/assert/imgLoaded_client.js @@ -1,4 +1,3 @@ -/* jshint browser: true */ 'use strict'; // returns true if the image is loaded and decoded, diff --git a/lib/browser/index.js b/lib/browser/index.js index bb4ddcc..e84f313 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -16,6 +16,9 @@ var builtIns = [ ]; function Browser(driver, options) { + var invocation = 'new Browser(driver, targetUrl, commandUrl)'; + assert.hasType(invocation + ' - requires (Object) driver', Object, driver); + this.driver = driver; this.capabilities = driver.capabilities; diff --git a/lib/testium-driver-sync.js b/lib/testium-driver-sync.js index e61897f..9b31b79 100644 --- a/lib/testium-driver-sync.js +++ b/lib/testium-driver-sync.js @@ -1,11 +1,24 @@ 'use strict'; +var path = require('path'); + var WebDriver = require('webdriver-http-sync'); var debug = require('debug')('testium-driver-sync:browser'); +var _ = require('lodash'); var Browser = require('./browser'); var Assertions = require('./assert'); +function applyMixin(obj, mixin) { + debug('Applying mixin to %s', obj.constructor.name, mixin); + var mixinFile = path.resolve(process.cwd(), mixin); + _.extend(obj, require(mixinFile)); +} + +function applyMixins(obj, mixins) { + _.each(mixins, _.partial(applyMixin, obj)); +} + function createDriver(testium) { var config = testium.config; @@ -21,27 +34,13 @@ function createDriver(testium) { }); browser.assert = new Assertions(driver, browser); + applyMixins(browser, config.get('mixins.browser', [])); + applyMixins(browser.assert, config.get('mixins.assert', [])); + // Default to reasonable size. // This fixes some phantomjs element size/position reporting. browser.setPageSize({ height: 768, width: 1024 }); - var skipPriming = false; - var keepCookies = false; - - if (skipPriming) { - debug('Skipping priming load'); - } else { - driver.navigateTo(testium.getInitialUrl()); - debug('Browser was primed'); - } - - if (keepCookies) { - debug('Keeping cookies around'); - } else { - debug('Clearing cookies for clean state'); - browser.clearCookies(); - } - return testium; } diff --git a/package.json b/package.json index dd03fb9..527a945 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,12 @@ "description": "Sync interface for testium", "main": "lib/testium-driver-sync.js", "scripts": { - "test": "eslint lib test && mocha" + "test": "eslint lib test && mocha", + "posttest": "npub verify" }, + "files": [ + "lib" + ], "repository": { "type": "git", "url": "git+https://github.com/testiumjs/testium-driver-sync.git" @@ -29,7 +33,8 @@ "eslint-config-airbnb": "~0.1.0", "mocha": "^2.3.3", "node-static": "~0.7.7", - "testium-core": "^1.1.2", + "npub": "^2.2.0", + "testium-core": "^1.3.0", "testium-example-app": "^1.0.4" }, "dependencies": { diff --git a/test/assert/element.test.js b/test/assert/element.test.js new file mode 100644 index 0000000..1d5e5c0 --- /dev/null +++ b/test/assert/element.test.js @@ -0,0 +1,227 @@ +import assert from 'assertive'; +import { extend, noop } from 'lodash'; + +import ElementMixin from '../../lib/assert/element'; + +describe('assert/element', () => { + describe('#elementHas', () => { + const selector = '.box'; + const text = 'something'; + const element = extend({ + driver: { + getElements() { + return [ { get() { return text; } } ]; + }, + }, + }, ElementMixin); + + describe('Attributes', () => { + const attributesObject = { + text: text, + value: text, + id: /something/, + }; + + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementHasAttributes(undefined, attributesObject)); + }); + + it('fails if selector is not a string', () => { + assert.throws(() => + element.elementHasAttributes(999, attributesObject)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementHasAttributes(selector, attributesObject)); + }); + }); + + describe('Text', () => { + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementHasText(undefined, text)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementHasText(999, text)); + }); + + it('fails if text is undefined', () => { + assert.throws(() => + element.elementHasText(selector, undefined)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementHasText(selector, text)); + }); + }); + + describe('Value', () => { + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementHasValue(undefined, text)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementHasValue(999, text)); + }); + + it('fails if text is undefined', () => { + assert.throws(() => + element.elementHasValue(selector, undefined)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementHasValue(selector, text)); + }); + }); + }); + + describe('#elementLacks', () => { + const selector = '.box'; + const text = 'something'; + const element = extend({ + driver: { + getElements() { return [ { get() { return 'else'; } } ]; }, + }, + }, ElementMixin); + + describe('Text', () => { + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementLacksText(undefined, text)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementLacksText(999, text)); + }); + + it('fails if text is undefined', () => { + assert.throws(() => + element.elementLacksText(selector, undefined)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementLacksText(selector, text)); + }); + }); + + describe('Value', () => { + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementLacksValue(undefined, text)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementLacksValue(999, text)); + }); + + it('fails if text is undefined', () => { + assert.throws(() => + element.elementLacksValue(selector, undefined)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementLacksValue(selector, text)); + }); + }); + }); + + describe('#elementIsVisible', () => { + const element = extend({ + browser: { + getElementWithoutError() { + return { isVisible() { return true; } }; + }, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementIsVisible(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementIsVisible(noop)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementIsVisible('.box')); + }); + }); + + describe('#elementNotVisible', () => { + const element = extend({ + browser: { + getElementWithoutError() { + return { isVisible() { return false; } }; + }, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementNotVisible(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementNotVisible(noop)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementNotVisible('.box')); + }); + }); + + describe('#elementExists', () => { + const element = extend({ + browser: { + getElementWithoutError() { return {}; }, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementExists(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementExists(noop)); + }); + + it('returns the element if all conditions are met', () => { + assert.truthy(element.elementExists('.box')); + }); + }); + + describe('#elementDoesntExist', () => { + const element = extend({ + browser: { + getElementWithoutError() { return null; }, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => + element.elementDoesntExist(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => + element.elementDoesntExist(noop)); + }); + + it('succeeds if all conditions are met', () => { + element.elementDoesntExist('.box'); + }); + }); +}); + diff --git a/test/assert/imgLoaded.test.js b/test/assert/imgLoaded.test.js new file mode 100644 index 0000000..b7e7758 --- /dev/null +++ b/test/assert/imgLoaded.test.js @@ -0,0 +1,24 @@ +import assert from 'assertive'; +import { extend, noop } from 'lodash'; + +import ImgLoadedMixin from '../../lib/assert/imgLoaded'; + +describe('imgLoaded', () => { + const context = extend({ + browser: { + evaluate() { return true; }, + }, + }, ImgLoadedMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => context.imgLoaded(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => context.imgLoaded(noop)); + }); + + it('succeeds if all conditions are met', () => { + context.imgLoaded('.thumb'); + }); +}); diff --git a/test/assert/navigation.test.js b/test/assert/navigation.test.js new file mode 100644 index 0000000..e5e9b51 --- /dev/null +++ b/test/assert/navigation.test.js @@ -0,0 +1,24 @@ +import assert from 'assertive'; +import { extend } from 'lodash'; + +import NavigationMixin from '../../lib/assert/navigation'; + +describe('assert.navigations', () => { + const context = extend({ + browser: { + getStatusCode() { return 200; }, + }, + }, NavigationMixin); + + it('fails if expectedStatus is undefined', () => { + assert.throws(() => context.httpStatus(undefined)); + }); + + it('fails if expectedStatus is not a number', () => { + assert.throws(() => context.httpStatus('200')); + }); + + it('succeeds if expectedStatus is a number', () => { + context.httpStatus(200); + }); +}); diff --git a/test/browser/cookie.test.js b/test/browser/cookie.test.js new file mode 100644 index 0000000..e2262da --- /dev/null +++ b/test/browser/cookie.test.js @@ -0,0 +1,70 @@ +import assert from 'assertive'; +import { extend, noop } from 'lodash'; + +import CookieMixin from '../../lib/browser/cookie'; + +describe('cookie', () => { + describe('#setCookie', () => { + const cookie = extend({ + driver: { + setCookie() {}, + }, + }, CookieMixin); + + it('fails if cookie is undefined', () => { + assert.throws(() => cookie.setCookie(undefined)); + }); + + it('fails if cookie does not contain name', () => { + assert.throws(() => cookie.setCookie({ value: 'chicago' })); + }); + + it('fails if cookie does not contain value', () => { + assert.throws(() => cookie.setCookie({ name: 'division' })); + }); + }); + + describe('#getCookie', () => { + const cookie = extend({ + driver: { + getCookies() { return []; }, + }, + }, CookieMixin); + + it('fails if name is undefined', () => { + assert.throws(() => cookie.getCookie(undefined)); + }); + + it('fails if name is not a String', () => { + assert.throws(() => cookie.getCookie(noop)); + }); + + it('succeeds if name is a String', () => { + cookie.getCookie('division'); + }); + }); + + describe('#getHeader', () => { + const testiumCookie = { + name: '_testium_', + value: 'eyJoZWFkZXJzIjp7InNlcnZlciI6Im5vZGUtc3RhdGljLzAuNy4wIiwiY2FjaGUtY29udHJvbCI6Im1heC1hZ2U9MzYwMCIsImV0YWciOiJcIjE1ODI5ODQtMjUyNi0xMzkxNTI5MDM2MDAwXCIiLCJkYXRlIjoiTW9uLCAwMyBNYXIgMjAxNCAwNDo0MDoxNCBHTVQiLCJsYXN0LW1vZGlmaWVkIjoiVHVlLCAwNCBGZWIgMjAxNCAxNTo1MDozNiBHTVQiLCJjb250ZW50LXR5cGUiOiJ0ZXh0L2h0bWwiLCJjb250ZW50LWxlbmd0aCI6IjI1MjYiLCJjb25uZWN0aW9uIjoia2VlcC1hbGl2ZSIsIkNhY2hlLUNvbnRyb2wiOiJuby1zdG9yZSJ9LCJzdGF0dXNDb2RlIjoyMDB9', + }; + const cookie = extend({ + driver: { + getCookies() { return [ testiumCookie ]; }, + }, + }, CookieMixin); + + it('fails if name is undefined', () => { + assert.throws(() => cookie.getHeader(undefined)); + }); + + it('fails if name is not a String', () => { + assert.throws(() => cookie.getHeader(noop)); + }); + + it('succeeds if name is a String', () => { + cookie.getHeader('user-agent'); + }); + }); +}); diff --git a/test/browser/element.test.js b/test/browser/element.test.js new file mode 100644 index 0000000..d6ba6e3 --- /dev/null +++ b/test/browser/element.test.js @@ -0,0 +1,92 @@ +import assert from 'assertive'; +import { extend, noop } from 'lodash'; + +import ElementMixin from '../../lib/browser/element'; + +describe('element', () => { + describe('#getElement', () => { + const element = extend({ + driver: { + getElement() {}, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => element.getElement(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => element.getElement(noop)); + }); + + it('succeeds if selector is a String', () => { + element.getElement('.box'); + }); + }); + + describe('#waitForElementVisible', () => { + const element = extend({ + driver: { + setElementTimeout() {}, + getElement() { + return { isVisible() { return true; } }; + }, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => element.waitForElementVisible(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => element.waitForElementVisible(noop)); + }); + + it('succeeds if selector is a String', () => { + element.waitForElementVisible('.box'); + }); + }); + + describe('#waitForElementNotVisible', () => { + const element = extend({ + driver: { + setElementTimeout() {}, + getElement() { return { isVisible() { return false; } }; }, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => element.waitForElementNotVisible(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => element.waitForElementNotVisible(noop)); + }); + + it('succeeds if selector is a String', () => { + element.waitForElementNotVisible('.box'); + }); + }); + + describe('#click', () => { + const element = extend({ + driver: { + getElement() { + return { click() {} }; + }, + }, + }, ElementMixin); + + it('fails if selector is undefined', () => { + assert.throws(() => element.click(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => element.click(noop)); + }); + + it('succeeds if selector is a String', () => { + element.click('.box'); + }); + }); +}); diff --git a/test/browser/index.test.js b/test/browser/index.test.js new file mode 100644 index 0000000..28d51ad --- /dev/null +++ b/test/browser/index.test.js @@ -0,0 +1,52 @@ +import assert from 'assertive'; +import { noop } from 'lodash'; + +import Browser from '../../lib/browser'; + +class FakeWebDriver { + navigateTo() {} + getUrl() {} + getCurrentWindowHandle() {} + clearCookies() {} + setPageSize() {} +} + +describe('API', () => { + describe('construction', () => { + const driver = new FakeWebDriver(); + const targetUrl = 'http://127.0.0.1:1000'; + const commandUrl = 'http://127.0.0.1:2000'; + + it('fails if driver is undefined', () => { + assert.throws(() => + new Browser(undefined, {targetUrl, commandUrl})); + }); + + it('fails if driver is not an object', () => { + assert.throws(() => + new Browser('Not a driver', {targetUrl, commandUrl})); + }); + + it('succeeds if all conditions are met', () => + new Browser(driver)); + }); + + describe('#evaluate', () => { + it('fails if clientFunction is undefined', () => { + const err = assert.throws(() => + Browser.prototype.evaluate.call({}, undefined)); + assert.include('requires (Function|String) clientFunction', err.message); + }); + + it('fails if clientFunction is not a Function or String', () => { + const err = assert.throws(() => + Browser.prototype.evaluate.call({}, 999)); + assert.include('requires (Function|String) clientFunction', err.message); + }); + + it('succeeds if all conditions are met', done => { + const dummyContext = { driver: { evaluate() { done(); } } }; + Browser.prototype.evaluate.call(dummyContext, noop); + }); + }); +}); diff --git a/test/browser/input.test.js b/test/browser/input.test.js new file mode 100644 index 0000000..4fbd8b1 --- /dev/null +++ b/test/browser/input.test.js @@ -0,0 +1,76 @@ +import assert from 'assertive'; +import { extend, noop } from 'lodash'; + +import InputMixin from '../../lib/browser/input'; + +describe('input api', () => { + describe('#type', () => { + const element = { type() {} }; + const input = extend({ + getExistingElement() { return element; }, + }, InputMixin); + const selector = '.box'; + const keys = 'puppies'; + + it('fails if selector is undefined', () => { + assert.throws(() => input.type(undefined, keys)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => input.type(noop, keys)); + }); + + it('fails if keys is not defined', () => { + assert.throws(() => input.type(selector)); + }); + + it('succeeds if all conditions are met', () => { + input.type(selector, keys); + }); + }); + + describe('#clear', () => { + const element = { clear() {} }; + const input = extend({ + getExistingElement() { return element; }, + }, InputMixin); + const selector = '.box'; + + it('fails if selector is undefined', () => { + assert.throws(() => input.clear(undefined)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => input.clear(noop)); + }); + + it('succeeds if all conditions are met', () => { + input.clear(selector); + }); + }); + + describe('#clearAndType', () => { + const element = { clear() {}, type() {} }; + const input = extend({ + getExistingElement() { return element; }, + }, InputMixin); + const selector = '.box'; + const keys = 'puppies'; + + it('fails if selector is undefined', () => { + assert.throws(() => input.clearAndType(undefined, keys)); + }); + + it('fails if selector is not a String', () => { + assert.throws(() => input.clearAndType(noop, keys)); + }); + + it('fails if keys is not defined', () => { + assert.throws(() => input.clearAndType(selector)); + }); + + it('succeeds if all conditions are met', () => { + input.clearAndType(selector, keys); + }); + }); +}); diff --git a/test/integration/header.test.js b/test/integration/header.test.js index 8fdd653..e4573e1 100644 --- a/test/integration/header.test.js +++ b/test/integration/header.test.js @@ -5,7 +5,7 @@ describe('header', () => { let browser; before(async () => (browser = await getBrowser())); - describe('can be retireved', () => { + describe('can be retrieved', () => { before(() => { browser.navigateTo('/'); browser.assert.httpStatus(200); diff --git a/test/mini-testium-mocha.js b/test/mini-testium-mocha.js index 3f09361..7ee5db4 100644 --- a/test/mini-testium-mocha.js +++ b/test/mini-testium-mocha.js @@ -1,18 +1,14 @@ // This is a minimal version of `testium-mocha`. // We're trying to avoid cyclic dependencies. -import initTestium from 'testium-core'; -import {once} from 'lodash'; +import TestiumCore from 'testium-core'; import createDriver from '../'; let browser = null; -async function createBrowser() { - const testium = await initTestium().then(createDriver); - browser = testium.browser; +export async function getBrowser() { + browser = await TestiumCore.getBrowser({ driver: createDriver }); return browser; } after(() => browser && browser.close()); - -export const getBrowser = once(createBrowser);