Repository: microsoft/playwright
File: packages/playwright/src/worker/testInfo.ts (TypeScript)

Code before:
      // consider it a timeout.
      if (!this._interruptedPromise.isDone() && (error instanceof TimeoutManagerError))
        this._failWithError(error);
      throw error;
    }
  }

  _isFailure() {
    return this.status !== 'skipped' && this.status !== this.expectedStatus;
  }

  _currentHookType() {
    const type = this._timeoutManager.currentSlotType();
    return ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].includes(type) ? type : undefined;
  }

  _setDebugMode() {
    this._timeoutManager.setIgnoreTimeouts();
  }

  async _didFinishTestFunction() {
    const shouldPause = (this._workerParams.pauseAtEnd && !this._isFailure()) || (this._workerParams.pauseOnError && this._isFailure());
    if (shouldPause) {
      const location = (this._isFailure() ? this._errorLocation() : await this._testEndLocation()) ?? { file: this.file, line: this.line, column: this.column };
      const step = this._addStep({ category: 'hook', title: 'Paused', location });
      const { action } = await this._callbacks.onTestPaused!({ testId: this.testId, stepId: step.stepId, errors: this._isFailure() ? this.errors : [] });
      step.complete({});
      if (action === 'abort')
        this._interrupt();
    }
    await this._onDidFinishTestFunctionCallback?.();
  }

  private _errorLocation(): Location | undefined {
    if (this.error?.stack)
      return filteredStackTrace(this.error.stack.split('\n'))[0];
  }

  private async _testEndLocation(): Promise<Location | undefined> {
    try {
      const source = await fs.promises.readFile(this.file, 'utf-8');
      return findTestEndLocation(source, { file: this.file, line: this.line, column: this.column });
    } catch {}
  }

  // ------------ TestInfo methods ------------

  async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) {
    const step = this._addStep({
      title: `Attach ${escapeWithQuotes(name, '"')}`,
      category: 'test.attach',

Review comment (suggestion):
We should race this against `this._interruptedPromise` - otherwise user won't be able to Ctrl+C while on pause.

Code after:
      // consider it a timeout.
      if (!this._interruptedPromise.isDone() && (error instanceof TimeoutManagerError))
        this._failWithError(error);
      throw error;
    }
  }

  _isFailure() {
    return this.status !== 'skipped' && this.status !== this.expectedStatus;
  }

  _currentHookType() {
    const type = this._timeoutManager.currentSlotType();
    return ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].includes(type) ? type : undefined;
  }

  _setDebugMode() {
    this._timeoutManager.setIgnoreTimeouts();
  }

  async _didFinishTestFunction() {
    const shouldPause = (this._workerParams.pauseAtEnd && !this._isFailure()) || (this._workerParams.pauseOnError && this._isFailure());
    if (shouldPause) {
      const location = (this._isFailure() ? this._errorLocation() : await this._testEndLocation()) ?? { file: this.file, line: this.line, column: this.column };
      const step = this._addStep({ category: 'hook', title: 'Paused', location });
      const result = await Promise.race([
        this._callbacks.onTestPaused!({ testId: this.testId, stepId: step.stepId, errors: this._isFailure() ? this.errors : [] }),
        this._interruptedPromise.then(() => 'interrupted' as const),
      ]);
      step.complete({});
      if (result !== 'interrupted') {
        if (result.action === 'abort')
          this._interrupt();
        if (result.action === undefined)
          await this._interruptedPromise;
      }
    }
    await this._onDidFinishTestFunctionCallback?.();
  }

  private _errorLocation(): Location | undefined {
    if (this.error?.stack)
      return filteredStackTrace(this.error.stack.split('\n'))[0];
  }

  private async _testEndLocation(): Promise<Location | undefined> {
    try {
      const source = await fs.promises.readFile(this.file, 'utf-8');
      return findTestEndLocation(source, { file: this.file, line: this.line, column: this.column });
    } catch {}
  }