text: const { existsSync, readFileSync } = require('fs');
  const glob = require('glob');
  const fsPath = require('path');
  const { resolve } = require('path');
  
  const container = require('./container');
  const Config = require('./config');
  const event = require('./event');
  const runHook = require('./hooks');
  const output = require('./output');
  
  /**
   * CodeceptJS runner
   */
  class Codecept {
    /**
     * Create CodeceptJS runner.
     * Config and options should be passed
     *
     * @param {*} config
     * @param {*} opts
     */
    constructor(config, opts) {
      this.config = Config.create(config);
      this.opts = opts;
      this.testFiles = new Array(0);
      this.requireModules(config.require);
    }
  
    /**
     * Require modules before codeceptjs running
     *
     * @param {string[]} requiringModules
     */
    requireModules(requiringModules) {
      if (requiringModules) {
        requiringModules.forEach((requiredModule) => {
          const isLocalFile = existsSync(requiredModule) || existsSync(`${requiredModule}.js`);
          if (isLocalFile) {
            requiredModule = resolve(requiredModule);
          }
          require(requiredModule);
        });
      }
    }
  
    /**
     * Initialize CodeceptJS at specific directory.
     * If async initialization is required, pass callback as second parameter.
     *
     * @param {string} dir
     */
    init(dir) {
      this.initGlobals(dir);
      // initializing listeners
      container.create(this.config, this.opts);
      this.runHooks();
    }
  
    /**
     * Creates global variables
     *
     * @param {string} dir
     */
    initGlobals(dir) {
      global.codecept_dir = dir;
      global.output_dir = fsPath.resolve(dir, this.config.output);
  
      if (!this.config.noGlobals) {
        global.actor = global.codecept_actor = require('./actor');
        global.Helper = global.codecept_helper = require('./helper');
        global.pause = require('./pause');
        global.within = require('./within');
        global.session = require('./session');
        global.DataTable = require('./data/table');
        global.locate = locator => require('./locator').build(locator);
        global.inject = container.support;
        global.secret = require('./secret').secret;
        global.codeceptjs = require('./index'); // load all objects
  
        // BDD
        const stepDefinitions = require('./interfaces/bdd');
        global.Given = stepDefinitions.Given;
        global.When = stepDefinitions.When;
        global.Then = stepDefinitions.Then;
      }
    }
  
    /**
     * Executes hooks.
     */
    runHooks() {
      // default hooks
      runHook(require('./listener/steps'));
      runHook(require('./listener/config'));
      runHook(require('./listener/helpers'));
      runHook(require('./listener/exit'));
      runHook(require('./listener/trace'));
  
      // custom hooks
      this.config.hooks.forEach(hook => runHook(hook));
    }
  
    /**
     * Executes bootstrap.
     * If bootstrap is async, second parameter is required.
     *
     * @param {Function} [done]
     */
    runBootstrap(done) {
      runHook(this.config.bootstrap, done, 'bootstrap');
    }
  
    /**
     * Executes teardown.
     * If teardown is async a parameter is provided.
     *
     * @param {*} done
     */
    teardown(done = undefined) {
      runHook(this.config.teardown, done, 'teardown');
    }
  
    /**
     * Loads tests by pattern or by config.tests
     *
     * @param {string} [pattern]
     */
    loadTests(pattern) {
      const options = {
        cwd: global.codecept_dir,
      };
  
      let patterns = [pattern];
      if (!pattern) {
        patterns = [];
        if (this.config.tests && !this.opts.features) patterns.push(this.config.tests);
        if (this.config.gherkin.features && !this.opts.tests) patterns.push(this.config.gherkin.features);
      }
  
      for (pattern of patterns) {
        glob.sync(pattern, options).forEach((file) => {
          if (!fsPath.isAbsolute(file)) {
            file = fsPath.join(global.codecept_dir, file);
          }
          this.testFiles.push(fsPath.resolve(file));
        });
      }
    }
  
    /**
     * Run a specific test or all loaded tests.
     *
     * @param {string} [test]
     */
    run(test) {
      const mocha = container.mocha();
      mocha.files = this.testFiles;
      if (test) {
        if (!fsPath.isAbsolute(test)) {
          test = fsPath.join(global.codecept_dir, test);
        }
        mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
      }
      const done = () => {
        event.emit(event.all.result, this);
        event.emit(event.all.after, this);
      };
  
      try {
        event.emit(event.all.before, this);
        mocha.run(() => this.teardown(done));
      } catch (e) {
        this.teardown(done);
        output.error(e.stack);
        throw new Error(e);
      }
    }
  
    static version() {
      return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version;
    }
  }
  
  module.exports = Codecept;
  