Repository: microsoft/playwright
File: packages/playwright/src/runner/dispatcher.ts (TypeScript)

Code before:
      // The test has finished, but attachments are still coming. Just ignore them.
      return;
    }
    const attachment = {
      name: params.name,
      path: params.path,
      contentType: params.contentType,
      body: params.body !== undefined ? Buffer.from(params.body, 'base64') : undefined
    };
    data.result.attachments.push(attachment);
    if (params.stepId) {
      const step = data.steps.get(params.stepId);
      if (step)
        step.attachments.push(attachment);
      else
        this._reporter.onStdErr?.('Internal error: step id not found: ' + params.stepId);
    }
  }

  private _onErrors(params: TestErrorPayload) {
    const data = this._dataByTestId.get(params.testId)!;
    if (!data)
      return;
    const { result } = data;
    for (const error of params.errors)
      addLocationAndSnippetToError(this._config.config, error);
    result.errors.push(...params.errors);
    result.error = result.errors[0];
  }

  private _failTestWithErrors(test: TestCase, errors: TestError[]) {
    const runData = this._dataByTestId.get(test.id);
    // There might be a single test that has started but has not finished yet.
    let result: TestResult;
    if (runData) {
      result = runData.result;
    } else {
      result = test._appendTestResult();
      this._reporter.onTestBegin?.(test, result);
    }
    result.errors.push(...errors);
    result.error = result.errors[0];
    result.status = errors.length ? 'failed' : 'skipped';
    this._reportTestEnd(test, result);
    this._failedTests.add(test);
  }

  private _massSkipTestsFromRemaining(testIds: Set<string>, errors: TestError[]) {
    for (const test of this._remainingByTestId.values()) {
      if (!testIds.has(test.id))
        continue;

Review comment (bug):
I guess we should check `this._failureTracker.hasReachedMaxFailures()` similar to line 321 new code?

Code after:
      // The test has finished, but attachments are still coming. Just ignore them.
      return;
    }
    const attachment = {
      name: params.name,
      path: params.path,
      contentType: params.contentType,
      body: params.body !== undefined ? Buffer.from(params.body, 'base64') : undefined
    };
    data.result.attachments.push(attachment);
    if (params.stepId) {
      const step = data.steps.get(params.stepId);
      if (step)
        step.attachments.push(attachment);
      else
        this._reporter.onStdErr?.('Internal error: step id not found: ' + params.stepId);
    }
  }

  private _onTestErrors(params: TestErrorsPayload) {
    if (this._failureTracker.hasReachedMaxFailures()) {
      // Do not show more than one error to avoid confusion.
      return;
    }

    const data = this._dataByTestId.get(params.testId)!;
    if (!data)
      return;
    const { test, result } = data;
    for (const error of params.errors) {
      result.errors.push(error);
      result.error = result.errors[0];
      this._reporter.onTestError?.(test, result, error);
    }
  }

  private _failTestWithErrors(test: TestCase, errors: TestError[]) {
    const runData = this._dataByTestId.get(test.id);
    // There might be a single test that has started but has not finished yet.
    let result: TestResult;
    if (runData) {
      result = runData.result;
    } else {
      result = test._appendTestResult();
      this._reporter.onTestBegin?.(test, result);
    }
    for (const error of errors) {
      result.errors.push(error);
      result.error = result.errors[0];
      this._reporter.onTestError?.(test, result, error);
    }