直前の実行時に失敗したテストケースのみ再実行 in Playwright

:::note alert
(2024/05/07 追記)
Playwright [v1.44.0](https://github.com/microsoft/playwright/releases/tag/v1.44.0) のリリースで CLI のオプションに `--last-failed` が追加された。このオプションを使用すれば、「直前に実行したテストの結果から失敗したテストケースのみを再実行する」を実現できる。
:::

## はじめに

### 実現したいこと

[Playwright](https://playwright.dev/) では公式ドキュメントの [Retries](https://playwright.dev/docs/test-retries) のページで紹介されているように実行中に失敗したケースを再実行する機能が提供されている。この機能はテスト実行時に何かしらの要因によって失敗したテストケース、すなわち不安定なテストケースを同じテスト実行のプロセス中に再実行する機能である。しかし、テスト実行が完了した結果、失敗したテストケースのみを再度実行する機能は Playwright では提供されていない。この記事では、直前に実行したテストの結果から失敗したテストケースのみを再実行する方法を紹介する。

### 使い所

例えば、テスト実行時にデータベースに特定のテストデータを用意しておく必要がある特定のテストケースがあるとする。テスト実行時に、この必要なテストデータが用意できていない、もしくは意図したものになっていない場合、そのテストケースは失敗する。他のテストケースには、そのテストデータは依存しないため、テスト実行時に問題なく成功する。テスト実行後にテストデータの欠陥に気づき、テストデータを修正した後に、そのテストケースのみを再実行したいという場合に、前述の「以前に実行したテストの結果から失敗したテストケースのみを再実行する方法」があると便利である。

## 実装

### Playwright で特定のテストケースを実行する方法

公式ドキュメントの [Command line](https://playwright.dev/docs/test-cli) で紹介されているように、以下の指定方法がある。

- `filePath:lineNumber` の指定

    ```shell
    npx playwright test my-spec.ts:42
    ```

- `testTitle` の指定

    ```shell
    npx playwright test -g "add a todo item"
    ```

今回はひとつ目の `filePath:lineNumber` を指定する方法を使用する。

### レポート機能による実行結果の保存

前述の `filePath:lineNumber` の情報をテスト実行時に保存する必要がある。Playwright では、テスト実行結果をレポートとして保存する機能が提供されている。この機能を使用して、テスト実行結果を保存する。レポートの出力には、独自実装したレポート機能を使用することができる。テストケースごとに `filePath:lineNumber` の情報と `outcome="expected"|"unexpected"|"skipped"|"flaky"` の情報を含む配列を JSON 形式で出力するレポート機能を実装した。以下のリポジトリで公開している。

https://github.com/aYukiYoshida/playwright-simple-json-reporter

レポートの出力例を以下に示す。

```JSON
{
  "startedAt": 1713507478073,
  "durationInMs": 27545.525999999998,
  "status": "passed",
  "results": [
    {
      "id": "308f7d0e05acf652cf55-dff0c71519df34ca7ba8",
      "project": "setup",
      "location": "setup/login.setup.ts:22:5",
      "title": "Login and Setup as Bob",
      "outcome": "expected",
      "durationInMs": 5113
    },
    {
      "id": "884fc53766d15c58cb3b-f843d31e795e7fc28ff0",
      "project": "chrome",
      "location": "tests/chat.spec.ts:129:7",
      "title": "Send a chat message",
      "outcome": "expected",
      "durationInMs": 6602
    },
    {
      "id": "269790c8a8e01a6a79d9-a66c0172f374246b12a5",
      "project": "teardown",
      "location": "teardown/chat.teardown.ts:77:7",
      "title": "Delete chat messages",
      "outcome": "expected",
      "durationInMs": 10126
    }
  ]
}
```

### 実行ごとにレポートの出力先を変更する

Playwright の GitHub リポジトリの [Issue#19010](https://github.com/microsoft/playwright/issues/19010) を参考にして、実行した時刻をフォルダ名に含めて、実行ごとにレポートの出力先を変更するようにした。
また、一意に最新の結果のフォルダを参照するために、シンボリックリンクを作成するようにした。ここでは、そのスクリプトは割愛する。

```typescript:playwright.config.ts
import moment from "moment";
import { defineConfig } from "@playwright/test";

const reportFolder = `report/report-${moment().format("YYYY-MM-DD[T]HH-mm-ss")`;

export default defineConfig({
  reporter: [
    [
      "playwright-simple-json-reporter",
      {
        outputFolder: reportFolder,
        name: "result.json",
        testMatch: /.*\.spec\.ts/,
      },
    ],
  ],
});
```

### 前回失敗したテストケースのみを再実行する

`outcome` が `unexpected` のテストケースのみを実行する。

```javascript
const report = JSON.parse(fs.readFileSync(latestReport).toString());
if (report.status !== "passed"){
  const targets = report.results.filter(
    (result) => result.outcome === "unexpected"
  ).map((result) => result.location);
  proc.execSync(`npm test -- ${targets.join(" ")} ${process.argv.slice(2).join(" ")}`, {stdio: 'inherit'});
} else {
  console.info("There is no failed test case in the latest report");
}
```


<!-- zenn article id: 8d6e4edbdc0627 -->