text: "use strict";
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.kDefaultConfigFiles = exports.builtInReporters = exports.Runner = void 0;
  
  var _rimraf = _interopRequireDefault(require("rimraf"));
  
  var fs = _interopRequireWildcard(require("fs"));
  
  var path = _interopRequireWildcard(require("path"));
  
  var _util = require("util");
  
  var _dispatcher = require("./dispatcher");
  
  var _util2 = require("./util");
  
  var _test = require("./test");
  
  var _loader = require("./loader");
  
  var _multiplexer = require("./reporters/multiplexer");
  
  var _dot = _interopRequireDefault(require("./reporters/dot"));
  
  var _github = _interopRequireDefault(require("./reporters/github"));
  
  var _line = _interopRequireDefault(require("./reporters/line"));
  
  var _list = _interopRequireDefault(require("./reporters/list"));
  
  var _json = _interopRequireDefault(require("./reporters/json"));
  
  var _junit = _interopRequireDefault(require("./reporters/junit"));
  
  var _empty = _interopRequireDefault(require("./reporters/empty"));
  
  var _html = _interopRequireDefault(require("./reporters/html"));
  
  var _minimatch = require("minimatch");
  
  var _webServer2 = require("./webServer");
  
  var _async = require("playwright-core/lib/utils/async");
  
  var _utils = require("playwright-core/lib/utils/utils");
  
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  /**
   * Copyright 2019 Google Inc. All rights reserved.
   * Modifications copyright (c) Microsoft Corporation.
   *
   * 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.
   */
  const removeFolderAsync = (0, _util.promisify)(_rimraf.default);
  const readDirAsync = (0, _util.promisify)(fs.readdir);
  const readFileAsync = (0, _util.promisify)(fs.readFile);
  const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs'];
  exports.kDefaultConfigFiles = kDefaultConfigFiles;
  
  class Runner {
    constructor(configOverrides, options = {}) {
      this._loader = void 0;
      this._reporter = void 0;
      this._internalGlobalSetups = [];
      this._loader = new _loader.Loader(options.defaultConfig || {}, configOverrides);
    }
  
    async loadConfigFromResolvedFile(resolvedConfigFile) {
      return this._loader.loadConfigFile(resolvedConfigFile);
    }
  
    loadEmptyConfig(configFileOrDirectory) {
      return this._loader.loadEmptyConfig(configFileOrDirectory);
    }
  
    static resolveConfigFile(configFileOrDirectory) {
      const resolveConfig = configFile => {
        if (fs.existsSync(configFile)) return configFile;
      };
  
      const resolveConfigFileFromDirectory = directory => {
        for (const configName of kDefaultConfigFiles) {
          const configFile = resolveConfig(path.resolve(directory, configName));
          if (configFile) return configFile;
        }
      };
  
      if (!fs.existsSync(configFileOrDirectory)) throw new Error(`${configFileOrDirectory} does not exist`);
  
      if (fs.statSync(configFileOrDirectory).isDirectory()) {
        // When passed a directory, look for a config file inside.
        const configFile = resolveConfigFileFromDirectory(configFileOrDirectory);
        if (configFile) return configFile; // If there is no config, assume this as a root testing directory.
  
        return null;
      } else {
        // When passed a file, it must be a config file.
        const configFile = resolveConfig(configFileOrDirectory);
        return configFile;
      }
    }
  
    async _createReporter(list) {
      const defaultReporters = {
        dot: list ? ListModeReporter : _dot.default,
        line: list ? ListModeReporter : _line.default,
        list: list ? ListModeReporter : _list.default,
        github: _github.default,
        json: _json.default,
        junit: _junit.default,
        null: _empty.default,
        html: _html.default
      };
      const reporters = [];
  
      for (const r of this._loader.fullConfig().reporter) {
        const [name, arg] = r;
  
        if (name in defaultReporters) {
          reporters.push(new defaultReporters[name](arg));
        } else {
          const reporterConstructor = await this._loader.loadReporter(name);
          reporters.push(new reporterConstructor(arg));
        }
      }
  
      if (process.env.PW_TEST_REPORTER) {
        const reporterConstructor = await this._loader.loadReporter(process.env.PW_TEST_REPORTER);
        reporters.push(new reporterConstructor());
      }
  
      const someReporterPrintsToStdio = reporters.some(r => {
        const prints = r.printsToStdio ? r.printsToStdio() : true;
        return prints;
      });
  
      if (reporters.length && !someReporterPrintsToStdio) {
        // Add a line/dot/list-mode reporter for convenience.
        // Important to put it first, jsut in case some other reporter stalls onEnd.
        if (list) reporters.unshift(new ListModeReporter());else reporters.unshift(!process.env.CI ? new _line.default({
          omitFailures: true
        }) : new _dot.default({
          omitFailures: true
        }));
      }
  
      return new _multiplexer.Multiplexer(reporters);
    }
  
    addInternalGlobalSetup(internalGlobalSetup) {
      this._internalGlobalSetups.push(internalGlobalSetup);
    }
  
    async runAllTests(options = {}) {
      var _this$_reporter$onEnd, _this$_reporter2, _legacyGlobalTearDown;
  
      this._reporter = await this._createReporter(!!options.listOnly);
  
      const config = this._loader.fullConfig();
  
      let legacyGlobalTearDown;
  
      if (process.env.PW_TEST_LEGACY_GLOBAL_SETUP_MODE) {
        legacyGlobalTearDown = await this._performGlobalSetup(config);
        if (!legacyGlobalTearDown) return {
          status: 'failed'
        };
      }
  
      const result = await (0, _async.raceAgainstTimeout)(() => this._run(!!options.listOnly, options.filePatternFilter || [], options.projectFilter), config.globalTimeout);
      let fullResult;
  
      if (result.timedOut) {
        var _this$_reporter$onErr, _this$_reporter;
  
        (_this$_reporter$onErr = (_this$_reporter = this._reporter).onError) === null || _this$_reporter$onErr === void 0 ? void 0 : _this$_reporter$onErr.call(_this$_reporter, createStacklessError(`Timed out waiting ${config.globalTimeout / 1000}s for the entire test run`));
        fullResult = {
          status: 'timedout'
        };
      } else {
        fullResult = result.result;
      }
  
      await ((_this$_reporter$onEnd = (_this$_reporter2 = this._reporter).onEnd) === null || _this$_reporter$onEnd === void 0 ? void 0 : _this$_reporter$onEnd.call(_this$_reporter2, fullResult));
      await ((_legacyGlobalTearDown = legacyGlobalTearDown) === null || _legacyGlobalTearDown === void 0 ? void 0 : _legacyGlobalTearDown()); // Calling process.exit() might truncate large stdout/stderr output.
      // See https://github.com/nodejs/node/issues/6456.
      // See https://github.com/nodejs/node/issues/12921
  
      await new Promise(resolve => process.stdout.write('', () => resolve()));
      await new Promise(resolve => process.stderr.write('', () => resolve()));
      return fullResult;
    }
  
    async listTestFiles(configFile, projectNames) {
      const filesByProject = await this._collectFiles([], projectNames);
      const report = {
        projects: []
      };
  
      for (const [project, files] of filesByProject) {
        report.projects.push({
          name: project.config.name,
          testDir: path.resolve(configFile, project.config.testDir),
          files: files
        });
      }
  
      return report;
    }
  
    async _run(list, testFileReFilters, projectNames) {
      const filesByProject = await this._collectFiles(testFileReFilters, projectNames);
      return await this._runFiles(list, filesByProject, testFileReFilters);
    }
  
    async _collectFiles(testFileReFilters, projectNames) {
      const testFileFilter = testFileReFilters.length ? (0, _util2.createFileMatcher)(testFileReFilters.map(e => e.re)) : () => true;
      let projectsToFind;
      let unknownProjects;
  
      if (projectNames) {
        projectsToFind = new Set();
        unknownProjects = new Map();
        projectNames.forEach(n => {
          const name = n.toLocaleLowerCase();
          projectsToFind.add(name);
          unknownProjects.set(name, n);
        });
      }
  
      const projects = this._loader.projects().filter(project => {
        if (!projectsToFind) return true;
        const name = project.config.name.toLocaleLowerCase();
        unknownProjects.delete(name);
        return projectsToFind.has(name);
      });
  
      if (unknownProjects && unknownProjects.size) {
        const names = this._loader.projects().map(p => p.config.name).filter(name => !!name);
  
        if (!names.length) throw new Error(`No named projects are specified in the configuration file`);
        const unknownProjectNames = Array.from(unknownProjects.values()).map(n => `"${n}"`).join(', ');
        throw new Error(`Project(s) ${unknownProjectNames} not found. Available named projects: ${names.map(name => `"${name}"`).join(', ')}`);
      }
  
      const files = new Map();
  
      for (const project of projects) {
        const allFiles = await collectFiles(project.config.testDir);
        const testMatch = (0, _util2.createFileMatcher)(project.config.testMatch);
        const testIgnore = (0, _util2.createFileMatcher)(project.config.testIgnore);
        const extensions = ['.js', '.ts', '.mjs', '.tsx', '.jsx'];
  
        const testFileExtension = file => extensions.includes(path.extname(file));
  
        const testFiles = allFiles.filter(file => !testIgnore(file) && testMatch(file) && testFileFilter(file) && testFileExtension(file));
        files.set(project, testFiles);
      }
  
      return files;
    }
  
    async _runFiles(list, filesByProject, testFileReFilters) {
      var _this$_reporter$onBeg, _this$_reporter3;
  
      const allTestFiles = new Set();
  
      for (const files of filesByProject.values()) files.forEach(file => allTestFiles.add(file));
  
      const config = this._loader.fullConfig();
  
      const fatalErrors = []; // 1. Add all tests.
  
      const preprocessRoot = new _test.Suite('');
  
      for (const file of allTestFiles) {
        const fileSuite = await this._loader.loadTestFile(file, 'runner');
        if (fileSuite._loadError) fatalErrors.push(fileSuite._loadError);
  
        preprocessRoot._addSuite(fileSuite);
      } // 2. Filter tests to respect column filter.
  
  
      filterByFocusedLine(preprocessRoot, testFileReFilters); // 3. Complain about only.
  
      if (config.forbidOnly) {
        const onlyTestsAndSuites = preprocessRoot._getOnlyItems();
  
        if (onlyTestsAndSuites.length > 0) fatalErrors.push(createForbidOnlyError(config, onlyTestsAndSuites));
      } // 4. Filter only
  
  
      if (!list) filterOnly(preprocessRoot); // 5. Complain about clashing.
  
      const clashingTests = getClashingTestsPerSuite(preprocessRoot);
      if (clashingTests.size > 0) fatalErrors.push(createDuplicateTitlesError(config, clashingTests)); // 6. Generate projects.
  
      const fileSuites = new Map();
  
      for (const fileSuite of preprocessRoot.suites) fileSuites.set(fileSuite._requireFile, fileSuite);
  
      const outputDirs = new Set();
      const rootSuite = new _test.Suite('');
  
      for (const [project, files] of filesByProject) {
        const grepMatcher = (0, _util2.createTitleMatcher)(project.config.grep);
        const grepInvertMatcher = project.config.grepInvert ? (0, _util2.createTitleMatcher)(project.config.grepInvert) : null;
        const projectSuite = new _test.Suite(project.config.name);
        projectSuite._projectConfig = project.config;
        if (project.config.fullyParallel) projectSuite._parallelMode = 'parallel';
  
        rootSuite._addSuite(projectSuite);
  
        for (const file of files) {
          const fileSuite = fileSuites.get(file);
          if (!fileSuite) continue;
  
          for (let repeatEachIndex = 0; repeatEachIndex < project.config.repeatEach; repeatEachIndex++) {
            const cloned = project.cloneFileSuite(fileSuite, repeatEachIndex, test => {
              const grepTitle = test.titlePath().join(' ');
              if (grepInvertMatcher !== null && grepInvertMatcher !== void 0 && grepInvertMatcher(grepTitle)) return false;
              return grepMatcher(grepTitle);
            });
            if (cloned) projectSuite._addSuite(cloned);
          }
        }
  
        outputDirs.add(project.config.outputDir);
      } // 7. Fail when no tests.
  
  
      let total = rootSuite.allTests().length;
      if (!total) fatalErrors.push(createNoTestsError()); // 8. Compute shards.
  
      let testGroups = createTestGroups(rootSuite);
      const shard = config.shard;
  
      if (shard) {
        const shardGroups = [];
        const shardTests = new Set(); // Each shard gets some tests.
  
        const shardSize = Math.floor(total / shard.total); // First few shards get one more test each.
  
        const extraOne = total - shardSize * shard.total;
        const currentShard = shard.current - 1; // Make it zero-based for calculations.
  
        const from = shardSize * currentShard + Math.min(extraOne, currentShard);
        const to = from + shardSize + (currentShard < extraOne ? 1 : 0);
        let current = 0;
  
        for (const group of testGroups) {
          // Any test group goes to the shard that contains the first test of this group.
          // So, this shard gets any group that starts at [from; to)
          if (current >= from && current < to) {
            shardGroups.push(group);
  
            for (const test of group.tests) shardTests.add(test);
          }
  
          current += group.tests.length;
        }
  
        testGroups = shardGroups;
        filterSuiteWithOnlySemantics(rootSuite, () => false, test => shardTests.has(test));
        total = rootSuite.allTests().length;
      }
  
      config.__testGroupsCount = testGroups.length; // 9. Report begin
  
      (_this$_reporter$onBeg = (_this$_reporter3 = this._reporter).onBegin) === null || _this$_reporter$onBeg === void 0 ? void 0 : _this$_reporter$onBeg.call(_this$_reporter3, config, rootSuite); // 10. Bail out on errors prior to running global setup.
  
      if (fatalErrors.length) {
        for (const error of fatalErrors) {
          var _this$_reporter$onErr2, _this$_reporter4;
  
          (_this$_reporter$onErr2 = (_this$_reporter4 = this._reporter).onError) === null || _this$_reporter$onErr2 === void 0 ? void 0 : _this$_reporter$onErr2.call(_this$_reporter4, error);
        }
  
        return {
          status: 'failed'
        };
      } // 11. Bail out if list mode only, don't do any work.
  
  
      if (list) return {
        status: 'passed'
      }; // 12. Remove output directores.
  
      try {
        await Promise.all(Array.from(outputDirs).map(outputDir => removeFolderAsync(outputDir).catch(async error => {
          if (error.code === 'EBUSY') {
            // We failed to remove folder, might be due to the whole folder being mounted inside a container:
            //   https://github.com/microsoft/playwright/issues/12106
            // Do a best-effort to remove all files inside of it instead.
            const entries = await readDirAsync(outputDir).catch(e => []);
            await Promise.all(entries.map(entry => removeFolderAsync(path.join(outputDir, entry))));
          } else {
            throw error;
          }
        })));
      } catch (e) {
        var _this$_reporter$onErr3, _this$_reporter5;
  
        (_this$_reporter$onErr3 = (_this$_reporter5 = this._reporter).onError) === null || _this$_reporter$onErr3 === void 0 ? void 0 : _this$_reporter$onErr3.call(_this$_reporter5, (0, _util2.serializeError)(e));
        return {
          status: 'failed'
        };
      } // 13. Run Global setup.
  
  
      let globalTearDown;
  
      if (!process.env.PW_TEST_LEGACY_GLOBAL_SETUP_MODE) {
        globalTearDown = await this._performGlobalSetup(config);
        if (!globalTearDown) return {
          status: 'failed'
        };
      }
  
      const result = {
        status: 'passed'
      }; // 14. Run tests.
  
      try {
        const sigintWatcher = new _utils.SigIntWatcher();
        let hasWorkerErrors = false;
        const dispatcher = new _dispatcher.Dispatcher(this._loader, testGroups, this._reporter);
        await Promise.race([dispatcher.run(), sigintWatcher.promise()]);
  
        if (!sigintWatcher.hadSignal()) {
          // We know for sure there was no Ctrl+C, so we remove custom SIGINT handler
          // as soon as we can.
          sigintWatcher.disarm();
        }
  
        await dispatcher.stop();
        hasWorkerErrors = dispatcher.hasWorkerErrors();
  
        if (!sigintWatcher.hadSignal()) {
          const failed = hasWorkerErrors || rootSuite.allTests().some(test => !test.ok());
          result.status = failed ? 'failed' : 'passed';
        } else {
          result.status = 'interrupted';
        }
      } catch (e) {
        var _this$_reporter$onErr4, _this$_reporter6;
  
        (_this$_reporter$onErr4 = (_this$_reporter6 = this._reporter).onError) === null || _this$_reporter$onErr4 === void 0 ? void 0 : _this$_reporter$onErr4.call(_this$_reporter6, (0, _util2.serializeError)(e));
        return {
          status: 'failed'
        };
      } finally {
        var _globalTearDown;
  
        await ((_globalTearDown = globalTearDown) === null || _globalTearDown === void 0 ? void 0 : _globalTearDown());
      }
  
      return result;
    }
  
    async _performGlobalSetup(config) {
      const result = {
        status: 'passed'
      };
      const internalGlobalTeardowns = [];
      let globalSetupResult;
      let webServer;
  
      const tearDown = async () => {
        await this._runAndReportError(async () => {
          if (globalSetupResult && typeof globalSetupResult === 'function') await globalSetupResult(this._loader.fullConfig());
        }, result);
        await this._runAndReportError(async () => {
          if (config.globalTeardown) await (await this._loader.loadGlobalHook(config.globalTeardown, 'globalTeardown'))(this._loader.fullConfig());
        }, result);
        await this._runAndReportError(async () => {
          var _webServer;
  
          await ((_webServer = webServer) === null || _webServer === void 0 ? void 0 : _webServer.kill());
        }, result);
        await this._runAndReportError(async () => {
          for (const internalGlobalTeardown of internalGlobalTeardowns) await internalGlobalTeardown();
        }, result);
      };
  
      await this._runAndReportError(async () => {
        for (const internalGlobalSetup of this._internalGlobalSetups) internalGlobalTeardowns.push(await internalGlobalSetup());
  
        webServer = config.webServer ? await _webServer2.WebServer.create(config.webServer, this._reporter) : undefined;
        if (config.globalSetup) globalSetupResult = await (await this._loader.loadGlobalHook(config.globalSetup, 'globalSetup'))(this._loader.fullConfig());
      }, result);
  
      if (result.status !== 'passed') {
        await tearDown();
        return;
      }
  
      return tearDown;
    }
  
    async _runAndReportError(callback, result) {
      try {
        await callback();
      } catch (e) {
        var _this$_reporter$onErr5, _this$_reporter7;
  
        result.status = 'failed';
        (_this$_reporter$onErr5 = (_this$_reporter7 = this._reporter).onError) === null || _this$_reporter$onErr5 === void 0 ? void 0 : _this$_reporter$onErr5.call(_this$_reporter7, (0, _util2.serializeError)(e));
      }
    }
  
  }
  
  exports.Runner = Runner;
  
  function filterOnly(suite) {
    const suiteFilter = suite => suite._only;
  
    const testFilter = test => test._only;
  
    return filterSuiteWithOnlySemantics(suite, suiteFilter, testFilter);
  }
  
  function filterByFocusedLine(suite, focusedTestFileLines) {
    const filterWithLine = !!focusedTestFileLines.find(f => f.line !== null);
    if (!filterWithLine) return;
  
    const testFileLineMatches = (testFileName, testLine) => focusedTestFileLines.some(({
      re,
      line
    }) => {
      re.lastIndex = 0;
      return re.test(testFileName) && (line === testLine || line === null);
    });
  
    const suiteFilter = suite => {
      return !!suite.location && testFileLineMatches(suite.location.file, suite.location.line);
    };
  
    const testFilter = test => testFileLineMatches(test.location.file, test.location.line);
  
    return filterSuite(suite, suiteFilter, testFilter);
  }
  
  function filterSuiteWithOnlySemantics(suite, suiteFilter, testFilter) {
    const onlySuites = suite.suites.filter(child => filterSuiteWithOnlySemantics(child, suiteFilter, testFilter) || suiteFilter(child));
    const onlyTests = suite.tests.filter(testFilter);
    const onlyEntries = new Set([...onlySuites, ...onlyTests]);
  
    if (onlyEntries.size) {
      suite.suites = onlySuites;
      suite.tests = onlyTests;
      suite._entries = suite._entries.filter(e => onlyEntries.has(e)); // Preserve the order.
  
      return true;
    }
  
    return false;
  }
  
  function filterSuite(suite, suiteFilter, testFilter) {
    for (const child of suite.suites) {
      if (!suiteFilter(child)) filterSuite(child, suiteFilter, testFilter);
    }
  
    suite.tests = suite.tests.filter(testFilter);
    const entries = new Set([...suite.suites, ...suite.tests]);
    suite._entries = suite._entries.filter(e => entries.has(e)); // Preserve the order.
  }
  
  async function collectFiles(testDir) {
    if (!fs.existsSync(testDir)) return [];
    if (!fs.statSync(testDir).isDirectory()) return [];
  
    const checkIgnores = (entryPath, rules, isDirectory, parentStatus) => {
      let status = parentStatus;
  
      for (const rule of rules) {
        const ruleIncludes = rule.negate;
        if (status === 'included' === ruleIncludes) continue;
        const relative = path.relative(rule.dir, entryPath);
  
        if (rule.match('/' + relative) || rule.match(relative)) {
          // Matches "/dir/file" or "dir/file"
          status = ruleIncludes ? 'included' : 'ignored';
        } else if (isDirectory && (rule.match('/' + relative + '/') || rule.match(relative + '/'))) {
          // Matches "/dir/subdir/" or "dir/subdir/" for directories.
          status = ruleIncludes ? 'included' : 'ignored';
        } else if (isDirectory && ruleIncludes && (rule.match('/' + relative, true) || rule.match(relative, true))) {
          // Matches "/dir/donotskip/" when "/dir" is excluded, but "!/dir/donotskip/file" is included.
          status = 'ignored-but-recurse';
        }
      }
  
      return status;
    };
  
    const files = [];
  
    const visit = async (dir, rules, status) => {
      const entries = await readDirAsync(dir, {
        withFileTypes: true
      });
      entries.sort((a, b) => a.name.localeCompare(b.name));
      const gitignore = entries.find(e => e.isFile() && e.name === '.gitignore');
  
      if (gitignore) {
        const content = await readFileAsync(path.join(dir, gitignore.name), 'utf8');
        const newRules = content.split(/\r?\n/).map(s => {
          s = s.trim();
          if (!s) return; // Use flipNegate, because we handle negation ourselves.
  
          const rule = new _minimatch.Minimatch(s, {
            matchBase: true,
            dot: true,
            flipNegate: true
          });
          if (rule.comment) return;
          rule.dir = dir;
          return rule;
        }).filter(rule => !!rule);
        rules = [...rules, ...newRules];
      }
  
      for (const entry of entries) {
        if (entry === gitignore || entry.name === '.' || entry.name === '..') continue;
        if (entry.isDirectory() && entry.name === 'node_modules') continue;
        const entryPath = path.join(dir, entry.name);
        const entryStatus = checkIgnores(entryPath, rules, entry.isDirectory(), status);
        if (entry.isDirectory() && entryStatus !== 'ignored') await visit(entryPath, rules, entryStatus);else if (entry.isFile() && entryStatus === 'included') files.push(entryPath);
      }
    };
  
    await visit(testDir, [], 'included');
    return files;
  }
  
  function getClashingTestsPerSuite(rootSuite) {
    function visit(suite, clashingTests) {
      for (const childSuite of suite.suites) visit(childSuite, clashingTests);
  
      for (const test of suite.tests) {
        const fullTitle = test.titlePath().slice(2).join(' ');
        if (!clashingTests.has(fullTitle)) clashingTests.set(fullTitle, []);
        clashingTests.set(fullTitle, clashingTests.get(fullTitle).concat(test));
      }
    }
  
    const out = new Map();
  
    for (const fileSuite of rootSuite.suites) {
      const clashingTests = new Map();
      visit(fileSuite, clashingTests);
  
      for (const [title, tests] of clashingTests.entries()) {
        if (tests.length > 1) out.set(title, tests);
      }
    }
  
    return out;
  }
  
  function buildItemLocation(rootDir, testOrSuite) {
    if (!testOrSuite.location) return '';
    return `${path.relative(rootDir, testOrSuite.location.file)}:${testOrSuite.location.line}`;
  }
  
  function createTestGroups(rootSuite) {
    // This function groups tests that can be run together.
    // Tests cannot be run together when:
    // - They belong to different projects - requires different workers.
    // - They have a different repeatEachIndex - requires different workers.
    // - They have a different set of worker fixtures in the pool - requires different workers.
    // - They have a different requireFile - reuses the worker, but runs each requireFile separately.
    // - They belong to a parallel suite.
    // Using the map "workerHash -> requireFile -> group" makes us preserve the natural order
    // of worker hashes and require files for the simple cases.
    const groups = new Map();
  
    const createGroup = test => {
      return {
        workerHash: test._workerHash,
        requireFile: test._requireFile,
        repeatEachIndex: test.repeatEachIndex,
        projectIndex: test._projectIndex,
        tests: []
      };
    };
  
    for (const projectSuite of rootSuite.suites) {
      for (const test of projectSuite.allTests()) {
        let withWorkerHash = groups.get(test._workerHash);
  
        if (!withWorkerHash) {
          withWorkerHash = new Map();
          groups.set(test._workerHash, withWorkerHash);
        }
  
        let withRequireFile = withWorkerHash.get(test._requireFile);
  
        if (!withRequireFile) {
          withRequireFile = {
            general: createGroup(test),
            parallel: []
          };
          withWorkerHash.set(test._requireFile, withRequireFile);
        }
  
        let insideParallel = false;
  
        for (let parent = test.parent; parent; parent = parent.parent) insideParallel = insideParallel || parent._parallelMode === 'parallel';
  
        if (insideParallel) {
          const group = createGroup(test);
          group.tests.push(test);
          withRequireFile.parallel.push(group);
        } else {
          withRequireFile.general.tests.push(test);
        }
      }
    }
  
    const result = [];
  
    for (const withWorkerHash of groups.values()) {
      for (const withRequireFile of withWorkerHash.values()) {
        if (withRequireFile.general.tests.length) result.push(withRequireFile.general);
        result.push(...withRequireFile.parallel);
      }
    }
  
    return result;
  }
  
  class ListModeReporter {
    onBegin(config, suite) {
      // eslint-disable-next-line no-console
      console.log(`Listing tests:`);
      const tests = suite.allTests();
      const files = new Set();
  
      for (const test of tests) {
        // root, project, file, ...describes, test
        const [, projectName,, ...titles] = test.titlePath();
        const location = `${path.relative(config.rootDir, test.location.file)}:${test.location.line}:${test.location.column}`;
        const projectTitle = projectName ? `[${projectName}] › ` : ''; // eslint-disable-next-line no-console
  
        console.log(`  ${projectTitle}${location} › ${titles.join(' ')}`);
        files.add(test.location.file);
      } // eslint-disable-next-line no-console
  
  
      console.log(`Total: ${tests.length} ${tests.length === 1 ? 'test' : 'tests'} in ${files.size} ${files.size === 1 ? 'file' : 'files'}`);
    }
  
  }
  
  function createForbidOnlyError(config, onlyTestsAndSuites) {
    const errorMessage = ['=====================================', ' --forbid-only found a focused test.'];
  
    for (const testOrSuite of onlyTestsAndSuites) {
      // Skip root and file.
      const title = testOrSuite.titlePath().slice(2).join(' ');
      errorMessage.push(` - ${buildItemLocation(config.rootDir, testOrSuite)} > ${title}`);
    }
  
    errorMessage.push('=====================================');
    return createStacklessError(errorMessage.join('\n'));
  }
  
  function createDuplicateTitlesError(config, clashingTests) {
    const errorMessage = ['========================================', ' duplicate test titles are not allowed.'];
  
    for (const [title, tests] of clashingTests.entries()) {
      errorMessage.push(` - title: ${title}`);
  
      for (const test of tests) errorMessage.push(`   - ${buildItemLocation(config.rootDir, test)}`);
    }
  
    errorMessage.push('========================================');
    return createStacklessError(errorMessage.join('\n'));
  }
  
  function createNoTestsError() {
    return createStacklessError(`=================\n no tests found.\n=================`);
  }
  
  function createStacklessError(message) {
    return {
      message
    };
  }
  
  const builtInReporters = ['list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html'];
  exports.builtInReporters = builtInReporters;