こんにちは。SEQ(Software Engineer in Quality)のtsubonneです。
私たちのチームは、現在自動テストの基盤開発、さらには開発フィードバックサイクルの高速化を目指した開発を進めています。
freee QA Advent Calendar 2025 15日目です。
本日は私が2025/10に入社してからの取り組みの中で仕組み化を行なった人間とAIが「ストーリー」で協調するワークフローとナレッジ獲得の仕組み 【ストーリー駆動ハーネス】 と題しまして一本書かせていただきます。
freeeのE2Eテストはアドベントカレンダー4日目の記事にあるように、Playwright*1 をベースにした実装基盤で開発しています。
freeeに入社して、私が最初に与えられたミッションは要素指定に起因する壊れやすいE2EテストをAI活用して自動化する ことでした。
このミッションを行うにあたって、freeeのE2Eテストの現場では以下のような課題が存在しました。
課題解決にあたり、最初は自律型コーディングエージェントDevin*3 での自動化を検討しましたが、現状ではコンテキストとナレッジ不足によりコストがかさんでしまい、Claude Code*4 との伴走によるナレッジ蓄積を優先する形にしました。
datatest-id付与のAI対応事例はDevinでのfreee内対応事例が存在していたため、まずはDevinによる自動化ワークフローを検討することにしました。
実装事例は大変シンプルな要件のみをワンショットで指示しDevinSessionに作業させるものでした。この1つのDevinSessionにてfreee会計フロントエンド、PlaywrightベースのE2Eテスト基盤の2つのリポジトリに対して修正を行います。
実際のDevinプロンプト例:
E2Eのpage objectのセレクタについて改善したい。 (GitHub URL省略)/lib/pages/fizz/buzz/hoge.ts#L220 のセレクタが壊れやすいので改善したい。freee-kaikeiの実装側に適切にdata-testidを付与するe2e-testsのセレクタを1に合わせて修正する
こちらのプロンプトを参考に実際に修正対応を行わせてみたところ、修正作業自体は行えたものの早速問題が出てきました。
上記の内容をもとに、改めてfreee会計フロントエンドでのdata-testid付与の状況について調査することにしました。
freee会計フロントエンドの実装を調査したところ、以下のようなAI活用における障壁を発見しました。
data-testid 付与対象となるコードの記述も多様な状況にある。このような「新旧アーキテクチャの混在と実装の多様性」 が大きな障壁であり、単にワンパターンでのアプローチでは難しいことがわかりました。
発生した問題を鑑みて、以下の課題を改めて整理・解決する必要があると再認識しました。
まずは人の判断を介在させる AIワークフローを採用し、実際にdata-testid付与の作業をAIと並走し完遂していくことで、信頼できるナレッジの蓄積を目指すことにしました。ナレッジ蓄積にあたって、世間・freee内的にもデファクトスタンダードになりつつあるClaude Code を選択しました。
Claude Codeを選択した理由としては以下になります。
まずは私とエージェントの作業プロセスを管理しやすい形で可視化するための作業ログ を外部ファイルとして保存することを考えました。ローカルにてClaude Codeと一緒にdata-testid付与対応を実際に行っていき、蓄積された手順や工程に応じた調査手法やトラブルシューティングを振り返ってナレッジ化するイメージです。
コーディングエージェントを扱うにあたって、AIの回答精度を保つためにはセッションに与えるコンテキスト を設計するのが非常に重要になってきます。LLMには処理できるトークン数(コンテキストウィンドウ)に上限があり、大量のコードやログを読み込むとこの上限に達してしまいます。
セッションコンテキストが圧迫されると作業を進めてく上で以下のような不都合が起こってしまいます。
今回のミッションではリポジトリを横断的に作業させる関係上、Claude Codeの1セッションでauto compactを走らせずにコンテキストを保持して作業させることは難しいと感じていました。セッションを横断して作業状況を引き継ぐための進捗管理も必要だったため、ストーリー(スクラムなどで使われる作業単位)でコンテキストがまとまるように管理していくことにしました。
このストーリーをClaude Codeのエージェントハーネス*5 として機能させストーリーを中心に駆動させる仕組みをストーリー駆動ハーネス として命名し、設計することにしました。
今回のミッションであるリポジトリを横断したdata-testidの付与作業の作業工程を整理すると、以下のイメージになります。
こちらの作業工程を含むタスクに必要なコンテキスト情報を含んだ「ストーリー」を中心に、エージェントを駆動させるためのエージェントハーネスを構築しました。
準備フェーズ
調査フェーズ
実装フェーズ
検証フェーズ
ストーリー駆動ハーネスは以下の要素で構成されます:
このエージェントハーネスを利用することで、以下を実現します。
1つのストーリーの内容はディレクトリ単位で管理し、構成は以下のように定義します。
story.yml: 作業全体の進捗管理とセッション開始時に参照させるファイル群を記録。subtasks/: 各工程の作業計画とプロセスログを記録。sessions/: 人間が指示したプロンプトの記録(セッションごとに管理)。worktree/: エージェント用のgit作業環境を格納(git worktreeを使用してブランチごとに独立した作業ディレクトリを作成)。以下は、実際にdata-testid付与対応を行ったストーリーのツリーになります。
├── sessions│ ├── 20251210-015001.yml│ └── 20251210-015621.yml├── story.yml├── subtasks│ ├── 01_preparation/. # git worktree, branchの準備フェーズ│ ├── 02_investigation/ # 調査フェーズ│ ├── 03_implementation_frontend/. # 実装フェーズ frontend│ ├── 03_implementation_e2e/ # 実装フェーズ E2E│ └── 04_verification # 検証フェーズ frontend└── worktree ├── freee-kaikei-feature-add-data-testid └── e2e-feature-add-data-testid
ストーリーは Claude Code上での /command をトリガーとし、エージェントに作業を実行させます。
/story-create: 事前に定義したストーリー構造に基づき作業要件・リポジトリ・フェーズなどの必須項目の補完と作業のプランニングを人とエージェントで行い、ストーリーの作成を行う。# ===================================================================# 作成されるstory.yml (抜粋)# ===================================================================# ストーリー管理用に付与するmetadata群metadata:id:"20251210-freee-kaikei-data-testid: ****.tsの要素指定をdata-testid化"title:"data-testid: wizards.tsの要素指定をdata-testid化"description:"e2e-tests:lib/pages/accounting/annual_reports/final_return/wizards.tsの..要素指定をdata-testid付与を行いgetByTestId指定に修正"created_at:"2025-12-10T00:42:58"status:"implementation_e2e"template:"data-testid-migration"current_phase:"implementation_e2e"# ストーリーで予定している作業フェーズとそのステータスphases:-id:"preparation"name:"準備フェーズ"status:"completed"started_at:"2025-12-10T00:42:58"completed_at:"2025-12-11T15:52:00"-id:"investigation"name:"調査フェーズ"status:"completed"started_at: 2025-12-09T15:52:44completed_at: 2025-12-09T16:00:34-id:"implementation_frontend"name:"実装フェーズ(フロントエンド)"status:"completed"started_at: 2025-12-09T16:26:24completed_at: 2025-12-09T16:50:01
/story-resume: 指定されたストーリーディレクトリのstory.ymlをもとにエージェントが作業開始・再開に必要なコンテキストを読ませる。/story-update: 各フェーズごとにセッションを終了する前に作業ログを記録させる。/story-updateの実行時に更新を行わせる。# ===================================================================# subtasks/investigation.yml 調査フェーズで蓄積された作業ログ (抜粋)# ===================================================================phase: investigationstatus: completedstarted_at:"2025-12-10T00:52:47"completed_at:"2025-12-10T00:52:47""summary: | 調査フェーズを完了しました。****.ts含む申告関連の3つのPageObjectファイルで locator("..")を5箇所特定し、対応するReact/TSXコンポーネントを特定しました。 すべてセクション特定パターンであり、data-testid命名戦略を決定しました。completed_tasks:-task:"locator(\"..\")使用箇所の特定"details: |-合計5箇所のlocator("..")使用を特定-すべて「親コンテナ系(セクション特定)」パターン-ファイル別内訳: *****.ts: 2箇所 *****.ts: 1箇所 *****.ts: 2箇所...
/story-complete: 指定されたストーリーディレクトリ内の各ログファイルとstory.ymlを更新しストーリーディレクトリをアーカイブする。/story-review: 指定されたストーリーディレクトリ内の各ログファイルとstory.ymlを参照し人とエージェントの作業プロセスをレビューする。ストーリー駆動ハーネスを用いたClaude Codeとの伴走により、実際にdata-testid付与作業を10件ほど対応しました。件数としてはまだ少ないですが、以下の当初の目的が達成できたことを確認できました。
story.yml とsubtasks による進捗管理が可能になり、AIの作業プロセスのブラックボックス化を防げた。現在は蓄積されたdata-testid対応のストーリーから作業手順や対応方法に関してのナレッジを抽出し、対象の作業に特化したストーリーテンプレート の作成と導入を検討しています。
私に与えられたミッションは本来AIが自律的に作業を完遂するワークフローの構築し.data-testid付与を自動化することなので、 現状は下準備が一つ進んだといった状況です。
さらなる自動化に向けて以下の課題に取り組んでいます。
完全自動化は道半ばですが、AIに任せる前に何を準備すべきかの再認識とナレッジの元になるストーリーを蓄積する仕組みを整備することができました。
現状のコーディングエージェントは「魔法の杖」ではなく、きちんとしたストーリー(作業単位)とコンテキスト/ナレッジがあって初めて力を発揮するツールだと改めて感じています。
本記事が、コーディングエージェント導入を検討している方や、AI活用で試行錯誤している方の参考になれば幸いです。
明日は、freeeサインのQAエンジニアのmatsujun-san が「転職1年目のQA活動記 - 経験を活かし、新たに学んだこと」について記事を書いてくれます。お楽しみに〜!
それでは、よい品質を〜
*1:https://playwright.dev : Microsoftが開発したモダンなWebアプリケーション向けのUI自動化・テストフレームワーク
*2:https://testing-library.com : ユーザーの視点に立ってUI(ユーザーインターフェース)をテストするために設計された、JavaScriptのライブラリ群の総称
*3:https://docs.devin.ai : 「世界初のAIソフトウェアエンジニア」を目指して設計された、自律的に開発タスク全体を実行できる大規模言語モデルベースのエージェント
*4:https://code.claude.com/docs/en/overview : Anthropic社が開発した、コード生成・デバッグ・修正をCLI環境で自律的に実行できるAIコーディングエージェント
*5:https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents : AIエージェントが複雑で長時間にわたるタスクを安定して遂行するための「土台」や「フレームワーク」
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。