text: /**
   * @license
   * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
   * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
   * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
   * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
   * Code distributed by Google as part of the polymer project is also
   * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
   */
   
  /**
   * THIS FILE IS AUTOMATICALLY GENERATED! 
   * To make changes to browser.js, please edit the source files in the repo's `browser/` directory!
   */
  
  (function () {
  'use strict';
  
  /**
   * @param {function()} callback A function to call when the active web component
   *     frameworks have loaded.
   */
  function whenFrameworksReady(callback) {
    debug('whenFrameworksReady');
    var done = function() {
      debug('whenFrameworksReady done');
      callback();
    };
  
    // If webcomponents script is in the document, wait for WebComponentsReady.
    if (document.querySelector('script[src*="webcomponents"]')) {
      debug('WebComponentsReady?');
      window.addEventListener('WebComponentsReady', function wcReady() {
        window.removeEventListener('WebComponentsReady', wcReady);
        debug('WebComponentsReady');
        done();
      });
    } else {
      done();
    }
  }
  
  /**
   * @param {number} count
   * @param {string} kind
   * @return {string} '<count> <kind> tests' or '<count> <kind> test'.
   */
  function pluralizedStat(count, kind) {
    if (count === 1) {
      return count + ' ' + kind + ' test';
    } else {
      return count + ' ' + kind + ' tests';
    }
  }
  
  /**
   * @param {string} path The URI of the script to load.
   * @param {function} done
   */
  function loadScript(path, done) {
    var script = document.createElement('script');
    script.src = path;
    if (done) {
      script.onload = done.bind(null, null);
      script.onerror = done.bind(null, 'Failed to load script ' + script.src);
    }
    document.head.appendChild(script);
  }
  
  /**
   * @param {string} path The URI of the stylesheet to load.
   * @param {function} done
   */
  function loadStyle(path, done) {
    var link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = path;
    if (done) {
      link.onload = done.bind(null, null);
      link.onerror = done.bind(null, 'Failed to load stylesheet ' + link.href);
    }
    document.head.appendChild(link);
  }
  
  /**
   * @param {...*} var_args Logs values to the console when the `debug`
   *     configuration option is true.
   */
  function debug(var_args) {
    if (!get('verbose')) return;
    var args = [window.location.pathname];
    args.push.apply(args, arguments);
    (console.debug || console.log).apply(console, args);
  }
  
  // URL Processing
  
  /**
   * @param {string} url
   * @return {{base: string, params: string}}
   */
  function parseUrl(url) {
    var parts = url.match(/^(.*?)(?:\?(.*))?$/);
    return {
      base: parts[1],
      params: getParams(parts[2] || ''),
    };
  }
  
  /**
   * Expands a URL that may or may not be relative to `base`.
   *
   * @param {string} url
   * @param {string} base
   * @return {string}
   */
  function expandUrl(url, base) {
    if (!base) return url;
    if (url.match(/^(\/|https?:\/\/)/)) return url;
    if (base.substr(base.length - 1) !== '/') {
      base = base + '/';
    }
    return base + url;
  }
  
  /**
   * @param {string=} opt_query A query string to parse.
   * @return {!Object<string, !Array<string>>} All params on the URL's query.
   */
  function getParams(opt_query) {
    var query = typeof opt_query === 'string' ? opt_query : window.location.search;
    if (query.substring(0, 1) === '?') {
      query = query.substring(1);
    }
    // python's SimpleHTTPServer tacks a `/` on the end of query strings :(
    if (query.slice(-1) === '/') {
      query = query.substring(0, query.length - 1);
    }
    if (query === '') return {};
  
    var result = {};
    query.split('&').forEach(function(part) {
      var pair = part.split('=');
      if (pair.length !== 2) {
        console.warn('Invalid URL query part:', part);
        return;
      }
      var key = decodeURIComponent(pair[0]);
      var value = decodeURIComponent(pair[1]);
  
      if (!result[key]) {
        result[key] = [];
      }
      result[key].push(value);
    });
  
    return result;
  }
  
  /**
   * Merges params from `source` into `target` (mutating `target`).
   *
   * @param {!Object<string, !Array<string>>} target
   * @param {!Object<string, !Array<string>>} source
   */
  function mergeParams(target, source) {
    Object.keys(source).forEach(function(key) {
      if (!(key in target)) {
        target[key] = [];
      }
      target[key] = target[key].concat(source[key]);
    });
  }
  
  /**
   * @param {string} param The param to return a value for.
   * @return {?string} The first value for `param`, if found.
   */
  function getParam(param) {
    var params = getParams();
    return params[param] ? params[param][0] : null;
  }
  
  /**
   * @param {!Object<string, !Array<string>>} params
   * @return {string} `params` encoded as a URI query.
   */
  function paramsToQuery(params) {
    var pairs = [];
    Object.keys(params).forEach(function(key) {
      params[key].forEach(function(value) {
        pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
      });
    });
    return (pairs.length > 0) ? ('?' + pairs.join('&')) : '';
  }
  
  /**
   * @param {!Location|string} location
   * @return {string}
   */
  function basePath(location) {
    return (location.pathname || location).match(/^.*\//)[0];
  }
  
  /**
   * @param {!Location|string} location
   * @param {string} basePath
   * @return {string}
   */
  function relativeLocation(location, basePath) {
    var path = location.pathname || location;
    if (path.indexOf(basePath) === 0) {
      path = path.substring(basePath.length);
    }
    return path;
  }
  
  /**
   * @param {!Location|string} location
   * @return {string}
   */
  function cleanLocation(location) {
    var path = location.pathname || location;
    if (path.slice(-11) === '/index.html') {
      path = path.slice(0, path.length - 10);
    }
    return path;
  }
  
  /**
   * Like `async.parallelLimit`, but our own so that we don't force a dependency
   * on downstream code.
   *
   * @param {!Array<function(function(*))>} runners Runners that call their given
   *     Node-style callback when done.
   * @param {number|function(*)} limit Maximum number of concurrent runners.
   *     (optional).
   * @param {?function(*)} done Callback that should be triggered once all runners
   *     have completed, or encountered an error.
   */
  function parallel(runners, limit, done) {
    if (typeof limit !== 'number') {
      done = limit;
      limit = 0;
    }
    if (!runners.length) return done();
  
    var called = false;
    var total = runners.length;
    var numActive = 0;
    var numDone = 0;
  
    function runnerDone(error) {
      if (called) return;
      numDone = numDone + 1;
      numActive = numActive - 1;
  
      if (error || numDone >= total) {
        called = true;
        done(error);
      } else {
        runOne();
      }
    }
  
    function runOne() {
      if (limit && numActive >= limit) return;
      if (!runners.length) return;
      numActive = numActive + 1;
      runners.shift()(runnerDone);
    }
    runners.forEach(runOne);
  }
  
  /**
   * Finds the directory that a loaded script is hosted on.
   *
   * @param {string} filename
   * @return {string?}
   */
  function scriptPrefix(filename) {
    var scripts = document.querySelectorAll('script[src*="' + filename + '"]');
    if (scripts.length !== 1) return null;
    var script = scripts[0].src;
    return script.substring(0, script.indexOf(filename));
  }
  
  var util = Object.freeze({
    whenFrameworksReady: whenFrameworksReady,
    pluralizedStat: pluralizedStat,
    loadScript: loadScript,
    loadStyle: loadStyle,
    debug: debug,
    parseUrl: parseUrl,
    expandUrl: expandUrl,
    getParams: getParams,
    mergeParams: mergeParams,
    getParam: getParam,
    paramsToQuery: paramsToQuery,
    basePath: basePath,
    relativeLocation: relativeLocation,
    cleanLocation: cleanLocation,
    parallel: parallel,
    scriptPrefix: scriptPrefix
  });
  
  // TODO(thedeeno): Consider renaming subsuite. IIRC, childRunner is entirely
  // distinct from mocha suite, which tripped me up badly when trying to add
  // plugin support. Perhaps something like 'batch', or 'bundle'. Something that
  // has no mocha correlate. This may also eliminate the need for root/non-root
  // suite distinctions.
  
  /**
   * A Mocha suite (or suites) run within a child iframe, but reported as if they
   * are part of the current context.
   */
  function ChildRunner(url, parentScope) {
    var urlBits = parseUrl(url);
    mergeParams(
        urlBits.params, getParams(parentScope.location.search));
    delete urlBits.params.cli_browser_id;
  
    this.url         = urlBits.base + paramsToQuery(urlBits.params);
    this.parentScope = parentScope;
  
    this.state = 'initializing';
  }
  
  // ChildRunners get a pretty generous load timeout by default.
  ChildRunner.loadTimeout = 60000;
  
  // We can't maintain properties on iframe elements in Firefox/Safari/???, so we
  // track childRunners by URL.
  ChildRunner._byUrl = {};
  
  /**
   * @return {ChildRunner} The `ChildRunner` that was registered for this window.
   */
  ChildRunner.current = function() {
    return ChildRunner.get(window);
  };
  
  /**
   * @param {!Window} target A window to find the ChildRunner of.
   * @param {boolean} traversal Whether this is a traversal from a child window.
   * @return {ChildRunner} The `ChildRunner` that was registered for `target`.
   */
  ChildRunner.get = function(target, traversal) {
    var childRunner = ChildRunner._byUrl[target.location.href];
    if (childRunner) return childRunner;
    if (window.parent === window) {  // Top window.
      if (traversal) {
        console.warn('Subsuite loaded but was never registered. This most likely is due to wonky history behavior. Reloading...');
        window.location.reload();
      }
      return null;
    }
    // Otherwise, traverse.
    return window.parent.WCT._ChildRunner.get(target, true);
  };
  
  /**
   * Loads and runs the subsuite.
   *
   * @param {function} done Node-style callback.
   */
  ChildRunner.prototype.run = function(done) {
    debug('ChildRunner#run', this.url);
    this.state = 'loading';
    this.onRunComplete = done;
  
    this.iframe = document.createElement('iframe');
    this.iframe.src = this.url;
    this.iframe.classList.add('subsuite');
  
    var container = document.getElementById('subsuites');
    if (!container) {
      container = document.createElement('div');
      container.id = 'subsuites';
      document.body.appendChild(container);
    }
    container.appendChild(this.iframe);
  
    // let the iframe expand the URL for us.
    this.url = this.iframe.src;
    ChildRunner._byUrl[this.url] = this;
  
    this.timeoutId = setTimeout(
        this.loaded.bind(this, new Error('Timed out loading ' + this.url)), ChildRunner.loadTimeout);
  
    this.iframe.addEventListener('error',
        this.loaded.bind(this, new Error('Failed to load document ' + this.url)));
  
    this.iframe.contentWindow.addEventListener('DOMContentLoaded', this.loaded.bind(this, null));
  };
  
  /**
   * Called when the sub suite's iframe has loaded (or errored during load).
   *
   * @param {*} error The error that occured, if any.
   */
  ChildRunner.prototype.loaded = function(error) {
    debug('ChildRunner#loaded', this.url, error);
  
    // Not all targets have WCT loaded (compatiblity mode)
    if (this.iframe.contentWindow.WCT) {
      this.share = this.iframe.contentWindow.WCT.share;
    }
  
    if (error) {
      this.signalRunComplete(error);
      this.done();
    }
  };
  
  /**
   * Called in mocha/run.js when all dependencies have loaded, and the child is
   * ready to start running tests
   *
   * @param {*} error The error that occured, if any.
   */
  ChildRunner.prototype.ready = function(error) {
    debug('ChildRunner#ready', this.url, error);
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
    }
    if (error) {
      this.signalRunComplete(error);
      this.done();
    }
  };
  
  /** Called when the sub suite's tests are complete, so that it can clean up. */
  ChildRunner.prototype.done = function done() {
    debug('ChildRunner#done', this.url, arguments);
  
    // make sure to clear that timeout
    this.ready();
    this.signalRunComplete();
  
    if (!this.iframe) return;
    // Be safe and avoid potential browser crashes when logic attempts to interact
    // with the removed iframe.
    setTimeout(function() {
      this.iframe.parentNode.removeChild(this.iframe);
      this.iframe = null;
    }.bind(this), 1);
  };
  
  ChildRunner.prototype.signalRunComplete = function signalRunComplete(error) {
    if (!this.onRunComplete) return;
    this.state = 'complete';
    this.onRunComplete(error);
    this.onRunComplete = null;
  };
  
  /**
   * The global configuration state for WCT's browser client.
   */
  var _config = {
    /**
     * `.js` scripts to be loaded (synchronously) before WCT starts in earnest.
     *
     * Paths are relative to `scriptPrefix`.
     */
    environmentScripts: [
      'stacky/browser.js',
      'async/lib/async.js',
      'lodash/lodash.js',
      'mocha/mocha.js',
      'chai/chai.js',
      'sinonjs/sinon.js',
      'sinon-chai/lib/sinon-chai.js',
      'accessibility-developer-tools/dist/js/axs_testing.js'
    ],
  
    environmentImports: [
      'test-fixture/test-fixture.html'
    ],
  
    /** Absolute root for client scripts. Detected in `setup()` if not set. */
    root: null,
  
    /** By default, we wait for any web component frameworks to load. */
    waitForFrameworks: true,
  
    /** Alternate callback for waiting for tests.
     * `this` for the callback will be the window currently running tests.
     */
    waitFor: null,
  
    /** How many `.html` suites that can be concurrently loaded & run. */
    numConcurrentSuites: 1,
  
    /** Whether `console.error` should be treated as a test failure. */
    trackConsoleError: true,
  
    /** Configuration passed to mocha.setup. */
    mochaOptions: {
      timeout: 10 * 1000
    },
  
    /** Whether WCT should emit (extremely verbose) debugging log messages. */
    verbose: false,
  };
  
  /**
   * Merges initial `options` into WCT's global configuration.
   *
   * @param {Object} options The options to merge. See `browser/config.js` for a
   *     reference.
   */
  function setup(options) {
    var childRunner = ChildRunner.current();
    if (childRunner) {
      _deepMerge(_config, childRunner.parentScope.WCT._config);
      // But do not force the mocha UI
      delete _config.mochaOptions.ui;
    }
  
    if (options && typeof options === 'object') {
      _deepMerge(_config, options);
    }
  
    if (!_config.root) {
      // Sibling dependencies.
      var root = scriptPrefix('browser.js');
      _config.root = basePath(root.substr(0, root.length - 1));
      if (!_config.root) {
        throw new Error('Unable to detect root URL for WCT sources. Please set WCT.root before including browser.js');
      }
    }
  }
  
  /**
   * Retrieves a configuration value.
   *
   * @param {string} key
   * @return {*}
   */
  function get(key) {
    return _config[key];
  }
  
  // Internal
  
  function _deepMerge(target, source) {
    Object.keys(source).forEach(function (key) {
      if (target[key] !== null && typeof target[key] === 'object' && !Array.isArray(target[key])) {
        _deepMerge(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    });
  }
  
  var htmlSuites$1 = [];
  var jsSuites$1 = [];
  
  // We process grep ourselves to avoid loading suites that will be filtered.
  var GREP = getParam('grep');
  // work around mocha bug (https://github.com/mochajs/mocha/issues/2070)
  if (GREP) {
    GREP = GREP.replace(/\\\./g, '.');
  }
  
  /**
   * Loads suites of tests, supporting both `.js` and `.html` files.
   *
   * @param {!Array.<string>} files The files to load.
   */
  function loadSuites(files) {
    files.forEach(function(file) {
      if (/\.js(\?.*)?$/.test(file)) {
        jsSuites$1.push(file);
      } else if (/\.html(\?.*)?$/.test(file)) {
        htmlSuites$1.push(file);
      } else {
        throw new Error('Unknown resource type: ' + file);
      }
    });
  }
  
  /**
   * @return {!Array.<string>} The child suites that should be loaded, ignoring
   *     those that would not match `GREP`.
   */
  function activeChildSuites() {
    var subsuites = htmlSuites$1;
    if (GREP) {
      var cleanSubsuites = [];
      for (var i = 0, subsuite; subsuite = subsuites[i]; i++) {
        if (GREP.indexOf(cleanLocation(subsuite)) !== -1) {
          cleanSubsuites.push(subsuite);
        }
      }
      subsuites = cleanSubsuites;
    }
    return subsuites;
  }
  
  /**
   * Loads all `.js` sources requested by the current suite.
   *
   * @param {!MultiReporter} reporter
   * @param {function} done
   */
  function loadJsSuites(reporter, done) {
    debug('loadJsSuites', jsSuites$1);
  
    var loaders = jsSuites$1.map(function(file) {
      // We only support `.js` dependencies for now.
      return loadScript.bind(util, file);
    });
  
    parallel(loaders, done);
  }
  
  /**
   * @param {!MultiReporter} reporter
   * @param {!Array.<string>} childSuites
   * @param {function} done
   */
  function runSuites(reporter, childSuites, done) {
    debug('runSuites');
  
    var suiteRunners = [
      // Run the local tests (if any) first, not stopping on error;
      _runMocha.bind(null, reporter),
    ];
  
    // As well as any sub suites. Again, don't stop on error.
    childSuites.forEach(function(file) {
      suiteRunners.push(function(next) {
        var childRunner = new ChildRunner(file, window);
        reporter.emit('childRunner start', childRunner);
        childRunner.run(function(error) {
          reporter.emit('childRunner end', childRunner);
          if (error) reporter.emitOutOfBandTest(file, error);
          next();
        });
      });
    });
  
    parallel(suiteRunners, get('numConcurrentSuites'), function(error) {
      reporter.done();
      done(error);
    });
  }
  
  /**
   * Kicks off a mocha run, waiting for frameworks to load if necessary.
   *
   * @param {!MultiReporter} reporter Where to send Mocha's events.
   * @param {function} done A callback fired, _no error is passed_.
   */
  function _runMocha(reporter, done, waited) {
    if (get('waitForFrameworks') && !waited) {
      var waitFor = (get('waitFor') || whenFrameworksReady).bind(window);
      waitFor(function() {
        _fixCustomElements();
        _runMocha(reporter, done, true);
      });
      return;
    }
    debug('_runMocha');
    var mocha = window.mocha;
    var Mocha = window.Mocha;
  
    mocha.reporter(reporter.childReporter(window.location));
    mocha.suite.title = reporter.suiteTitle(window.location);
    mocha.grep(GREP);
  
    // We can't use `mocha.run` because it bashes over grep, invert, and friends.
    // See https://github.com/visionmedia/mocha/blob/master/support/tail.js#L137
    var runner = Mocha.prototype.run.call(mocha, function(error) {
      if (document.getElementById('mocha')) {
        Mocha.utils.highlightTags('code');
      }
      done(); // We ignore the Mocha failure count.
    });
  
    // Mocha's default `onerror` handling strips the stack (to support really old
    // browsers). We upgrade this to get better stacks for async errors.
    //
    // TODO(nevir): Can we expand support to other browsers?
    if (navigator.userAgent.match(/chrome/i)) {
      window.onerror = null;
      window.addEventListener('error', function(event) {
        if (!event.error) return;
        if (event.error.ignore) return;
        runner.uncaught(event.error);
      });
    }
  }
  /**
   * In Chrome57 custom elements in the document might not get upgraded when
   * there is a high GC https://bugs.chromium.org/p/chromium/issues/detail?id=701601
   * We clone and replace the ones that weren't upgraded.
   */
  function _fixCustomElements() {
    // Bail out if it is not Chrome 57.
    var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
    var isM57 = raw && raw[2] === '57';
    if (!isM57) return;
  
    var elements = document.body.querySelectorAll('*:not(script):not(style)');
    var constructors = {};
    for (var i = 0; i < elements.length; i++) {
      var el = elements[i];
      // This child has already been cloned and replaced by its parent, skip it!
      if (!el.isConnected) continue;
  
      var tag = el.localName;
      // Not a custom element!
      if (tag.indexOf('-') === -1) continue;
  
      // Memoize correct constructors.
      constructors[tag] = constructors[tag] || document.createElement(tag).constructor;
      // This one was correctly upgraded.
      if (el instanceof constructors[tag]) continue;
  
      debug('_fixCustomElements: found non-upgraded custom element ' + el);
      var clone = document.importNode(el, true);
      el.parentNode.replaceChild(clone, el);
    }
  }
  
  // We capture console events when running tests; so make sure we have a
  // reference to the original one.
  var console$1 = window.console;
  
  var FONT = ';font: normal 13px "Roboto", "Helvetica Neue", "Helvetica", sans-serif;';
  var STYLES = {
    plain:   FONT,
    suite:   'color: #5c6bc0' + FONT,
    test:    FONT,
    passing: 'color: #259b24' + FONT,
    pending: 'color: #e65100' + FONT,
    failing: 'color: #c41411' + FONT,
    stack:   'color: #c41411',
    results: FONT + 'font-size: 16px',
  };
  
  // I don't think we can feature detect this one...
  var userAgent = navigator.userAgent.toLowerCase();
  var CAN_STYLE_LOG   = userAgent.match('firefox') || userAgent.match('webkit');
  var CAN_STYLE_GROUP = userAgent.match('webkit');
  // Track the indent for faked `console.group`
  var logIndent = '';
  
  function log(text, style) {
    text = text.split('\n').map(function(l) { return logIndent + l; }).join('\n');
    if (CAN_STYLE_LOG) {
      console$1.log('%c' + text, STYLES[style] || STYLES.plain);
    } else {
      console$1.log(text);
    }
  }
  
  function logGroup(text, style) {
    if (CAN_STYLE_GROUP) {
      console$1.group('%c' + text, STYLES[style] || STYLES.plain);
    } else if (console$1.group) {
      console$1.group(text);
    } else {
      logIndent = logIndent + '  ';
      log(text, style);
    }
  }
  
  function logGroupEnd() {
    if (console$1.groupEnd) {
      console$1.groupEnd();
    } else {
      logIndent = logIndent.substr(0, logIndent.length - 2);
    }
  }
  
  function logException(error) {
    log(error.stack || error.message || error, 'stack');
  }
  
  /**
   * A Mocha reporter that logs results out to the web `console`.
   *
   * @param {!Mocha.Runner} runner The runner that is being reported on.
   */
  function Console(runner) {
    Mocha.reporters.Base.call(this, runner);
  
    runner.on('suite', function(suite) {
      if (suite.root) return;
      logGroup(suite.title, 'suite');
    }.bind(this));
  
    runner.on('suite end', function(suite) {
      if (suite.root) return;
      logGroupEnd();
    }.bind(this));
  
    runner.on('test', function(test) {
      logGroup(test.title, 'test');
    }.bind(this));
  
    runner.on('pending', function(test) {
      logGroup(test.title, 'pending');
    }.bind(this));
  
    runner.on('fail', function(test, error) {
      logException(error);
    }.bind(this));
  
    runner.on('test end', function(test) {
      logGroupEnd();
    }.bind(this));
  
    runner.on('end', this.logSummary.bind(this));
  }
  
  /** Prints out a final summary of test results. */
  Console.prototype.logSummary = function logSummary() {
    logGroup('Test Results', 'results');
  
    if (this.stats.failures > 0) {
      log(pluralizedStat(this.stats.failures, 'failing'), 'failing');
    }
    if (this.stats.pending > 0) {
      log(pluralizedStat(this.stats.pending, 'pending'), 'pending');
    }
    log(pluralizedStat(this.stats.passes, 'passing'));
  
    if (!this.stats.failures) {
      log('test suite passed', 'passing');
    }
    log('Evaluated ' + this.stats.tests + ' tests in ' + this.stats.duration + 'ms.');
    logGroupEnd();
  };
  
  /**
   * @license
   * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
   * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
   * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
   * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
   * Code distributed by Google as part of the polymer project is also
   * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
   */
  
  /**
   * WCT-specific behavior on top of Mocha's default HTML reporter.
   *
   * @param {!Mocha.Runner} runner The runner that is being reported on.
   */
  function HTML(runner) {
    var output = document.createElement('div');
    output.id = 'mocha';
    document.body.appendChild(output);
  
    runner.on('suite', function(test) {
      this.total = runner.total;
    }.bind(this));
  
    Mocha.reporters.HTML.call(this, runner);
  }
  
  // Woo! What a hack. This just saves us from adding a bunch of complexity around
  // style loading.
  var style = document.createElement('style');
  style.textContent = 'html, body {' +
                      '  position: relative;' +
                      '  height: 100%;' +
                      '  width:  100%;' +
                      '  min-width: 900px;' +
                      '}' +
                      '#mocha, #subsuites {' +
                      '  height: 100%;' +
                      '  position: absolute;' +
                      '  top: 0;' +
                      '}' +
                      '#mocha {' +
                      '  box-sizing: border-box;' +
                      '  margin: 0 !important;' +
                      '  overflow-y: auto;' +
                      '  padding: 60px 20px;' +
                      '  right: 0;' +
                      '  left: 500px;' +
                      '}' +
                      '#subsuites {' +
                      '  -ms-flex-direction: column;' +
                      '  -webkit-flex-direction: column;' +
                      '  display: -ms-flexbox;' +
                      '  display: -webkit-flex;' +
                      '  display: flex;' +
                      '  flex-direction: column;' +
                      '  left: 0;' +
                      '  width: 500px;' +
                      '}' +
                      '#subsuites .subsuite {' +
                      '  border: 0;' +
                      '  width: 100%;' +
                      '  height: 100%;' +
                      '}' +
                      '#mocha .test.pass .duration {' +
                      '  color: #555 !important;' +
                      '}';
  document.head.appendChild(style);
  
  var STACKY_CONFIG = {
    indent: '  ',
    locationStrip: [
      /^https?:\/\/[^\/]+/,
      /\?.*$/,
    ],
    filter: function(line) {
      return line.location.match(/\/web-component-tester\/[^\/]+(\?.*)?$/);
    },
  };
  
  // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36-46
  var MOCHA_EVENTS = [
    'start',
    'end',
    'suite',
    'suite end',
    'test',
    'test end',
    'hook',
    'hook end',
    'pass',
    'fail',
    'pending',
    'childRunner end'
  ];
  
  // Until a suite has loaded, we assume this many tests in it.
  var ESTIMATED_TESTS_PER_SUITE = 3;
  
  /**
   * A Mocha-like reporter that combines the output of multiple Mocha suites.
   *
   * @param {number} numSuites The number of suites that will be run, in order to
   *     estimate the total number of tests that will be performed.
   * @param {!Array.<!Mocha.reporters.Base>} reporters The set of reporters that
   *     should receive the unified event stream.
   * @param {MultiReporter} parent The parent reporter, if present.
   */
  function MultiReporter(numSuites, reporters, parent) {
    this.reporters = reporters.map(function(reporter) {
      return new reporter(this);
    }.bind(this));
  
    this.parent = parent;
    this.basePath = parent && parent.basePath || basePath(window.location);
  
    this.total = numSuites * ESTIMATED_TESTS_PER_SUITE;
    // Mocha reporters assume a stream of events, so we have to be careful to only
    // report on one runner at a time...
    this.currentRunner = null;
    // ...while we buffer events for any other active runners.
    this.pendingEvents = [];
  
    this.emit('start');
  }
  
  /**
   * @param {!Location|string} location The location this reporter represents.
   * @return {!Mocha.reporters.Base} A reporter-like "class" for each child suite
   *     that should be passed to `mocha.run`.
   */
  MultiReporter.prototype.childReporter = function childReporter(location) {
    var name = this.suiteTitle(location);
    // The reporter is used as a constructor, so we can't depend on `this` being
    // properly bound.
    var self = this;
    function reporter(runner) {
      runner.name = name;
      self.bindChildRunner(runner);
    }
    reporter.title = name;
    return reporter;
  };
  
  /** Must be called once all runners have finished. */
  MultiReporter.prototype.done = function done() {
    this.complete = true;
    this.flushPendingEvents();
    this.emit('end');
  };
  
  /**
   * Emit a top level test that is not part of any suite managed by this reporter.
   *
   * Helpful for reporting on global errors, loading issues, etc.
   *
   * @param {string} title The title of the test.
   * @param {*} opt_error An error associated with this test. If falsy, test is
   *     considered to be passing.
   * @param {string} opt_suiteTitle Title for the suite that's wrapping the test.
   * @param {?boolean} opt_estimated If this test was included in the original
   *     estimate of `numSuites`.
   */
  MultiReporter.prototype.emitOutOfBandTest = function emitOutOfBandTest(title, opt_error, opt_suiteTitle, opt_estimated) {
    debug('MultiReporter#emitOutOfBandTest(', arguments, ')');
    var root = new Mocha.Suite();
    root.title = opt_suiteTitle || '';
    var test = new Mocha.Test(title, function() {
    });
    test.parent = root;
    test.state  = opt_error ? 'failed' : 'passed';
    test.err    = opt_error;
  
    if (!opt_estimated) {
      this.total = this.total + ESTIMATED_TESTS_PER_SUITE;
    }
  
    var runner = {total: 1};
    this.proxyEvent('start', runner);
    this.proxyEvent('suite', runner, root);
    this.proxyEvent('test', runner, test);
    if (opt_error) {
      this.proxyEvent('fail', runner, test, opt_error);
    } else {
      this.proxyEvent('pass', runner, test);
    }
    this.proxyEvent('test end', runner, test);
    this.proxyEvent('suite end', runner, root);
    this.proxyEvent('end', runner);
  };
  
  /**
   * @param {!Location|string} location
   * @return {string}
   */
  MultiReporter.prototype.suiteTitle = function suiteTitle(location) {
    var path = relativeLocation(location, this.basePath);
    path = cleanLocation(path);
    return path;
  };
  
  // Internal Interface
  
  /** @param {!Mocha.runners.Base} runner The runner to listen to events for. */
  MultiReporter.prototype.bindChildRunner = function bindChildRunner(runner) {
    MOCHA_EVENTS.forEach(function(eventName) {
      runner.on(eventName, this.proxyEvent.bind(this, eventName, runner));
    }.bind(this));
  };
  
  /**
   * Evaluates an event fired by `runner`, proxying it forward or buffering it.
   *
   * @param {string} eventName
   * @param {!Mocha.runners.Base} runner The runner that emitted this event.
   * @param {...*} var_args Any additional data passed as part of the event.
   */
  MultiReporter.prototype.proxyEvent = function proxyEvent(eventName, runner, var_args) {
    var extraArgs = Array.prototype.slice.call(arguments, 2);
    if (this.complete) {
      console.warn('out of order Mocha event for ' + runner.name + ':', eventName, extraArgs);
      return;
    }
  
    if (this.currentRunner && runner !== this.currentRunner) {
      this.pendingEvents.push(arguments);
      return;
    }
    debug('MultiReporter#proxyEvent(', arguments, ')');
  
    // This appears to be a Mocha bug: Tests failed by passing an error to their
    // done function don't set `err` properly.
    //
    // TODO(nevir): Track down.
    if (eventName === 'fail' && !extraArgs[0].err) {
      extraArgs[0].err = extraArgs[1];
    }
  
    if (eventName === 'start') {
      this.onRunnerStart(runner);
    } else if (eventName === 'end') {
      this.onRunnerEnd(runner);
    } else {
      this.cleanEvent(eventName, runner, extraArgs);
      this.emit.apply(this, [eventName].concat(extraArgs));
    }
  };
  
  /**
   * Cleans or modifies an event if needed.
   *
   * @param {string} eventName
   * @param {!Mocha.runners.Base} runner The runner that emitted this event.
   * @param {!Array.<*>} extraArgs
   */
  MultiReporter.prototype.cleanEvent = function cleanEvent(eventName, runner, extraArgs) {
    // Suite hierarchy
    if (extraArgs[0]) {
      extraArgs[0] = this.showRootSuite(extraArgs[0]);
    }
  
    // Normalize errors
    if (eventName === 'fail') {
      extraArgs[1] = Stacky.normalize(extraArgs[1], STACKY_CONFIG);
    }
    if (extraArgs[0] && extraArgs[0].err) {
      extraArgs[0].err = Stacky.normalize(extraArgs[0].err, STACKY_CONFIG);
    }
  };
  
  /**
   * We like to show the root suite's title, which requires a little bit of
   * trickery in the suite hierarchy.
   *
   * @param {!Mocha.Runnable} node
   */
  MultiReporter.prototype.showRootSuite = function showRootSuite(node) {
    var leaf = node = Object.create(node);
    while (node && node.parent) {
      var wrappedParent = Object.create(node.parent);
      node.parent = wrappedParent;
      node = wrappedParent;
    }
    node.root = false;
  
    return leaf;
  };
  
  /** @param {!Mocha.runners.Base} runner */
  MultiReporter.prototype.onRunnerStart = function onRunnerStart(runner) {
    debug('MultiReporter#onRunnerStart:', runner.name);
    this.total = this.total - ESTIMATED_TESTS_PER_SUITE + runner.total;
    this.currentRunner = runner;
  };
  
  /** @param {!Mocha.runners.Base} runner */
  MultiReporter.prototype.onRunnerEnd = function onRunnerEnd(runner) {
    debug('MultiReporter#onRunnerEnd:', runner.name);
    this.currentRunner = null;
    this.flushPendingEvents();
  };
  
  /**
   * Flushes any buffered events and runs them through `proxyEvent`. This will
   * loop until all buffered runners are complete, or we have run out of buffered
   * events.
   */
  MultiReporter.prototype.flushPendingEvents = function flushPendingEvents() {
    var events = this.pendingEvents;
    this.pendingEvents = [];
    events.forEach(function(eventArgs) {
      this.proxyEvent.apply(this, eventArgs);
    }.bind(this));
  };
  
  var ARC_OFFSET = 0; // start at the right.
  var ARC_WIDTH  = 6;
  
  /**
   * A Mocha reporter that updates the document's title and favicon with
   * at-a-glance stats.
   *
   * @param {!Mocha.Runner} runner The runner that is being reported on.
   */
  function Title(runner) {
    Mocha.reporters.Base.call(this, runner);
  
    runner.on('test end', this.report.bind(this));
  }
  
  /** Reports current stats via the page title and favicon. */
  Title.prototype.report = function report() {
    this.updateTitle();
    this.updateFavicon();
  };
  
  /** Updates the document title with a summary of current stats. */
  Title.prototype.updateTitle = function updateTitle() {
    if (this.stats.failures > 0) {
      document.title = pluralizedStat(this.stats.failures, 'failing');
    } else {
      document.title = pluralizedStat(this.stats.passes, 'passing');
    }
  };
  
  /**
   * Draws an arc for the favicon status, relative to the total number of tests.
   *
   * @param {!CanvasRenderingContext2D} context
   * @param {number} total
   * @param {number} start
   * @param {number} length
   * @param {string} color
   */
  function drawFaviconArc(context, total, start, length, color) {
    var arcStart = ARC_OFFSET + Math.PI * 2 * (start / total);
    var arcEnd   = ARC_OFFSET + Math.PI * 2 * ((start + length) / total);
  
    context.beginPath();
    context.strokeStyle = color;
    context.lineWidth   = ARC_WIDTH;
    context.arc(16, 16, 16 - ARC_WIDTH / 2, arcStart, arcEnd);
    context.stroke();
  }
  
  /** Updates the document's favicon w/ a summary of current stats. */
  Title.prototype.updateFavicon = function updateFavicon() {
    var canvas = document.createElement('canvas');
    canvas.height = canvas.width = 32;
    var context = canvas.getContext('2d');
  
    var passing = this.stats.passes;
    var pending = this.stats.pending;
    var failing = this.stats.failures;
    var total   = Math.max(this.runner.total, passing + pending + failing);
    drawFaviconArc(context, total, 0,                 passing, '#0e9c57');
    drawFaviconArc(context, total, passing,           pending, '#f3b300');
    drawFaviconArc(context, total, pending + passing, failing, '#ff5621');
  
    this.setFavicon(canvas.toDataURL());
  };
  
  /** Sets the current favicon by URL. */
  Title.prototype.setFavicon = function setFavicon(url) {
    var current = document.head.querySelector('link[rel="icon"]');
    if (current) {
      document.head.removeChild(current);
    }
  
    var link = document.createElement('link');
    link.rel = 'icon';
    link.type = 'image/x-icon';
    link.href = url;
    link.setAttribute('sizes', '32x32');
    document.head.appendChild(link);
  };
  
  /**
   * @param {CLISocket} socket The CLI socket, if present.
   * @param {MultiReporter} parent The parent reporter, if present.
   * @return {!Array.<!Mocha.reporters.Base} The reporters that should be used.
   */
  function determineReporters(socket, parent) {
    // Parents are greedy.
    if (parent) {
      return [parent.childReporter(window.location)];
    }
  
    // Otherwise, we get to run wild without any parental supervision!
    var reporters = [Title, Console];
  
    if (socket) {
      reporters.push(function(runner) {
        socket.observe(runner);
      });
    }
  
    if (htmlSuites$1.length > 0 || jsSuites$1.length > 0) {
      reporters.push(HTML);
    }
  
    return reporters;
  }
  
  /**
   * Yeah, hideous, but this allows us to be loaded before Mocha, which is handy.
   */
  function injectMocha(Mocha) {
    _injectPrototype(Console, Mocha.reporters.Base.prototype);
    _injectPrototype(HTML,    Mocha.reporters.HTML.prototype);
    // Mocha doesn't expose its `EventEmitter` shim directly, so:
    _injectPrototype(MultiReporter,   Object.getPrototypeOf(Mocha.Runner.prototype));
  }
  
  function _injectPrototype(klass, prototype) {
    var newPrototype = Object.create(prototype);
    // Only support
    Object.keys(klass.prototype).forEach(function(key) {
      newPrototype[key] = klass.prototype[key];
    });
  
    klass.prototype = newPrototype;
  }
  
  /**
   * Loads all environment scripts ...synchronously ...after us.
   */
  function loadSync() {
    debug('Loading environment scripts:');
    var a11ySuite = 'web-component-tester/data/a11ySuite.js';
    var scripts = get('environmentScripts');
    var a11ySuiteWillBeLoaded = window.__generatedByWct || scripts.indexOf(a11ySuite) > -1;
    if (!a11ySuiteWillBeLoaded) {
      // wct is running as a bower dependency, load a11ySuite from data/
      scripts.push(a11ySuite);
    }
    scripts.forEach(function(path) {
      var url = expandUrl(path, get('root'));
      debug('Loading environment script:', url);
      // Synchronous load.
      document.write('<script src="' + encodeURI(url) + '"></script>'); // jshint ignore:line
    });
    debug('Environment scripts loaded');
  
    var imports = get('environmentImports');
    imports.forEach(function(path) {
      var url = expandUrl(path, get('root'));
      debug('Loading environment import:', url);
      // Synchronous load.
      document.write('<link rel="import" href="' + encodeURI(url) + '">'); // jshint ignore:line
    });
    debug('Environment imports loaded');
  }
  
  /**
   * We have some hard dependencies on things that should be loaded via
   * `environmentScripts`, so we assert that they're present here; and do any
   * post-facto setup.
   */
  function ensureDependenciesPresent() {
    _ensureMocha();
    _checkChai();
  }
  
  function _ensureMocha() {
    var Mocha = window.Mocha;
    if (!Mocha) {
      throw new Error('WCT requires Mocha. Please ensure that it is present in WCT.environmentScripts, or that you load it before loading web-component-tester/browser.js');
    }
    injectMocha(Mocha);
    // Magic loading of mocha's stylesheet
    var mochaPrefix = scriptPrefix('mocha.js');
    // only load mocha stylesheet for the test runner output
    // Not the end of the world, if it doesn't load.
    if (mochaPrefix && window.top === window.self) {
      loadStyle(mochaPrefix + 'mocha.css');
    }
  }
  
  function _checkChai() {
    if (!window.chai) {
      debug('Chai not present; not registering shorthands');
      return;
    }
  
    window.assert = window.chai.assert;
    window.expect = window.chai.expect;
  }
  
  // We may encounter errors during initialization (for example, syntax errors in
  // a test file). Hang onto those (and more) until we are ready to report them.
  var globalErrors = [];
  
  /**
   * Hook the environment to pick up on global errors.
   */
  function listenForErrors() {
    window.addEventListener('error', function(event) {
      globalErrors.push(event.error);
    });
  
    // Also, we treat `console.error` as a test failure. Unless you prefer not.
    var origConsole = console;
    var origError   = console.error;
    console.error = function wctShimmedError() {
      origError.apply(origConsole, arguments);
      if (get('trackConsoleError')) {
        throw 'console.error: ' + Array.prototype.join.call(arguments, ' ');
      }
    };
  }
  
  var interfaceExtensions = [];
  
  /**
   * Registers an extension that extends the global `Mocha` implementation
   * with new helper methods. These helper methods will be added to the `window`
   * when tests run for both BDD and TDD interfaces.
   */
  function extendInterfaces(helperName, helperFactory) {
    interfaceExtensions.push(function() {
      var Mocha = window.Mocha;
      // For all Mocha interfaces (probably just TDD and BDD):
      Object.keys(Mocha.interfaces).forEach(function(interfaceName) {
        // This is the original callback that defines the interface (TDD or BDD):
        var originalInterface = Mocha.interfaces[interfaceName];
        // This is the name of the "teardown" or "afterEach" property for the
        // current interface:
        var teardownProperty = interfaceName === 'tdd' ? 'teardown' : 'afterEach';
        // The original callback is monkey patched with a new one that appends to
        // the global context however we want it to:
        Mocha.interfaces[interfaceName] = function(suite) {
          // Call back to the original callback so that we get the base interface:
          originalInterface.apply(this, arguments);
          // Register a listener so that we can further extend the base interface:
          suite.on('pre-require', function(context, file, mocha) {
            // Capture a bound reference to the teardown function as a convenience:
            var teardown = context[teardownProperty].bind(context);
            // Add our new helper to the testing context. The helper is generated
            // by a factory method that receives the context, the teardown function
            // and the interface name and returns the new method to be added to
            // that context:
            context[helperName] = helperFactory(context, teardown, interfaceName);
          });
        };
      });
    });
  }
  
  /**
   * Applies any registered interface extensions. The extensions will be applied
   * as many times as this function is called, so don't call it more than once.
   */
  function applyExtensions() {
    interfaceExtensions.forEach(function(applyExtension) {
      applyExtension();
    });
  }
  
  extendInterfaces('fixture', function (context, teardown) {
  
    // Return context.fixture if it is already a thing, for backwards
    // compatibility with `test-fixture-mocha.js`:
    return context.fixture || function fixture(fixtureId, model) {
  
      // Automatically register a teardown callback that will restore the
      // test-fixture:
      teardown(function () {
        document.getElementById(fixtureId).restore();
      });
  
      // Find the test-fixture with the provided ID and create it, returning
      // the results:
      return document.getElementById(fixtureId).create(model);
    };
  });
  
  /**
   * stub
   *
   * The stub addon allows the tester to partially replace the implementation of
   * an element with some custom implementation. Usage example:
   *
   * beforeEach(function() {
   *   stub('x-foo', {
   *     attached: function() {
   *       // Custom implementation of the `attached` method of element `x-foo`..
   *     },
   *     otherMethod: function() {
   *       // More custom implementation..
   *     },
   *     getterSetterProperty: {
   *       get: function() {
   *         // Custom getter implementation..
   *       },
   *       set: function() {
   *         // Custom setter implementation..
   *       }
   *     },
   *     // etc..
   *   });
   * });
   */
  extendInterfaces('stub', function(context, teardown) {
  
    return function stub(tagName, implementation) {
      // Find the prototype of the element being stubbed:
      var proto = document.createElement(tagName).constructor.prototype;
  
      // For all keys in the implementation to stub with..
      var stubs = Object.keys(implementation).map(function(key) {
        // Stub the method on the element prototype with Sinon:
        return sinon.stub(proto, key, implementation[key]);
      });
  
      // After all tests..
      teardown(function() {
        stubs.forEach(function(stub) {
          stub.restore();
        });
      });
    };
  });
  
  // replacement map stores what should be
  var replacements = {};
  var replaceTeardownAttached = false;
  
  /**
   * replace
   *
   * The replace addon allows the tester to replace all usages of one element with
   * another element within all Polymer elements created within the time span of
   * the test. Usage example:
   *
   * beforeEach(function() {
   *   replace('x-foo').with('x-fake-foo');
   * });
   *
   * All annotations and attributes will be set on the placement element the way
   * they were set for the original element.
   */
  extendInterfaces('replace', function (context, teardown) {
    return function replace(oldTagName) {
      return {
        with: function (tagName) {
          // Standardizes our replacements map
          oldTagName = oldTagName.toLowerCase();
          tagName = tagName.toLowerCase();
  
          replacements[oldTagName] = tagName;
  
          // If the function is already a stub, restore it to original
          if (document.importNode.isSinonProxy) {
            return;
          }
  
          if (!Polymer.Element) {
            Polymer.Element = function () { };
            Polymer.Element.prototype._stampTemplate = function () { };
          }
  
          // Keep a reference to the original `document.importNode`
          // implementation for later:
          var originalImportNode = document.importNode;
  
          // Use Sinon to stub `document.ImportNode`:
          sinon.stub(document, 'importNode', function (origContent, deep) {
            var templateClone = document.createElement('template');
            var content = templateClone.content;
            var inertDoc = content.ownerDocument;
  
            // imports node from inertDoc which holds inert nodes.
            templateClone.content.appendChild(inertDoc.importNode(origContent, true));
  
            // optional arguments are not optional on IE.
            var nodeIterator = document.createNodeIterator(content,
              NodeFilter.SHOW_ELEMENT, null, true);
            var node;
  
            // Traverses the tree. A recently-replaced node will be put next, so
            // if a node is replaced, it will be checked if it needs to be
            // replaced again.
            while (node = nodeIterator.nextNode()) {
              var currentTagName = node.tagName.toLowerCase();
  
              if (replacements.hasOwnProperty(currentTagName)) {
                currentTagName = replacements[currentTagName];
  
                // find the final tag name.
                while (replacements[currentTagName]) {
                  currentTagName = replacements[currentTagName];
                }
  
                // Create a replacement:
                var replacement = document.createElement(currentTagName);
  
                // For all attributes in the original node..
                for (var index = 0; index < node.attributes.length; ++index) {
                  // Set that attribute on the replacement:
                  replacement.setAttribute(
                    node.attributes[index].name, node.attributes[index].value);
                }
  
                // Replace the original node with the replacement node:
                node.parentNode.replaceChild(replacement, node);
              }
            }
  
            return originalImportNode.call(this, content, deep);
          });
  
          if (!replaceTeardownAttached) {
            // After each test...
            teardown(function () {
              replaceTeardownAttached = true;
              // Restore the stubbed version of `document.importNode`:
              if (document.importNode.isSinonProxy) {
                document.importNode.restore();
              }
  
              // Empty the replacement map
              replacements = {};
            });
          }
        }
      };
    };
  });
  
  // Mocha global helpers, broken out by testing method.
  //
  // Keys are the method for a particular interface; values are their analog in
  // the opposite interface.
  var MOCHA_EXPORTS = {
    // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/tdd.js
    tdd: {
      'setup': '"before"',
      'teardown': '"after"',
      'suiteSetup': '"beforeEach"',
      'suiteTeardown': '"afterEach"',
      'suite': '"describe" or "context"',
      'test': '"it" or "specify"',
    },
    // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/bdd.js
    bdd: {
      'before': '"setup"',
      'after': '"teardown"',
      'beforeEach': '"suiteSetup"',
      'afterEach': '"suiteTeardown"',
      'describe': '"suite"',
      'context': '"suite"',
      'xdescribe': '"suite.skip"',
      'xcontext': '"suite.skip"',
      'it': '"test"',
      'xit': '"test.skip"',
      'specify': '"test"',
      'xspecify': '"test.skip"',
    },
  };
  
  /**
   * Exposes all Mocha methods up front, configuring and running mocha
   * automatically when you call them.
   *
   * The assumption is that it is a one-off (sub-)suite of tests being run.
   */
  function stubInterfaces() {
    Object.keys(MOCHA_EXPORTS).forEach(function (ui) {
      Object.keys(MOCHA_EXPORTS[ui]).forEach(function (key) {
        window[key] = function wrappedMochaFunction() {
          _setupMocha(ui, key, MOCHA_EXPORTS[ui][key]);
          if (!window[key] || window[key] === wrappedMochaFunction) {
            throw new Error('Expected mocha.setup to define ' + key);
          }
          window[key].apply(window, arguments);
        };
      });
    });
  }
  
  // Whether we've called `mocha.setup`
  var _mochaIsSetup = false;
  
  /**
   * @param {string} ui Sets up mocha to run `ui`-style tests.
   * @param {string} key The method called that triggered this.
   * @param {string} alternate The matching method in the opposite interface.
   */
  function _setupMocha(ui, key, alternate) {
    var mochaOptions = get('mochaOptions');
    if (mochaOptions.ui && mochaOptions.ui !== ui) {
      var message = 'Mixing ' + mochaOptions.ui + ' and ' + ui + ' Mocha styles is not supported. ' +
        'You called "' + key + '". Did you mean ' + alternate + '?';
      throw new Error(message);
    }
    if (_mochaIsSetup) return;
  
    applyExtensions();
    mochaOptions.ui = ui;
    mocha.setup(mochaOptions);  // Note that the reporter is configured in run.js.
  }
  
  var SOCKETIO_ENDPOINT = window.location.protocol + '//' + window.location.host;
  var SOCKETIO_LIBRARY  = SOCKETIO_ENDPOINT + '/socket.io/socket.io.js';
  
  /**
   * A socket for communication between the CLI and browser runners.
   *
   * @param {string} browserId An ID generated by the CLI runner.
   * @param {!io.Socket} socket The socket.io `Socket` to communicate over.
   */
  function CLISocket(browserId, socket) {
    this.browserId = browserId;
    this.socket    = socket;
  }
  
  /**
   * @param {!Mocha.Runner} runner The Mocha `Runner` to observe, reporting
   *     interesting events back to the CLI runner.
   */
  CLISocket.prototype.observe = function observe(runner) {
    this.emitEvent('browser-start', {
      url: window.location.toString(),
    });
  
    // We only emit a subset of events that we care about, and follow a more
    // general event format that is hopefully applicable to test runners beyond
    // mocha.
    //
    // For all possible mocha events, see:
    // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36
    runner.on('test', function(test) {
      this.emitEvent('test-start', {test: getTitles(test)});
    }.bind(this));
  
    runner.on('test end', function(test) {
      this.emitEvent('test-end', {
        state:    getState(test),
        test:     getTitles(test),
        duration: test.duration,
        error:    test.err,
      });
    }.bind(this));
  
    runner.on('fail', function(test, err) {
      // fail the test run if we catch errors outside of a test function
      if (test.type !== 'test') {
        this.emitEvent('browser-fail', 'Error thrown outside of test function: ' + err.stack);
      }
    }.bind(this));
  
    runner.on('childRunner start', function(childRunner) {
      this.emitEvent('sub-suite-start', childRunner.share);
    }.bind(this));
  
    runner.on('childRunner end', function(childRunner) {
      this.emitEvent('sub-suite-end', childRunner.share);
    }.bind(this));
  
    runner.on('end', function() {
      this.emitEvent('browser-end');
    }.bind(this));
  };
  
  /**
   * @param {string} event The name of the event to fire.
   * @param {*} data Additional data to pass with the event.
   */
  CLISocket.prototype.emitEvent = function emitEvent(event, data) {
    this.socket.emit('client-event', {
      browserId: this.browserId,
      event:     event,
      data:      data,
    });
  };
  
  /**
   * Builds a `CLISocket` if we are within a CLI-run environment; short-circuits
   * otherwise.
   *
   * @param {function(*, CLISocket)} done Node-style callback.
   */
  CLISocket.init = function init(done) {
    var browserId = getParam('cli_browser_id');
    if (!browserId) return done();
    // Only fire up the socket for root runners.
    if (ChildRunner.current()) return done();
  
    loadScript(SOCKETIO_LIBRARY, function(error) {
      if (error) return done(error);
  
      var socket = io(SOCKETIO_ENDPOINT);
      socket.on('error', function(error) {
        socket.off();
        done(error);
      });
  
      socket.on('connect', function() {
        socket.off();
        done(null, new CLISocket(browserId, socket));
      });
    });
  };
  
  // Misc Utility
  
  /**
   * @param {!Mocha.Runnable} runnable The test or suite to extract titles from.
   * @return {!Array.<string>} The titles of the runnable and its parents.
   */
  function getTitles(runnable) {
    var titles = [];
    while (runnable && !runnable.root && runnable.title) {
      titles.unshift(runnable.title);
      runnable = runnable.parent;
    }
    return titles;
  }
  
  /**
   * @param {!Mocha.Runnable} runnable
   * @return {string}
   */
  function getState(runnable) {
    if (runnable.state === 'passed') {
      return 'passing';
    } else if (runnable.state == 'failed') {
      return 'failing';
    } else if (runnable.pending) {
      return 'pending';
    } else {
      return 'unknown';
    }
  }
  
  /**
   * @license
   * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
   * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
   * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
   * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
   * Code distributed by Google as part of the polymer project is also
   * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
   */
  
  // Make sure that we use native timers, in case they're being stubbed out.
  var setInterval           = window.setInterval;           // jshint ignore:line
  var setTimeout$1            = window.setTimeout;            // jshint ignore:line
  var requestAnimationFrame = window.requestAnimationFrame; // jshint ignore:line
  
  /**
   * Runs `stepFn`, catching any error and passing it to `callback` (Node-style).
   * Otherwise, calls `callback` with no arguments on success.
   *
   * @param {function()} callback
   * @param {function()} stepFn
   */
  window.safeStep = function safeStep(callback, stepFn) {
    var err;
    try {
      stepFn();
    } catch (error) {
      err = error;
    }
    callback(err);
  };
  
  /**
   * Runs your test at declaration time (before Mocha has begun tests). Handy for
   * when you need to test document initialization.
   *
   * Be aware that any errors thrown asynchronously cannot be tied to your test.
   * You may want to catch them and pass them to the done event, instead. See
   * `safeStep`.
   *
   * @param {string} name The name of the test.
   * @param {function(?function())} testFn The test function. If an argument is
   *     accepted, the test will be treated as async, just like Mocha tests.
   */
  window.testImmediate = function testImmediate(name, testFn) {
    if (testFn.length > 0) {
      return testImmediateAsync(name, testFn);
    }
  
    var err;
    try {
      testFn();
    } catch (error) {
      err = error;
    }
  
    test(name, function(done) {
      done(err);
    });
  };
  
  /**
   * An async-only variant of `testImmediate`.
   *
   * @param {string} name
   * @param {function(?function())} testFn
   */
  window.testImmediateAsync = function testImmediateAsync(name, testFn) {
    var testComplete = false;
    var err;
  
    test(name, function(done) {
      var intervalId = setInterval(function() {
        if (!testComplete) return;
        clearInterval(intervalId);
        done(err);
      }, 10);
    });
  
    try {
      testFn(function(error) {
        if (error) err = error;
        testComplete = true;
      });
    } catch (error) {
      err = error;
      testComplete = true;
    }
  };
  
  /**
   * Triggers a flush of any pending events, observations, etc and calls you back
   * after they have been processed.
   *
   * @param {function()} callback
   */
  window.flush = function flush(callback) {
    // Ideally, this function would be a call to Polymer.dom.flush, but that doesn't
    // support a callback yet (https://github.com/Polymer/polymer-dev/issues/851),
    // ...and there's cross-browser flakiness to deal with.
  
    // Make sure that we're invoking the callback with no arguments so that the
    // caller can pass Mocha callbacks, etc.
    var done = function done() { callback(); };
  
    // Because endOfMicrotask is flaky for IE, we perform microtask checkpoints
    // ourselves (https://github.com/Polymer/polymer-dev/issues/114):
    var isIE = navigator.appName == 'Microsoft Internet Explorer';
    if (isIE && window.Platform && window.Platform.performMicrotaskCheckpoint) {
      var reallyDone = done;
      done = function doneIE() {
        Platform.performMicrotaskCheckpoint();
        setTimeout$1(reallyDone, 0);
      };
    }
  
    // Everyone else gets a regular flush.
    var scope;
    if (window.Polymer && window.Polymer.dom && window.Polymer.dom.flush) {
      scope = window.Polymer.dom;
    } else if (window.Polymer && window.Polymer.flush) {
      scope = window.Polymer;
    } else if (window.WebComponents && window.WebComponents.flush) {
      scope = window.WebComponents;
    }
    if (scope) {
      scope.flush();
    }
  
    // Ensure that we are creating a new _task_ to allow all active microtasks to
    // finish (the code you're testing may be using endOfMicrotask, too).
    setTimeout$1(done, 0);
  };
  
  /**
   * Advances a single animation frame.
   *
   * Calls `flush`, `requestAnimationFrame`, `flush`, and `callback` sequentially
   * @param {function()} callback
   */
  window.animationFrameFlush = function animationFrameFlush(callback) {
    flush(function() {
      requestAnimationFrame(function() {
        flush(callback);
      });
    });
  };
  
  /**
   * DEPRECATED: Use `flush`.
   * @param {function} callback
   */
  window.asyncPlatformFlush = function asyncPlatformFlush(callback) {
    console.warn('asyncPlatformFlush is deprecated in favor of the more terse flush()');
    return window.flush(callback);
  };
  
  /**
   *
   */
  window.waitFor = function waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime) {
    timeoutTime = timeoutTime || Date.now() + (timeout || 1000);
    intervalOrMutationEl = intervalOrMutationEl || 32;
    try {
      fn();
    } catch (e) {
      if (Date.now() > timeoutTime) {
        throw e;
      } else {
        if (isNaN(intervalOrMutationEl)) {
          intervalOrMutationEl.onMutation(intervalOrMutationEl, function() {
            waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
          });
        } else {
          setTimeout$1(function() {
            waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
          }, intervalOrMutationEl);
        }
        return;
      }
    }
    next();
  };
  
  // You can configure WCT before it has loaded by assigning your custom
  // configuration to the global `WCT`.
  setup(window.WCT);
  
  // Maybe some day we'll expose WCT as a module to whatever module registry you
  // are using (aka the UMD approach), or as an es6 module.
  var WCT = window.WCT = {};
  // A generic place to hang data about the current suite. This object is reported
  // back via the `sub-suite-start` and `sub-suite-end` events.
  WCT.share = {};
  // Until then, we get to rely on it to expose parent runners to their children.
  WCT._ChildRunner = ChildRunner;
  WCT._config      = _config;
  
  
  // Public Interface
  
  /**
   * Loads suites of tests, supporting both `.js` and `.html` files.
   *
   * @param {!Array.<string>} files The files to load.
   */
  WCT.loadSuites = loadSuites;
  
  
  // Load Process
  
  listenForErrors();
  stubInterfaces();
  loadSync();
  
  // Give any scripts on the page a chance to declare tests and muck with things.
  document.addEventListener('DOMContentLoaded', function() {
    debug('DOMContentLoaded');
  
    ensureDependenciesPresent();
  
    // We need the socket built prior to building its reporter.
    CLISocket.init(function(error, socket) {
      if (error) throw error;
  
      // Are we a child of another run?
      var current = ChildRunner.current();
      var parent  = current && current.parentScope.WCT._reporter;
      debug('parentReporter:', parent);
  
      var childSuites    = activeChildSuites();
      var reportersToUse = determineReporters(socket, parent);
      // +1 for any local tests.
      var reporter = new MultiReporter(childSuites.length + 1, reportersToUse, parent);
      WCT._reporter = reporter; // For environment/compatibility.js
  
      // We need the reporter so that we can report errors during load.
      loadJsSuites(reporter, function(error) {
        // Let our parent know that we're about to start the tests.
        if (current) current.ready(error);
        if (error) throw error;
  
        // Emit any errors we've encountered up til now
        globalErrors.forEach(function onError(error) {
          reporter.emitOutOfBandTest('Test Suite Initialization', error);
        });
  
        runSuites(reporter, childSuites, function(error) {
          // Make sure to let our parent know that we're done.
          if (current) current.done();
          if (error) throw error;
        });
      });
    });
  });
  
  }());
  //# sourceMappingURL=browser.js.map