メトリクス取得のためのPlaywrightのレポート機能の活用

## はじめに

この記事は、Playwright のレポート機能を活用して、テスト実行結果からメトリクスを取得する方法を紹介する。

## Playwright のレポート機能

[Playwright のレポート機能](https://playwright.dev/docs/test-reporters)には、[カスタムレポーター](https://playwright.dev/docs/test-reporters#custom-reporters)がある。このレポーターは、[ビルトインのレポーター](https://playwright.dev/docs/test-reporters#built-in-reporters)の他に、独自に実装したレポーターを使用して、テスト結果をレポートとして出力することができる。

## カスタムレポーターの実装

### レポータークラスの実装

カスタムレポーターを実装するためには、[`Reporter`](https://playwright.dev/docs/api/class-reporter)クラスを拡張したクラスを定義する。

```typescript:src/custom-reporter/index.ts
class CustomReporter implements Reporter {
    // implement custom reporter
}

export default CustomReporter;
```

このクラスでは、主に以下のメソッドを定義することで、テスト実行のメトリクスに必要な値をレポート出力することができる。

- [`onEnd(fullResult: FullResult): void`](https://playwright.dev/docs/api/class-reporter#reporter-on-end): テスト実行終了時に呼び出される
- [`onTestEnd(test: Test, result: TestResult): void`](https://playwright.dev/docs/api/class-reporter#reporter-on-test-end): テストケースの実行終了時に呼び出される

このレポーターを使用するためには、`playwright.config.ts` で `reporter` オプションにカスタムレポーターを指定する。

```typescript:playwright.config.ts
export default defineConfig({
  reporter: [
    [
      "./src/custom-reporter/index.ts"
    ],
  ]
})
```

### レポーターのオプション定義

カスタムレポーターのクラスのコンストラクタの引数でオプションを定義することができる。
例えば、出力するレポートのファイル名の指定などで用いることができる。

```typescript:src/custom-reporter/index.ts
class CustomReporter implements Reporter {
  constructor(options: {filename: string} = {}) {
    this.filename = options.filename ?? "report.json";
  }
}
```

オプションの指定は、`playwright.config.ts` で以下のように指定する。

```typescript:playwright.config.ts
export default defineConfig({
  reporter: [
      // タプルの2番目の要素にオプションを指定する
    [
      "./src/custom-reporter/index.ts",
      {
        filename: "custom-report.json"
      }
    ],
  ]
})
```

## テスト実行のメトリクス

よく利用すると思われるメトリクスを以下に示す。

### `onEnd` メソッドで取得できるメトリクス

- テスト実行開始時刻: `FullResult.startTime: Date`
- テスト実行結果: `FullResult.status: 'passed' | 'failed' | 'timedout' | 'interrupted'`
- テスト実行時間 (ミリ秒単位): `FullResult.duration: number`

### `onTestEnd` メソッドで取得できるメトリクス

- テストケースの実行結果: [`TestCase.outcome`](https://playwright.dev/docs/api/class-testcase#test-case-outcome)
- テストケースの実行時間 (ミリ秒単位): [`TestResult.duration`](https://playwright.dev/docs/api/class-testresult#test-result-duration)

### 実装例

以下にJSONファイルに上記のメトリクスを出力する例を示す。

```typescript:src/custom-reporter/index.ts
import {
  TestCase,
  TestResult,
  Reporter,
  FullResult,
} from "@playwright/test/reporter";

type ResultOfTestCase = {
  id: string;
  outcome:   "skipped" | "expected" | "unexpected" | "flaky";
  durationInMs: number;
}

type ResultOfTestSuite = {
  startedAt: number;
  durationInMs: number;
  status: "passed"|"failed"|"timedout""interrupted"|"unknown";
  results: ResultOfTestCase[],
}


class CustomReporter implements Reporter {
  results: ResultOfTestCase[] = [];
  private filename: string;
  constructor(options: {filename: string} = {}) {
    this.filename = options.filename ?? "report.json";
  }

  onTestEnd(testCase: TestCase, testResult: TestResult): void {
    const result: ResultOfTestCase = {
      id: testCase.id,
      outcome: testCase.outcome(),
      durationInMs: testResult.duration,
    };
    this.results.push(result);
  }

  onEnd(fullResult: FullResult) {
    const result: ResultOfTestSuite = {
      startedAt: fullResult.startTime.getTime(),
      durationInMs: fullResult.duration,
      status: fullResult.status,
      results: this.results,
    };
    fs.writeFileSync(
      this.filename,
      JSON.stringify(report, null, 2),
    );
  }
}
```

<!-- zenn article id: 3b8703c05611e4 -->