- なぜそれが必要なのでしょうか?
- 過去にどんな問題がありましたか?
- 脆弱なテスト問題をどのように解決したのでしょうか?
- ページオブジェクトを正しく実装するには?
- ローカルでのテストの実行
- どこに助けを求めますか?
GitLab QAのページオブジェクト
GitLab QAでは、_Pages Objectsと_呼ばれる既知のパターンを使っています。
これは、GitLabのQAシナリオを駆動するために使用するGitLabのすべてのページのための抽象化を構築したことを意味します。 フォームへの入力やボタンのクリックなど、ページ上で何かを行うときはいつでも、GitLabのこの領域に関連付けられたページオブジェクトを介してのみ行います。
例えば、GitLab QAテストハーネスがGitLabにサインインするとき、ユーザーログインとユーザーパスワードを入力する必要があります。 そのために、Page::Main::Login
とsign_in_using_credentials
メソッドというクラスがあり、user_login
とuser_password
フィールドに関する知識を持つ唯一のコード部分です。
なぜそれが必要なのでしょうか?
ページオブジェクトが必要なのは、重複を減らし、GitLabのソースコードで誰かがセレクタを変更するたびに問題が発生しないようにする必要があるからです。
GitLab QAに100個の仕様があり、アサーションを行う前に毎回GitLabにサインインする必要があるとします。 ページオブジェクトがなければ、揮発性のヘルパーに頼るか、Capybaraのメソッドを直接呼び出す必要があります。*_spec.rb
ファイル/テスト例のすべてでfill_in :user_login
を呼び出すことを想像してみてください。
後で誰かがこのページに関連するビューのt.text_field :login
をt.text_field :username
に変更すると、異なるフィールド識別子が生成されます。
どこでもPage::Main::Login.perform(&:sign_in_using_credentials)
を使っているので、GitLab にサインインするときにはページオブジェクトが唯一のソースとなり、fill_in :user_login
をfill_in :user_username
に更新する必要があります。
過去にどんな問題がありましたか?
パフォーマンス上の理由と、パッケージのビルドとすべてのテストに時間がかかるためです。
そのため、_新しいセッションビューで_誰かがt.text_field :login
をt.text_field :username
に変更しても、GitLab QA の毎晩のパイプラインが失敗するまで、あるいは誰かがマージリクエストでpackage-and-qa
アクションをトリガーするまで、私たちはその変更を知ることができません。
明らかに、このような変更はすべてのテストを壊してしまうでしょう。 私たちはこの問題を「壊れやすいテストの問題」と呼んでいます。
GitLab QAの信頼性と堅牢性を高めるために、GitLab CE / EEビューとGitLab QAの間にカップリングを導入することでこの問題を解決する必要がありました。
脆弱なテスト問題をどのように解決したのでしょうか?
現在のところ、新しいPage::Base
派生クラスを追加するとき、ページオブジェクトが依存するすべてのセレクタも定義する必要があります。
CE / EE リポジトリにコードをプッシュすると、qa:selectors
サニティテストジョブが CI パイプラインの一部として実行されます。
このテストでは、qa/page
ディレクトリに実装したすべてのページオブジェクトを検証します。テストに失敗した場合、ビュー/セレクタの定義がないか、無効であることが通知されます。
ページオブジェクトを正しく実装するには?
ページオブジェクトとそれが実際に実装されるGitLabビューの間の結合を定義するDSLを構築しました。 以下の例をご覧ください。
module Page
module Main
class Login < Page::Base
view 'app/views/devise/passwords/edit.html.haml' do
element :password_field
element :password_confirmation
element :change_password_button
end
view 'app/views/devise/sessions/_new_base.html.haml' do
element :login_field
element :password_field
element :sign_in_button
end
# ...
end
end
end
要素の定義
view
DSLメソッドは、要素をレンダリングするRails View、パーシャル、またはVueコンポーネントに対応します。
element
DSLメソッドは、対応するdata-qa-selector=element_name_snaked
データ属性をビューファイルに追加する必要がある要素を宣言します。
実際のビューのコードにマッチする値 (文字列あるいは正規表現) を定義することもできますが、 2 つの理由により、この方法は推奨されません:
- 一貫性: 要素を定義する方法は一つしかありません。
- 懸念事項の分離:QAは、他のコンポーネントが使用するコードやクラス(例:
js-*
クラスなど)を再利用する代わりに、専用のdata-qa-*
属性を使用します。
view 'app/views/my/view.html.haml' do
### Good ###
# Implicitly require the CSS selector `[data-qa-selector="logout_button"]` to be present in the view
element :logout_button
### Bad ###
## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
# Require `f.submit "Sign in"` to be present in `my/view.html.haml
element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern
## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
# Match every line in `my/view.html.haml` against
# `/link_to .* "My Profile"/` regexp.
element :profile_link, /link_to .* "My Profile"/ # rubocop:disable QA/ElementWithPattern
end
ビューへの要素の追加
次の要素があるとすると…
view 'app/views/my/view.html.haml' do
element :login_field
element :password_field
element :sign_in_button
end
これらの要素をビューに追加するには、定義されている各要素にdata-qa-selector
属性を追加して、Rails View、パーシャル、または Vue コンポーネントを変更する必要があります。
この場合、data-qa-selector="login_field"
、data-qa-selector="password_field"
、そしてdata-qa-selector="sign_in_button"
アプリ/views/my/view.html.haml
= f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required.", data: { qa_selector: 'login_field' }
= f.password_field :password, class: "form-control bottom", required: true, title: "This field is required.", data: { qa_selector: 'password_field' }
= f.submit "Sign in", class: "btn btn-success", data: { qa_selector: 'sign_in_button' }
注意すべきこと
- 要素名と
qa_selector
は一致し、snake_caseされていなければなりません。 - 要素が無条件にページに表示される場合は、要素に
required: true
を追加します。動的要素検証を参照してください。 - 既存のPage Objectsに
.qa-selector
。.qa-selector
CSSクラスよりも、data-qa-selector
の定義方法を優先すべきです。
data-qa-selector
対.qa-selector
GitLab 12.1 で導入されました。
ビュー内の要素を定義するには、2つの方法がサポートされています。
-
data-qa-selector
属性 -
.qa-selector
クラス
既存の.qa-selector
クラスはすべて非推奨とみなし、data-qa-selector
の定義方法を選ぶべきです。
動的要素選択
GitLab 12.5 で導入されました。
自動テストでありがちなのは、単一の「one-of-many」要素を選択することです。 複数の項目からなるリストで、何を選択するのかを区別するにはどうすればよいでしょうか。 この回避策として最も一般的なのは、テキストマッチングです。 その代わりに、テキストではなく、一意の識別子で特定の要素をマッチングするのがよりよい方法です。
これを回避するために、data-qa-*
拡張可能な選択メカニズムを追加しました。
使用例
例1
次のようなRailsビューがあるとします (例としてGitLabイシューを使用します):
%ul.issues-list
- @issues.each do |issue|
%li.issue{data: { qa_selector: 'issue', qa_issue_title: issue.title } }= link_to issue
Railsモデルでマッチングすることで、特定のイシューを選択することができます。
class Page::Project::Issues::Index < Page::Base
def has_issue?(issue)
has_element? :issue, issue_title: issue
end
end
私たちのテストでは、この特定のイシューが存在することを検証することができます。
describe 'Issue' do
it 'has an issue titled "hello"' do
Page::Project::Issues::Index.perform do |index|
expect(index).to have_issue('hello')
end
end
end
例2
インデックスによって…
%ol
- @some_model.each_with_index do |model, idx|
%li.model{ data: { qa_selector: 'model', qa_index: idx } }
expect(the_page).to have_element(:model, index: 1) #=> select on the first model that appears in the list
例外
場合によっては、セレクタを追加することができなかったり、追加する価値がなかったりすることもあります。
UIコンポーネントの中には、サードパーティによってメンテナンスされているものも含め、外部のライブラリを使用しているものがあります。 ライブラリがGitLabによってメンテナンスされている場合でも、セレクタのサニティテストはGitLabプロジェクト内のコードに対してのみ実行されるため、ライブラリ内のコードに対してビューのパスを指定することはできません。
このようなまれなケースでは、ページオブジェクトメソッドでCSSセレクタを使用するのが合理的で、element
。
ページに関する定義
ページによっては、共通の動作があったり、EE固有のメソッドを追加するEE固有のモジュールが前置されていたりします。
これらのモジュールは
-
QA::Page::PageConcern
モジュールからextend QA::Page::PageConcern
で拡張。 -
self.prepended
メソッドをオーバーライドする必要がある場合は、include
/prepend
他のモジュール自身をオーバーライドする必要がある場合は、view
またはelements
を定義します。 -
self.prepended
で最初にsuper
を呼び出します。 - 他のモジュールをインクルード/プリペンドし、
base.class_eval
ブロックでview
/elements
を定義し、モジュールをプリペンドするクラスで定義されていることを確認します。
これらの手順により、サニティ・セレクター・チェックが適切に問題を検出することが保証されます。
例えば、qa/qa/ee/page/merge_request/show.rb
は、qa/qa/page/merge_request/show.rb
にEE固有のメソッドを追加しています(QA::Page::MergeRequest::Show.prepend_if_ee('QA::EE::Page::MergeRequest::Show')
と一緒です)。以下はその実装方法です(関連する部分のみを示し、上記の4つのステップをインラインコメントで参照しています):
module QA
module EE
module Page
module MergeRequest
module Show
extend QA::Page::PageConcern # 1.
def self.prepended(base) # 2.
super # 3.
base.class_eval do # 4.
prepend Page::Component::LicenseManagement
view 'app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue' do
element :head_mismatch, "The source branch HEAD has recently changed."
end
[...]
end
end
end
end
end
end
end
ローカルでのテストの実行
開発者は、qa:selectors
テストを実行することができます。
bin/qa Test::Sanity::Selectors
qa
ディレクトリ内部から。
どこに助けを求めますか?
より詳しい情報が必要な場合は、Slack の#quality
チャンネルで助けを求めてください(内部、GitLab チームのみ)。
チームメンバーでなくても貢献したい場合は、GitLab CE issue trackerに~QA
のラベルを付けてイシューを登録してください。