이전 포스팅에서 selectors 문법을 이용해 web elements에 접근하는 방법을 알아봤다.
thenable 문법을 이용해 선택한 엘리먼트를 재사용할 수 있었지만, 그럼에도 불구하고 달라지는 라벨의 텍스트나 달력의 날짜에 따라 동일한 코드가 반복되는 것은 막을 수 없다.
POM(Page Object Model)은 web elements를 선택할 때 재사용 코드를 사용할 수 있도록 해주는 디자인 패턴이다. POM에서 각 페이지들은 클래스로 구현되며, 특정 페이지에서 테스트를 진행할때 이 클래스에서 정의한 메소드를 호출하여 테스트를 진행해야 한다.
로그인 페이지에서 테스트를 진행할 때마다 각 it
에 cy.visit('/login')
을 작성하는 것은 확실히 번거로운 일이다.
PODP를 이용해 어떻게 번거로운 일을 줄일 수 있는지 살펴보자.
앞서 말했지만 cypress 폴더 밑에는 4가지의 폴더가 기본적으로 존재한다. 리마인드 해보면 다음과 같이 정리할 수 있다.
POM 파일은 support/page_objects 이하에 작성한다. support 밑에만 들어가면 폴더 이름은 상관 없다.
이번에 작성할 클래스는 각 페이지로 이동 하는 것을 도와준다.
대충 페이지 좌측에 네비게이션 바가 세로로 있고 바에는 각 페이지로 이동할 수 있는 버튼이 있다고 가정한다. 각 버튼을 누르면 드롭다운 리스트가 나타나고 세부 페이지로 이동한다.
여기서 조심해야 하는 점은 드롭다운
리스트는 토글 형식의 컴포넌트이기에 순서를 잘못 작성하면 드롭다운 내부의 상세 리스트에는 cypress가 접근하지 못한다.
이를 판별하는 if else를 모든 it
내부에 작성하는 것은 불필요한 반복 코드를 양산하기 때문에 POM에 해당 기능을 넣어 재사용 가능한 로직을 만들었다.
function selectGroupMenuItem(groupName) {
cy.contains('a', groupName)
.then( menu => {
cy.wrap(menu)
.find('.isExpanded div div')
.invoke('attr', 'date-test')
.then(attr => {
if(attr.includes("closed")) {
cy.wrap(menu).click();
}
})
})
}
export class NavigationPage {
formPage() {
selectGroupMenuItem("Form")
cy.contains("This is Form Page").click();
}
loginPage() {
selectGroupMenuItem("Login")
cy.contains('This is Login Page').click();
}
};
export const navigateTo = new NavigationPage();
다음과 같이 navigateTo라는 변수에 정의한 NavigationPage POM을 인스턴스화 하여 외부 파일에서 사용할 수 있는 모듈로 만들어줬다.
이제 이 모듈을 사용하는 intergration 파일 내부에서는 페이지에 접근하거나 폼을 누르는 행위는 NavigationPage 내부에 추상화된 로직을 호출하기만 하면 된다.
//test.spec.js
import { navigateTo } from "../support/page_objects/navigationPage";
describe("POM Test", () => {
beforeEach('start', () => {
cy.visit('/')
})
it('should navgation POM fit with each page', () => {
navigateTo.formPage();
navigateTo.loginPage();
})
});
POM을 쓰니 테스트 파일 내부 코드의 구독성이 올라갔다.
POM을 이용해 로그인하는 과정을 추상화 해보자.
// cypress/support/form_objects/loginPage
class LoginPage {
submitLoginInform(name,password ) {
cy.contains("div", "Sign In")
.find('form')
.then(form => {
cy.wrap(form).find('[placeholder="ID"]').type(email)
cy.wrap(form).find('[placeholder="Password"]').type(password)
})
}
}
export handleLogin = new LoginPage();
//test.spec.js
import { navigateTo } from "../support/page_objects/navigationPage";
import { handleLogin } from "../support/page_objects/loginPage";
describe("POM Test", () => {
beforeEach('start', () => {
cy.visit('/')
})
it('should navgation POM fit with each page', () => {
navigateTo.formPage();
navigateTo.loginPage();
})
it("should login page with using POM", () => {
navigateTo.loginPage();
handleLogin.submitLoginForm("dante", "dante password")
})
});
POM을 이용해 코드 재사용성을 높였지만 앞선 예제에서 POM 모듈을 각기 다른 테스트 파일에 매번 임포트 시켜줘야 한다. 테스트 코드 작성 작업을 협업한다면 어떤 POM이 정의되었는지, 어느 클래스에 작성되었는지 살펴봐야 한다.
cypress에서는 탑 레벨에서 사용하는 함수들을 좀더 간편하게 재사용할 수 있는데, 커스텀 커맨드를 만들면 된다.
support/commmands.js 파일에 커스텀 커맨드를 작성해보자.
// support/commmands.js
Cypress.Commands.add('openHomePage', () => {
cy.visit('/')
});
Cypress.Commands.add('login', () => {
cy.visit('/login');
cy.get('[placeholder="Email"]').type('dante');
cy.get('[placeholder="Password"]').type('dante password');
cy.get('form').submit();
})
그리고 정의한 커스텀 커맨드는 아래처럼 index.js 파일에서 임포트 해주어야 한다.
// support/index.js
import './commands'
이제 사용하는 쪽에서는 cy.login() 만 호출하면 로그인을 할 수 있게 되었다. 매우 간편하다.
이번 시간에는 POM과 Custom Command에 대해 알아보았다.
다음 시간에는 http rest api, api mocking 방법에 대해 알아보자.