- 왜 이것이 필요한가요?
- 과거에 어떤 문제가 있었나요?
- 불안정한 테스트 문제는 어떻게 해결했나요?
- 페이지 객체를 올바르게 구현하는 방법은 무엇인가요?
- 로컬에서 테스트 실행
- 도움을 요청할 수 있는 곳은 어디인가요?
GitLab QA에서의 페이지 객체
GitLab QA에서는 페이지 객체 라는 알려진 패턴을 사용하고 있습니다.
이는 GitLab의 모든 페이지에 대한 추상화를 구축하여 GitLab QA 시나리오를 실행하는 데 사용합니다. 페이지에서 양식을 작성하거나 버튼을 선택하는 등의 작업을 할 때 해당 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'
로 변경하면 다른 필드 식별자가 생성되어 모든 테스트가 실패하게 됩니다.
우리는 GitLab에 로그인하기 위해 Page::Main::Login.perform(&:sign_in_using_credentials)
를 사용하기 때문에 페이지 객체가 GitLab에 로그인하려는 경우에는 단일 진실 공급원이며, 우리는 fill_in 'user-login'
을 fill_in 'user-username'
로 한 곳에서만 업데이트해야 합니다.
과거에 어떤 문제가 있었나요?
성능 문제로 인해 모든 커밋에 대해 QA 테스트를 실행하지 않고 있으며, 패키지를 빌드하고 모든 것을 테스트하는 데 걸리는 시간 때문에 그렇게 하지 않고 있습니다.
그래서 누군가가 new session 보기에서 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 뷰, 부분 또는 Vue 컴포넌트에 해당하는 요소를 렌더링합니다.
element
DSL 메서드는 각 요소에 대해 해당하는 testid=element-name
데이터 속성이 뷰 파일에 추가되어야 함을 선언합니다.
또한 실제 뷰 코드와 일치하는 값을(문자열 또는 정규 표현식) 정의할 수도 있지만 이것은 사용이 중단되었으며 다음 두 가지 이유로 권장되지 않습니다:
- 일관성: 요소를 정의하는 유일한 방법이 있습니다.
- 관심사의 분리: 테스트는 다른 컴포넌트에서 재사용되는 코드나 클래스를 재사용하는 대신 전용
data-testid
속성을 사용합니다(예:js-*
클래스 등).
view 'app/views/my/view.html.haml' do
### 좋은 예제 ###
# 뷰에 `[data-testid="logout-button"]` CSS 선택기가 있어야 함
element 'logout-button'
### 나쁜 예제 ###
## 이것은 `QA/ElementWithPattern` RuboCop cop에 의해 사용이 중단되고 금지됩니다.
# `my/view.html.haml`에서 `f.submit "Sign in"`을 요구해야 함
element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern
## 이것은 `QA/ElementWithPattern` RuboCop cop에 의해 사용이 중단되고 금지됩니다.
# 모든 `my/view.html.haml`의 줄을 `/link_to .* "My Profile"/` 정규 표현식과 일치시켜야 함.
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-testid
속성을 렌더링해야 합니다.
우리의 경우 data-testid="login-field"
, data-testid="password-field"
및 data-testid="sign-in-button"
app/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: { testid: 'login_field' }
= f.password_field :password, class: "form-control bottom", required: true, title: "This field is required.", data: { testid: 'password_field' }
= f.submit "Sign in", class: "btn btn-confirm", data: { testid: 'sign_in_button' }
주의할 점:
- 요소의 이름과
data-testid
는 일치하고 kebab 케이스여야 합니다. - 요소가 무조건적으로 페이지에 나타나는 경우, 요소에
required: true
를 추가해야 합니다. Dynamic element validation을 참조하십시오. - 페이지 객체에서
data-qa-selector
클래스를 볼 수 없어야 합니다. 우리는 요소 정의에data-testid
메서드를 사용해야 합니다(data-testid
와 관련한).
data-testid
vs data-qa-selector
- GitLab 16.1에서 도입됨
기존의 data-qa-selector
클래스는 사용이 중단되었다고 간주되며 우리는 data-testid
정의 방법을 사용해야 합니다.
동적 요소 선택
자동화된 테스트에서 흔히 발생하는 일은 “여러 요소 중 하나”를 선택하는 것입니다. 디렉터리에서 여러 항목 중에서 어떻게 차이점을 찾을 수 있는지에 대한 자세한 내용은 차후 업데이트하겠습니다.
위 내용을 바탕으로 data-qa-*
확장 가능한 선택 메커니즘을 추가했습니다.
예시
예시 1
다음과 같은 Rails 뷰(예: GitLab 이슈)가 있다고 가정합시다.
%ul.issues-list
- @issues.each do |issue|
%li.issue{data: { testid: 'issue', qa_issue_title: issue.title } }= link_to issue
테스트에서 이 특정 이슈가 존재하는지 유효성을 검사할 수 있습니다.
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: { testid: 'model', qa_index: idx } }
expect(the_page).to have_element(:model, index: 1) #=> 리스트에서 처음 나타나는 모델을 선택
예외
어떤 경우에는 선택기를 추가하는 것이 불가능하거나 가치가 없는 경우도 있을 수 있습니다.
일부 UI 컴포넌트는 서드 파티가 유지보수하는 외부 라이브러리를 사용합니다.
GitLab이 유지보수하는 라이브러리라 하더라도, 선택기 안전성 테스트는 GitLab 프로젝트 내의 코드에 대해서만 실행되므로, 라이브러리 내 코드에 대한 보기의 경로를 지정할 수 없습니다.
이러한 드문 경우에는 페이지 오브젝트 메소드에서 CSS 선택기를 사용하고, ‘element’를 추가할 수 없는 이유에 대해 설명하는 주석을 달면 합리적입니다.
페이지 관심사 정의
일부 페이지는 공통 동작을 공유하거나/또는 EE(Enterprise Edition)-특정 모듈로 시작되어, EE-특정 메소드를 추가할 수 있습니다.
이러한 모듈은 다음을 해야 합니다:
-
QA::Page::PageConcern
모듈에서 확장하고,extend QA::Page::PageConcern
로 확장합니다. - 다른 모듈을
include
/prepend
해야하는 경우self.prepended
메소드를 재정의하고,view
또는elements
를 정의합니다. -
self.prepended
에서 가장 먼저super
를 호출합니다. - 다른 모듈을
include
/prepend
하고,view
/elements
를 정의하면서 이러한 클래스의 정의가 확실히 되도록base.class_eval
블록에 넣습니다.
이러한 단계들은 선택기의 안정성 검사가 문제를 적절하게 감지할 수 있도록 합니다.
예를 들어, qa/qa/ee/page/merge_request/show.rb
는 EE-특정 메소드를 qa/qa/page/merge_request/show.rb
에 추가합니다 (QA::Page::MergeRequest::Show.prepend_mod_with('Page::MergeRequest::Show', namespace: QA)
). 아래는 어떻게 구현되는지 (관련 부분만 표시하고, 인라인 주석에 설명된 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
디렉터리에서 다음과 같이 실행하여 qa:selectors
테스트를 실행할 수 있습니다.
bin/qa Test::Sanity::Selectors
도움을 요청할 수 있는 곳은 어디인가요?
더 많은 정보가 필요한 경우, 내부적으로 GitLab 팀 회원만이 Slack의 #test-platform
채널에서 도움을 요청하세요.
팀 멤버가 아니고 기여에 도움이 필요한 경우, ~QA
라벨을 사용하여 GitLab CE 이슈 트래커에서 이슈를 열어주세요.