自然言語テストツール「kotoba」v0.0.1リリース - 203個のパターンマッチングで実現する高速アサーション

![kotoba - Natural Language Web Testing](https://raw.githubusercontent.com/0xkaz/kotoba/main/og_image_social.png)

## はじめに

2025年6月20日、Webアプリケーションのテストを自然言語で記述できるツール「kotoba」v0.0.1をリリースしました。この記事では、203個のパターンマッチングによる高速アサーション機能の実装と、段階的フォールバック戦略による6倍の性能改善について詳しく解説します。

## kotoba とは

kotobaは、自然言語でWebテストを記述できるPythonツールです。PlaywrightとLLMを組み合わせて、以下のような日本語の指示でブラウザ操作を自動化できます。

```
「ログイン」ボタンをクリックして
メールアドレス欄に「test@example.com」を入力
パスワード欄に「password123」を入力
「送信」ボタンをクリック
「ログイン成功」というメッセージが表示されていることを確認
```

## 課題: LLM処理のボトルネック

自然言語テストツールの最大の課題は処理速度です。すべての指示をLLMで処理すると：

- **平均処理時間**: 1.1-1.6秒/指示
- **原因**: LLMの推論処理が必要
- **影響**: 大規模なテストスイートで実行時間が膨大に

この課題を解決するため、頻出パターンを事前に定義し、LLMへの依存を最小限に抑える戦略を採用しました。

## 解決策: 段階的フォールバック戦略

### 1. アーキテクチャ設計

kotobaに以下の段階的処理フローを実装しました：

```
自然言語指示
    ↓
【Stage 1】アサーションパターンマッチング (< 1ms)
    ↓ (一致)
✅ アサーション実行
    ↓ (不一致)
【Stage 2】LLMによる一般アクション処理 (100-1000ms)
    ↓
🎯 ブラウザアクション実行
```

### 2. 25種類のアサーションタイプ

包括的なテスト検証のため、以下のアサーションタイプを実装：

```python
class AssertionType(Enum):
    # テキスト関連
    TEXT_EXISTS = "text_exists"
    TEXT_NOT_EXISTS = "text_not_exists"
    TEXT_EQUALS = "text_equals"
    TEXT_CONTAINS = "text_contains"
    
    # 要素関連
    ELEMENT_EXISTS = "element_exists"
    ELEMENT_VISIBLE = "element_visible"
    ELEMENT_ENABLED = "element_enabled"
    
    # URL/タイトル関連
    URL_CONTAINS = "url_contains"
    TITLE_CONTAINS = "title_contains"
    
    # フォーム関連
    INPUT_VALUE_EQUALS = "input_value_equals"
    CHECKBOX_CHECKED = "checkbox_checked"
    # ... 他13種類
```

### 3. 203個のパターンマッチング実装

自然言語の多様性に対応するため、以下のカテゴリで203個のパターンを実装：

#### 基本的な日本語パターン
```python
# テキスト存在確認
(r"[「""]?(.+?)[」""]?が(?:表示されて|出て|見えて)?(?:いる|いること|いることを)(?:確認|チェック|検証)", 
 AssertionType.TEXT_EXISTS, "text"),

# 丁寧語対応
(r"[「""]?(.+?)[」""]?が(?:表示されて|出て|見えて)?(?:いる|いること|いることを)(?:確認|チェック|検証)(?:します|してください|お願いします)", 
 AssertionType.TEXT_EXISTS, "text"),
```

#### 口語・疑問形パターン
```python
# 関西弁
(r"[「""]?(.+?)[」""]?(?:が|って)(?:出てる|見える)(?:で|やん|やんか|わ|な)(?:？|\?)?", 
 AssertionType.TEXT_EXISTS, "text"),

# 疑問形
(r"[「""]?(.+?)[」""]?(?:は|が)(?:表示されて|見えて)(?:いますか|いるでしょうか|いるか)(?:？|\?)?", 
 AssertionType.TEXT_EXISTS, "text"),
```

#### 英語・中国語パターン
```python
# 英語
(r"(?:verify|check|confirm|assert)(?:\s+that)?\s+[\"'](.+?)[\"']\s+(?:is|exists?|appears?|is\s+(?:visible|displayed|present))", 
 AssertionType.TEXT_EXISTS, "text"),

# 中国語
(r"(?:验证|检查|确认|断言)[\"'""](.+?)[\"'""](?:存在|显示|出现)", 
 AssertionType.TEXT_EXISTS, "text"),
```

#### 専門分野パターン
```python
# IT・Web業界
(r"[「""]?(.+?)[」""]?(?:が|の)(?:レンダリング|描画|出力)(?:が|は)(?:正常|適切|問題なし)(?:か|かどうか)(?:確認|検証|チェック)", 
 AssertionType.TEXT_EXISTS, "text"),

# フォーム要素
(r"[「""]?(.+?)[」""]?(?:ボタン|button|Button|按钮|按鈕)(?:が|は)(?:表示|存在|クリック可能|押せる|有効)(?:になって|で|に)(?:いる|いること)(?:を|について)?(?:確認|検証|チェック)", 
 AssertionType.ELEMENT_EXISTS, "button"),
```

## 実装の技術的詳細

### パターンマッチングエンジン

```python
class AssertionPatternMatcher:
    @classmethod
    def parse(cls, instruction: str) -> Optional[Dict[str, Any]]:
        for pattern, assertion_type, param_type in cls.PATTERNS:
            match = re.search(pattern, instruction)
            if match:
                # 柔軟な引用符対応
                text = match.groups()[0].strip()
                text = text.strip('「」""''\'\'""')
                return {
                    "type": assertion_type,
                    "expected": text,
                    "selector": None
                }
        return None  # LLMフォールバック
```

### アサーション実行エンジン

```python
class AssertionExecutor:
    async def execute(self, assertion: Assertion) -> AssertionResult:
        start_time = time.time()
        
        try:
            if assertion.type == AssertionType.TEXT_EXISTS:
                # 複数セレクター戦略で堅牢性確保
                selectors = [
                    f"text={assertion.expected}",
                    f"text=*{assertion.expected}*",
                    f":has-text('{assertion.expected}')"
                ]
                
                for selector in selectors:
                    elements = await self.page.query_selector_all(selector)
                    if elements:
                        return AssertionResult(passed=True, ...)
                        
        except Exception as e:
            return AssertionResult(passed=False, error_message=str(e))
```

## パフォーマンス改善結果

### 処理時間の劇的改善

| パターン数 | LLM利用率 | 平均処理時間 | 効率向上 |
|------------|-----------|--------------|----------|
| 54個 (初期) | 30% | 300ms | ベースライン |
| 130個 (中間) | 10% | 100ms | **3倍高速化** |
| 203個 (現在) | 5% | 50ms | **6倍高速化** |
| 500個 (目標) | 1% | 10ms | **30倍高速化** |

### テスト成功率の向上

- **成功率**: 100% (6/6テストケース)
- **エラー処理**: 堅牢なフォールバック機構
- **国際化対応**: 日本語、英語、中国語

## 実際の使用例

### YAML形式のテストケース
```yaml
name: "アサーション機能テスト"
base_url: "https://example.com"

test_cases:
  - name: "基本的なテキスト確認"
    steps:
      - instruction: "「Example Domain」が表示されていることを確認"
      - instruction: "URLにexample.comが含まれていることをチェック"
      
  - name: "口語表現での確認"
    steps:
      - instruction: "Example Domainって表示されてる？"
      - instruction: "Example Domainが見えてますか？"
```

### 実行結果
```json
{
  "assertion_result": {
    "type": "text_exists",
    "passed": true,
    "expected": "テキスト 'Example Domain' が1つ以上存在",
    "actual": 1,
    "execution_time_ms": 6.17
  }
}
```

## 今後の展望

### Phase 2: 機械学習支援パターン生成
- ログデータからの自動パターン抽出
- 使用頻度による動的最適化

### Phase 3: 究極の高速化
- 500個以上のパターン実装
- 1ms台の処理速度実現
- コミュニティ貢献によるパターンDB構築

## まとめ

kotobaのアサーション機能実装により、以下を達成しました：

1. **6倍の高速化**: 300ms → 50ms
2. **203個のパターン**: 多様な自然言語表現に対応
3. **100%テスト成功率**: 堅牢なエラーハンドリング
4. **多言語対応**: 日本語、英語、中国語

この取り組みは、自然言語処理とWebテスト自動化の融合において、新たな可能性を示しています。パターンマッチングとLLMを組み合わせることで、使いやすさと高速性を両立させることができました。

kotoba v0.0.1は2025年6月20日にリリースされ、オープンソースとして公開されています。今後も継続的な改善により、世界最高性能の自然言語テストツールを目指していきます。

---

**GitHub**: [kotoba](https://github.com/0xkaz/kotoba)  
**リリース日**: 2025年6月20日（v0.0.1）  
**技術スタック**: Python, Playwright, LLM, Regex