こんにちは。GMO NIKKO / GMOインターネットグループ デベロッパーエキスパートの石丸です。2024年秋に開催された「Kaigi on Rails 2024」に参加しました。2日間にわたって様々なセッションに参加しましたが、特に印象に残ったのがイベント1日目のセッション「Capybara+生成AIでどこまで本当に自然言語のテストを書けるか?」です。本記事では、そのセッションで紹介されていた自然言語によるブラウザ自動テストを実際に試した結果や所感について紹介します。
はじめに
今回紹介する生成AIを活用した自動テストツールは「Charai」という名前でGitHubに公開されています。Charai(Chat + Ruby + AI) driver for Capybara. Prototype impl for Kaigi on Rails 2024 presentation
このツールは主に以下の流れで動作します。
自然言語でテストケースを記述生成AIが自然言語の指示や画面のキャプチャを受け取り、ブラウザからDOM情報を取得する受け取った情報やDOMデータを解析し、ブラウザ操作(クリック、テキスト入力、スクロールなど)を行うRubyコードに変換する生成されたコードをCapybaraドライバを利用して実行し、ブラウザを操作する
ユーザーは自然言語で直感的にテスト内容を記述でき、また、生成AIが実際のブラウザの状態をもとに次の動作を判断するため、人間が行うブラウザ操作に近い挙動を再現することが可能です。
詳細は「Kaigi on Rails 2024」のセッションページ内の発表動画やスライドをご確認ください。
Capybara+生成AIでどこまで本当に自然言語のテストを書けるか? | Kaigi on Rails 2024
検証用のアプリケーション
自動テストの挙動確認用に、サンプルとして以下のようなアプリケーションを作成しました。
記事投稿を行うシンプルなWebアプリケーションで、トップページには投稿の一覧を表示し、各投稿には編集や削除などの基本的な機能を実装しました。投稿の新規作成は画面右上の「New Post」から行います。
セットアップ
基本的なセットアップは README に書かれている通りですが、今回の検証にあたって私がやったことを一通り紹介します。
Gemfile
「Charai」はgemとして公開されているため、 Gemfile に gem 'charai' を追加し、bundle install を実行します。
Firefox Developer Edition
macOS環境の場合は /Applications/Firefox Developer Edition.app にインストールします。
Capybara
spec/support/capybara.rb ファイルを作成し、以下のように設定します。
require 'capybara/rspec'
config = Charai::OpenaiConfiguration.new(
model: 'gpt-4o-mini',
api_key: ENV['OPENAI_API_KEY'] || 'xxxxxxx'
)
Capybara.register_driver :charai do |app|
Charai::Driver.new(app, openai_configuration: config)
end
Capybara.register_driver :charai_headless do |app|
Charai::Driver.new(app, openai_configuration: config, headless: true)
end
Capybara.current_driver = :charai
Capybara.javascript_driver = :charai
RSpec.configure do |config|
config.around(:each, type: :feature) do |example|
Capybara.current_session.driver.callback = {
on_chat_conversation: ->(_content_hash, answer) {
formatted_answer = answer.gsub(/```/, '').strip
puts "🤖 ChatAI Response:\n-------------------------"
puts formatted_answer
puts "-------------------------\n\n"
}
}
example.run
end
end
RSpec
実際のテストは以下のように書きました。page.driver.additional_instruction にテスト対象のページ構成を記述し、page.driverにテストしたい内容を自然言語で指定します。今回は新規記事の投稿処理を実行するように自然言語で指示しました。必要なDSLは visit のみです。
require 'rails_helper'
RSpec.describe '投稿編集のE2Eテスト', type: :feature do
before do
page.driver.additional_instruction = <<~MARKDOWN
* Posts一覧画面には、投稿の一覧と各投稿ごとの「Edit」「Delete」ボタン、および画面右上の「New Post」ボタンが表示されています
* 画面右上の「New Post」ボタンをクリックすると、新規作成画面(New Post)に遷移します
* New Post画面には「Title」入力欄と「Body」入力欄があり、右下に「Create Post」ボタンがあります
* Edit Post画面には「Title」入力欄と「Body」入力欄があり、右下に「Update Post」ボタンがあります
* Posts一覧画面へ戻るには、画面下の「Back」ボタンをクリックします
MARKDOWN
end
it 'New Post 画面で投稿を作成し、一覧にタイトルと本文が表示されること' do
visit 'http://localhost:3000/'
page.driver << <<~MARKDOWN
* Posts一覧画面で「New Post」ボタンをクリックしてください
* New Post画面に遷移したことを確認してください
* 「Title」入力欄に「Sample Title」を入力してください
* 「Body」入力欄に「This is a sample body.」を入力してください
* 「Create Post」ボタンをクリックしてください
* Posts一覧画面に戻り、「Sample Title」というタイトルが一覧に表示されていることを確認してください
* 同じ行に「This is a sample body.」という本文が表示されていることを確認してください
MARKDOWN
end
end
テスト実行
セットアップが完了したため、早速テストを実行してみます。以下の画面に対して、投稿の新規作成が成功するかどうかを検証してみます。
新規作成ボタンのクリック
spec/support/capybara.rb でOpenAIからのresponseをコンソールに出力するように設定したため、リアルタイムで以下のようなレスポンスを確認することが可能です。
$ rspec spec/web/charai_sample_spec.rb
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.querySelector(".new-post-button").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.querySelector(".new-post").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.body.innerHTML)')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.click(x: 100, y: 100)
driver.sleep_seconds(2)
driver.capture_screenshot
-------------------------
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.querySelector(".btn.btn-outline-primary").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.click(x: 910, y: 126)
driver.sleep_seconds(2)
driver.capture_screenshot
-------------------------
上記のレスポンスは、新規作成ボタン「New Post」の位置を特定するまでの過程です。まず初めに、新規作成ボタンに .new-post-button や .new-post のクラスを持つ要素の位置とサイズを取得しようとしていますが、実際の画面ではこれらのクラスは付与されていないため、最初のクリック処理が失敗していることがわかります。その後、対象ページのHTMLを取得し、最終的には .btn.btn-outline-primary というクラスを持つボタンの位置情報から正確なクリック位置 (x: 910, y: 126) を特定することで、新規作成画面への遷移が成功しました。
TitleとBodyの入力
「New Post」をクリックすると以下のような画面に遷移します。
この画面に対しては、自然言語で以下のように指示しました。
「Title」入力欄に「Sample Title」を入力してください「Body」入力欄に「This is a sample body.」を入力してください「Create Post」ボタンをクリックしてください
OpenAIのレスポンスは以下のようになりました。
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.querySelector("input[name=\'title\']").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.querySelector("input").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.body.innerHTML)')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.execute_script('JSON.stringify(document.querySelector("#post_title").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.click(x: 512, y: 233)
driver.type_text("Sample Title")
driver.execute_script('JSON.stringify(document.querySelector("#post_body").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.click(x: 512, y: 368)
driver.type_text("This is a sample body.")
driver.execute_script('JSON.stringify(document.querySelector(".btn.btn-outline-primary").getBoundingClientRect())')
-------------------------
🤖 ChatAI Response:
-------------------------
driver.click(x: 905, y: 469)
driver.sleep_seconds(2)
driver.capture_screenshot
-------------------------
タイトル、ボディそれぞれの入力欄の位置とサイズを取得し、取得したフォームに対してクリックやテキストを入力するコードが生成されていることが確認できます。最後に .btn.btn-outline-primary の位置情報をもとに「Create Post」のクリック位置を特定し、無事にクリック処理が完了したことが確認できました。
投稿の新規作成が完了し、トップの一覧画面に先ほど作成した投稿が表示されていることを確認できました。
まとめ
今回は、生成AIを活用した自然言語によるブラウザ自動テスト「Charai」をご紹介しました。今回の記事ではテストの成功例を紹介しましたが、実際の成功率は50%ほどで精度面ではまだ発展途上な印象です。なお、今回の記事で紹介したテストは約35秒で完了し、 gpt-4o-mini を指定した場合、1回あたりの実行コストは0.01ドル以下でした。速度面でも課題はありますが、精度と速度が改善されれば実用レベルに大きく近づく印象です。個人的にも、今回紹介した生成AIを活用したブラウザ自動テストや、AIエージェントによるブラウザ操作などの技術に非常に興味があるため、今後も面白い技術や事例があればこのブログで紹介させていただきます。生成AIの進化に伴い、数年後にはE2Eテストの環境も大きく変わっているかもしれませんね。