【生成AI×E2Eテスト】Shortestとは？

## はじめに

E2Eテストに生成AIを組み込んでいるShortestというライブラリを見つけたので、調査も兼ねて簡単に概要を記事にします！

### これまでのE2Eテストの課題
システムやサービスの品質保証のために 多かれ少なかれ`End-to-End (E2E) テスト`を書く機会は多いかと思います。しかし、E2Eテストには以下のような課題がありました。

- **テストコードが長い・複雑**  
  テストケースが増えるにつれて、テストコードの記述量やメンテナンスコストが膨れ上がり、バグ修正や機能追加に追随しづらくなる。  

- **UIや要素の変更に弱い**  
  デザインのリニューアルや小さなUI変更が起きるだけでテストが壊れることが多く、そのたびにコード修正が必要になる。  

Shortestはテストケースを英語や日本語などの文章として記述し、それをAIが解釈してPlaywrightを通じて実行するため、柔軟なテスト設計が可能です。さらに、自然言語の変更でシナリオを更新できるため、UI変更への追従やテスト仕様の見直しをより簡単に行えます。

## Shortestとは？
Shortestは、[Claude](https://www.anthropic.com/) と [Playwright](https://playwright.dev/) を組み合わせた「自然言語ベースのE2Eテスト」フレームワークです。  

https://github.com/anti-work/shortest

下記のような特徴があります。

- 自然言語ベースのE2E
  テストシナリオを英語や日本語の文章として記述し、AI（Claude）がその内容を解析してPlaywright経由でテストを実行する。 
- GitHub 2FA・Mailosaur などにも対応
  実際の認証フローやメール検証など、実運用のシナリオをAIテストに組み込みやすい。  
- DB連携やAPIテストも可能
  コールバック関数やAPIリクエスト機能を通じて、DB検証やREST APIも自然言語ベースでテストできる。 

## インストール・環境設定

### インストール

```bash
# npmの場合
npm install -D @antiwork/shortest

# pnpmの場合
pnpm add -D @antiwork/shortest
```

### 初期設定
#### 1. `shortest.config.ts` の作成
テストの実行設定やAnthropic APIキーを設定します。
```typescript
import type { ShortestConfig } from "@antiwork/shortest";

export default {
  headless: false,
  baseUrl: "http://localhost:3000",
  testPattern: "**/*.test.ts",
  anthropicKey: process.env.ANTHROPIC_API_KEY,
} satisfies ShortestConfig;

```

#### 2. `.shortest/` を `.gitignore` に追加
スクリーンショットやキャッシュが保存されるため、ソース管理から外します。
```bash
echo ".shortest/" >> .gitignore
```

#### 3. Anthropic APIキーを環境変数に設定
`ANTHROPIC_API_KEY` に自分のAPIキーを割り当てます。`.env.local` などに記載し、プロジェクト固有で管理しても問題ありません。

## 基本的なテストの書き方
Shortest では、自然言語の指示 + パラメータ を渡すことでE2Eテストを定義します。

```typescript:login.test.ts
import { shortest } from "@antiwork/shortest";

shortest("Login to the app using email and password", {
  email: process.env.GITHUB_USERNAME,
  password: process.env.GITHUB_PASSWORD,
});
```

`shortest("自然言語のシナリオ", パラメータ)` という形でテストを記述します。第2引数に記述するパラメータは事前にライブラリ側で定義されているわけではなく、任意のパラメータを指定します。（AIモデルが与えられたパラメータを解釈して、適切なテスト動作を実行します。）

`pnpm shortest` または `npx shortest` などで実行可能です。

### ※ 実行結果の例
下記は`login.test.ts`を指定してshortestを実行した例です。

```bash
name@mac src % pnpm shortest e2e/login.test.ts

📄 login.test.ts
Screenshot saved to: /Users/name/projects/test-project/src/.shortest/screenshots/screenshot-2025-01-03T09-29-32-259Z.png
Screenshot saved to: /Users/name/projects/test-project/src/.shortest/screenshots/screenshot-2025-01-03T09-29-35-529Z.png
⏳ Waiting for 3 seconds...
Screenshot saved to: /Users/name/projects/test-project/src/.shortest/screenshots/screenshot-2025-01-03T09-30-23-326Z.png
  ✓ Login to the app using email and password
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 Tests    1 passed (1)
 Duration   63.20s
 Start at   18:29:25

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
```

テスト実行後にスクリーンショットが`.shortest`に保存されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270253/9b88c147-aa10-8297-c333-307397c5b50e.png)

## コールバック関数（afterフックなど）の活用
テストの実行終了後、独自アサーションやDB検証を行いたい場合はフックを使えます。

```typescript
// afterフックでDBをチェックする例
import { shortest } from "@antiwork/shortest";
import { db } from "@/lib/db/drizzle";
import { users } from "@/lib/db/schema";
import { eq } from "drizzle-orm";

shortest("Login to the app using username and password", {
  username: process.env.USERNAME,
  password: process.env.PASSWORD,
}).after(async ({ page }) => {
  // ローカルストレージなどからユーザID取得
  const clerkId = await page.evaluate(() => {
    return window.localStorage.getItem("clerk-user");
  });

  if (!clerkId) {
    throw new Error("User not found in database");
  }

  // DB上で該当ユーザがいるか検証
  const [user] = await db
    .select()
    .from(users)
    .where(eq(users.clerkId, clerkId))
    .limit(1);

  expect(user).toBeDefined();
});
```

## 外部サービスやフック

### GitHub 2FA
Shortest は GitHubの二段階認証にも対応しています。`OTP Secret` を用意して以下のように実行することで、自動でワンタイムパスコードを生成し、2FAを通したログインテストが可能になります。

```bash
shortest --github-code --secret=<OTP_SECRET>
```

### Mailosaurによるメール検証
メール認証のテストで `Mailosaur` を用いる場合も、最初の自然言語指示で「ユーザ登録して確認メールを検証する」などと指定しておき、実行後にメール内容をアサートするフローを簡単に取り込むことができます。
専用のAPI機能やコールバックの中で、取得したメール内容を検証する形で組み合わせられます。

### Lifecycle Hooks（beforeAll, afterAllなど）
テスト全体を通して事前処理や後処理を行うには、`shortest.beforeAll` や `shortest.afterAll` が使えます。

```typescript
import { shortest } from "@antiwork/shortest";

shortest.beforeAll(async ({ page }) => {
  // loginなど共有の事前処理
});

shortest.afterAll(async ({ page }) => {
  // セッションを終了させるなどの後処理
});
```

## CI に組み込む
GitHub Actionsで実行させるためには下記のように実装します。
```yaml
name: E2E Tests
on: [push, pull_request]
jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Node
        uses: actions/setup-node@v2
        with:
          node-version: 18
      - name: Install dependencies
        run: pnpm install
      - name: Run tests in headless mode
        run: pnpm shortest --headless
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          # 他の必要なSecrets (例: GITHUB_TOTP_SECRET)など
```

## 導入時の注意点
### Anthropic APIのコスト
AIリクエスト量が多いほどAPIコストが増えるため、注意が必要。もっとAPI料金安くなってほしい...。（前述した「Login to the app using email and password」のような簡単なテストを実行した時のAPI料金は0.2$ほどでした。）

## 最後に
本記事では、Shortest を使った自然言語ベースのE2Eテストについて、基本的な導入から外部サービス連携・CI/CD統合までをざっくりとご紹介しました。テスト実行にかかるAPIの料金については追加で検証が必要そうですが、割と使えそうだなというのが所感なので、これから折を見て使っていこうと思います！

## 参考文献
https://github.com/anti-work/shortest