"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.addTestCommand = addTestCommand;
exports.addGenerateHtmlCommand = addGenerateHtmlCommand;
exports.addShowHtmlCommand = addShowHtmlCommand;

var _fs = _interopRequireDefault(require("fs"));

var _open = _interopRequireDefault(require("open"));

var _path = _interopRequireDefault(require("path"));

var _runner = require("./runner");

var _profiler = require("./profiler");

var _loader = require("./loader");

var _htmlBuilder = require("./html/htmlBuilder");

var _httpServer = require("../utils/httpServer");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * 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.
 */

/* eslint-disable no-console */
const defaultTimeout = 30000;
const defaultReporter = process.env.CI ? 'dot' : 'list';
const tsConfig = 'playwright.config.ts';
const jsConfig = 'playwright.config.js';
const mjsConfig = 'playwright.config.mjs';
const defaultConfig = {
  preserveOutput: 'always',
  reporter: [[defaultReporter]],
  reportSlowTests: {
    max: 5,
    threshold: 15000
  },
  timeout: defaultTimeout,
  updateSnapshots: 'missing',
  workers: Math.ceil(require('os').cpus().length / 2)
};

function addTestCommand(program) {
  const command = program.command('test [test-filter...]');
  command.description('Run tests with Playwright Test');
  command.option('--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`);
  command.option('--headed', `Run tests in headed browsers (default: headless)`);
  command.option('-c, --config <file>', `Configuration file, or a test directory with optional "${tsConfig}"/"${jsConfig}"`);
  command.option('--forbid-only', `Fail if test.only is called (default: false)`);
  command.option('-g, --grep <grep>', `Only run tests matching this regular expression (default: ".*")`);
  command.option('-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`);
  command.option('--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`);
  command.option('-j, --workers <workers>', `Number of concurrent workers, use 1 to run in a single worker (default: number of CPU cores / 2)`);
  command.option('--list', `Collect all the tests and report them, but do not run`);
  command.option('--max-failures <N>', `Stop after the first N failures`);
  command.option('--output <dir>', `Folder for output artifacts (default: "test-results")`);
  command.option('--quiet', `Suppress stdio`);
  command.option('--repeat-each <N>', `Run each test N times (default: 1)`);
  command.option('--reporter <reporter>', `Reporter to use, comma-separated, can be ${_runner.builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${defaultReporter}")`);
  command.option('--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`);
  command.option('--shard <shard>', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`);
  command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: run all projects)`);
  command.option('--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})`);
  command.option('-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`);
  command.option('-x', `Stop after the first failure`);
  command.action(async (args, opts) => {
    try {
      await runTests(args, opts);
    } catch (e) {
      console.error(e);
      process.exit(1);
    }
  });
  command.on('--help', () => {
    console.log('');
    console.log('Arguments [test-filter...]:');
    console.log('  Pass arguments to filter test files. Each argument is treated as a regular expression.');
    console.log('');
    console.log('Examples:');
    console.log('  $ test my.spec.ts');
    console.log('  $ test --headed');
    console.log('  $ test --browser=webkit');
  });
}

function addGenerateHtmlCommand(program) {
  const command = program.command('generate-report');
  command.description('Generate HTML report');
  command.option('-c, --config <file>', `Configuration file, or a test directory with optional "${tsConfig}"/"${jsConfig}"`);
  command.option('--output <dir>', `Folder for output artifacts (default: "playwright-report")`, 'playwright-report');
  command.action(async opts => {
    await generateHTMLReport(opts);
  }).on('--help', () => {
    console.log('');
    console.log('Examples:');
    console.log('');
    console.log('  $ generate-report');
  });
}

function addShowHtmlCommand(program) {
  const command = program.command('show-report');
  command.description('Show HTML report for last run');
  command.option('-c, --config <file>', `Configuration file, or a test directory with optional "${tsConfig}"/"${jsConfig}"`);
  command.option('--output <dir>', `Folder for output artifacts (default: "playwright-report")`, 'playwright-report');
  command.action(async opts => {
    const output = await generateHTMLReport(opts);
    const server = new _httpServer.HttpServer();
    server.routePrefix('/', (request, response) => {
      let relativePath = request.url;
      if (relativePath === '/') relativePath = '/index.html';

      const absolutePath = _path.default.join(output, ...relativePath.split('/'));

      return server.serveFile(response, absolutePath);
    });
    (0, _open.default)(await server.start());
  }).on('--help', () => {
    console.log('');
    console.log('Examples:');
    console.log('');
    console.log('  $ show-report');
  });
}

async function generateHTMLReport(opts) {
  const output = opts.output;
  delete opts.output;
  const loader = await createLoader(opts);
  const outputFolders = new Set(loader.projects().map(p => p.config.outputDir));
  const reportFiles = new Set();

  for (const outputFolder of outputFolders) {
    const reportFolder = _path.default.join(outputFolder, 'report');

    const files = _fs.default.readdirSync(reportFolder).filter(f => f.endsWith('.report'));

    for (const file of files) reportFiles.add(_path.default.join(reportFolder, file));
  }

  new _htmlBuilder.HtmlBuilder([...reportFiles], output, loader.fullConfig().rootDir);
  return output;
}

async function createLoader(opts) {
  if (opts.browser) {
    const browserOpt = opts.browser.toLowerCase();
    if (!['all', 'chromium', 'firefox', 'webkit'].includes(browserOpt)) throw new Error(`Unsupported browser "${opts.browser}", must be one of "all", "chromium", "firefox" or "webkit"`);
    const browserNames = browserOpt === 'all' ? ['chromium', 'firefox', 'webkit'] : [browserOpt];
    defaultConfig.projects = browserNames.map(browserName => {
      return {
        name: browserName,
        use: {
          browserName
        }
      };
    });
  }

  const overrides = overridesFromOptions(opts);
  if (opts.headed) overrides.use = {
    headless: false
  };
  const loader = new _loader.Loader(defaultConfig, overrides);

  async function loadConfig(configFile) {
    if (_fs.default.existsSync(configFile)) {
      if (process.stdout.isTTY) console.log(`Using config at ` + configFile);
      const loadedConfig = await loader.loadConfigFile(configFile);
      if ('projects' in loadedConfig && opts.browser) throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`);
      return true;
    }

    return false;
  }

  async function loadConfigFromDirectory(directory) {
    const configNames = [tsConfig, jsConfig, mjsConfig];

    for (const configName of configNames) {
      if (await loadConfig(_path.default.resolve(directory, configName))) return true;
    }

    return false;
  }

  if (opts.config) {
    const configFile = _path.default.resolve(process.cwd(), opts.config);

    if (!_fs.default.existsSync(configFile)) throw new Error(`${opts.config} does not exist`);

    if (_fs.default.statSync(configFile).isDirectory()) {
      // When passed a directory, look for a config file inside.
      if (!(await loadConfigFromDirectory(configFile))) {
        // If there is no config, assume this as a root testing directory.
        loader.loadEmptyConfig(configFile);
      }
    } else {
      // When passed a file, it must be a config file.
      await loadConfig(configFile);
    }
  } else if (!(await loadConfigFromDirectory(process.cwd()))) {
    // No --config option, let's look for the config file in the current directory.
    // If not, scan the world.
    loader.loadEmptyConfig(process.cwd());
  }

  return loader;
}

async function runTests(args, opts) {
  await (0, _profiler.startProfiling)();
  const loader = await createLoader(opts);
  const filePatternFilters = args.map(arg => {
    const match = /^(.*):(\d+)$/.exec(arg);
    return {
      re: forceRegExp(match ? match[1] : arg),
      line: match ? parseInt(match[2], 10) : null
    };
  });
  const runner = new _runner.Runner(loader);
  const result = await runner.run(!!opts.list, filePatternFilters, opts.project || undefined);
  await (0, _profiler.stopProfiling)(undefined);
  if (result === 'sigint') process.exit(130);
  process.exit(result === 'passed' ? 0 : 1);
}

function forceRegExp(pattern) {
  const match = pattern.match(/^\/(.*)\/([gi]*)$/);
  if (match) return new RegExp(match[1], match[2]);
  return new RegExp(pattern, 'gi');
}

function overridesFromOptions(options) {
  const isDebuggerAttached = !!require('inspector').url();
  const shardPair = options.shard ? options.shard.split('/').map(t => parseInt(t, 10)) : undefined;
  return {
    forbidOnly: options.forbidOnly ? true : undefined,
    globalTimeout: isDebuggerAttached ? 0 : options.globalTimeout ? parseInt(options.globalTimeout, 10) : undefined,
    grep: options.grep ? forceRegExp(options.grep) : undefined,
    grepInvert: options.grepInvert ? forceRegExp(options.grepInvert) : undefined,
    maxFailures: options.x ? 1 : options.maxFailures ? parseInt(options.maxFailures, 10) : undefined,
    outputDir: options.output ? _path.default.resolve(process.cwd(), options.output) : undefined,
    quiet: options.quiet ? options.quiet : undefined,
    repeatEach: options.repeatEach ? parseInt(options.repeatEach, 10) : undefined,
    retries: options.retries ? parseInt(options.retries, 10) : undefined,
    reporter: options.reporter && options.reporter.length ? options.reporter.split(',').map(r => [resolveReporter(r)]) : undefined,
    shard: shardPair ? {
      current: shardPair[0],
      total: shardPair[1]
    } : undefined,
    timeout: isDebuggerAttached ? 0 : options.timeout ? parseInt(options.timeout, 10) : undefined,
    updateSnapshots: options.updateSnapshots ? 'all' : undefined,
    workers: options.workers ? parseInt(options.workers, 10) : undefined
  };
}

function resolveReporter(id) {
  if (_runner.builtInReporters.includes(id)) return id;

  const localPath = _path.default.resolve(process.cwd(), id);

  if (_fs.default.existsSync(localPath)) return localPath;
  return require.resolve(id, {
    paths: [process.cwd()]
  });
}