티스토리 뷰
이번에 이직하게 되면서 온보딩 과제로 E2E 테스트 코드 작성 과제를 받게 되었습니다.
그 과정에서 고민했던 부분들을 정리해보았습니다.
문제 및 현황
기존에는 제품 배포 시 기획자분들이 수동으로 일일이 테스트를 하고 있던 상황이었습니다. 제품의 크기가 작은편이 아니어서 어떨때는 하루종일 테스트만 하게되는 상황도 있다고 합니다. 이를 해결하기 위해 E2E 테스트 코드를 작성하여 테스트를 자동화하기로 하였습니다.
테스트 도구는 이미 playwright가 도입되어 있었고 인증 관련 부분의 테스트코드가 이미 작성되어 있어 그대로 Playwright를 사용하기로 하였습니다.
비용 최소화를 위한 고민
먼저 최소한의 공수로 최대한의 효과를 내기 위해 고민하였습니다. 테스트코드에서 관리시 가장 비용이 많이 드는 부분은 '테스트코드를 작성할 때', '기능이 수정되거나 UI가 수정되었을 때 등의 테스트코드의 관리' 라고 생각했습니다. 먼저 첫번째 '테스트코드를 작성할 때'의 비용을 줄이는 방법을 찾아보았습니다.
Playwright MCP
처음에는 테스트 코드 관리도 비용이기 때문에 상대적으로 관리하기 쉬운 자연어 기반으로 테스트를 진행하려고 시도하였습니다. Playwright MCP를 활용하여 테스트를 시도하였는데 Playwright MCP는 직접 브라우저를 제어하여 테스트를 할 수 있도록 해주기 때문에 다음과 같이 자연어로 테스트를 할 수 있습니다.
하지만 두가지 이유로 Playwright MCP를 활용한 자연어로 테스트만으로는 부적절하다는 생각이 들었습니다.
첫째로 테스트를 실행할때마다 과정이 달라졌습니다. 같은 자연어를 입력하더라도 어떨때는 특정 요소를 찾는데 여러 단계가 필요하기도 하고 어떨때에는 한번에 찾을때도 있었습니다. 이런 부분이 테스트를 불안정하게 만들 것이라 생각하여 부적절하다고 판단하였습니다.
둘째로 AI 토큰 사용량입니다. 현재 회사의 제품이 복잡하고 방대할 뿐더러 직접 관리중인 테스트케이스의 양 자체가 컸습니다. 이 많은 양의 테스트 케이스를 Agent로 실행하기에는 많은 토큰을 사용해야합니다. 대략적인 추정치로는 전수 테스트를 한번 할때마다 십몇만원에서 수십만원의 토큰이 사용될 것으로 추정하였습니다.
때문에 최초 1회 테스트 코드 작성시에만 Playwright MCP의 도움을 받아 테스트코드를 작성하고 그 이후의 테스트 부터는 작성된 테스트코드를 실행하기로 하였습니다. 다음 영상처럼 브라우저로 직접 테스트한 결과를 기반으로 테스트 코드를 작성할 수 있습니다.
작성된 코드는 Repository에 올려두었습니다.
Playwright Agents
앞서 이야기했듯 테스트코드에서 관리시 가장 비용이 많이 드는 부분 중 하나가 '기능이 수정되거나 UI가 수정되었을 때 등의 테스트코드의 관리' 입니다. 때문에 테스트 코드 관리시에도 AI Agent의 도움을 받고자 하였습니다. 마침 Playwright에서 지원하는 Playwright Test Agents 라는 Agent가 있었고 이를 살펴보았습니다.
Playwright Test Agents의 각 Agents는 이름 그대로 테스트 계획을 세워 Markdown 파일로 저장해주는 Agent인 Planner, Markdown에 작성된 테스트 계획을 토대로 테스트 코드를 작성해주는 Generator, 기능 수정, UI 변경 등으로 인해 테스트가 실패했을때 테스트 코드를 수정해주는 Healer가 있습니다. (자세한 내용은 공식문서 참조부탁드립니다.)
이 중 테스트 코드 유지보수를 위해서 Healer를 사용하는 것을 고려하였습니다. Healer를 사용하면 다음처럼 UI 구현부 코드를 직접 참조하여 테스트코드를 수정해 줍니다.
하지만 회사에서 Cursor만 사용하고 있었고 Playwright Test Agents는 VScode, Claude, OpenCode만 지원하여 사용하는 것을 보류하였습니다. (추후 Claude를 회사 계정으로 사용할 수 있게 되면 도입가능할지도..?)
번외로 Playwright Agent의 Planner와 Generator는 이미 따로 기획자분들이 테스트 케이스를 엑셀로 관리하고 있는 우리 회사의 상황과 맞지 않는다고 판단하여 사용하지 않았습니다.
효율 이외의 고민들...
Best Practice
Playwright 공식문서에서는 Best Practice 가이드라인을 제공한다. 이는 Playwright를 사용할 때 뿐만 아니라 E2E 테스트 전반에 도움이 될만한 Best Pratice인 것 같다.
위 Best Practice와 테스트 코드를 작성하며 얻은 몇가지 인사이트를 바탕으로 나만의 테스트 코드 작성 규칙을 세웠다. (자세한 내용은 공식문서 참조 부탁드립니다.)
- 유저 입장에서는 보이지도 않고 상관없는 css 선택자 보단 유저가 볼 수 있는 요소(getByRole, getByText, getByLabel 등)로 테스트하라.
- 마찬가지로 사용자가 알 수 없는 배열, 데이터 등을 검증하지 말고 사용자가 볼 수 있는 요소를 검증하라.
- 테스트는 독립적으로 실행될 수 있어야 한다.
- Third party의 테스트는 피해라. 자신이 제어할 수 있는 부분만 테스트하라.
- waitForTimeout 사용은 피하라. 이는 Flaky(불안정한) 테스트를 유발할 수 있다. 대신 toBeEnable같은 검증 함수(검증함수는 자동으로 대기한다)를 사용하거나 waitFor({state: 'visible'}) 등의 의도가 명확한 메소드를 사용하라.
- Page Object Model 내부에서 비즈니스 로직을 검증하지 말고 test 함수 내부에서 검증하라.
이 외에도 회사 코드와 제품, 테스트 케이스에 맞추어 몇가지 룰을 더 정의하였습니다.
접근성
위 규칙에서 말했듯 유저가 볼 수 있거나 유저에게 영향이 있는 요소로 테스트를 하기 위해서는 접근성이 좋아야합니다. 이 테스트를 진행하기 전에는 접근성은 SEO 최적화나 장애인의 불편함없는 서비스 이용을 위해 지원해야하는 것 정도로 생각하고 있었습니다. 하지만 이번 테스트 코드를 작성하면서 접근성을 지키면 테스트 하기에도 편하다는 것을 깨달았습니다. 그 예로 자주 바뀌는 css 선택자 대신 role 속성, label 등으로 가져오면 스타일 변경으로 인해 테스트가 깨질 확률이 더 적어 테스트 코드를 유지보수하기 쉽습니다. (테스트코드도 코드다!) 추가적으로 일정 규모 이상의 민간기관에도 접근성을 준수해야할 의무가 법적으로 명시되어 있습니다. (장애인 차별 금지법 참고)
접근성을 준수하기 위해 Sematic tag를 사용하고 role 속성을 부여 해주었습니다. (Playwright의 getByRole에서 지원하는 role 목록)
또한 Icon만 있는 버튼에 aria-label을 사용하여 getByLabel로 해당 요소를 가져온다던지 Skeleton UI에 aria-busy 속성을 사용하여 toHaveAttribute 메소드로 로딩이 끝날때가지 기다린다리는 방법 등 aria 속성들도 적용하였습니다.
접근성에 관해서는 https://www.w3.org/TR/wai-aria-1.2/ 이 문서를 참조하시면 좋을 것 같습니다.
Page Object Model
마찬가지로 위 규칙에서 잠깐 언급했듯 Page Object Model(POM)이란 개념을 사용하고 있습니다. POM은 이름 그대로 페이지를 모델링한 객체입니다. 쉽게 생각하면 페이지 내부에서 할 수 있는 행동을 메소드화 시켰다고 볼 수 있습니다. 다음은 간단한 예제입니다.
import type { Page, Locator } from '@playwright/test'
class LoginPage {
readonly idInput: Locator
readonly passwordInput: Locator
readonly loginButton: Locator
constructor(page: Page) {
this.page = page
this.idInput = page.getByLabel('Username')
this.passwordInput = page.getByLabel('Password')
this.loginButton = page.getByRole('button', { name: 'Login' })
}
async login(id: string, password: string) {
await this.idInput.fill(id)
await this.passwordInput.fill(password)
await loginButton.click()
}
async clickRegister() {}
async clickFindPassword() {}
...
}
test('Login test', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.login('id', 'password')
await expect(...로그인 검증 로직)
...
})
이 때 테스트 코드를 작성하다보면 비즈니스 검증 로직이 POM속으로 들어가 종종 메소드가 재사용이 힘들어지거나 메소드의 이름만 보고 어떤 검증로직이 들어있는지 알기 힘든 경우가 발생하였습니다. 따라서 비즈니스 검증 로직은 최대한 test할 때 검증하는 것으로 규칙을 세웠습니다.
테스트 결과 커스텀
Playwright에서는 기본적으로 테스트 결과를 다음과 같은 html 형태로 제공합니다.


하지만 위처럼 테스트 결과 보고서가 생성될 경우 PO분들이 어떤부분에서 어떤 이유로 테스트가 실패하였는지 알아보기 어려울 것이라고 생각하였습니다. 때문에 test.step 함수를 활용하여 PO분들이 관리하는 TC의 ID들과 테스트 코드가 1:1 대응되도록 만들었습니다.
test('유즈 케이스 번호', async ({ page }) => {
test.step('테스트 케이스 1번', async () => {})
test.step('테스트 케이스 2번', async () => {})
...
})
추가로 Playwright에서 Custom reporter를 만들 수 있게 해주는 Reporter 인터페이스를 통해 PO분들도 테스트 결과를 쉽게 받아볼 수 있도록 하였습니다. 이 Repoter를 활용하면 원하는 파일 형태로 만들거나 Notion API, Teams Webhook 등 외부 업무도구의 API들을 활용하여 다양하게 테스트 결과를 커스텀 할 수 있습니다. 추가적으로 test에 Annotation을 활용하면 더 많은 정보를 담은 보고서를 만들 수 있습니다.
마치며
그동안 E2E 테스트를 다루어 본적이 없는데 이번 기회로 다루어보게 되면서 많은 것들을 경험하고 고민해보게 되었습니다. 기존에는 E2E 테스트 코드도 그냥 테스트 코드를 작성하면 그만이라고 생각했지만 생각보다 많은 디테일들이 필요한다는 것을 깨달았습니다. 이번에 작성한 테스트코드도 신경써서 관리하지 않으면 방치될 것을 알기에 쉽게 관리할 수 있는 방법을 찾아보고 적용해야할 것 같습니다. (노코드 기반의 E2E 자동화 테스트 도구를 만들어보고 싶다는 욕심도...) E2E 테스트로 고민하는 분들에게 이 글이 조금이나마 도움이 되었으면 좋겠습니다. 그럼 이만 글 줄이도록 하겠습니다. 감사합니다.
'Web' 카테고리의 다른 글
| 웹 프론트 Pan Zoom 개발기 (0) | 2021.03.23 |
|---|---|
| CORS(Cross-Origin-Resource-Sharing) (0) | 2019.05.02 |
- Total
- Today
- Yesterday
- js 파일 다운로드
- playwright healer
- browser 대용량 파일 다운로드
- ARIA
- playwright agents
- playwright mcp
- 테스트
- E2E
- Playwright
- 접근성
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
