"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

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

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

var _utils = require("../../utils/utils");

var _base = require("./base");

var _json = require("./json");

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.
 */
class HtmlReporter {
  constructor() {
    this._reportFolder = void 0;
    this._resourcesFolder = void 0;
    this._sourceProcessor = void 0;
    this.config = void 0;
    this.suite = void 0;
    this._reportFolder = _path.default.resolve(process.cwd(), process.env[`PLAYWRIGHT_HTML_REPORT`] || 'playwright-report');
    this._resourcesFolder = _path.default.join(this._reportFolder, 'resources');
    this._sourceProcessor = new SourceProcessor(this._resourcesFolder);

    _fs.default.mkdirSync(this._resourcesFolder, {
      recursive: true
    });

    const appFolder = _path.default.join(__dirname, '..', '..', 'web', 'htmlReport');

    for (const file of _fs.default.readdirSync(appFolder)) _fs.default.copyFileSync(_path.default.join(appFolder, file), _path.default.join(this._reportFolder, file));
  }

  onBegin(config, suite) {
    this.config = config;
    this.suite = suite;
  }

  async onEnd() {
    const stats = {
      expected: 0,
      unexpected: 0,
      skipped: 0,
      flaky: 0
    };
    this.suite.allTests().forEach(t => {
      ++stats[t.outcome()];
    });
    const output = {
      config: { ...this.config,
        rootDir: (0, _json.toPosixPath)(this.config.rootDir),
        projects: this.config.projects.map(project => {
          return {
            outputDir: (0, _json.toPosixPath)(project.outputDir),
            repeatEach: project.repeatEach,
            retries: project.retries,
            metadata: project.metadata,
            name: project.name,
            testDir: (0, _json.toPosixPath)(project.testDir),
            testIgnore: (0, _json.serializePatterns)(project.testIgnore),
            testMatch: (0, _json.serializePatterns)(project.testMatch),
            timeout: project.timeout
          };
        })
      },
      stats,
      suites: this.suite.suites.map(s => this._serializeSuite(s))
    };

    _fs.default.writeFileSync(_path.default.join(this._reportFolder, 'report.json'), JSON.stringify(output));
  }

  _relativeLocation(location) {
    if (!location) return {
      file: '',
      line: 0,
      column: 0
    };
    return {
      file: (0, _json.toPosixPath)(_path.default.relative(this.config.rootDir, location.file)),
      line: location.line,
      column: location.column,
      sha1: this._sourceProcessor.copySourceFile(location.file)
    };
  }

  _serializeSuite(suite) {
    return {
      title: suite.title,
      location: this._relativeLocation(suite.location),
      suites: suite.suites.map(s => this._serializeSuite(s)),
      tests: suite.tests.map(t => this._serializeTest(t))
    };
  }

  _serializeTest(test) {
    const testId = (0, _utils.calculateSha1)(test.titlePath().join('|'));
    return {
      testId,
      title: test.title,
      location: this._relativeLocation(test.location),
      expectedStatus: test.expectedStatus,
      timeout: test.timeout,
      annotations: test.annotations,
      retries: test.retries,
      ok: test.ok(),
      outcome: test.outcome(),
      results: test.results.map(r => this._serializeResult(testId, test, r))
    };
  }

  _serializeResult(testId, test, result) {
    return {
      retry: result.retry,
      workerIndex: result.workerIndex,
      startTime: result.startTime.toISOString(),
      duration: result.duration,
      status: result.status,
      error: result.error,
      failureSnippet: (0, _base.formatResultFailure)(test, result, '').join('') || undefined,
      attachments: this._createAttachments(testId, result),
      stdout: result.stdout,
      stderr: result.stderr,
      steps: this._serializeSteps(test, result.steps)
    };
  }

  _serializeSteps(test, steps) {
    return steps.map(step => {
      return {
        title: step.title,
        category: step.category,
        startTime: step.startTime.toISOString(),
        duration: step.duration,
        error: step.error,
        steps: this._serializeSteps(test, step.steps),
        failureSnippet: step.error ? (0, _base.formatError)(step.error, test.location.file) : undefined,
        ...this._sourceProcessor.processStackTrace(step.data.stack),
        log: step.data.log || undefined
      };
    });
  }

  _createAttachments(testId, result) {
    const attachments = [];

    for (const attachment of result.attachments) {
      if (attachment.path) {
        const sha1 = (0, _utils.calculateSha1)(attachment.path) + _path.default.extname(attachment.path);

        try {
          _fs.default.copyFileSync(attachment.path, _path.default.join(this._resourcesFolder, sha1));

          attachments.push({ ...attachment,
            body: undefined,
            sha1
          });
        } catch (e) {}
      } else if (attachment.body && isTextAttachment(attachment.contentType)) {
        attachments.push({ ...attachment,
          body: attachment.body.toString()
        });
      } else {
        const sha1 = (0, _utils.calculateSha1)(attachment.body) + '.dat';

        try {
          _fs.default.writeFileSync(_path.default.join(this._resourcesFolder, sha1), attachment.body);

          attachments.push({ ...attachment,
            body: undefined,
            sha1
          });
        } catch (e) {}
      }
    }

    if (result.stdout.length) attachments.push(this._stdioAttachment(testId, result, 'stdout'));
    if (result.stderr.length) attachments.push(this._stdioAttachment(testId, result, 'stderr'));
    return attachments;
  }

  _stdioAttachment(testId, result, type) {
    const sha1 = `${testId}.${result.retry}.${type}`;

    const fileName = _path.default.join(this._resourcesFolder, sha1);

    for (const chunk of type === 'stdout' ? result.stdout : result.stderr) {
      if (typeof chunk === 'string') _fs.default.appendFileSync(fileName, chunk + '\n');else _fs.default.appendFileSync(fileName, chunk);
    }

    return {
      name: type,
      contentType: 'application/octet-stream',
      sha1
    };
  }

}

function isTextAttachment(contentType) {
  if (contentType.startsWith('text/')) return true;
  if (contentType.includes('json')) return true;
  return false;
}

class SourceProcessor {
  constructor(resourcesFolder) {
    this.sourceCache = new Map();
    this.sha1Cache = new Map();
    this.resourcesFolder = void 0;
    this.resourcesFolder = resourcesFolder;
  }

  processStackTrace(stack) {
    stack = stack || [];
    const frames = [];
    let preview;

    for (const frame of stack) {
      if (!frame.file || !frame.line || !frame.column) continue;
      const sha1 = this.copySourceFile(frame.file);
      const jsonFrame = {
        file: frame.file,
        line: frame.line,
        column: frame.column,
        sha1
      };
      frames.push(jsonFrame);
      if (frame === stack[0]) preview = this.readPreview(jsonFrame);
    }

    return {
      stack: frames,
      preview
    };
  }

  copySourceFile(file) {
    let sha1;

    if (this.sha1Cache.has(file)) {
      sha1 = this.sha1Cache.get(file);
    } else {
      if (_fs.default.existsSync(file)) {
        sha1 = (0, _utils.calculateSha1)(file) + _path.default.extname(file);

        _fs.default.copyFileSync(file, _path.default.join(this.resourcesFolder, sha1));
      }

      this.sha1Cache.set(file, sha1);
    }

    return sha1;
  }

  readSourceFile(file) {
    let source;

    if (this.sourceCache.has(file)) {
      source = this.sourceCache.get(file);
    } else {
      try {
        const text = _fs.default.readFileSync(file, 'utf8');

        const lines = text.split('\n');
        const lineStart = [0];

        for (const line of lines) lineStart.push(lineStart[lineStart.length - 1] + line.length + 1);

        source = {
          text,
          lineStart
        };
      } catch (e) {}

      this.sourceCache.set(file, source);
    }

    return source;
  }

  readPreview(frame) {
    const source = this.readSourceFile(frame.file);
    if (source === undefined) return;
    if (frame.line - 1 >= source.lineStart.length) return;
    const text = source.text;
    const pos = source.lineStart[frame.line - 1] + frame.column - 1;
    return new SourceParser(text).readPreview(pos);
  }

}

const kMaxPreviewLength = 100;

class SourceParser {
  constructor(text) {
    this.text = void 0;
    this.pos = void 0;
    this.text = text;
  }

  readPreview(pos) {
    let prefix = '';
    this.pos = pos - 1;

    while (true) {
      if (this.pos < pos - kMaxPreviewLength) return;
      this.skipWhiteSpace(-1);
      if (this.text[this.pos] !== '.') break;
      prefix = '.' + prefix;
      this.pos--;
      this.skipWhiteSpace(-1);

      while (this.text[this.pos] === ')' || this.text[this.pos] === ']') {
        const expr = this.readBalancedExpr(-1, this.text[this.pos] === ')' ? '(' : '[', this.text[this.pos]);
        if (expr === undefined) return;
        prefix = expr + prefix;
        this.skipWhiteSpace(-1);
      }

      const id = this.readId(-1);
      if (id !== undefined) prefix = id + prefix;
    }

    if (prefix.length > kMaxPreviewLength) return;
    this.pos = pos;
    const suffix = this.readBalancedExpr(+1, ')', '(');
    if (suffix === undefined) return;
    return prefix + suffix;
  }

  skipWhiteSpace(dir) {
    while (this.pos >= 0 && this.pos < this.text.length && /[\s\r\n]/.test(this.text[this.pos])) this.pos += dir;
  }

  readId(dir) {
    const start = this.pos;

    while (this.pos >= 0 && this.pos < this.text.length && /[\p{L}0-9_]/u.test(this.text[this.pos])) this.pos += dir;

    if (this.pos === start) return;
    return dir === 1 ? this.text.substring(start, this.pos) : this.text.substring(this.pos + 1, start + 1);
  }

  readBalancedExpr(dir, stopChar, stopPair) {
    let result = '';
    let quote = '';
    let lastWhiteSpace = false;
    let balance = 0;
    const start = this.pos;

    while (this.pos >= 0 && this.pos < this.text.length) {
      if (this.pos < start - kMaxPreviewLength || this.pos > start + kMaxPreviewLength) return;
      let whiteSpace = false;

      if (quote) {
        whiteSpace = false;

        if (dir === 1 && this.text[this.pos] === '\\') {
          result = result + this.text[this.pos] + this.text[this.pos + 1];
          this.pos += 2;
          continue;
        }

        if (dir === -1 && this.text[this.pos - 1] === '\\') {
          result = this.text[this.pos - 1] + this.text[this.pos] + result;
          this.pos -= 2;
          continue;
        }

        if (this.text[this.pos] === quote) quote = '';
      } else {
        if (this.text[this.pos] === '\'' || this.text[this.pos] === '"' || this.text[this.pos] === '`') {
          quote = this.text[this.pos];
        } else if (this.text[this.pos] === stopPair) {
          balance++;
        } else if (this.text[this.pos] === stopChar) {
          balance--;

          if (!balance) {
            this.pos += dir;
            result = dir === 1 ? result + stopChar : stopChar + result;
            break;
          }
        }

        whiteSpace = /[\s\r\n]/.test(this.text[this.pos]);
      }

      const char = whiteSpace ? ' ' : this.text[this.pos];
      if (!lastWhiteSpace || !whiteSpace) result = dir === 1 ? result + char : char + result;
      lastWhiteSpace = whiteSpace;
      this.pos += dir;
    }

    return result;
  }

}

var _default = HtmlReporter;
exports.default = _default;