chromium/chrome/test/data/webui/mocha_adapter_simple.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Mocha adapter for WebUI tests.
 *   1) Uses `window.domAutomationController` to signal test completion
 *      (success or error) back to the WebUIMochaBrowserTest class instance.
 *   2) Emits console messages as Mocha tests are making progress.
 *
 * To use, include mocha.js and mocha_adapter_simple.js along with the Mocha
 * test code.
 */

// Messages passed back to WebUIMochaBrowserTest C++ class.
enum TestStatus {
  FAILURE = 'FAILURE',
  PENDING = 'PENDING',
  SUCCESS = 'SUCCESS',
}

class WebUiMochaBrowserTestReporter extends Mocha.reporters.Base {
  private indents_: number = 0;

  constructor(runner: Mocha.Runner, options: Mocha.MochaOptions) {
    super(runner, options);

    const stats = runner.stats!;

    const constants = Mocha.Runner.constants;
    runner
        .once(
            constants.EVENT_RUN_BEGIN,
            () => {
              console.info('start');
            })
        .on(constants.EVENT_SUITE_BEGIN,
            () => {
              this.increaseIndent_();
            })
        .on(constants.EVENT_SUITE_END,
            () => {
              this.decreaseIndent_();
            })
        .on(constants.EVENT_TEST_BEGIN,
            test => {
              console.info(`${this.indent_()}started: ${test.fullTitle()}`);
              window.domAutomationController.send(TestStatus.PENDING);
            })
        .on(constants.EVENT_TEST_PASS,
            test => {
              console.info(`${this.indent_()} passed: ${test.fullTitle()}`);
            })
        .on(constants.EVENT_TEST_FAIL,
            (test, err) => {
              let message = `${this.indent_()} failed: ${test.fullTitle()}\n`;

              if (err.stack) {
                message += err.stack;
              } else {
                message += err.toString();
              }

              console.info(message);
            })
        .once(constants.EVENT_RUN_END, () => {
          console.info(
              `end: ${stats.passes}/${stats.passes + stats.failures} ok`);
          const success = stats.failures === 0 && stats.passes > 0;
          console.info(
              'TEST all complete, status=' + (success ? 'PASS' : 'FAIL') +
              ', duration=' + Math.round(stats.duration!) + 'ms');
          if (stats.failures + stats.passes === 0) {
            console.info(
                'No tests were found. Look for any uncaught errors that might ' +
                'have caused this');
          }
          window.domAutomationController.send(
              success ? TestStatus.SUCCESS : TestStatus.FAILURE);
        });
  }

  private indent_() {
    return Array(this.indents_).join('  ');
  }

  private increaseIndent_() {
    this.indents_++;
  }

  private decreaseIndent_() {
    this.indents_--;
  }
}

// Helper function provided to make running a single Mocha test more robust.
function runMochaTest(suiteName: string, testName: string) {
  const escapedTestName = testName.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
  mocha.grep(new RegExp('^' + suiteName + ' ' + escapedTestName + '$')).run();
}

// Helper function provided to make running a single Mocha suite more robust.
function runMochaSuite(suiteName: string) {
  mocha.grep(new RegExp('^' + suiteName + ' ')).run();
}

Object.assign(window, {runMochaSuite, runMochaTest});

// Configure mocha.
mocha.setup({
  // Use TDD interface instead of BDD.
  ui: 'tdd',
  // Use custom reporter to interface with WebUIMochaBrowserTest C++ class.
  reporter: WebUiMochaBrowserTestReporter,
  // Mocha timeouts are set to 2 seconds initially. This isn't nearly enough for
  // slower bots (e.g., Dr. Memory). Disable timeouts globally, because the C++
  // will handle it (and has scaled timeouts for slower bots).
  timeout: '0',
});