sw-testing-helpers
Version:
A set of helper files used to test Propel and sw-toolbox.
300 lines (272 loc) • 9.49 kB
JavaScript
/**
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
;
/* eslint-env node, browser, worker, mocha */
/**
* The results from a set of Mocha tests
*
* @typedef {Object} MochaTestResults
* @property {Array<MochaTestResult>} passed Tests that have passed
* @property {Array<MochaTestResult>} failed Tests that have failed
*/
/**
* @typedef {Object} MochaTestResult
* @property {String} parentTitle Title of the parent test suite
* @property {String} title Title of the test case
* @property {String} state State of the test - 'passed' or 'failed'
* @property {String} errMessage This is defined if the test threw an error
*/
/**
* <p>This class is a helper that will run Mocha tests and offers consistent
* error reporting.</p>
*
* @example <caption>Usage in Browser Window</caption>
* <script src="/node_modules/sw-testing-helpers/browser/mocha-utils.js">
* </script>
* <script>
* console.log(window.goog.mochaUtils);
* </script>
*
* @example <caption>Usage in Service Worker</caption>
* importScripts('/node_modules/sw-testing-helpers/browser/mocha-utils.js');
* console.log(self.goog.mochaUtils);
*
* @example <caption>Usage in Node</caption>
* const mochaUtils = require('sw-testing-helpers').mochaUtils;
* console.log(mochaUtils);
*/
class MochaUtils {
/**
* Start Mocha tests in a browser, checking for leaks and
* collect passed / failed results, resolving the promise with the results
* in a friendly format.
*
* @return {Promise<MochaTestResults>} The resutls from the Mocha test
*/
startInBrowserMochaTests() {
return new Promise(resolve => {
let topLevelTitle = null;
let rawTestData = [];
let passedTests = [];
let failedTests = [];
mocha.checkLeaks();
const runResults = mocha.run();
if (runResults.total === 0) {
resolve({
topLevelTitle: topLevelTitle,
testResults: {
raw: rawTestData,
passed: passedTests,
failed: failedTests
}
});
return;
}
// pass, fail and end events allow up to capture results and
// determine when to publish test results
runResults.on('pass', test => {
const parseableTest = this._getFriendlyTestResult(test);
rawTestData.push(parseableTest);
passedTests.push(parseableTest);
})
.on('fail', test => {
const parseableTest = this._getFriendlyTestResult(test);
rawTestData.push(parseableTest);
failedTests.push(parseableTest);
})
.on('end', () => {
resolve({
topLevelTitle: topLevelTitle,
testResults: {
raw: rawTestData,
passed: passedTests,
failed: failedTests
}
});
});
// No tests so end won't be called
if (mocha.suite.suites.length === 0) {
resolve({
topLevelTitle: topLevelTitle,
testResults: {
raw: rawTestData,
passed: passedTests,
failed: failedTests
}
});
} else {
topLevelTitle = mocha.suite.suites[0].title;
}
});
}
/**
* <p>Register a service worker and send a message to the service
* worker to start running Mocha tests in the worker.</p>
*
* <p>It's expected that the service worker will import `mocha-utils.js` to
* make this work seamlessly.</p>
*
* <p>This requires `window-utils.js` to be added to the page.</p>
*
* @param {String} swPath The path to a service worker
* @return {Promise.<MochaTestResults>} Promise resolves when the tests
* in the service worker have completed, returned the results.
*/
registerServiceWorkerMochaTests(swPath) {
const sendMessage = (swController, testName, timeout) => {
return new Promise((resolve, reject) => {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
resolve(event.data);
};
swController.postMessage(testName,
[messageChannel.port2]);
if (timeout) {
setTimeout(() => reject(new Error('Message Timeout')), timeout);
}
});
};
return window.goog.swUtils.activateSW(swPath)
.then(iframe => {
return iframe.contentWindow.navigator.serviceWorker.ready
.then(registration => {
return registration.active;
})
.then(sw => {
return sendMessage(sw, 'ready-check', 400)
.then(msgResponse => {
if (!msgResponse.ready) {
return Promise.reject();
}
return sw;
})
.catch(() => {
throw new Error('Service worker failed to respond to the ready ' +
'check. Have you imported browser/mocha-utils.js in the SW?');
});
})
.then(sw => {
return sendMessage(sw, 'start-tests');
})
.then(msgResponse => {
if (!msgResponse.testResults) {
throw new Error('Unexpected test result: ' + msgResponse);
} else if (msgResponse.testResults.raw.length > 0 && describe) {
describe('[SW Internal Results] ' + msgResponse.topLevelTitle,
function() {
msgResponse.testResults.raw.forEach((testResult) => {
it(testResult.title, function() {
if (testResult.state !== 'passed') {
const error = new Error(testResult.errMessage);
error.stack = testResult.stack;
throw error;
}
});
});
}
);
}
});
});
}
/**
* <p>Print the User Agent of the browser, load the page
* the Mocha tests are in and wait for the results.</p>
*
* @param {String} browserName Name to be printed with the browsers UserAgent
* @param {WebDriver} driver Instance of a {@link http://selenium.googlecode.com/git/docs/api/javascript/class_webdriver_WebDriver.html | WebDriver}
* @param {String} url URL of that has mocha tests.
* @return {Promise<MochaTestResults>} Returns the results from the
* browsers tests
*/
startWebDriverMochaTests(browserName, driver, url) {
return driver.get(url)
.then(() => {
// We get webdriver to wait until window.testsuite.testResults is defined.
// This is set in the in browser mocha tests when the tests have finished
// successfully
return driver.wait(function() {
return driver.executeScript(function() {
return (typeof window.testsuite !== 'undefined') &&
(typeof window.testsuite.testResults !== 'undefined');
});
});
})
.then(() => {
// This simply retrieves the test results from the inbrowser mocha tests
return driver.executeScript(function() {
return window.testsuite.testResults;
});
});
}
/**
* @private
* @param {Object} testResult The Mocha test result to be filtered.
* @return {Object} A friendlier interpretation of the mocha test result.
*/
_getFriendlyTestResult(testResult) {
const friendlyResult = {
parentTitle: testResult.parent.title,
title: testResult.title,
state: testResult.state,
};
if (testResult.err) {
friendlyResult.errMessage = testResult.err.message;
friendlyResult.stack = testResult.err.stack;
}
return friendlyResult;
}
/**
* @param {object} testResults Tests to convert to a friendly message
* @return {String} The results in a pretty string.
*/
prettyPrintResults(testResults) {
let prettyResultsString = ``;
/* eslint-disable no-console */
testResults.raw.forEach((testResult) => {
let testResultString = ``;
switch(testResult.state) {
case 'passed':
testResultString += '✔️ [Passed] ';
break;
case 'failed':
testResultString += '❌ [Failed] ';
break;
default:
testResultString += '❓ [Unknown] ';
break;
}
testResultString += `${testResult.parentTitle} > ` +
`${testResult.title}\n`;
if (testResult.state === 'failed') {
const pad = ' ';
const indentedStack = testResult.stack.split('\n').join(`\n${pad}`);
testResultString += `\n${pad}${testResult.errMessage}\n\n`;
testResultString += `${pad}[Stack Trace]\n`;
testResultString += `${pad}${indentedStack}\n`;
}
prettyResultsString += testResultString + '\n';
});
/* eslint-enable no-console */
return prettyResultsString;
}
}
if (typeof module === 'undefined' || typeof module.exports === 'undefined') {
throw new Error('To use MochaUtils in the browser please use the ' +
'browser/mocha-utils.js file');
}
module.exports = new MochaUtils();