妙高市の方言チャットボットVerUp:Claude Code に実装を任せたら、ブラウザまで動かしてテストしてくれた
Claude Code に実装を任せたら、ブラウザまで動かしてテストしてくれた
前回の記事では、1ステップ方式と2ステップ方式を比較し、パターン A(直接方言回答)を採用することを決めました。
今回はいよいよフェーズ 3 の実装です。改修プランにそって Claude Code にコードの書き直しをお願いし、そのままテストまで進んでもらいました。
実装:Claude Code に丸投げした
改修のやることリストは、あらかじめ `improvement_plan.md` としてまとめてありました。「このプランに沿って `app.py` を改修して」と Claude Code に渡したところ、一気に実装してくれました。
主な変更点は 3 つです。
1. モデルを新しいものへ
# 変更前
model = "gpt-4o-mini"
# 変更後
MODEL = "gpt-5.4-mini"実験フェーズで使っていた `gpt-5.4-mini` に統一しました。定数 `MODEL` として管理するようになったので、次に変更するときも 1 箇所だけ直せば済みます。
2. API を Responses API に切り替え
会話履歴の管理方法が大きく変わりました。
$$
\begin{array}{|l|l|l|}
\hline
\text{項目} & \text{旧実装} & \text{新実装} \\
\hline
\text{API メソッド} & \texttt{chat.completions.create} & \texttt{responses.create} \\
\text{会話履歴} & \text{全メッセージを毎回送信} & \texttt{previous\_response\_id} \text{で参照} \\
\text{コンテキスト超過} & \text{自前管理なし} & \texttt{truncation="auto"} \text{で自動処理} \\
\hline
\end{array}
$$
旧実装では会話が長くなるほどトークン数が増え続けていましたが、新実装では「直前の応答 ID を渡すだけ」で会話を継続できます。2 回目以降のメッセージに履歴トークンが乗らないため、コストが抑えられます。
3. チャット+翻訳の 2 モード構成
サイドバーにラジオボタンを追加し、2 つのモードを切り替えられるようにしました。
チャットモード ← previous_response_id で会話継続
翻訳モード ← 毎回独立リクエスト・標準語 | 妙高弁の対訳 2 列表示モードを切り替えると、セッション状態が全リセットされる仕様です。翻訳モードは「次の文の翻訳」に前の会話を持ち込む必要がないので、毎回独立したリクエストを送ります。
テストのパート 1:Playwright なしで確認
実装が終わると、Claude Code は自動でテストを始めようとしました。そのとき提案してきたのが「Playwright を使ったブラウザテスト」。
Playwright は Microsoft が開発したブラウザ自動化ライブラリで、プログラムからブラウザを操作できます。ただしインストールが必要だったので、そのときは「Playwright はスキップして」と伝えました。
すると代替手法を 3 つ組み合わせて確認を進めてくれました。
手法 1:HTTP 200 確認
.venv/bin/streamlit run app.py --server.headless true --server.port 8502 &
sleep 5
curl -s "http://localhost:8502/" -o /dev/null -w "HTTP status: %{http_code}\n"
# → HTTP status: 200アプリが起動エラーなく立ち上がるかをチェック。`import` エラーや文法エラーがあればここで落ちます。
手法 2:構文チェック(`ast.parse`)
import ast
with open("app.py") as f:
src = f.read()
ast.parse(src) # 構文 OKPython の抽象構文木(AST)へのパースが通るかを確認。文法的に正しいコードかどうかを検証します。
手法 3:モックを使ったロード確認
本物の Streamlit と OpenAI をテスト用の「偽物」に差し替えて `app.py` を実行し、セッション状態・定数・プロンプトを取り出して確認しました。
app.py ロード成功
current_mode: チャットモード ← init_session() が正しく動作
MODEL: gpt-5.4-mini ← モデル名 OK
MAX_TURNS: 10 ← 会話上限 OK
MAX_INPUT_CHARS: 30 ← 文字制限 OK
CHAT_PROMPT length: 5209 ← 辞書が正しく展開されている
TRANSLATION_PROMPT length: 4647 ← 翻訳プロンプトも OK辞書が埋め込まれたプロンプトの長さや定数が仕様どおりかを機械的に検証できます。
Playwright があったら何ができたか
テスト後に「もし Playwright がインストールされていたら何を確認するつもりだったか」を聞いてみました。
$$
\begin{array}{|l|l|}
\hline
\text{確認項目} & \text{Playwright での方法} \\
\hline
\text{サイドバーにラジオボタンが表示されているか} & \text{スクリーンショット目視} \\
\text{モード切替で UI が変わるか} & \text{切替前後のスクリーンショット比較} \\
\text{30 文字超入力で警告が出るか} & \text{31 文字を入力して送信} \rightarrow \text{警告テキスト確認} \\
\text{10 回上限に達したときのメッセージ} & \text{ループで 10 回送信後にメッセージ要素を探す} \\
\text{翻訳モードの対訳 2 列表示} & \texttt{st.columns} \text{ で生成された要素を XPath で確認} \\
\text{モード切替でセッションがリセットされるか} & \text{チャット後に翻訳モードへ切替} \rightarrow \text{ログが消えているか確認} \\
\hline
\end{array}
$$
この結果を見て、UI の動作確認には Playwright がないと限界があると実感しました。
テストのパート 2:Playwright を入れてブラウザを動かす
「やっぱりやってみよう」と思い直して、Playwright をインストールしました。
.venv/bin/pip install playwright
.venv/bin/playwright install chromium`playwright install chromium` で Chromium のバイナリがダウンロードされます。仮想環境にインストールするだけなので、本番の `requirements.txt` には追加しません。
インストールが終わると、Claude Code がスクリプトを走らせてテストを実行してくれました。ヘッドレスモード(画面を表示せずバックグラウンドで動く)で Chromium が起動し、アプリを操作していきます。
確認したこと
6 つのシナリオで 13 項目を確認しました。
$$
\begin{array}{|l|l|r|}
\hline
\text{テスト} & \text{確認内容} & \text{結果} \\
\hline
\text{初期表示} & \text{タイトル・ラジオボタン・チャット UI} & \text{PASS(4 件)} \\
\text{チャット UI} & \text{残り回数表示・入力欄・送信ボタン} & \text{PASS(3 件)} \\
\text{文字数制限} & \text{31 文字入力で警告が出るか} & \text{PASS(1 件)} \\
\text{空入力} & \text{空で送信したときの警告} & \text{PASS(1 件)} \\
\text{翻訳モード切替} & \text{翻訳 UI への切替・送信ボタンが消えるか} & \text{PASS(3 件)} \\
\text{セッションリセット} & \text{チャットモードに戻ったとき回数が 10 に戻るか} & \text{PASS(1 件)} \\
\hline
\text{合計} & & \text{13 件全件 PASS} \\
\hline
\end{array}
$$
すべて問題なく通過しました。
スクリーンショットで動作を確認
テストスクリプトの節目に `page.screenshot(path="...")` を入れて、PNG ファイルとして保存するよう指示しました。画面が表示されないヘッドレスモードでもスクリーンショットが残せるのが便利なところです。
例として初期表示のスクリーンショットを載せます。左サイドバーに「モード選択」ラジオボタン、タイトル「妙高市 方言チャットボット」、「残り会話回数: 10 / 10」が確認できます。

文字数制限警告・翻訳モード切替など他のスクリーンショットは、末尾の GitHub テストレポートで確認できます。
X や Web 記事でブラウザ自動化テストの話は読んだことがあったのですが、実際に目の前でやってくれると、その実行力に感心してしまいます。
Streamlit のテストで押さえておくポイント
メモとして書いておきます。Playwright で Streamlit アプリを確認するときは、操作直後に `wait_for_load_state("networkidle")` を呼ぶのが重要です。
page.locator("button", has_text="送信").click()
page.wait_for_load_state("networkidle") # ← これを入れないと次の確認が失敗するStreamlit は画面更新に WebSocket 通信を使うため、ボタンクリック直後はまだ再描画中です。`networkidle`(通信が落ち着いた状態)を待つことで、確認タイミングのズレを防げます。
残った課題
Playwright のテストでもカバーしきれなかったことがあります。
$$
\begin{array}{|l|l|}
\hline
\text{未確認項目} & \text{理由} \\
\hline
\text{実際の API コールで方言会話・翻訳が動くか} & \text{テスト環境で本物の API キーを使わなかった} \\
\text{10 回上限に達したときのメッセージ} & \text{API コールなしでは会話カウントが増えないため} \\
\text{翻訳モードで対訳が実際に表示されるか} & \text{同上} \\
\hline
\end{array}
$$
これらは API キーを使ったエンドツーエンドテスト(E2E テスト)か、実際に手で動かして確認するしかありません。次は自分の手でアプリを触ってみる番です。
次回:実際に動かしてみる
Claude Code に実装とテストを任せた結果、コードの品質確認まで自動でやってもらえました。
次回は、私が実際に API キーを使ってアプリを動かし、方言チャットと翻訳機能が意図どおりに動くかを確認します。
参考(GitHub リポジトリへのリンク)
Claude Code が何をしたかが分かります。
Claude Code のテストレポート(Playwright)--Claude Code がブラウザを操作してチェックしているのが分かります。
続く
書いている人について
最後まで読んでいただき、ありがとうございます。
私のnoteの紹介記事です。
よろしければ、お読みいただければと思います。
この記事の連載マガジンです。
私の生まれ故郷の歴史
