code cleanup, better eslint rules and related test configuration

This commit is contained in:
Peter Snyder 2017-10-23 03:29:59 -05:00
parent 5580148632
commit ae18079a67
35 changed files with 195 additions and 151 deletions

5
.eslintignore Normal file
View file

@ -0,0 +1,5 @@
node_modules/
add-on/lib/third_party/
add-on/lib/standards.js
add-on/config/js/third_party/
test.config.js

View file

@ -7,12 +7,22 @@
"mocha": true
},
"extends": [
"eslint:recommended"
"eslint:recommended"
],
"rules": {
"no-console": "off",
"no-trailing-spaces": "error",
"no-unused-vars": ["error", {"varsIgnorePattern": "ignore"}],
"semi": ["error", "always"]
"semi": ["error", "always"],
"indent": ["error", 4],
"eol-last": "error",
"quotes": ["error", "double"],
"comma-dangle": ["error", "only-multiline"],
"function-paren-newline": ["error"],
"max-len": ["error", {"ignoreStrings": true, "code": 100}],
"require-jsdoc": "error",
"valid-jsdoc": "error",
"prefer-const": "error",
"no-template-curly-in-string": "error",
"no-unused-expressions": "error"
}
}

View file

@ -81,7 +81,8 @@
if (label === "rulesForDomains") {
const matchHostNameBound = domainMatcherLib.matchHostName.bind(undefined, Object.keys(domainRules));
const matchHostName = domainMatcherLib.matchHostName;
const matchHostNameBound = matchHostName.bind(undefined, Object.keys(domainRules));
const rulesForDomains = data.map(matchHostNameBound);
const domainToRuleMapping = {};
@ -113,7 +114,7 @@
const newHeaders = details.requestHeaders.map(function (header) {
if (header.name.indexOf("Cookie") === -1) {
header;
return header;
}
const cookieValue = header.value;

View file

@ -40,7 +40,7 @@
<script src="../lib/defaults.js"></script>
<script src="../lib/standards.js"></script>
<script src="../lib/storage.js"></script>
<script src="js/lib/vue.js"></script>
<script src="js/third_party/vue.js"></script>
<script src="js/state.js"></script>
<script src="js/components/domain-rules.vue.js"></script>
<script src="js/components/web-api-standards.vue.js"></script>

View file

@ -27,10 +27,15 @@
<div class="form-group" v-bind:class="{ 'has-error': errorMessage }">
<label for="newDomainName">Add New Domain Rule</label>
<input class="form-control" v-model.trim="newDomain" placeholder="*.example.org">
<input
class="form-control"
v-model.trim="newDomain"
placeholder="*.example.org">
</div>
<button type="submit" class="btn btn-default btn-block" @click="newDomainSubmitted">Add Rule</button>
<button type="submit"
class="btn btn-default btn-block"
@click="newDomainSubmitted">Add Rule</button>
</div>
`,
data: function () {

View file

@ -10,7 +10,7 @@
const standardsCookieName = constants.cookieName;
const doc = window.document;
const script = doc.createElement('script');
const script = doc.createElement("script");
const rootElm = doc.head || doc.documentElement;
// First see if we can read the standards to block out of the cookie

View file

@ -15,12 +15,12 @@
* The `standardsToBlock` array must be a subset of all the standards
* documented in data/standards.
*
* @param array standardsToBlock
* @param {array} standardsToBlock
* An array of strings, each a standard that should be blocked.
* @param bool shouldLog
* @param {boolean} shouldLog
* Whether logging should be enabled.
*
* @return string
* @return {string}
* A cookie safe string encoding the above values.
*/
const toCookieValue = function (standardsToBlock, shouldLog) {
@ -45,10 +45,10 @@
* standard names, and two, a boolean flag of whether the logging option
* is enabled.
*
* @param string data
* @param {string} data
* A string created from `toCookieValue`
*
* @return [array, bool]
* @return {[array, bool]}
* An array of strings of standard names (representing standards to
* block), and a boolean describing whether to log blocking
* behavior.

View file

@ -17,7 +17,7 @@ window.WEB_API_MANAGER.defaults.lite = [
"WebRTC 1.0: Real-time Communication Between Browser",
"WebGL Specification",
"Geometry Interfaces Module Level 1",
"Web Notifications",
"Web Notifications"
];
window.WEB_API_MANAGER.defaults.conservative = [
@ -35,7 +35,7 @@ window.WEB_API_MANAGER.defaults.conservative = [
"UI Events Specification",
"WebGL Specification",
"Web Audio API",
"Scalable Vector Graphics (SVG) 1.1 (Second Edition)",
"Scalable Vector Graphics (SVG) 1.1 (Second Edition)"
];
window.WEB_API_MANAGER.defaults.aggressive = window.WEB_API_MANAGER.defaults.conservative.concat([

View file

@ -6,11 +6,11 @@
const matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
const escapeStringRegexp = function (aString) {
if (typeof aString !== 'string') {
throw new TypeError('Expected a string');
if (typeof aString !== "string") {
throw new TypeError("Expected a string");
}
return aString.replace(matchOperatorsRe, '\\$&');
return aString.replace(matchOperatorsRe, "\\$&");
};
// From https://www.npmjs.com/package/matcher
@ -24,19 +24,19 @@
return reCache.get(cacheKey);
}
const negated = pattern[0] === '!';
const negated = pattern[0] === "!";
if (negated) {
pattern = pattern.slice(1);
}
pattern = escapeStringRegexp(pattern).replace(/\\\*/g, '.*');
pattern = escapeStringRegexp(pattern).replace(/\\\*/g, ".*");
if (negated && shouldNegate) {
pattern = `(?!${pattern})`;
}
const re = new RegExp(`^${pattern}$`, 'i');
const re = new RegExp(`^${pattern}$`, "i");
re.negated = negated;
reCache.set(cacheKey, re);
@ -61,10 +61,23 @@
return prev;
};
const matchHostName = function (domainRegExes, hostName) {
// of the URL being requested.
/**
* Returns the match patern that matches given host name.
*
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns
*
* @param {array} matchPatterns
* An array of strings, each describing a match patern.
* @param {string} hostName
* A string depicting a host name.
*
* @return {string|undefined}
* A match patern string that matches the hostname, or undefined if no
* patterns match.
*/
const matchHostName = function (matchPatterns, hostName) {
const matchingUrlReduceFunctionBound = matchingUrlReduceFunction.bind(undefined, hostName);
const matchingPattern = domainRegExes
const matchingPattern = matchPatterns
.filter((aRule) => aRule !== defaultKey)
.sort()
.reduce(matchingUrlReduceFunctionBound, undefined);
@ -72,9 +85,23 @@
return matchingPattern || undefined;
};
const matchUrl = function (domainRegExes, url) {
/**
* Returns the match patern that matches the host of a given url.
*
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns
*
* @param {array} matchPatterns
* An array of strings, each describing a match patern.
* @param {string} url
* A string depicting a url.
*
* @return {string|undefined}
* A match patern string that matches the host portion of the given
* url, or undefined if no patterns match.
*/
const matchUrl = function (matchPatterns, url) {
const hostName = extractHostNameFromUrl(url);
return matchHostName(domainRegExes, hostName);
return matchHostName(matchPatterns, hostName);
};
window.WEB_API_MANAGER.domainMatcherLib = {

View file

@ -16,10 +16,10 @@
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest/HttpHeaders
* @see https://w3c.github.io/webappsec-csp/
*
* @param object header
* @param {object} header
* An object describing a HTTP header
*
* @return boolean
* @return {boolean}
* true if the given object depicts a CSP policy with the above stated
* properties, and false in all other cases.
*/
@ -63,13 +63,13 @@
* @see https://w3c.github.io/webappsec-csp/#strict-dynamic-usage
* @see https://w3c.github.io/webappsec-csp/#grammardef-hash-source
*
* @param string cspInstruction
* @param {string} cspInstruction
* The value of a HTTP header defining a CSP instruction.
* @param string scriptHash
* @param {string} scriptHash
* A hash value, in the form of "sha256-<some hash>", that is a valid
* hash source description.
*
* @return string|false
* @return {string|false}
* Returns false if the CSP instruction looks malformed (ie we
* couldn't find either a "script-src" or "default-src" section),
* otherwise, a new value CSP instruction with the given hash allowed.

View file

@ -4,7 +4,13 @@
"use strict";
window.WEB_API_MANAGER = {
constants: {
// The name of the cookie that will be used to push domain
// configuration information into pages.
cookieName: "_wamtcstandards",
// The value in the packed array of options thats used to
// include the shouldLog option in the in bitfield encoded in
// the above cookie.
shouldLogKey: "shouldLogKey"
}
};

View file

@ -18,10 +18,19 @@
const bucketSize = 8;
/**
* Encodes a buffer (such as a Uint8Array) to a base64 encoded string.
*
* @param {ArrayBuffer} buf
* A buffer of binary data.
*
* @return {string}
* A base64 encoded string.
*/
const bufferToBase64 = function (buf) {
const binstr = Array.prototype.map.call(buf, function (ch) {
return String.fromCharCode(ch);
}).join('');
}).join("");
return window.btoa(binstr);
};
@ -61,12 +70,12 @@
*
* This function is the inverse of the `unpack` function from this module.
*
* @param array options
* @param {array} options
* An array of all possible options that might need to be encoded.
* @param array selected
* @param {array} selected
* An array containing zero or more elements from option.
*
* @return string
* @return {string}
* A base64 encoded string, which encodes which elements in `options`
* were in the provided `selected` array.
*/
@ -83,11 +92,11 @@
for (i = 0; i < numBuckets; i += 1) {
let bitfield = 0;
let currentBucket = binnedOptions[i];
const currentBucket = binnedOptions[i];
for (j = 0; j < currentBucket.length; j += 1) {
let currentOption = currentBucket[j];
const currentOption = currentBucket[j];
if (selected.indexOf(currentOption) !== -1) {
bitfield |= 1 << j;
}
@ -108,13 +117,13 @@
*
* This function is the inverse of the `pack` function from this module.
*
* @param array options
* @param {array} options
* An array of all possible options that might need to be encoded.
* @param string data
* @param {string} data
* A base64 encoded string, generated from the `pack` function in
* this module.
*
* @return array
* @return {array}
* An array of zero or more elements, each of which will be in the
* options array.
*/
@ -131,12 +140,12 @@
let i, j;
for (i = 0; i < bitFields.length; i += 1) {
let currentBitField = bitFields[i];
let currentOptionsBin = binnedOptions[i];
const currentBitField = bitFields[i];
const currentOptionsBin = binnedOptions[i];
for (j = 0; j < bucketSize; j += 1) {
if (currentBitField & (1 << j)) {
let currentOption = currentOptionsBin[j];
const currentOption = currentOptionsBin[j];
result.push(currentOption);
}
}

View file

@ -85,8 +85,7 @@
}
};
let blockingProxy;
blockingProxy = new Proxy(defaultFunction, {
const blockingProxy = new Proxy(defaultFunction, {
get: function (ignore, property) {
logKeyPath();
@ -189,16 +188,16 @@
* but with the window.WEB_API_MANAGER_PAGE object set up
* correctly to block the desired functions.
*
* @param object standards
* @param {object} standards
* A mapping of standard names to information about those standards.
* The structure of this object should match whats in data/standards.js
* @param array standardNamesToBlock
* @param {array} standardNamesToBlock
* An array of strings, which must be a subset of the keys of the
* standards object.
* @param bool shouldLog
* @param {boolean} shouldLog
* Whether to log the behavior of the blocking proxy.
*
* @return [string, hash]
* @return {[string, string]}
* Returns an array containing two values. First, JavaScript code
* that instruments the DOM of page's its injected into to render the
* standardNamesToBlock standards un-reachable, and second, a

File diff suppressed because one or more lines are too long

View file

@ -37,8 +37,8 @@
{
"matches": ["*://*/*"],
"js": [
"lib/vendor/js.cookie.js",
"lib/vendor/sjcl.js",
"lib/third_party/js.cookie.js",
"lib/third_party/sjcl.js",
"lib/init.js",
"lib/standards.js",
"lib/pack.js",
@ -52,8 +52,8 @@
],
"background": {
"scripts": [
"lib/vendor/URI.js",
"lib/vendor/sjcl.js",
"lib/third_party/URI.js",
"lib/third_party/sjcl.js",
"lib/init.js",
"lib/standards.js",
"lib/pack.js",

View file

@ -9,7 +9,7 @@
const configureButton = doc.getElementById("config-page-link");
const listGroupElm = doc.querySelector("ul.list-group");
const addDomainRuleToListElm = function (hostToRuleMapping, listElm, aHostName) {
const addRuleToList = function (hostToRuleMapping, listElm, aHostName) {
const domainRule = hostToRuleMapping[aHostName];
@ -53,9 +53,9 @@
doc.body.className = "loaded";
const domainNames = Object.keys(response);
const addDomainRuleToListElmBound = addDomainRuleToListElm.bind(undefined, response, listGroupElm);
const addRuleToListBound = addRuleToList.bind(undefined, response, listGroupElm);
domainNames.forEach(addDomainRuleToListElmBound);
domainNames.forEach(addRuleToListBound);
});
}
);

View file

@ -1,17 +1,7 @@
const gulp = require("gulp");
const fs = require("fs");
gulp.task('default', function () {
const isLineAComment = function (aLine) {
const lineStartsWithComment = (
aLine.indexOf("// ") === 0 ||
aLine.indexOf("/*") === 0 ||
aLine.indexOf(" */") === 0 ||
aLine.indexOf(" * ") === 0
);
return lineStartsWithComment;
};
gulp.task("default", function () {
const builtScriptComment = "/** This file is automatically generated. **/\n";
const standardsDefDir = "data/standards";
@ -26,14 +16,20 @@ gulp.task('default', function () {
const fileContents = fs.readFileSync(standardsDefDir + "/" + next, {encoding: "utf8"});
const standardContents = JSON.parse(fileContents);
const nameParts = [standardContents.info.name, standardContents.info.subsection_name].filter(part => !!part);
const stdName = standardContents.info.name;
const stdSubName = standardContents.info.subsection_name;
const nameParts = [stdName, stdSubName].filter(part => !!part);
const standardIdentifier = nameParts.join(": ").trim();
standardContents.info.identifier = standardIdentifier;
prev[standardIdentifier] = standardContents;
return prev;
}, {});
const renderedStandardsModule = builtScriptComment + `window.WEB_API_MANAGER.standards = ${JSON.stringify(combinedStandards)};`;
let renderedStandardsModule = builtScriptComment + "\n";
renderedStandardsModule += "window.WEB_API_MANAGER.standards = ";
renderedStandardsModule += JSON.stringify(combinedStandards) + ";";
fs.writeFileSync("add-on/lib/standards.js", renderedStandardsModule);
});

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "web-api-manager",
"version": "0.9.2",
"version": "0.9.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -24,7 +24,8 @@
"clean": "rm -Rf dist/",
"bundle": "gulp && web-ext -s add-on -a dist build --overwrite-dest",
"firefox": "web-ext -s add-on run",
"test:lint": "node_modules/eslint/bin/eslint.js test/functional/*.js test/functional/**/*.js add-on/background_scripts/*.js add-on/config/js/*.js add-on/config/js/components/*.js add-on/lib/*.js add-on/content_scripts/*.js",
"test:lint": "node_modules/eslint/bin/eslint.js .",
"test:lint:fix": "node_modules/eslint/bin/eslint.js --fix .",
"test:func": "npm run clean; npm run bundle && ln -s `ls dist/` dist/webapi_manager.zip && node_modules/mocha/bin/mocha test/functional/*.js",
"test": "npm run test:lint && npm run test:func"
},

View file

@ -14,7 +14,16 @@ describe("Content-Security-Protocol tests", function () {
const [server, testUrl] = testServer.start(function (headers) {
// Add the CSP header to every request
headers['Content-Security-Protocol'] = "default-src https: data: 'unsafe-inline' 'unsafe-eval'; child-src https: data: blob:; connect-src https: data: blob:; font-src https: data:; img-src https: data: blob:; media-src https: data: blob:; object-src https:; script-src https: data: blob: 'unsafe-inline' 'unsafe-eval'; style-src https: 'unsafe-inline'; block-all-mixed-content; upgrade-insecure-requests; report-uri https://capture.condenastdigital.com/csp/pitchfork;";
const pitchforkCSP = [
"default-src https: data: 'unsafe-inline' 'unsafe-eval';",
"child-src https: data: blob:; connect-src https: data: blob:;",
"font-src https: data:; img-src https: data: blob:;",
"media-src https: data: blob:;",
"object-src https:;",
"script-src https: data: blob: 'unsafe-inline' 'unsafe-eval';",
"style-src https: 'unsafe-inline';",
];
headers["Content-Security-Protocol"] = pitchforkCSP.join(" ");
});
const svgTestScript = injected.testSVGTestScript();

View file

@ -21,12 +21,12 @@ module.exports.temporaryAddOnInstallScript = (function () {
const funcToInject = function () {
const {Components, AddonManager} = window;
let fileUtils = Components.utils.import('resource://gre/modules/FileUtils.jsm');
let FileUtils = fileUtils.FileUtils;
let callback = arguments[arguments.length - 1];
Components.utils.import('resource://gre/modules/AddonManager.jsm');
const fileUtils = Components.utils.import("resource://gre/modules/FileUtils.jsm");
const FileUtils = fileUtils.FileUtils;
const callback = arguments[arguments.length - 1];
Components.utils.import("resource://gre/modules/AddonManager.jsm");
let listener = {
const listener = {
onInstallEnded: function(install, addon) {
callback([addon.id, 0]);
},
@ -39,7 +39,7 @@ module.exports.temporaryAddOnInstallScript = (function () {
}
};
let file = new FileUtils.File(arguments[0]);
const file = new FileUtils.File(arguments[0]);
AddonManager.addAddonListener(listener);
AddonManager.installTemporaryAddon(file).catch(error => {
@ -73,7 +73,7 @@ module.exports.setStandardsAsBlockedScript = (function () {
const funcSource = stripFuncFromSource(funcToInject.toString());
return function (standardsToBlock) {
return funcSource.replace('"###REPLACE###"', JSON.stringify(standardsToBlock));
return funcSource.replace("\"###REPLACE###\"", JSON.stringify(standardsToBlock));
};
}());

View file

@ -20,7 +20,7 @@ module.exports.start = function (callback) {
const httpServer = http.createServer(function (req, res) {
let headers = {"Content-Type": "text/html; charset=utf-8"};
const headers = {"Content-Type": "text/html; charset=utf-8"};
if (callback !== undefined) {
callback(headers);

View file

@ -49,9 +49,7 @@ module.exports.promiseSetFormAndSubmit = function (driver, values) {
module.exports.promiseAddonButton = function (driver) {
driver.setContext(Context.CHROME);
return driver.wait(until.elementLocated(
by.css("[tooltiptext='WebAPI Manager']")
), 2000);
return driver.wait(until.elementLocated(by.css("[tooltiptext='WebAPI Manager']")), 2000);
};
module.exports.promiseExtensionConfigPage = function (driver) {
@ -68,9 +66,7 @@ module.exports.promiseExtensionConfigPage = function (driver) {
module.exports.promiseAddonConfigButton = function (driver) {
driver.setContext(Context.CHROME);
return driver.wait(until.elementLocated(
by.id("config-page-link")
), 2000);
return driver.wait(until.elementLocated(by.id("config-page-link")), 2000);
};
module.exports.promiseSetBlockingRules = function (driver, standardsToBlock) {
@ -83,18 +79,18 @@ module.exports.promiseSetBlockingRules = function (driver, standardsToBlock) {
module.exports.promiseGetDriver = function () {
let driver = new webdriver.Builder()
.forBrowser('firefox')
const driver = new webdriver.Builder()
.forBrowser("firefox")
.build();
driver.setContext(Context.CHROME);
let fileLocation = path.join(process.cwd(), "dist", "webapi_manager.zip");
const fileLocation = path.join(process.cwd(), "dist", "webapi_manager.zip");
// This manually installs the add-on as a temporary add-on.
// Hopefully selenium/geckodriver will get a way to do this soon:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1298025
let installAddOnPromise = driver.executeAsyncScript(
const installAddOnPromise = driver.executeAsyncScript(
injectedScripts.temporaryAddOnInstallScript(),
fileLocation
);

View file

@ -5,7 +5,7 @@ let testParams;
try {
testParams = require("../../test.config.js");
} catch (e) {
throw "Unable to load a test.config.js module in the project root. Copy test.config.example.js to test.config.js and try again";
throw "Unable to load a test.config.js module in the project root. Copy test.config.example.js to test.config.js.";
}
const injected = require("./lib/injected");
const webdriver = require("selenium-webdriver");
@ -42,15 +42,11 @@ describe("Logging into popular sites", function () {
return driverReference.get("https://github.com/login");
})
.then(function () {
return driverReference.wait(until.elementLocated(
by.name("password")
), 2000);
return driverReference.wait(until.elementLocated(by.name("password")), 2000);
})
.then(() => utils.promiseSetFormAndSubmit(driverReference, formValues))
.then(function () {
return driverReference.wait(until.elementLocated(
by.css("body.logged-in")
), 2000);
return driverReference.wait(until.elementLocated(by.css("body.logged-in")), 2000);
})
.then(function () {
driverReference.quit();
@ -75,15 +71,11 @@ describe("Logging into popular sites", function () {
})
.then(() => driverReference.get("https://github.com/login"))
.then(function () {
return driverReference.wait(until.elementLocated(
by.name("password")
), 2000);
return driverReference.wait(until.elementLocated(by.name("password")), 2000);
})
.then(() => utils.promiseSetFormAndSubmit(driverReference, formValues))
.then(function () {
return driverReference.wait(until.elementLocated(
by.css("body.logged-in")
), 2000);
return driverReference.wait(until.elementLocated(by.css("body.logged-in")), 2000);
})
.then(() => driverReference.executeAsyncScript(svgTestScript))
.then(function () {
@ -123,15 +115,11 @@ describe("Logging into popular sites", function () {
return driverReference.get("https://www.facebook.com/");
})
.then(function () {
return driverReference.wait(until.elementsLocated(
by.name("email")
), 5000);
return driverReference.wait(until.elementsLocated(by.name("email")), 5000);
})
.then(() => utils.promiseSetFormAndSubmit(driverReference, formValues))
.then(function () {
return driverReference.wait(until.elementLocated(
by.css("div[data-click='profile_icon']")
), 10000);
return driverReference.wait(until.elementLocated(by.css("div[data-click='profile_icon']")), 10000);
})
.then(function () {
driverReference.quit();
@ -158,47 +146,38 @@ describe("Logging into popular sites", function () {
it("Log in", function (done) {
let driverReference;
let driver;
utils.promiseGetDriver()
.then(function (driver) {
driverReference = driver;
return driverReference.get("https://www.youtube.com");
.then(function (testDriver) {
driver = testDriver;
return driver.get("https://www.youtube.com");
})
.then(function () {
return driverReference.wait(until.elementsLocated(
by.css("#buttons ytd-button-renderer a")
), 5000);
return driver.wait(until.elementsLocated(by.css("#buttons ytd-button-renderer a")), 5000);
})
.then(anchors => anchors[anchors.length - 1].click())
.then(() => utils.pause(2000))
.then(function () {
return driverReference.wait(until.elementLocated(
by.name("identifier")
), 5000);
return driver.wait(until.elementLocated(by.name("identifier")), 5000);
})
.then(identifierElm => driverReference.wait(until.elementIsVisible(identifierElm)))
.then(identifierElm => identifierElm.sendKeys(testParams.google.username, keys.ENTER))
.then(idElm => driver.wait(until.elementIsVisible(idElm)))
.then(idElm => idElm.sendKeys(testParams.google.username, keys.ENTER))
.then(() => utils.pause(2000))
.then(function () {
return driverReference.wait(until.elementLocated(
by.name("password")
), 5000);
return driver.wait(until.elementLocated(by.name("password")), 5000);
})
.then(passwordElm => driverReference.wait(until.elementIsVisible(passwordElm)))
.then(passwordElm => driver.wait(until.elementIsVisible(passwordElm)))
.then(passwordElm => passwordElm.sendKeys(testParams.google.password, keys.ENTER))
.then(function () {
return driverReference.wait(until.elementLocated(
by.css("ytd-app")
), 10000);
return driver.wait(until.elementLocated(by.css("ytd-app")), 10000);
})
.then(function () {
driverReference.quit();
driver.quit();
done();
})
.catch(function (e) {
driverReference.quit();
console.log(e);
.catch(function () {
driver.quit();
done(new Error("Was not able to log in"));
});
});