Redmineの不安定なシステムテストを修正した


My Redmine

こんにちは、日高です。今回は、Redmineのシステムテストの安定化に取り組んだ話をしたいと思います。

システムテストとは

Redmineには、自身の動作を検証するためのいくつかのテストが含まれています。システムテストはそのうちの1つです。

Railsガイド より

システムテストはアプリケーションのユーザー操作のテストに使えます。テストは、実際のブラウザまたはヘッドレスブラウザに対して実行されます。システムテストではそのために背後でCapybaraを使います。

システムテストは、実際にブラウザ上でシナリオを動かして行うテストのため不安定になりがちです。

Defect #42200 InlineAutocompleteSystemTest login test fails randomly

https://www.redmine.org/issues/42200

今回解決したのは、ユーザー名やチケット番号、Wikiページ名を入力するときのオートコンプリート機能のテスト(test/system/inline_autocomplete_test.rb)で発生する問題です。

オートコンプリート機能とは

オートコンプリート機能とは、例えば、チケットの説明欄で「#Add」を入力したときに、題名に「Add」を含むチケットを候補として表示する機能です。


オートコンプリート機能

「#」に続く文字を入力するたびに、入力内容が題名に含まれるチケットを検索し、結果の候補をメニューに表示します。 例えば、「#Add」を入力した場合、「#」「#A」「#Ad」「#Add」の内容ごとに一連の処理が行われます。

発生する問題

このテストでは、次の2つの問題が発生する可能性があります。

(1) ログインテストの失敗

Failure:
InlineAutocompleteSystemTest#test_inline_autocompletion_of_wiki_page_links [test/application_system_test_case.rb:71]:

Expected: "/login"
  Actual: "/my/page"

ログインしていない状態でマイページ(/my/page)にアクセスしたとき、ログインページにリダイレクトされることを期待しますが、 実際にはマイページが表示されてしまうという問題です。 考えられる原因は、ログインしていない状態でログインが必要なページにアクセスした場合の処理に問題があるか、 そもそもログインしている状態でマイページにアクセスしたか、のいずれかです。

(2) StaleElementReferenceError

Error:
  InlineAutocompleteSystemTest#test_inline_autocomplete_for_users_on_issues_bulk_edit_show_autocomplete:
  Selenium::WebDriver::Error::StaleElementReferenceError: stale element reference: stale element not found in the current frame

テストコードの中で参照していたHTML要素が何らかの理由で参照できなくなってしまった場合に発生する問題です。 詳細はUnderstanding Common Errors | Seleniumを参照してください。

原因

詳しい原因を説明する前に、まずはこれらの問題がテストコードのどこで発生しているのか確認しましょう。

以下のコードは、これらの問題が再現する最小限のテストコードです(実際のコードは こちら)。 このテストを実行すると、test_1test_2の順でテストが実行されます。このとき、(1)(2)の問題はコードコメントに記載した箇所で発生する可能性があります。

class InlineAutocompleteSystemTest < ApplicationSystemTestCase
  def self.test_order = :sorted

  def test_1
    Issue.generate!(subject: 'abcd', project_id: 1, tracker_id: 1)

    log_user('jsmith', 'jsmith') # <== (a)
    visit 'projects/1/issues/new'

    fill_in 'Description', :with > '#abcd' # <== (b)

    within('.tribute-container') do
      assert page.has_text? "abcd" # <== (2)
    end
  end

  def test_2
    log_user('jsmith', 'jsmith') # <== (1)
  end
end

このテストの大まかなシナリオは次の通りです。

[1-3]では、画面上の結果は期待通りとなって成功しますが、実際には[1-2]で最初の1文字目の「#」を入力した時点で、 結果のメニューに「abcd」のチケットが含まれるため、その時点でテストはパスし完了します。


「#」を入力した時のオートコンプリートの結果

しかし、[1-2]では「#abcd」が入力されているため、「#a」「#ab」「#abc」「#abcd」の入力内容に対する4回のオートコンプリート(チケットの検索とメニューへの反映処理)もすでに実行された状態です。 それらの処理のいくつかは、後続の[1-3]や次のtest_2で実行されます。

以下は、再現コードのテストを実行したときにオートコンプリートの処理がどのように実行されるかを示すログです。わかりやすいように一部省略・整形しています。 完全なログはこちらから確認できます。

test_1が開始
  GET /my/page
  GET /login?back_url=http%3A%2F%2F127.0.0.1%3A44235%2Fmy%2Fpage
  POST /login (_redmine_session=WU42TUpI...)
  ...
  GET /issues/auto_complete?project_id=ecookbook&q= (_redmine_session=WU42TUpI...)
  GET /issues/auto_complete?project_id=ecookbook&q=a (_redmine_session=WU42TUpI...)
  GET /issues/auto_complete?project_id=ecookbook&q=ab (_redmine_session=WU42TUpI...)

(ここでtest_1は終了し、test_2が続けて実行)

test_2が開始
  GET /my/page (_redmine_session=cGhWTktW...)
  GET /issues/auto_complete?project_id=ecookbook&q=abc (_redmine_session=WU42TUpI...)
  GET /issues/auto_complete?project_id=ecookbook&q=abcd(_redmine_session=WU42TUpI...)

「#a」「#ab」「#abc」「#abcd」のオートコンプリートの処理も実行されていることが確認できます。「#abc」「#abcd」に至っては、test_2のテスト中に実行されています。

以上より、(1)(2)の問題が起こる原因は次のように考えられます。

(1) ログインテストの失敗

実は、これが発生するメカニズムや原因ははっきりしていません。 ただ、通常テストごとにセッション(ログイン状態)はリセットされますが、test_1の処理が完了しないままtest_2を実行していることの影響によって、 セッションが完全にリセットされないままtest_2が実行されているのではないかと考えられます。

(2) StaleElementReferenceError

こちらも詳細はわかっていませんが、例えば、再現コードの(2)の箇所で結果メニューのHTML要素(.tribute-container)を参照する直前に、 「#a」「#ab」のオートコンプリートの処理が完了して結果メニューの要素の書き換えが発生すると、 参照しようとしていた要素を見つけることができず、StaleElementReferenceErrorが発生する可能性がありそうです。

解決

これまでの説明からわかるように、現在のテストコードでは、「#abcd」が入力されたときに発生するすべてのオートコンプリート処理の完了を待たずに期待値を評価しています。 これは、意図したものではなく、テストとしても適切とは言えないと思います。それらを踏まえ、入力された値に対する処理がすべて完了した後にテストするようにテストコードを修正しました。

再現コードを例にすると、修正内容は次のような形です。 実際の修正内容は#42200を参照してください。

@@ -7,7 +7,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
     log_user('jsmith', 'jsmith') # <== (a)
     visit 'projects/1/issues/new'

-    fill_in 'Description', :with => '#abcd' # <== (b)
+    fill_in 'Description', :with => '#' # <== (b)

     within('.tribute-container') do
       assert page.has_text? "abcd" # <== (2)

最後に

同じくして、Patch #42244 Fix random failures in IssuesTest#testbulkcopy due to StaleElementReferenceError にて、 同様の原因で不安定になっていたIssuesTestの問題についても解決しました。

これらの対応によってシステムテストが安定してきたので、次はRedmineのCI(GitHub Actions)でシステムテストを動かすように提案していきたいと思っています。

My Redmine

【スタッフ募集中】
「My Redmine」など弊社提供サービスのお問い合わせ対応を担当するカスタマーサポートスタッフを募集しています。
弊社での勤務に関心をお持ちの方は、知り合いの弊社社員・関係者を通じてご連絡ください。採用情報の詳細


こちらの記事もオススメです!
RedmineプラグインのシステムテストをPlaywrightで動かす
Playwrightは環境構築が簡単なのが魅力的です。GitHub Actionsでの自動テストも簡単に設定できます。
ChatGPTで番号札を作ってみた
忘年会の番号札をChatGPTで作ってみた話です。
資料「Redmineの意外と知らない便利機能」をRedmine 6.0に更新しました
「Redmineの以外と知らない便利機能」をおよそ3年ぶりに更新、Redmine 6.0対応になりました。
Redmineのテキスト書式をTextileからCommonMark Markdownに変換するプラグインを試した話
Textile から CommonMark Markdown への変換例をもとに、変換前後の結果や注意点をまとめています。
初心者でもできた!ハワイ旅行でのスマホ利用とオプショナルツアー予約サービス活用術
SIMカードや持ち運びWi-fiなしでスマホを利用できました。
ファーエンドテクノロジーからのお知らせ(2025/04/23更新)
社員研修に伴うサポート体制変更・休業のお知らせ(5/20〜23)
社員研修に伴い、5月20日〜23日はサポート体制の変更および休業とさせていただきます。
オープンソースカンファレンス2025 Nagoyaに弊社代表の前田が登壇(ブース出展あり)
オープンソースカンファレンス(OSC)2025 Nagoyaに弊社代表の前田が登壇。『Redmineの意外と知らない便利な機能(Redmine 6.0 対応版)』をテーマに発表します。
エンタープライズプラン向け「優先サポート」を開始
My Redmineでは、エンタープライズプランをご契約のお客様向けにサポート対応を優先的に行う「優先サポート(プライオリティサポート)」を開始いたしました。
プロジェクト管理ツール「RedMica」バージョン 3.1.0をリリース Redmine互換のオープンソースソフトウェア
ファーエンドテクノロジー株式会社は、2024年11月19日(日本時間)、Redmine互換のプロジェクト管理ソフトウェア「RedMica 3.1.0」をリリースしました。
Redmineの最新情報をメールでお知らせする「Redmine News」配信中
新バージョンやセキュリティ修正のリリース情報、そのほか最新情報を迅速にお届け