Rails 7 でシステムテストが不安定にfailする場合はTurboのpreviewをOFFにして確認してみるといい
tl;dr
3行まとめ
- Rails 7 で導入された Turbo の preview と、 Capybara の要素出現まで待つ振る舞いの相性が悪いように見える
-
<meta name="turbo-cache-control" content="no-preview">
を HTML に追加すれば preview を OFF にできる - OFF にすることで fail することが抑止されるなら preview が原因なのでそのケアをすべき
Rails 7 で導入された Turbo とは
Railsガイドだと言及はここらへん。
「HTML をレスポンスする素朴な Rails アプリケーション実装のままで、SPA のようなページ読み込みの高速化を実現する仕組み」と自分は認識している。Rails 7 でデフォルト有効になっている。
Turbo の cache と preview
Turbo にはページを高速化するための機能が含まれている。Turbo による cache は通常ブラウザが行っているものと同等なものなので理解は容易だけど、preview が Turbo 独特の振る舞いと言えそう。
ページ遷移の1例でキャッシュがない場合を以下とする。
- リンクをクリック(User)
- 遷移先のページをリクエスト(Turbo)
- 遷移先のページをレスポンス(App)
- 表示している画面のDOMツリーをレスポンスで置換(Turbo)
キャッシュがあるがコンテンツを再取得する必要もある場合に preview が発動する。
- リンクをクリック(User)
- 遷移先のページをリクエスト + 表示している画面のDOMツリーをキャッシュで置換(Turbo)
- 遷移先のページをレスポンス(App)
- 表示している画面のDOMツリーをレスポンスで置換(Turbo)
この 2. のキャッシュで置換する振る舞いが preview と呼ばれるものになる。App からのレスポンスを待っている間も何かしらコンテンツが表示されることになるのでユーザーが無のコンテンツを浴びる時間が減る=ユーザー体験が向上する、というロジックらしい。
Capybara の要素出現まで待つ振る舞い
Rails におけるシステムテスト、つまりは E2E テストにおけるブラウザオートメーションのデフォルトの仕組みは Capybara である。
README の Key benefits にも書いてある点で、
Powerful synchronization features mean you never have to manually wait for asynchronous processes to complete.
ページ遷移して遷移先画面の input に入力して submit ボタンを押すという動作は Capybara で以下のように記述できる。
visit articles_url
click_on 'New Article'
fill_in 'article[title]', with: 'Title of New Article'
click_on 'Create Article'
assert_text 'Title of New Article'
例えば fill_in
呼び出し時点で、その locator で表現される要素(この例だと input[name="article[title]"]
となる要素を期待している)がまだ画面に存在しなければ、出現=画面遷移が完了するまで待つことが Capybara 内部の実装で担保されている。わざわざ sleep を入れて固定時間待つ必要などない。
Turbo の preview によって壊れるシステムテスト
どのように壊れるか、サンプルを作ってみた。
Getting Started レベルの素朴な Rails アプリになっている。アプリケーションの振る舞いを問題ない場合のテストのスクリーンショットで表現してみたのが以下の通り。
入力した内容を title にして Article モデルを作成するだけになっている。
これをベースに以下変更で壊れるテストを作り出してみた。
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index 8f84556..e847e03 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -4,6 +4,7 @@ class ArticlesController < ApplicationController
end
def new
+ sleep 2
@article = Article.new
end
diff --git a/test/system/articles_test.rb b/test/system/articles_test.rb
index 304bd23..be9f738 100644
--- a/test/system/articles_test.rb
+++ b/test/system/articles_test.rb
@@ -6,11 +6,15 @@ class ArticlesTest < ApplicationSystemTestCase
page.save_screenshot '1.png'
click_on 'New Article'
+ has_button? 'back'
page.save_screenshot '2.png'
+ click_on 'back'
+ click_on 'New Article'
fill_in 'article[title]', with: 'Title of New Article'
page.save_screenshot '3.png'
+ sleep 3
click_on 'Create Article'
assert_text 'Title of New Article'
意図的に sleep
を入れている。この結果のスクリーンショットが以下の通り。
Title を入力しているはずなのに空で登録されている。これにより Title が入力されることを期待する assertion で fail する機序となる。
実際に以下のシーケンスになっているとみている。
- リンクをクリック(User)
- 遷移先のページをリクエスト + 表示している画面のDOMツリーをキャッシュで置換(Turbo)
- インプットに入力(User)
- 遷移先のページをレスポンス(App)
- 表示している画面のDOMツリーをレスポンスで置換(Turbo)
- submit ボタン押下(User)
Turbo の cache と preview を Opt Out する
Opt Out する方法が Turbo のドキュメントに書いてある。
<meta name="turbo-cache-control" content="no-cache">
<meta name="turbo-cache-control" content="no-preview">
一律 OFF にしたければ layouts/application.html.erb
に記述してしまうのが簡単。
システムテストが不安定に fail するならば一度試して確認してみることをオススメします、というのが本記事の内容でした。
Turbo の preview が原因だった場合のケア方法
大きく2つあると考えている。
- Turbo の preview は OFF にする
- Turbo がリクエスト中であることを検知して、適切に待つ
前者は、まぁ preview 自体が新しい概念っぽく、これが本当にユーザー体験上必要か?と言われてもまだ評価は難しいと捉えた。実際に今自分が関わっているプロジェクトでは OFF にするという決定をした。
後者のやり方としては、以下記事にある「プログレスバーの有無で DOM が差し替えられたかどうかを判断する方法」が参考にできます。
ただ個人的意見として、テストコードが画面実装や詳細な振る舞いについて知っている状態になるということが好ましくない。Capybaraのレイヤーあたりで隠蔽されて解決されないものだろうか?あたりは引き続き考えてみたい。
Discussion