Playwright로 E2E 테스트 자동화하기
1. E2E 테스트의 중요성과 Playwright의 등장
현대 웹 애플리케이션은 복잡한 사용자 인터페이스와 상호작용을 특징으로 합니다. 이러한 애플리케이션의 품질을 보장하기 위해서는 단위 테스트, 통합 테스트뿐만 아니라, 실제 사용자의 관점에서 전체 시스템이 제대로 작동하는지 검증하는 종단 간(End-to-End, E2E) 테스트가 필수적입니다. E2E 테스트는 사용자가 애플리케이션과 상호작용하는 모든 시나리오를 시뮬레이션하여, 시스템 전반에 걸친 잠재적인 문제를 발견하고 사용자 경험을 최적화하는 데 결정적인 역할을 합니다.
하지만 E2E 테스트는 종종 구축하기 어렵고, 느리며, 불안정하다는 인식이 있습니다. 브라우저 환경 설정, 요소 탐색의 어려움, 비동기 작업 처리, 그리고 테스트 간의 의존성 관리 등 다양한 도전 과제들이 존재하기 때문입니다. 이러한 문제점들을 해결하고 개발자들이 더 쉽고 효율적으로 E2E 테스트를 자동화할 수 있도록 돕기 위해 다양한 테스트 프레임워크들이 등장했습니다. 그중에서도 마이크로소프트에서 개발한 Playwright는 강력한 기능과 뛰어난 안정성으로 빠르게 주목받고 있는 최신 웹 테스트 자동화 도구입니다.
Playwright는 Chromium, Firefox, WebKit 등 모든 주요 브라우저를 단일 API로 지원하며, 자동 대기(auto-wait) 기능, 병렬 테스트 실행, 모바일 에뮬레이션, 네트워크 요청 가로채기 등 E2E 테스트에 필요한 거의 모든 기능을 제공합니다. 이 글에서는 Playwright를 활용하여 E2E 테스트를 자동화하는 방법을 단계별로 살펴보고, 실전 코드 예제를 통해 Playwright의 강력함을 경험할 것입니다.
2. Playwright란 무엇인가?
Playwright는 웹 애플리케이션의 E2E 테스트를 자동화하고 웹 스크래핑, PDF 생성 등 다양한 브라우저 자동화 작업을 수행할 수 있는 Node.js 라이브러리입니다. Selenium, Cypress와 같은 기존 도구들의 장점을 흡수하고 단점을 보완하여, 더욱 빠르고 안정적이며 강력한 테스트 환경을 제공하는 것을 목표로 합니다.
2.1. Playwright의 핵심 특징
Playwright가 개발자들에게 사랑받는 주요 특징들은 다음과 같습니다.
- 크로스 브라우저 지원: Chromium (Chrome, Edge), Firefox, WebKit (Safari) 등 모든 주요 웹 브라우저를 단일 API로 제어할 수 있습니다. 이는 다양한 브라우저 환경에서의 호환성 테스트를 쉽게 만듭니다.
- 크로스 플랫폼 지원: Windows, macOS, Linux 환경에서 모두 작동하며, CI/CD 환경에서도 원활하게 통합됩니다.
- 다양한 언어 지원: Node.js (JavaScript/TypeScript) 외에도 Python, Java, .NET 등 여러 프로그래밍 언어를 공식적으로 지원합니다. 이 가이드에서는 TypeScript를 중심으로 설명합니다.
- 자동 대기(Auto-wait): Playwright는 요소가 나타나거나, 활성화되거나, 애니메이션이 끝날 때까지 자동으로 기다립니다. 이는 테스트의 불안정성을 크게 줄여줍니다.
- 병렬 테스트 실행: 여러 테스트를 동시에, 격리된 브라우저 컨텍스트에서 실행하여 테스트 시간을 획기적으로 단축합니다.
- 강력한 선택자(Selectors): CSS, XPath, 텍스트, 역할(role), 테스트 ID 등 다양한 선택자를 지원하며,
has-text,has등의 의사 클래스를 통해 복잡한 요소를 정확하게 찾을 수 있습니다. - 모바일 에뮬레이션: 다양한 모바일 장치의 뷰포트, 사용자 에이전트, 터치 이벤트 등을 에뮬레이션하여 반응형 디자인 테스트를 쉽게 수행할 수 있습니다.
- 네트워크 가로채기: HTTP/HTTPS 요청 및 응답을 가로채고 수정하여, API 응답을 모의(mock)하거나 특정 네트워크 조건을 시뮬레이션할 수 있습니다.
- 스크린샷 및 비디오 녹화: 테스트 실패 시 자동으로 스크린샷을 찍거나, 전체 테스트 과정을 비디오로 녹화하여 디버깅을 용이하게 합니다.
2.2. Playwright vs. 다른 E2E 테스트 도구
Playwright는 기존의 E2E 테스트 도구들과 비교했을 때 여러 가지 차별점을 가집니다. 대표적인 도구인 Selenium, Cypress와 비교하여 Playwright의 위치를 이해하는 데 도움이 되는 표를 살펴보겠습니다.
| 특징/도구 | Playwright | Cypress | Selenium |
|---|---|---|---|
| **아키텍처** | 브라우저 내부/외부 모두 제어 (Node.js 프로세스에서 직접 통신) | 브라우저 내부에서 실행 (브라우저와 동일한 이벤트 루프) | 브라우저 외부에서 WebDriver API를 통해 제어 |
| **언어 지원** | JS/TS, Python, Java, .NET (공식) | JS/TS (주로) | JS, Python, Java, C#, Ruby 등 (다양) |
| **브라우저 지원** | Chromium, Firefox, WebKit (모두 지원) | Chrome, Edge, Firefox (WebKit 제한적) | 모든 주요 브라우저 (WebDriver 구현에 따라) |
| **병렬 실행** | 기본 지원, 매우 효율적 | 유료 서비스(Cypress Cloud)에서 지원, 제한적 | Grid를 통해 지원, 설정 복잡 |
| **자동 대기** | 강력한 내장 자동 대기 기능 | 강력한 내장 자동 대기 기능 | 수동 대기(implicit/explicit waits) 필요 |
| **멀티 탭/윈도우** | 완벽 지원 | 지원 안함 (동일 탭/도메인 내에서만 가능) | 완벽 지원 |
| **크로스 도메인** | 완벽 지원 | 제한적 (동일 도메인에서만 동작) | 완벽 지원 |
| **네트워크 제어** | 강력한 API 지원 (요청/응답 가로채기, 모킹) | 강력한 API 지원 | 제한적 (프록시 설정 필요) |
| **모바일 에뮬레이션** | 강력 지원 | 제한적 | 제한적 |
| **디버깅** | Playwright Inspector, Trace Viewer, VS Code 연동 | Cypress Test Runner UI, Chrome DevTools | 브라우저 DevTools, 로그 |
Playwright는 브라우저 내부와 외부 모두를 제어할 수 있는 독특한 아키텍처 덕분에 Cypress의 "브라우저 내부 실행" 방식이 가지는 제약(크로스 도메인, 멀티 탭 등)을 극복하면서도 Selenium의 복잡한 설정과 느린 성능 문제를 해결했습니다.
3. Playwright 설치 및 기본 설정
이제 Playwright를 설치하고 기본적인 프로젝트를 설정하여 첫 번째 테스트를 작성해 보겠습니다.
3.1. Node.js 설치 확인
Playwright는 Node.js 환경에서 실행되므로, 먼저 Node.js가 설치되어 있는지 확인해야 합니다. 터미널에서 다음 명령어를 실행합니다.
node -v
npm -v
만약 설치되어 있지 않다면, Node.js 공식 웹사이트에서 최신 버전을 다운로드하여 설치합니다.
3.2. Playwright 프로젝트 초기화
새로운 프로젝트 폴더를 생성하고 해당 폴더로 이동한 후, 다음 명령어를 실행하여 Playwright를 초기화합니다.
mkdir my-playwright-project
cd my-playwright-project
npm init playwright@latest
이 명령어는 Playwright 설치 마법사를 시작합니다. 몇 가지 질문에 답해야 합니다.
- Do you want to use TypeScript or JavaScript? (TypeScript를 권장합니다.)
- Where to put your end-to-end tests? (기본값인
tests/를 사용합니다.) - Add a GitHub Actions workflow? (CI/CD 통합에 관심 있다면
Yes를 선택해도 좋지만, 이 가이드에서는No를 선택하고 나중에 수동으로 설명합니다.) - Install Playwright browsers (Chromium, Firefox, WebKit)? (Yes를 선택하여 필요한 브라우저를 모두 설치합니다.)
설치가 완료되면, 다음과 같은 파일 및 폴더 구조가 생성됩니다.
my-playwright-project/
├── tests/
│ └── example.spec.ts # 기본 테스트 파일
├── playwright.config.ts # Playwright 설정 파일
├── package.json
├── package-lock.json
└── tsconfig.json # TypeScript 설정 파일 (TypeScript 선택 시)
3.3. playwright.config.ts 살펴보기
playwright.config.ts 파일은 Playwright 테스트의 전반적인 설정을 담당합니다. 주요 설정들을 살펴보겠습니다.
import { defineConfig, devices } from @playwright/test;
export default defineConfig({
testDir: ./tests, // 테스트 파일이 위치할 디렉토리
fullyParallel: true, // 모든 테스트를 병렬로 실행
forbidOnly: !!process.env.CI, // CI 환경에서 .only 테스트 금지
retries: process.env.CI ? 2 : 0, // CI 환경에서 실패한 테스트 재시도 횟수
workers: process.env.CI ? 1 : undefined, // CI 환경에서 워커 수 (기본적으로는 CPU 코어 수)
reporter: html, // 테스트 결과 리포터 (html, list, dot 등)
use: {
baseURL: http://localhost:3000, // 테스트할 애플리케이션의 기본 URL
trace: on-first-retry, // 실패한 테스트에 대해 트레이스(trace) 기록
},
projects: [
{
name: chromium,
use: { ...devices[Desktop Chrome] },
},
{
name: firefox,
use: { ...devices[Desktop Firefox] },
},
{
name: webkit,
use: { ...devices[Desktop Safari] },
},
// {
// name: Mobile Chrome,
// use: { ...devices[Pixel 5] },
// },
// {
// name: Mobile Safari,
// use: { ...devices[iPhone 12] },
// },
],
// 웹 서버를 시작해야 하는 경우 (예: 로컬 개발 서버)
// webServer: {
// command: npm run start,
// url: http://localhost:3000,
// reuseExistingServer: !process.env.CI,
// },
});
testDir: 테스트 파일들이 위치한 디렉토리를 지정합니다.fullyParallel:true로 설정하면 모든 테스트 파일과 테스트를 병렬로 실행합니다.retries: 테스트가 실패했을 때 재시도할 횟수를 설정합니다. CI 환경에서 유용합니다.reporter: 테스트 결과를 어떤 형식으로 보고할지 지정합니다.html은 대화형 HTML 리포트를 생성하여 디버깅에 매우 유용합니다.use.baseURL: 테스트할 웹 애플리케이션의 기본 URL을 설정합니다.page.goto(/)와 같이 상대 경로를 사용할 수 있게 해줍니다.projects: 어떤 브라우저에서 테스트를 실행할지 정의합니다.devices객체를 통해 다양한 데스크톱 및 모바일 장치를 쉽게 설정할 수 있습니다.webServer: 테스트를 실행하기 전에 로컬 개발 서버를 자동으로 시작해야 할 경우에 사용합니다.
4. 첫 번째 Playwright 테스트 작성하기
이제 tests/example.spec.ts 파일을 수정하여 실제 웹 페이지를 테스트하는 코드를 작성해 보겠습니다. 가상의 로그인 페이지를 테스트하는 시나리오를 예로 들어보겠습니다.
4.1. 테스트 파일 생성
tests/login.spec.ts 파일을 새로 생성하고 다음 내용을 추가합니다.
// tests/login.spec.ts
import { test, expect } from @playwright/test;
// 테스트 그룹을 정의합니다.
test.describe(로그인 기능, () => {
// 각 테스트가 시작되기 전에 실행될 설정 (예: 로그인 페이지로 이동)
test.beforeEach(async ({ page }) => {
await page.goto(https://codepick.kr/login); // 가상의 로그인 페이지 URL
});
test(유효한 자격 증명으로 로그인 성공, async ({ page }) => {
// 1. 사용자명 입력 필드를 찾고 값을 입력합니다.
await page.locator(#username).fill(testuser);
// 2. 비밀번호 입력 필드를 찾고 값을 입력합니다.
await page.locator(#password).fill(testpassword);
// 3. 로그인 버튼을 클릭합니다.
await page.locator(button[type="submit"]).click();
// 4. 로그인 성공 후 리다이렉트된 페이지의 URL이 특정 경로를 포함하는지 확인합니다.
await expect(page).toHaveURL(/.*dashboard/); // 가상의 대시보드 URL
// 5. 로그인 성공 메시지 또는 대시보드에 특정 요소가 보이는지 확인합니다.
await expect(page.locator(h1)).toHaveText(환영합니다, testuser님!);
});
test(잘못된 비밀번호로 로그인 실패, async ({ page }) => {
await page.locator(#username).fill(testuser);
await page.locator(#password).fill(wrongpassword); // 잘못된 비밀번호
await page.locator(button[type="submit"]).click();
// 로그인 실패 메시지가 보이는지 확인합니다.
await expect(page.locator(.error-message)).toBeVisible();
await expect(page.locator(.error-message)).toHaveText(잘못된 사용자명 또는 비밀번호입니다.);
// URL이 변경되지 않았거나 여전히 로그인 페이지인지 확인합니다.
await expect(page).toHaveURL(/.*login/);
});
});
코드 설명:
import { test, expect } from @playwright/test;: Playwright 테스트에 필요한test함수와expect어설션 라이브러리를 가져옵니다.test.describe(로그인 기능, () => { ... });: 관련된 테스트들을 그룹화하는 데 사용됩니다.test.beforeEach(async ({ page }) => { ... });: 각test블록이 실행되기 전에 호출되는 훅입니다. 여기서는 모든 로그인 테스트 전에 로그인 페이지로 이동합니다.page객체는 Playwright에서 브라우저 페이지를 제어하는 핵심 객체입니다.test(유효한 자격 증명으로 로그인 성공, async ({ page }) => { ... });: 개별 테스트 케이스를 정의합니다.await page.goto(https://codepick.kr/login);: 특정 URL로 브라우저를 이동시킵니다.await page.locator(#username).fill(testuser);: CSS 선택자#username으로 요소를 찾고, 해당 입력 필드에 testuser 값을 입력합니다. Playwright는locator를 사용하여 요소를 찾고, 해당 요소에 대한 다양한 액션을 수행할 수 있습니다.await page.locator(button[type="submit"]).click();:type="submit"속성을 가진 버튼을 찾아 클릭합니다.await expect(page).toHaveURL(/.*dashboard/);: 현재 페이지의 URL이 정규 표현식/.*dashboard/와 일치하는지 검증합니다. Playwright의expect는 Jest와 유사한 구문을 제공합니다.await expect(page.locator(h1)).toHaveText(환영합니다, testuser님!);:h1태그의 텍스트가 환영합니다, testuser님!인지 검증합니다.await expect(page.locator(.error-message)).toBeVisible();:.error-message클래스를 가진 요소가 화면에 보이는지 검증합니다.
4.2. 테스트 실행
테스트를 실행하려면 터미널에서 다음 명령어를 입력합니다.
npx playwright test
모든 브라우저(Chromium, Firefox, WebKit)에서 테스트가 실행되고 결과가 터미널에 출력됩니다.
특정 브라우저에서만 실행하고 싶다면 --project 옵션을 사용합니다.
npx playwright test --project=chromium
특정 테스트 파일만 실행하고 싶다면 파일 경로를 지정합니다.
npx playwright test tests/login.spec.ts
테스트가 성공적으로 실행되면 다음과 유사한 결과가 출력됩니다.
Running 2 tests using 2 workers
✓ tests/login.spec.ts:5:5 › 로그인 기능 › 유효한 자격 증명으로 로그인 성공 (6s)
✓ tests/login.spec.ts:18:5 › 로그인 기능 › 잘못된 비밀번호로 로그인 실패 (4s)
2 passed (10s)
테스트 실행 후 playwright-report 폴더가 생성되고, 그 안에 HTML 리포트가 생성됩니다. 다음 명령어로 리포트를 열 수 있습니다.
npx playwright show-report
이 리포트는 각 테스트의 상세 정보, 스크린샷, 비디오, 트레이스(trace) 등을 포함하여 디버깅에 매우 유용합니다.
5. 고급 Playwright 기능 활용하기
Playwright는 기본적인 사용자 인터랙션 외에도 다양한 고급 기능을 제공하여 복잡한 E2E 시나리오를 커버할 수 있도록 돕습니다.
5.1. 스크린샷 및 비디오 녹화
테스트 실패 시 스크린샷을 찍거나 테스트 과정을 비디오로 녹화하는 것은 디버깅에 큰 도움이 됩니다. Playwright는 이러한 기능을 기본적으로 지원합니다.
스크린샷:
page.screenshot()메서드를 사용합니다.typescripttest(특정 요소 스크린샷, async ({ page }) => { await page.goto(https://codepick.kr); await page.locator(.header).screenshot({ path: header.png }); // 특정 요소 스크린샷 await page.screenshot({ path: fullpage.png, fullPage: true }); // 전체 페이지 스크린샷 });playwright.config.ts에서use.screenshot: only-on-failure로 설정하면 실패 시에만 스크린샷을 찍도록 할 수 있습니다.비디오 녹화:
browser.newContext()메서드에recordVideo옵션을 전달하여 비디오를 녹화할 수 있습니다.playwright.config.ts에서use.video: on-first-retry또는retain-on-failure등으로 설정하면 실패 시 비디오를 저장합니다.typescript// playwright.config.ts 예시 use: { trace: on-first-retry, video: on-first-retry, // 테스트가 처음 실패했을 때 비디오를 기록 },비디오 파일은
test-results폴더 내에 저장됩니다.
5.2. 파일 업로드 및 다운로드
웹 애플리케이션에서 파일 업로드 및 다운로드 기능을 테스트하는 것은 일반적인 시나리오입니다.
파일 업로드:
page.setInputFiles()메서드를 사용합니다.typescripttest(파일 업로드 테스트, async ({ page }) => { await page.goto(https://codepick.kr/upload); // 가상의 파일 업로드 페이지 // 파일 입력 필드 (type="file")를 찾고 파일을 첨부합니다. await page.locator(input[type="file"]).setInputFiles(./path/to/my-file.txt); // 업로드 버튼 클릭 등의 추가 동작 await page.locator(#upload-button).click(); // 업로드 성공 확인 await expect(page.locator(.upload-status)).toHaveText(파일 업로드 성공!); });파일 다운로드:
page.waitForEvent(download)를 사용하여 다운로드 이벤트를 기다립니다.typescriptimport { test, expect } from @playwright/test; import fs from fs; // Node.js 파일 시스템 모듈 test(파일 다운로드 테스트, async ({ page }) => { await page.goto(https://codepick.kr/download); // 가상의 파일 다운로드 페이지 // 다운로드 링크 클릭 전에 다운로드 이벤트를 기다립니다. const [download] = await Promise.all([ page.waitForEvent(download), // 다운로드 이벤트 대기 page.locator(#download-link).click() // 다운로드 링크 클릭 ]); // 다운로드된 파일의 경로를 얻고 저장합니다. const path = await download.path(); // 임시 경로 const savePath = ./downloaded-file.txt; await download.saveAs(savePath); // 지정된 경로에 저장 // 파일이 성공적으로 다운로드되었는지 확인합니다. expect(fs.existsSync(savePath)).toBeTruthy(); console.log(`파일이 ${savePath}에 저장되었습니다.`); });
5.3. 네트워크 요청 가로채기 (Mocking)
API 응답을 모의(mock)하거나 특정 네트워크 조건을 시뮬레이션하는 것은 E2E 테스트의 안정성을 높이고 외부 의존성을 줄이는 데 매우 중요합니다.
test(API 응답 모의 테스트, async ({ page }) => {
// 특정 API 요청을 가로채고 모의 응답을 반환합니다.
await page.route(**/api/users, async route => {
const json = [{ id: 1, name: Mock User 1 }, { id: 2, name: Mock User 2 }];
await route.fulfill({ json });
});
await page.goto(https://codepick.kr/users); // 가상의 사용자 목록 페이지
// 모의 응답이 페이지에 제대로 표시되는지 확인합니다.
await expect(page.locator(text=Mock User 1)).toBeVisible();
await expect(page.locator(text=Mock User 2)).toBeVisible();
});
page.route()를 사용하여 특정 URL 패턴에 대한 요청을 가로채고 route.fulfill()로 사용자 정의 응답을 보낼 수 있습니다. 이는 백엔드 서버가 준비되지 않았을 때 프론트엔드 테스트를 진행하거나, 특정 에러 시나리오를 강제로 발생시킬 때 유용합니다.
5.4. 인증 및 세션 관리
로그인 상태를 유지하여 각 테스트마다 반복적으로 로그인하는 과정을 생략하면 테스트 시간을 단축하고 효율성을 높일 수 있습니다. Playwright는 storageState를 사용하여 사용자 세션을 저장하고 재사용하는 기능을 제공합니다.
로그인 상태 저장 (
auth.setup.ts생성):typescript// playwright/auth.setup.ts import { test as setup, expect } from @playwright/test; const AUTH_FILE = playwright/.auth/user.json; // 로그인 상태를 저장할 파일 경로 setup(로그인 및 상태 저장, async ({ page }) => { await page.goto(https://codepick.kr/login); await page.locator(#username).fill(testuser); await page.locator(#password).fill(testpassword); await page.locator(button[type="submit"]).click(); // 로그인 성공 후 대시보드 페이지로 리다이렉션될 때까지 기다립니다. await page.waitForURL(/.*dashboard/); await expect(page).toHaveURL(/.*dashboard/); // 현재 페이지의 인증 상태(쿠키, 로컬 스토리지 등)를 파일에 저장합니다. await page.context().storageState({ path: AUTH_FILE }); });playwright.config.ts에auth.setup.ts추가:typescript// playwright.config.ts import { defineConfig, devices } from @playwright/test; export default defineConfig({ // ... projects: [ // setup project를 먼저 정의하여 로그인 상태를 생성합니다. { name: setup, testMatch: /.*auth.setup\.ts/, // auth.setup.ts 파일만 실행 }, { name: chromium, use: { ...devices[Desktop Chrome], storageState: playwright/.auth/user.json, // 저장된 로그인 상태 사용 }, dependencies: [setup], // setup 프로젝트가 먼저 실행되도록 의존성 설정 }, // ... 다른 브라우저 프로젝트들도 동일하게 설정 ], // ... });로그인 상태를 사용하는 테스트 작성:
이제tests/dashboard.spec.ts와 같은 파일에서 로그인 과정 없이 바로 로그인된 상태로 테스트를 시작할 수 있습니다.typescript// tests/dashboard.spec.ts import { test, expect } from @playwright/test; test(로그인된 상태에서 대시보드 접근, async ({ page }) => { await page.goto(https://codepick.kr/dashboard); // 이미 로그인된 상태로 시작 await expect(page.locator(h1)).toHaveText(환영합니다, testuser님!); await expect(page.locator(.profile-info)).toBeVisible(); });
이 방법을 사용하면 npx playwright test 명령을 실행할 때 auth.setup.ts가 먼저 실행되어 로그인 상태를 저장하고, 이후의 모든 테스트는 저장된 로그인 상태를 사용하여 시작됩니다.
6. Playwright Best Practices
효율적이고 유지보수하기 쉬운 Playwright 테스트 코드를 작성하기 위한 몇 가지 모범 사례를 소개합니다.
6.1. 페이지 객체 모델 (Page Object Model, POM)
페이지 객체 모델은 웹 페이지의 UI 요소를 객체로 추상화하여 테스트 코드를 더 읽기 쉽고 유지보수하기 쉽게 만드는 디자인 패턴입니다. 각 페이지 또는 컴포넌트에 대한 클래스를 생성하고, 해당 클래스 내에 요소 선택자와 상호작용 메서드를 정의합니다.
예시:
pages/LoginPage.ts파일 생성:typescript// pages/LoginPage.ts import { Locator, Page } from @playwright/test; export class LoginPage { readonly page: Page; readonly usernameInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator; readonly errorMessage: Locator; constructor(page: Page) { this.page = page; this.usernameInput = page.locator(#username); this.passwordInput = page.locator(#password); this.loginButton = page.locator(button[type="submit"]); this.errorMessage = page.locator(.error-message); } async goto() { await this.page.goto(https://codepick.kr/login); } async login(username: string, password: string) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.loginButton.click(); } }테스트 코드에서 사용:
typescript// tests/login.spec.ts (POM 적용) import { test, expect } from @playwright/test; import { LoginPage } from ../pages/LoginPage; // LoginPage 클래스 임포트 test.describe(로그인 기능 (POM 적용), () => { test(유효한 자격 증명으로 로그인 성공, async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login(testuser, testpassword); await expect(page).toHaveURL(/.*dashboard/); }); test(잘못된 비밀번호로 로그인 실패, async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login(testuser, wrongpassword); await expect(loginPage.errorMessage).toBeVisible(); await expect(loginPage.errorMessage).toHaveText(잘못된 사용자명 또는 비밀번호입니다.); }); });
POM을 사용하면 UI 변경 시 해당 페이지 객체만 수정하면 되므로, 여러 테스트 파일에 걸쳐 있는 중복된 선택자 코드를 일일이 수정할 필요가 없어 유지보수성이 크게 향상됩니다.
6.2. 안정적인 선택자 사용
CSS 클래스나 XPath는 종종 변경될 수 있어 테스트의 불안정성을 초래할 수 있습니다. 가장 안정적인 선택자는 data-testid와 같은 커스텀 속성을 사용하는 것입니다.
HTML 예시:
<