Playwright 테스트 작성
Playwright를 활용한 테스트 자동화는 Page Object Model(POM)을 통해 코드의 재사용성과 유지보수성을 높일 수 있습니다. 유틸리티 함수와 Fixtures 기반 Custom Command로 반복 작업을 캡슐화해 간결한 테스트 작성이 가능하며, 환경 변수로 URL을 관리하면 환경별 테스트도 쉽게 처리할 수 있습니다. Role 기반 선택자와 다국어 지원 전략은 접근성과 국제화를 고려한 테스트를 설계하는 데 유용합니다. Mock API 활용과 데이터 분리로 테스트의 확장성과 커버리지를 극대화할 수 있습니다.

첫 테스트 작성
간단한 로그인 테스트
Playwright를 사용해 간단한 로그인 테스트를 작성하려면 다음 단계를 따르면 됩니다. 여기서는 TypeScript를 기준으로 작성합니다.
1. Playwright 설치
Playwright가 설치되어 있지 않다면 아래 명령어를 사용해 설치하세요.
npm install playwright
2. 테스트 파일 작성
tests/login.spec.ts
라는 파일을 생성하고 아래 코드를 작성합니다.
import { test, expect } from '@playwright/test';
test.describe('Login Test', () => {
test('should log in with valid credentials', async ({ page }) => {
// 1. 페이지 이동
await page.goto('https://example.com/login');
// 2. 이메일 및 비밀번호 입력
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
// 3. 로그인 버튼 클릭
await page.click('button[type="submit"]');
// 4. 로그인 성공 확인
await expect(page).toHaveURL('https://example.com/dashboard'); // 로그인 성공 후 이동할 URL
await expect(page.locator('text=Welcome')).toBeVisible(); // 로그인 후 보이는 특정 텍스트 확인
});
});
3. Playwright 설정 파일 구성 (선택 사항)
Playwright 설정 파일에서 기본 브라우저와 테스트 경로 등을 지정할 수 있습니다. playwright.config.ts
파일에 기본 설정을 추가합니다.
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30 * 1000,
retries: 1,
use: {
headless: true, // GUI 표시 여부
baseURL: 'https://example.com',
},
});
4. 테스트 실행
테스트를 실행하려면 아래 명령어를 실행합니다.
npx playwright test
5. 결과 확인
테스트가 성공하면 Playwright가 테스트를 통과했음을 터미널에 표시합니다. 실패할 경우 실패한 이유와 디버깅을 위한 스크린샷 등을 제공합니다.
추가 팁
- 테스트가 실패했을 때 스크린샷을 저장하려면 설정 파일에
screenshot: 'only-on-failure'
를 추가하세요. - 비밀번호 등의 민감한 데이터를 환경 변수로 관리하세요.
await page.waitForSelector()
등을 활용하여 특정 요소가 로드될 때까지 기다릴 수 있습니다.
이 테스트를 기반으로 Playwright의 다양한 기능을 확장해볼 수 있습니다.
playwright.config.ts
파일에 screenshot: 'only-on-failure'
옵션을 추가하려면 아래와 같이 작성할 수 있습니다.
수정된 playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30 * 1000, // 각 테스트 타임아웃 설정 (ms 단위)
retries: 1, // 테스트 실패 시 재시도 횟수
use: {
headless: true, // GUI 표시 여부
baseURL: 'https://example.com', // 기본 URL 설정
screenshot: 'only-on-failure', // 테스트 실패 시에만 스크린샷 저장
trace: 'retain-on-failure', // 실패 시 상세 트레이스도 저장
},
});
주요 옵션 설명
screenshot: 'only-on-failure'
: 테스트가 실패했을 경우에만 스크린샷을 자동으로 저장합니다.trace: 'retain-on-failure'
: Playwright의 Trace Viewer를 사용해 테스트 실행의 세부 로그를 저장합니다.testDir
: 테스트 파일들이 위치한 폴더를 지정합니다.
실행 후 결과
- 테스트가 실패하면, Playwright는 자동으로
test-results
디렉토리에 스크린샷을 저장합니다. - 실패한 테스트와 관련된 파일들은
playwright show-trace
명령으로 확인할 수 있는 트레이스 파일과 함께 제공됩니다.
실행 명령어
테스트 실행 후 결과를 확인하려면 아래 명령어를 실행합니다.
npx playwright test
스크린샷 파일과 트레이스 파일을 사용해 디버깅 과정을 더 효과적으로 진행할 수 있습니다.
테스트 실행과 결과 확인
Playwright로 테스트를 실행하고 결과를 확인하는 방법은 간단합니다. 아래 절차에 따라 실행하고 결과를 확인하세요.
1. 테스트 실행
Playwright 테스트를 실행하려면 터미널에서 다음 명령어를 입력합니다.
npx playwright test
옵션
특정 테스트 파일 실행
npx playwright test tests/login.spec.ts
브라우저별 실행
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit
UI를 보면서 실행 (GUI)
npx playwright test --headed
2. 테스트 결과 확인
결과 출력
테스트 실행 후, 터미널에 결과가 출력됩니다.
Running 1 test using 1 worker
✔ [chromium] › tests/login.spec.ts: Login Test › should log in with valid credentials (2s)
1 passed (2s)
테스트 실패 시
테스트가 실패하면 에러 메시지, 실패한 이유, 그리고 저장된 스크린샷, 로그, 트레이스 파일의 경로가 표시됩니다.
3. 실패한 테스트 디버깅
스크린샷 확인
테스트 실패 시 자동 저장된 스크린샷은 playwright-report
디렉토리에서 확인할 수 있습니다.
playwright-report
├── test-results/
│ ├── login.spec.ts-test-name-failed.png
Trace Viewer 사용
Playwright의 Trace Viewer를 통해 실패한 테스트 실행의 모든 단계를 확인할 수 있습니다.
- 트레이스 파일은 실패한 테스트와 함께
test-results
디렉토리에 저장됩니다. - 트레이스 보기 명령어 실행
npx playwright show-trace test-results/<trace-file-name>.zip
Trace Viewer에서는 클릭한 버튼, 페이지 상태, 콘솔 로그 등을 포함해 테스트 실행 전체를 시각적으로 확인할 수 있습니다.
4. 테스트 보고서 생성
Playwright는 테스트 실행 후 자동으로 HTML 보고서를 생성할 수 있습니다.
- 보고서 명령어 실행
npx playwright show-report
- 브라우저에서 보고서를 열어 테스트 통계, 각 테스트의 세부 실행 결과 등을 확인할 수 있습니다.
5. 예제 실행
실행 명령어
npx playwright test tests/login.spec.ts --headed
성공 시 결과
✔ [chromium] › tests/login.spec.ts: Login Test › should log in with valid credentials (2s)
1 passed (2s)
실패 시 결과
✘ [chromium] › tests/login.spec.ts: Login Test › should log in with valid credentials (5s)
Error: Expected page to have URL 'https://example.com/dashboard', but was 'https://example.com/error'
Error snapshot: test-results/login-test-failed.png
Trace: test-results/login-trace.zip
이 방법을 통해 테스트 실행과 결과를 직관적으로 확인할 수 있습니다. 문제가 발생하면 스크린샷과 Trace Viewer를 활용하여 디버깅을 진행하세요.
테스트 시나리오 설계
테스트 데이터 준비
Playwright 테스트에서 테스트 데이터는 중요합니다. 데이터 준비를 잘하면 테스트의 재사용성과 유지보수성이 향상됩니다. 아래에 테스트 데이터를 준비하고 관리하는 방법을 설명합니다.
1. 테스트 데이터 준비 방식
a. 하드코딩된 데이터 (간단한 테스트용)
테스트 파일 내에서 직접 데이터를 작성합니다. 초기 테스트 시 유용하지만, 유지보수 측면에서는 비효율적입니다.
const testData = {
email: 'test@example.com',
password: 'password123',
};
b. 외부 파일에서 데이터 불러오기
CSV, JSON 또는 YAML 파일 등 외부 파일을 활용하면 테스트 데이터를 관리하기 쉽습니다.
JSON 파일 예시 (test-data.json
)
{
"validUser": {
"email": "test@example.com",
"password": "password123"
},
"invalidUser": {
"email": "wrong@example.com",
"password": "wrongpass"
}
}
테스트 파일에서 JSON 데이터 사용
import testData from './test-data.json';
test('should log in with valid credentials', async ({ page }) => {
const { email, password } = testData.validUser;
await page.goto('https://example.com/login');
await page.fill('input[name="email"]', email);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
await expect(page).toHaveURL('https://example.com/dashboard');
});
c. 환경 변수 사용
환경 변수를 활용하면 민감한 데이터를 안전하게 관리할 수 있습니다.
.env
파일
EMAIL=test@example.com
PASSWORD=password123
환경 변수 로드 (예: dotenv
라이브러리 사용)
npm install dotenv
import * as dotenv from 'dotenv';
dotenv.config();
const email = process.env.EMAIL;
const password = process.env.PASSWORD;
test('should log in with valid credentials', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('input[name="email"]', email!);
await page.fill('input[name="password"]', password!);
await page.click('button[type="submit"]');
await expect(page).toHaveURL('https://example.com/dashboard');
});
2. 동적 데이터 생성
테스트 중에 매번 다른 데이터를 사용해야 하는 경우, 동적으로 데이터를 생성할 수 있습니다.
a. 이메일 생성 예시
const generateEmail = () => `testuser+${Date.now()}@example.com`;
test('should register a new user', async ({ page }) => {
const email = generateEmail();
const password = 'password123';
await page.goto('https://example.com/register');
await page.fill('input[name="email"]', email);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
await expect(page).toHaveURL('https://example.com/dashboard');
});
b. 라이브러리 활용:
faker.js 또는 chance.js와 같은 라이브러리를 사용해 임의의 데이터를 생성할 수 있습니다.
npm install @faker-js/faker
import { faker } from '@faker-js/faker';
test('should fill random user data', async ({ page }) => {
const randomName = faker.name.firstName();
const randomEmail = faker.internet.email();
const randomPassword = faker.internet.password();
await page.goto('https://example.com/register');
await page.fill('input[name="name"]', randomName);
await page.fill('input[name="email"]', randomEmail);
await page.fill('input[name="password"]', randomPassword);
await page.click('button[type="submit"]');
await expect(page).toHaveURL('https://example.com/dashboard');
});
3. 테스트 데이터 관리 전략
- 공통 데이터: 여러 테스트에서 사용하는 데이터는 별도의 파일로 분리하여 관리합니다.
- 시나리오별 데이터: 테스트 케이스별로 필요한 데이터를 별도로 정의합니다.
- 데이터베이스 초기화: 데이터베이스가 필요한 경우, 테스트 실행 전후로 초기화 스크립트를 작성합니다.
4. 데이터베이스 초기화 예시
a. Playwright Test Hooks 사용
beforeEach
나 beforeAll
에서 데이터베이스를 초기화합니다.
test.beforeEach(async () => {
// 데이터베이스 초기화 로직
await initializeDatabase();
});
b. REST API를 통한 데이터 준비:
API 호출을 통해 테스트 데이터를 사전에 준비합니다.
await page.request.post('https://example.com/api/setup', {
data: { email: 'test@example.com', password: 'password123' },
});
이러한 방식으로 테스트 데이터를 준비하면 유지보수성과 확장성이 크게 향상됩니다. 테스트 환경에 적합한 데이터 준비 방식을 선택해 적용하세요.
Playwright 테스트에서 .env
파일을 사용하여 서버 환경(예: 개발, QA, 상용)을 구분하고 실행하려면 dotenv
라이브러리를 활용하면 됩니다. 아래에 단계별로 설정하는 방법을 설명합니다.
1. .env
파일 준비
각 환경에 맞는 .env
파일을 생성합니다. 예를 들어, 개발, QA, 상용 환경에 대해 각각 .env.development
, .env.qa
, .env.production
파일을 작성합니다.
.env.development
BASE_URL=https://dev.example.com
EMAIL=devuser@example.com
PASSWORD=devpassword
.env.qa
BASE_URL=https://qa.example.com
EMAIL=qauser@example.com
PASSWORD=qapassword
.env.production
BASE_URL=https://prod.example.com
EMAIL=produser@example.com
PASSWORD=prodpassword
2. dotenv
설치
dotenv
라이브러리를 설치합니다.
npm install dotenv
3. Playwright 설정 파일 수정
playwright.config.ts
파일에서 환경 변수 파일을 로드하도록 설정합니다.
import { defineConfig } from '@playwright/test';
import * as dotenv from 'dotenv';
import * as path from 'path';
// 실행 환경 선택 (기본값: development)
const ENV = process.env.ENV || 'development';
dotenv.config({ path: path.resolve(__dirname, `.env.${ENV}`) });
export default defineConfig({
testDir: './tests',
timeout: 30 * 1000,
retries: 1,
use: {
baseURL: process.env.BASE_URL, // .env에서 가져온 BASE_URL
headless: true,
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
},
});
4. 테스트 파일에서 환경 변수 사용
테스트 파일에서 환경 변수 값을 사용할 수 있습니다.
import { test, expect } from '@playwright/test';
test('Login Test', async ({ page }) => {
const email = process.env.EMAIL!;
const password = process.env.PASSWORD!;
// 서버 URL로 이동
await page.goto('/login'); // baseURL이 자동으로 적용됨
// 로그인 데이터 입력
await page.fill('input[name="email"]', email);
await page.fill('input[name="password"]', password);
// 로그인 버튼 클릭
await page.click('button[type="submit"]');
// 로그인 성공 여부 확인
await expect(page).toHaveURL('/dashboard');
});
5. 실행 명령어
테스트 실행 시 환경을 지정하기 위해 ENV
변수를 설정합니다. 기본값은 development
입니다.
개발 환경 실행
ENV=development npx playwright test
QA 환경 실행
ENV=qa npx playwright test
상용 환경 실행
ENV=production npx playwright test
6. 환경 변수 확인 및 디버깅
환경 변수가 제대로 로드되지 않는 경우, console.log(process.env)
를 사용하여 환경 변수를 출력하고 확인할 수 있습니다.
7. 동적 환경에 따른 테스트 실행 예시
테스트 실행 중 동적으로 환경 변수를 변경하고 싶다면, Playwright의 CLI 옵션과 조합하여 활용할 수도 있습니다.
ENV=qa npx playwright test --project=chromium
이렇게 .env
파일을 활용하면 환경별 설정을 쉽게 관리하고 실행할 수 있으며, 개발, QA, 상용 환경에 맞는 테스트를 보다 효율적으로 수행할 수 있습니다.
Windows 환경에서 ENV=qa
와 같은 명령어를 실행하면 에러가 발생할 수 있습니다. 이는 Windows의 명령 프롬프트(cmd) 또는 PowerShell이 환경 변수를 설정하는 방식이 다르기 때문입니다. Windows에서는 set
명령을 사용해야 합니다.
아래에 Windows에서 Playwright 테스트 실행 시 환경 변수를 설정하는 방법을 안내합니다.
1. PowerShell에서 환경 변수 설정
PowerShell에서는 환경 변수를 ENV
로 설정한 뒤 실행할 수 있습니다.
$env:ENV="qa"; npx playwright test --ui
2. Command Prompt(cmd)에서 환경 변수 설정
Command Prompt에서는 set
명령어를 사용하여 환경 변수를 설정합니다.
set ENV=qa && npx playwright test --ui
3. OS에 관계없이 실행 스크립트 작성
OS에 따라 다르게 명령어를 실행하고 싶다면, package.json
의 scripts
섹션에 환경 변수 설정을 포함하여 스크립트를 작성할 수 있습니다.
예시
{
"scripts": {
"test:ui:dev": "cross-env ENV=development npx playwright test --ui",
"test:ui:qa": "cross-env ENV=qa npx playwright test --ui",
"test:ui:prod": "cross-env ENV=production npx playwright test --ui"
}
}
여기서 cross-env
는 OS와 무관하게 환경 변수를 설정할 수 있도록 도와줍니다.
cross-env
설치
npm install --save-dev cross-env
실행 명령어
npm run test:ui:qa
4. 환경 변수 문제 해결
테스트 중 환경 변수가 제대로 설정되었는지 확인하려면, process.env
를 출력하는 코드를 테스트 파일에 추가하세요.
console.log('ENV:', process.env.ENV);
console.log('BASE_URL:', process.env.BASE_URL);
이 방법을 통해 Windows 환경에서도 Playwright 테스트를 문제없이 실행할 수 있습니다. PowerShell, Command Prompt, 또는 스크립트를 사용하는 방법 중 편리한 것을 선택하세요!
테스트 흐름 설계
Playwright를 활용한 테스트 흐름 설계는 테스트의 목적, 범위, 데이터 준비, 실행 순서, 환경, 그리고 결과 분석을 포함하는 체계적인 접근법이 필요합니다. 아래에 단계별로 테스트 흐름 설계 방법을 안내합니다.
1. 테스트 목적 정의
테스트를 설계하기 전에 테스트의 목표를 명확히 설정하세요.
- 기능 테스트: 특정 기능(예: 로그인, 회원가입)이 제대로 작동하는지 확인.
- UI 테스트: 버튼, 링크, 입력 필드 등 UI 요소가 올바르게 작동하는지 확인.
- 통합 테스트: 여러 모듈이 서로 올바르게 동작하는지 확인.
- 성능 테스트: 페이지 로딩 시간, 서버 응답 시간 확인.
2. 테스트 시나리오 정의
테스트가 커버해야 할 주요 시나리오를 정의합니다.
로그인 테스트
- 정상 흐름: 유효한 사용자 정보로 로그인 시 성공.
- 예외 흐름: 잘못된 비밀번호, 빈 입력값 등으로 오류 메시지 확인.
- 보안 흐름: 로그인 상태에서 세션 만료 또는 재로그인 요청 처리.
주문 프로세스 테스트
- 상품 추가 → 장바구니 → 결제.
- 잘못된 결제 정보 처리 흐름.
3. 테스트 단계 설계
테스트는 일반적으로 아래 단계를 따릅니다.
a. 준비 단계
- 테스트 데이터 준비 (환경 변수, 외부 JSON 파일, API 호출 등).
- 테스트 환경 설정 (브라우저, 헤드리스 모드, 네트워크 설정 등).
- 초기 상태 확인 (로그아웃, 특정 페이지로 이동).
b. 실행 단계
- 페이지 방문 및 요소 상호작용 (클릭, 입력, 제출 등).
- 필요한 테스트 조건을 충족하기 위한 시뮬레이션.
c. 검증 단계
- 요소 상태 확인 (
toBeVisible
,toHaveText
,toHaveURL
등). - 성공 또는 실패 메시지 확인.
- API 응답 검증.
d. 정리 단계
- 세션 종료 또는 데이터 정리.
- 로그 파일 작성 또는 스크린샷 저장.
4. 테스트 데이터 준비
테스트 데이터는 유연하고 반복 가능해야 합니다.
- JSON 파일로 입력 데이터를 분리.
- 임의 데이터 생성 라이브러리 활용 (예: Faker.js).
- API 호출을 통해 사전 상태를 설정.
const testData = {
validUser: { email: 'test@example.com', password: 'password123' },
invalidUser: { email: 'wrong@example.com', password: 'wrongpass' },
};
5. 테스트 파일 구조
테스트 파일은 이해하기 쉽게 설계해야 합니다.
tests/
├── auth/
│ ├── login.spec.ts
│ ├── signup.spec.ts
├── product/
│ ├── search.spec.ts
│ ├── cart.spec.ts
└── order/
├── checkout.spec.ts
6. 테스트 실행 순서
테스트 실행 순서는 독립적이어야 하지만, 경우에 따라 특정 순서를 따를 수도 있습니다.
- 독립 테스트: 각 테스트는 다른 테스트에 의존하지 않음.
- 종속 테스트: 예를 들어, 주문 흐름은 로그인이 선행되어야 함.
Playwright는 test.describe
와 test.beforeEach
를 활용해 테스트를 그룹화하거나 공유 논리를 추가할 수 있습니다.
test.describe('Login Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com/login');
});
test('should log in with valid credentials', async ({ page }) => {
// 테스트 로직
});
test('should show error with invalid credentials', async ({ page }) => {
// 테스트 로직
});
});
7. 테스트 환경 설정
테스트를 실행할 환경(개발, QA, 상용)을 분리하여 관리하세요.
- Playwright Config: 환경별
baseURL
및 데이터 설정. .env
파일로 환경 변수 관리.
8. 테스트 성공 및 실패 기준
각 테스트 케이스에 대해 성공 및 실패 조건을 정의합니다.
- 페이지 URL 확인:
await expect(page).toHaveURL('/dashboard');
- 특정 텍스트 존재 확인:
await expect(page.locator('text=Success')).toBeVisible();
- 네트워크 요청 확인:
page.on('response', ...)
를 활용.
9. 테스트 결과 보고
테스트 결과를 저장하고 분석하기 위해 Playwright의 보고 기능을 활용하세요.
HTML 보고서 생성
npx playwright test --reporter=html
트레이스 파일 활용
npx playwright show-trace test-results/trace.zip
10. 예제 흐름
로그인 후 대시보드 접근 테스트
import { test, expect } from '@playwright/test';
test.describe('Dashboard Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('https://example.com/dashboard');
});
test('should display user data on dashboard', async ({ page }) => {
const userName = await page.locator('.user-name').textContent();
expect(userName).toBe('John Doe');
});
test('should show error for unauthorized access', async ({ page }) => {
await page.goto('https://example.com/admin');
await expect(page).toHaveText('Access Denied');
});
});
11. 결론
- 테스트 흐름 설계는 준비(데이터 및 환경 설정) → 실행(시뮬레이션) → 검증(기능 확인) → 정리(테스트 데이터 초기화)의 단계로 구성됩니다.
- 테스트 데이터와 환경을 분리하여 유지보수성을 높이고, Playwright의 강력한 기능을 활용해 간결하면서도 효과적인 테스트 흐름을 만드세요.
요소 선택 전략
CSS Selectors와 XPath
Playwright에서 CSS Selector와 XPath는 HTML 요소를 선택하는 데 사용됩니다. 두 가지 방법은 각각 장단점이 있으며, Playwright에서는 주로 CSS Selectors가 권장됩니다. 아래에서 CSS Selector와 XPath의 차이점, 사용법, 그리고 Playwright에서의 활용 방안을 설명합니다.
1. CSS Selector
CSS Selector는 HTML 문서에서 스타일을 적용할 때 사용하는 방식과 동일한 문법을 사용하여 요소를 선택합니다.
장점
- 간결하고 가독성이 좋음.
- 대부분의 Playwright 기능에서 기본적으로 지원.
- 최신 브라우저 표준과 호환성이 뛰어남.
사용 예시
a. 태그 선택
input
모든 <input>
요소를 선택합니다.
b. ID 선택
#username
id="username"
인 요소를 선택합니다.
c. 클래스 선택
.button
class="button"
인 요소를 선택합니다.
d. 속성 선택
input[type="text"]
type="text"
인 <input>
요소를 선택합니다.
e. 계층 구조 선택
div > span
<div>
의 직접 자식인 <span>
을 선택합니다.
div span
<div>
의 모든 자손 <span>
을 선택합니다.
f. 동적 상태 선택
button:enabled
활성화된 <button>
을 선택합니다.
Playwright에서 사용
await page.click('input[type="text"]');
await page.fill('#username', 'testuser');
2. XPath
XPath는 XML 문서에서 노드를 선택하기 위한 쿼리 언어로, HTML에서도 활용됩니다.
장점
- 복잡한 구조나 계층적 관계를 다루기에 적합.
- 요소의 정확한 위치를 지정할 수 있음.
사용 예시
a. 절대 경로
/html/body/div/span
문서 루트에서 시작해 특정 경로를 따라갑니다.
b. 상대 경로
//div/span
모든 <div>
아래에 있는 <span>
을 선택합니다.
c. 속성 선택
//input[@type="text"]
type="text"
속성을 가진 <input>
요소를 선택합니다.
d. 텍스트 기반 선택
//button[text()="Submit"]
텍스트가 Submit인 <button>
요소를 선택합니다.
e. 포함된 텍스트
//button[contains(text(), "Log")]
텍스트에 Log를 포함하는 <button>
을 선택합니다.
f. 계층적 관계
//div/*[1]
<div>
의 첫 번째 자식을 선택합니다.
Playwright에서 사용
XPath를 사용할 때는 page.locator()
또는 page.$()
에서 'xpath='
접두사를 붙여 사용합니다.
await page.click('xpath=//button[text()="Submit"]');
await page.fill('xpath=//input[@id="username"]', 'testuser');
3. CSS Selector와 XPath의 비교
특징 | CSS Selector | XPath |
---|---|---|
가독성 | 간결하고 읽기 쉬움 | 비교적 길고 복잡할 수 있음 |
표준 지원 | 모든 브라우저 표준에서 지원됨 | 브라우저에서 지원되지만 복잡한 문법 |
복잡한 구조 | 제한적 (계층적 관계 표현이 약함) | 계층적 관계 표현에 강함 |
속도 | 더 빠름 (브라우저에서 최적화됨) | 약간 느림 (엔진 처리 속도 차이) |
Playwright 기본 | 기본적으로 지원 | 'xpath=' 접두사 필요 |
4. Playwright에서의 활용 전략
CSS Selector 추천
- 요소 선택이 간단한 경우 (ID, 클래스, 속성 등).
- Playwright는 CSS Selectors를 기본적으로 지원하고 빠르기 때문에 가능한 한 사용 권장.
XPath 활용
- 요소가 고유한 ID나 클래스가 없을 때.
- 텍스트 기반으로 요소를 선택하거나, 복잡한 계층 관계를 다뤄야 할 때.
- 예: 다국어 웹사이트에서 버튼 텍스트가 언어에 따라 변경되는 경우.
5. 예제
CSS Selector 기반 로그인 테스트
await page.goto('https://example.com/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('https://example.com/dashboard');
XPath 기반 로그인 테스트
await page.goto('https://example.com/login');
await page.fill('xpath=//input[@id="username"]', 'testuser');
await page.fill('xpath=//input[@id="password"]', 'password123');
await page.click('xpath=//button[text()="Login"]');
await expect(page).toHaveURL('https://example.com/dashboard');
6. 결론
- CSS Selector는 단순하고 빠른 요소 선택에 적합하며, Playwright에서 기본적으로 권장됩니다.
- XPath는 복잡한 관계를 다룰 때 강력하지만, 필요할 때만 사용하는 것이 좋습니다.
- 두 가지를 조합하면 Playwright 테스트의 안정성과 유연성을 높일 수 있습니다. 상황에 따라 가장 적합한 방식을 선택하세요.
Text-based Selectors
Playwright에서 Text-based Selectors는 텍스트를 기준으로 HTML 요소를 선택하는 방법입니다. 이 방식은 직관적이고 간단하며, 특정 텍스트를 포함한 요소를 찾는 데 유용합니다. 텍스트 기반 선택은 버튼, 링크, 헤더, 라벨과 같이 화면에 보이는 텍스트를 확인하거나 특정 동작을 테스트할 때 자주 사용됩니다.
1. Text-based Selectors의 기본 형식
Playwright는 요소의 텍스트를 선택할 수 있는 간단한 구문을 제공합니다.
await page.click('text="Login"');
await page.click('text=Submit');
형식
text="..."
: 정확히 일치하는 텍스트를 가진 요소를 선택합니다.text=...
: 부분적으로 일치하는 텍스트를 가진 요소를 선택합니다.
2. 사용 예시
a. 버튼 클릭
await page.click('text="Submit"');
Submit이라는 텍스트가 포함된 버튼을 클릭합니다.
b. 부분 텍스트 일치
await page.click('text=Log');
텍스트에 Log라는 단어가 포함된 버튼 또는 링크를 클릭합니다. 예: "Login", "Logout" 모두 매칭됩니다.
c. 대소문자 구분
Playwright의 text
셀렉터는 기본적으로 대소문자를 구분하지 않습니다. 하지만 대소문자를 구분하고 싶다면 다음과 같이 css
필터를 사용할 수 있습니다.
await page.locator('text=Login').withText('Login').click();
3. 속성과 결합한 Text-based Selectors
a. 특정 태그와 함께 사용
await page.click('button:has-text("Submit")');
텍스트가 Submit인 버튼 요소를 선택합니다.
b. 특정 클래스와 결합
await page.click('.btn-primary:has-text("Submit")');
클래스가 btn-primary이고 텍스트가 Submit인 요소를 선택합니다.
c. 텍스트와 상위 요소 결합
await page.locator('div:has-text("Welcome")').click();
<div>
태그에 텍스트 Welcome이 포함된 요소를 선택합니다.
4. 정규식을 사용한 Text-based Selectors
Playwright는 **정규식(Regex)**을 사용하여 더 강력한 텍스트 검색을 지원합니다.
a. 정규식으로 정확히 일치
await page.click('text=/^Submit$/');
Submit이라는 정확한 텍스트가 포함된 요소를 선택합니다.
b. 정규식으로 부분 일치
await page.click('text=/Log/i');
텍스트에 Log가 포함되면 선택합니다. /i
플래그는 대소문자를 구분하지 않음을 나타냅니다.
5. 텍스트 선택 우선순위
Playwright는 지정된 텍스트를 기준으로 아래 요소들에 우선적으로 매칭합니다.
- 정확히 일치하는 텍스트
- 부분적으로 일치하는 텍스트
- 텍스트가 포함된 상위 요소
만약 페이지에 동일한 텍스트를 가진 여러 요소가 있다면, 첫 번째로 일치하는 요소가 선택됩니다. 특정 요소를 명확히 지정하려면 locator()
와 함께 필터를 추가하세요.
await page.locator('text="Login"').nth(1).click(); // 두 번째 요소 선택
6. Text-based Selectors의 활용 시나리오
a. 다국어 지원 테스트
텍스트 기반 선택은 UI가 여러 언어를 지원하는 경우 유용합니다. 예를 들어, 다국어 웹사이트에서 버튼의 텍스트가 변경되는지 확인할 수 있습니다.
await page.click('text=로그인'); // 한국어
await page.click('text=Login'); // 영어
b. 접근성 테스트
Playwright는 텍스트 기반 선택으로 접근성 라벨을 확인할 수 있습니다.
await page.click('text="Sign In"'); // 버튼 텍스트 확인
await page.locator('label:has-text("Email")'); // 라벨 텍스트 확인
7. Text-based Selectors와 다른 Selectors의 비교
Selector 유형 | 예시 | 사용 목적 |
---|---|---|
Text-based | text="Login" |
텍스트 기반 간단한 선택 |
CSS Selector | button.btn-primary |
스타일 클래스나 ID 기반 |
XPath Selector | //button[text()='Login'] |
텍스트와 계층적 구조를 함께 사용 |
Attribute Selector | input[name="email"] |
특정 속성 값으로 선택 |
8. 예제 코드: Text-based Selectors 활용
import { test, expect } from '@playwright/test';
test('Login page test', async ({ page }) => {
// 로그인 페이지 이동
await page.goto('https://example.com/login');
// 이메일 및 비밀번호 입력
await page.fill('text=Email', 'test@example.com');
await page.fill('text=Password', 'password123');
// 로그인 버튼 클릭
await page.click('text="Login"');
// 대시보드로 이동 확인
await expect(page).toHaveURL('https://example.com/dashboard');
await expect(page.locator('text="Welcome, User"')).toBeVisible();
});
9. 장점과 한계
장점
- 직관적이고 간단한 요소 선택.
- 텍스트가 명확한 요소를 찾기에 적합.
- 다국어 지원과 접근성 테스트에 유리.
한계
- 텍스트가 동적으로 변경되거나, 동일한 텍스트를 가진 요소가 많을 경우 정확도가 떨어질 수 있음.
- 속성이나 계층 구조가 중요한 경우 CSS나 XPath를 사용하는 것이 더 효과적.
Text-based Selectors는 Playwright에서 간단하고 빠르게 UI 요소를 선택하는 강력한 도구입니다. 특히, 텍스트가 중심이 되는 요소(버튼, 링크, 라벨 등)에 적합합니다. 하지만 복잡한 구조나 동적인 UI에서는 CSS 또는 XPath Selectors를 보완적으로 사용하는 것이 좋습니다.
다국어 지원 테스트는 다국어 웹사이트나 애플리케이션에서 언어별 UI와 기능이 제대로 동작하는지 확인하는 중요한 과정입니다. Playwright를 사용하면 다국어 지원 테스트를 쉽게 자동화할 수 있습니다. 아래에 다국어 지원 테스트를 설계하고 구현하는 방법을 단계별로 설명합니다.
1. 다국어 테스트 설계
다국어 테스트의 주요 목표는 다음과 같습니다.
- UI 텍스트(버튼, 라벨, 제목 등)가 각 언어로 올바르게 표시되는지 확인.
- 다국어 콘텐츠가 페이지 레이아웃을 깨지 않는지 확인.
- 언어 전환 시 기능이 제대로 작동하는지 확인.
- 기본 언어 및 언어 우선순위 설정이 올바르게 작동하는지 확인.
2. 다국어 테스트 구성 요소
a. 테스트 대상 언어 정의
지원하는 모든 언어 목록을 정의합니다.
const languages = ['en', 'ko', 'fr', 'es']; // 영어, 한국어, 프랑스어, 스페인어
b. 언어별 데이터 준비
각 언어별로 텍스트 데이터를 관리합니다. 예를 들어, JSON 파일을 활용할 수 있습니다.
translations.json
{
"en": {
"welcome": "Welcome",
"login": "Login",
"logout": "Logout"
},
"ko": {
"welcome": "환영합니다",
"login": "로그인",
"logout": "로그아웃"
},
"fr": {
"welcome": "Bienvenue",
"login": "Connexion",
"logout": "Déconnexion"
}
}
3. Playwright를 활용한 다국어 테스트 코드
a. 언어 전환 테스트
아래는 다국어 언어 전환 기능을 테스트하는 코드입니다.
import { test, expect } from '@playwright/test';
import translations from './translations.json';
const languages = ['en', 'ko', 'fr']; // 테스트할 언어 목록
test.describe('Multilingual Support Test', () => {
for (const lang of languages) {
test(`should display correct text for ${lang}`, async ({ page }) => {
// 언어를 URL 쿼리 매개변수로 전달 (예: /?lang=en)
await page.goto(`https://example.com/?lang=${lang}`);
// 페이지에서 번역된 텍스트 확인
await expect(page.locator('h1')).toHaveText(translations[lang].welcome); // "Welcome", "환영합니다", "Bienvenue"
await expect(page.locator('button.login')).toHaveText(translations[lang].login); // "Login", "로그인", "Connexion"
});
}
});
b. 언어 전환 기능 확인
언어 전환 버튼(또는 드롭다운)이 제대로 작동하는지 확인합니다.
test('Language switch functionality', async ({ page }) => {
// 기본 언어로 페이지 열기
await page.goto('https://example.com');
// 언어 전환 버튼 클릭 (예: 한국어 선택)
await page.click('button:has-text("한국어")');
// UI 텍스트가 변경되었는지 확인
await expect(page.locator('h1')).toHaveText('환영합니다');
await expect(page.locator('button.login')).toHaveText('로그인');
});
c. 언어별 레이아웃 테스트
다국어 텍스트가 레이아웃을 깨트리지 않는지 확인합니다.
test.describe('Layout Test for Different Languages', () => {
for (const lang of languages) {
test(`should maintain layout for ${lang}`, async ({ page }) => {
await page.goto(`https://example.com/?lang=${lang}`);
// 특정 요소의 크기나 위치 확인
const button = page.locator('button.login');
const buttonBox = await button.boundingBox();
expect(buttonBox.width).toBeGreaterThan(50); // 버튼 너비가 50px 이상인지 확인
expect(buttonBox.height).toBeGreaterThan(20); // 버튼 높이가 20px 이상인지 확인
});
}
});
d. 기본 언어 설정 테스트
언어 설정이 없는 경우 기본 언어가 제대로 설정되는지 확인합니다.
test('Default language should be English', async ({ page }) => {
// 언어 매개변수를 전달하지 않고 접속
await page.goto('https://example.com');
// 기본 언어 확인
await expect(page.locator('h1')).toHaveText('Welcome');
});
4. 테스트 환경 변수로 언어 설정
Playwright 테스트 실행 시 언어를 환경 변수로 설정해 실행할 수도 있습니다.
a. .env
파일
DEFAULT_LANG=en
b. 테스트 코드
환경 변수를 읽어와 테스트에 적용합니다.
import * as dotenv from 'dotenv';
dotenv.config();
const defaultLang = process.env.DEFAULT_LANG || 'en';
test('Default language environment variable test', async ({ page }) => {
await page.goto(`https://example.com/?lang=${defaultLang}`);
await expect(page.locator('h1')).toHaveText(translations[defaultLang].welcome);
});
5. 다국어 테스트 시 주의할 점
1. 언어 우선순위
브라우저 언어 설정 또는 Accept-Language
헤더를 테스트합니다.
await page.context().setExtraHTTPHeaders({
'Accept-Language': 'ko'
});
await page.goto('https://example.com');
2. UI 텍스트 길이
언어에 따라 텍스트 길이가 다를 수 있습니다. 긴 텍스트로 인해 UI가 깨지는지 확인하세요.
3. RTL 지원:
아랍어, 히브리어와 같이 오른쪽에서 왼쪽(RTL) 언어를 테스트할 경우 CSS 속성 direction
을 확인합니다.
const direction = await page.locator('html').evaluate((el) => getComputedStyle(el).direction);
expect(direction).toBe('rtl');
Playwright를 사용하면 다국어 지원 테스트를 효과적으로 자동화할 수 있습니다. 텍스트 기반 선택, 환경 변수, 언어 전환 테스트 등을 활용해 언어별 UI와 기능을 철저히 검증하세요. 이를 통해 다국어 웹사이트의 품질을 보장하고 다양한 사용자 경험을 향상시킬 수 있습니다.
Role 기반 선택자
Playwright에서 Role 기반 선택자는 접근성(Accessibility) 속성을 활용해 웹 요소를 선택하는 방법입니다. 이러한 선택자는 HTML 요소의 ARIA 역할(ARIA Roles)을 기반으로 작동하며, 접근성 속성이 잘 정의된 웹 애플리케이션에서 특히 유용합니다. Role 기반 선택자는 요소의 role
속성과 연관된 ARIA 속성을 참조해 요소를 쉽게 찾을 수 있도록 도와줍니다.
1. Role 기반 선택자의 기본 형식
Role 기반 선택자는 getByRole()
메서드를 사용합니다. 이는 접근성 속성(role
)과 함께 이름 또는 속성을 기준으로 요소를 선택할 수 있습니다.
기본 형식
page.getByRole(role: string, options?: { name?: string, exact?: boolean, checked?: boolean });
role
: 선택하려는 요소의 역할(예:button
,link
,heading
,checkbox
등).name
: 요소의 접근성 이름(레이블 또는 텍스트).exact
: 이름 매칭에서 정확히 일치할지 여부 (true
일 경우 대소문자 구분).- 기타 옵션:
checked
,selected
,expanded
등을 활용하여 상태 기반 요소를 선택.
2. 주요 Role 값
다음은 Playwright에서 사용할 수 있는 일반적인 Role 값입니다.
button
: 버튼.link
: 링크.textbox
: 텍스트 입력 필드.heading
: 헤더 요소.checkbox
: 체크박스.radio
: 라디오 버튼.combobox
: 드롭다운 선택 박스.dialog
: 모달 또는 팝업 창.menuitem
: 메뉴 항목.tab
: 탭 UI 요소.
3. Role 기반 선택자 사용 예시
a. 버튼 선택
role="button"
인 요소를 선택합니다.
await page.getByRole('button', { name: 'Submit' }).click();
b. 링크 선택
role="link"
인 요소를 선택합니다.
await page.getByRole('link', { name: 'Home' }).click();
c. 입력 필드 선택
role="textbox"
로 텍스트 입력 필드를 선택합니다.
await page.getByRole('textbox', { name: 'Username' }).fill('testuser');
await page.getByRole('textbox', { name: 'Password' }).fill('password123');
d. 체크박스 선택
role="checkbox"
로 체크박스를 선택하고 상태를 확인합니다.
await page.getByRole('checkbox', { name: 'Remember Me' }).check();
await expect(page.getByRole('checkbox', { name: 'Remember Me' })).toBeChecked();
e. 라디오 버튼 선택
role="radio"
로 라디오 버튼을 선택합니다.
await page.getByRole('radio', { name: 'Option 1' }).check();
f. 드롭다운 선택
role="combobox"
로 드롭다운을 선택합니다.
await page.getByRole('combobox', { name: 'Country' }).selectOption('South Korea');
g. 모달/다이얼로그 확인
role="dialog"
로 모달 창을 선택합니다.
await page.getByRole('dialog', { name: 'Confirmation' }).waitFor();
4. Role 기반 선택자와 상태 기반 속성
Role 기반 선택자는 추가적인 상태 속성을 지원합니다.
a. checked
속성
체크박스나 라디오 버튼의 상태를 확인합니다.
await page.getByRole('checkbox', { name: 'Accept Terms', checked: true });
b. selected
속성
선택된 옵션을 확인합니다.
await page.getByRole('option', { name: 'Option 1', selected: true });
c. expanded
속성
드롭다운이나 아코디언의 확장 상태를 확인합니다.
await page.getByRole('button', { name: 'More Options', expanded: true });
5. Role 기반 선택자의 장점
- 접근성 준수: ARIA 역할을 기반으로 하여 접근성 테스트에 적합.
- 의미 기반 선택: 요소의 시맨틱 역할을 활용하여 안정적인 선택 가능.
- 강력한 상태 확인: 요소의 상태(
checked
,expanded
,selected
)를 쉽게 확인 가능. - 다국어 지원에 유리:
name
옵션을 사용해 다국어 UI에서 언어별 레이블을 처리 가능.
6. Role 기반 선택자와 다른 선택자의 비교
선택자 유형 | 예시 | 특징 |
---|---|---|
Role 기반 | page.getByRole('button', { name: 'Login' }) |
접근성 속성을 활용해 의미 기반 선택. |
CSS 선택자 | page.locator('button.login') |
클래스나 ID를 사용해 시각적 선택. |
Text 기반 | page.getByText('Login') |
텍스트를 기반으로 요소를 선택. |
XPath 선택자 | page.locator('//button[text()="Login"]') |
구조적 계층을 기반으로 복잡한 선택 가능. |
7. 예제 코드: Role 기반 선택자 활용
로그인 테스트
import { test, expect } from '@playwright/test';
test('Login Test with Role Selectors', async ({ page }) => {
// 페이지 열기
await page.goto('https://example.com/login');
// 사용자 이름 및 비밀번호 입력
await page.getByRole('textbox', { name: 'Username' }).fill('testuser');
await page.getByRole('textbox', { name: 'Password' }).fill('password123');
// 로그인 버튼 클릭
await page.getByRole('button', { name: 'Login' }).click();
// 대시보드로 이동 확인
await expect(page).toHaveURL('https://example.com/dashboard');
});
8. 주의 사항
접근성 속성 부족한 페이지
- ARIA 역할이나
aria-label
속성이 없는 경우 Role 기반 선택자가 제대로 작동하지 않을 수 있습니다. - 이 경우 CSS Selector나 Text Selector를 보완적으로 사용하세요.
ARIA 역할의 중복
- 동일한
role
과name
을 가진 요소가 여러 개 있을 경우 첫 번째 요소가 선택됩니다. locator()
와 추가 필터를 조합해 특정 요소를 선택하세요.
await page.getByRole('button', { name: 'Login' }).nth(1).click();
정확한 접근성 이름 제공
- HTML 요소에
aria-label
,aria-labelledby
또는 텍스트 콘텐츠가 정확히 정의되어 있어야 합니다.
Role 기반 선택자는 접근성 테스트와 의미 기반 요소 선택에 매우 유용합니다. 특히 ARIA 속성을 잘 사용하는 웹사이트에서는 이 선택자를 사용해 테스트 안정성과 가독성을 크게 향상시킬 수 있습니다. 하지만 접근성 속성이 부족한 페이지에서는 다른 선택자와 조합해 사용하는 것이 좋습니다.
반복 작업과 리팩토링
Page Object Model
Page Object Model (POM)은 테스트 자동화에서 일반적으로 사용하는 설계 패턴으로, 웹 애플리케이션의 페이지를 객체로 표현하여 테스트 코드를 더 구조화하고 유지보수 가능하게 만듭니다. Playwright에서도 POM을 활용해 코드를 모듈화하고 재사용성을 극대화할 수 있습니다.
1. Page Object Model의 개념
POM에서는 각 페이지(또는 페이지의 섹션)를 클래스 형태로 표현합니다. 각 클래스는
- 페이지 요소를 캡슐화하고,
- 이 요소들과 상호작용하는 메서드를 제공합니다.
이렇게 하면 테스트 코드에서 UI 로직과 테스트 로직이 분리되어 코드의 가독성과 유지보수성이 향상됩니다.
2. Page Object Model의 장점
- 코드 재사용성: 동일한 페이지에 대한 테스트를 여러 개 작성할 때 중복 코드가 줄어듭니다.
- 유지보수성: 페이지 UI가 변경될 경우, 변경된 클래스 파일만 수정하면 됩니다.
- 가독성: 테스트 시나리오 코드가 간결하고 명확하게 유지됩니다.
- 테스트 로직과 UI 로직 분리: 테스트의 안정성과 가독성이 향상됩니다.
3. Page Object Model 구현하기
a. 디렉터리 구조
아래는 Playwright 프로젝트에서 POM을 활용할 때의 기본 디렉터리 구조 예시입니다:
project/
├── pages/
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
├── tests/
│ ├── login.spec.ts
├── playwright.config.ts
b. 페이지 클래스 구현
LoginPage.ts
로그인 페이지를 위한 클래스를 작성합니다.
import { Page } from '@playwright/test';
export class LoginPage {
private page: Page;
// 페이지에서 사용할 요소 정의
constructor(page: Page) {
this.page = page;
}
private usernameField = () => this.page.locator('input[name="username"]');
private passwordField = () => this.page.locator('input[name="password"]');
private loginButton = () => this.page.locator('button[type="submit"]');
private errorMessage = () => this.page.locator('.error-message');
// 요소와 상호작용하는 메서드 정의
async navigateToLoginPage(url: string) {
await this.page.goto(url);
}
async login(username: string, password: string) {
await this.usernameField().fill(username);
await this.passwordField().fill(password);
await this.loginButton().click();
}
async getErrorMessage(): Promise<string> {
return await this.errorMessage().innerText();
}
}
DashboardPage.ts
대시보드 페이지를 위한 클래스를 작성합니다.
import { Page } from '@playwright/test';
export class DashboardPage {
private page: Page;
constructor(page: Page) {
this.page = page;
}
private welcomeMessage = () => this.page.locator('h1');
async getWelcomeMessage(): Promise<string> {
return await this.welcomeMessage().innerText();
}
}
c. 테스트 파일 작성
login.spec.ts
페이지 객체를 활용해 테스트를 작성합니다.
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';
test.describe('Login Tests', () => {
test('should log in with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
// 로그인 페이지로 이동
await loginPage.navigateToLoginPage('https://example.com/login');
// 로그인 수행
await loginPage.login('testuser', 'password123');
// 대시보드 확인
const welcomeMessage = await dashboardPage.getWelcomeMessage();
expect(welcomeMessage).toBe('Welcome, Test User!');
});
test('should show error for invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
// 로그인 페이지로 이동
await loginPage.navigateToLoginPage('https://example.com/login');
// 잘못된 로그인 시도
await loginPage.login('testuser', 'wrongpassword');
// 에러 메시지 확인
const errorMessage = await loginPage.getErrorMessage();
expect(errorMessage).toBe('Invalid username or password.');
});
});
4. POM에서 데이터 분리
테스트 데이터가 많거나 다국어 지원 테스트를 하려면 데이터를 별도의 파일로 분리하는 것이 좋습니다.
데이터 파일 예시 (data/loginData.json
)
{
"validUser": {
"username": "testuser",
"password": "password123"
},
"invalidUser": {
"username": "testuser",
"password": "wrongpassword"
}
}
데이터 로드 방식
import loginData from '../data/loginData.json';
await loginPage.login(loginData.validUser.username, loginData.validUser.password);
5. Playwright의 Fixtures와 POM 통합
Playwright의 test.extend
기능을 사용해 POM과 테스트를 더 효율적으로 통합할 수 있습니다.
확장된 Fixture 예시
import { test as base, Page } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
type Pages = {
loginPage: LoginPage;
};
export const test = base.extend<Pages>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});
테스트에서 사용
test('should log in with valid credentials', async ({ loginPage }) => {
await loginPage.navigateToLoginPage('https://example.com/login');
await loginPage.login('testuser', 'password123');
});
6. POM 설계 팁
- 단일 책임 원칙: 각 페이지 클래스는 그 페이지와 관련된 요소와 로직만 포함해야 합니다.
- 재사용 가능한 메서드 작성: 일반적으로 사용되는 작업(예: 로그인, 폼 작성)은 메서드로 추상화하여 여러 테스트에서 재사용 가능하도록 합니다.
- 유지보수성 고려: 페이지 요소가 변경되었을 때 테스트 코드를 수정하지 않아도 되도록 클래스 파일만 수정하면 되게 설계합니다.
Page Object Model은 Playwright 테스트를 모듈화하고, 유지보수성과 확장성을 높이는 데 큰 도움을 줍니다. POM을 사용하면 테스트 로직과 UI 로직을 분리할 수 있어 더 깔끔하고 이해하기 쉬운 코드를 작성할 수 있습니다. Playwright의 강력한 API와 함께 활용하면 복잡한 테스트 시나리오도 쉽게 관리할 수 있습니다.
Page Object Model (POM)에서 페이지 클래스(login.page.ts
)는 페이지 요소만 정의하고, 실제 상호작용 동작은 별도의 유틸리티(utils.ts
)로 분리하면 구조가 더욱 모듈화됩니다. 이는 UI 요소와 동작 로직을 명확히 구분하므로 유지보수성과 재사용성을 높이는 데 유리합니다.
아래는 login.page.ts
와 유틸리티 분리 방식을 적용한 예제입니다.
1. 디렉터리 구조
다음과 같이 구조화합니다.
project/
├── pages/
│ ├── login.page.ts # 페이지 요소 정의
├── utils/
│ ├── auth.utils.ts # 로그인 관련 동작 정의
├── tests/
│ ├── login.spec.ts # 테스트 파일
├── playwright.config.ts
2. 페이지 요소 정의
login.page.ts
는 페이지의 요소를 정의하는 역할만 수행합니다.
login.page.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
private page: Page;
constructor(page: Page) {
this.page = page;
}
// 페이지 요소 정의
get usernameField(): Locator {
return this.page.locator('input[name="username"]');
}
get passwordField(): Locator {
return this.page.locator('input[name="password"]');
}
get loginButton(): Locator {
return this.page.locator('button[type="submit"]');
}
get errorMessage(): Locator {
return this.page.locator('.error-message');
}
// 네비게이션 메서드 (URL 이동만 포함)
async navigateToLoginPage(url: string): Promise<void> {
await this.page.goto(url);
}
}
3. 상호작용 로직 정의
auth.utils.ts
는 로그인 동작과 관련된 유틸리티 메서드를 정의합니다. 이 파일은 LoginPage
객체를 받아서 동작을 실행합니다.
auth.utils.ts
import { LoginPage } from '../pages/login.page';
export class AuthUtils {
private loginPage: LoginPage;
constructor(loginPage: LoginPage) {
this.loginPage = loginPage;
}
// 로그인 동작
async login(username: string, password: string): Promise<void> {
await this.loginPage.usernameField.fill(username);
await this.loginPage.passwordField.fill(password);
await this.loginPage.loginButton.click();
}
// 에러 메시지 가져오기
async getErrorMessage(): Promise<string> {
return await this.loginPage.errorMessage.innerText();
}
}
4. 테스트 파일
login.spec.ts
에서 LoginPage
와 AuthUtils
를 활용하여 테스트를 작성합니다.
login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
import { AuthUtils } from '../utils/auth.utils';
test.describe('Login Tests', () => {
test('should log in with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
const authUtils = new AuthUtils(loginPage);
// 로그인 페이지로 이동
await loginPage.navigateToLoginPage('https://example.com/login');
// 로그인 수행
await authUtils.login('testuser', 'password123');
// 대시보드 확인
await expect(page).toHaveURL('https://example.com/dashboard');
});
test('should show error for invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
const authUtils = new AuthUtils(loginPage);
// 로그인 페이지로 이동
await loginPage.navigateToLoginPage('https://example.com/login');
// 잘못된 로그인 시도
await authUtils.login('testuser', 'wrongpassword');
// 에러 메시지 확인
const errorMessage = await authUtils.getErrorMessage();
expect(errorMessage).toBe('Invalid username or password.');
});
});
5. 장점
a. 유지보수성
- UI가 변경되어도
login.page.ts
만 수정하면 됩니다. - 비즈니스 로직이 변경되어도
auth.utils.ts
만 수정하면 됩니다.
b. 재사용성
auth.utils.ts
의 메서드는 여러 테스트 파일에서 재사용 가능합니다.- 동일한 페이지 클래스(
LoginPage
)는 다른 유틸리티에서도 활용할 수 있습니다.
c. 가독성
- 테스트 파일은 동작과 결과 검증에만 집중하므로 간결하고 읽기 쉽습니다.
6. 확장 가능성
a. 다른 유틸리티 추가
auth.utils.ts
외에도 다른 도메인별 유틸리티를 만들 수 있습니다.
profile.utils.ts
: 프로필 관련 동작 정의.cart.utils.ts
: 장바구니 관련 동작 정의.
b. 공통 유틸리티 작성
테스트 환경 초기화, 데이터베이스 초기화 등 공통 작업은 별도의 유틸리티로 관리합니다.
이 구조는 테스트의 안정성과 유지보수성을 높이며, 대규모 프로젝트에서도 효율적인 테스트 코드 관리를 가능하게 합니다.
이 구조는 환경 변수에 저장된 base_url
을 참조해 페이지 이동 경로를 동적으로 구성하는 형태로, Playwright의 Page Object Model(POM) 설계에 유용합니다. 이를 통해 환경별 URL 관리가 더 쉬워지고, 코드의 재사용성과 유지보수성이 향상됩니다.
1. 구현 구조
.env
파일: 서버 환경별로base_url
을 저장합니다.navigateToPage
메서드: POM의 각 페이지 클래스에서 경로를 정의하고,base_url
과 결합해 최종 URL을 생성합니다.- 환경 변수 로드:
dotenv
라이브러리를 사용하여.env
파일을 로드합니다.
2. 디렉터리 구조
project/
├── pages/
│ ├── login.page.ts # LoginPage 클래스 (POM)
│ ├── dashboard.page.ts # DashboardPage 클래스 (POM)
├── tests/
│ ├── login.spec.ts # 테스트 파일
├── utils/
│ ├── env.utils.ts # 환경 변수 관리 유틸리티
├── .env # 환경 변수 파일
├── playwright.config.ts # Playwright 설정 파일
3. 환경 변수 설정
.env
파일
환경별 base_url
을 정의합니다.
BASE_URL=https://example.com
4. 환경 변수 관리 유틸리티
환경 변수를 로드하고 사용할 수 있는 유틸리티를 작성합니다.
env.utils.ts
import * as dotenv from 'dotenv';
// .env 파일 로드
dotenv.config();
export const ENV = {
BASE_URL: process.env.BASE_URL || 'http://localhost:3000',
};
5. POM에서 경로 관리
POM 클래스에서 경로를 path
로 정의하고, base_url
과 결합해 최종 URL을 생성하는 구조를 작성합니다.
login.page.ts
import { Page } from '@playwright/test';
import { ENV } from '../utils/env.utils';
export class LoginPage {
private page: Page;
// 경로 정의 (페이지별 path)
private path = '/login';
constructor(page: Page) {
this.page = page;
}
// 페이지 이동
async navigate(): Promise<void> {
const url = `${ENV.BASE_URL}${this.path}`;
await this.page.goto(url);
}
// 요소 정의
get usernameField() {
return this.page.locator('input[name="username"]');
}
get passwordField() {
return this.page.locator('input[name="password"]');
}
get loginButton() {
return this.page.locator('button[type="submit"]');
}
get errorMessage() {
return this.page.locator('.error-message');
}
}
dashboard.page.ts
import { Page } from '@playwright/test';
import { ENV } from '../utils/env.utils';
export class DashboardPage {
private page: Page;
// 경로 정의
private path = '/dashboard';
constructor(page: Page) {
this.page = page;
}
// 페이지 이동
async navigate(): Promise<void> {
const url = `${ENV.BASE_URL}${this.path}`;
await this.page.goto(url);
}
// 요소 정의
get welcomeMessage() {
return this.page.locator('h1');
}
}
6. 테스트 파일 작성
login.spec.ts
POM의 navigate()
메서드를 호출하여 각 페이지로 이동합니다.
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
import { DashboardPage } from '../pages/dashboard.page';
test.describe('Login Tests', () => {
test('should log in with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
// 로그인 페이지로 이동
await loginPage.navigate();
// 로그인 수행
await loginPage.usernameField.fill('testuser');
await loginPage.passwordField.fill('password123');
await loginPage.loginButton.click();
// 대시보드 페이지로 이동 확인
await expect(page).toHaveURL(`${process.env.BASE_URL}/dashboard`);
await expect(dashboardPage.welcomeMessage).toHaveText('Welcome, Test User!');
});
test('should show error for invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
// 로그인 페이지로 이동
await loginPage.navigate();
// 잘못된 로그인 시도
await loginPage.usernameField.fill('testuser');
await loginPage.passwordField.fill('wrongpassword');
await loginPage.loginButton.click();
// 에러 메시지 확인
const errorMessage = await loginPage.errorMessage.innerText();
expect(errorMessage).toBe('Invalid username or password.');
});
});
7. 장점
- 환경별 URL 관리 용이:
.env
파일만 수정하면 모든 테스트에서 적용됩니다. - 코드 재사용성: 각 페이지 클래스에
navigate()
를 정의해 동일한 로직을 재사용할 수 있습니다. - 가독성: 테스트 파일에서 URL 관리 로직을 분리하여 테스트 시나리오에 집중할 수 있습니다.
- 확장성: 여러 환경(QA, 개발, 상용 등)을 쉽게 테스트할 수 있습니다.
8. 확장: 환경별 테스트 실행
Playwright 테스트 실행 시 환경을 선택하여 다른 .env
파일을 로드할 수 있습니다.
환경 변수 파일 예시
.env.production
BASE_URL=https://example.com
.env.development
BASE_URL=http://localhost:3000
실행 명령어
cross-env
를 사용해 환경별로 테스트를 실행합니다:
npx cross-env NODE_ENV=development npx playwright test
npx cross-env NODE_ENV=production npx playwright test
이 접근 방식은 환경 변수로 base_url
을 관리하고, POM에서 각 페이지 경로를 정의하여 구조적인 테스트 코드를 작성할 수 있습니다. 이로 인해 환경별 설정을 쉽게 변경할 수 있고, URL 관리가 중앙 집중화되어 유지보수가 훨씬 간편해집니다.
Custom Command 작성
Playwright에서 Custom Command를 작성하면 반복적으로 사용하는 작업(예: 로그인, 데이터 입력, 특정 요소 확인)을 재사용 가능한 메서드로 만들어 코드를 더 간결하고 유지보수하기 쉽게 만들 수 있습니다. Playwright는 기본적으로 확장 가능한 구조를 제공하므로, Fixtures
또는 유틸리티 클래스를 사용해 Custom Command를 작성할 수 있습니다.
1. Custom Command 설계 방식
Custom Command는 아래 두 가지 방식으로 구현할 수 있습니다.
- 유틸리티 함수: 재사용 가능한 메서드를 별도 유틸리티 파일로 관리.
- Fixtures 확장: Playwright의
test.extend
를 사용하여 Custom Command를 확장.
2. 유틸리티 함수로 Custom Command 작성
유틸리티 함수는 특정 작업을 캡슐화하여 여러 테스트에서 재사용할 수 있습니다.
a. 디렉터리 구조
project/
├── utils/
│ ├── commands.utils.ts # Custom Command 정의
├── tests/
│ ├── example.spec.ts # 테스트 파일
b. Custom Command 작성
commands.utils.ts
로그인과 같은 반복 작업을 캡슐화한 함수:
import { Page } from '@playwright/test';
export async function login(page: Page, username: string, password: string): Promise<void> {
await page.goto('https://example.com/login');
await page.fill('input[name="username"]', username);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
}
export async function logout(page: Page): Promise<void> {
await page.click('button.logout');
}
c. 테스트에서 사용
example.spec.ts
Custom Command를 호출하여 테스트 작성
import { test, expect } from '@playwright/test';
import { login, logout } from '../utils/commands.utils';
test('Login and logout flow', async ({ page }) => {
// 로그인 수행
await login(page, 'testuser', 'password123');
// 대시보드 확인
await expect(page).toHaveURL('https://example.com/dashboard');
await expect(page.locator('h1')).toHaveText('Welcome, Test User!');
// 로그아웃 수행
await logout(page);
// 로그인 페이지로 리디렉션 확인
await expect(page).toHaveURL('https://example.com/login');
});
3. Fixtures를 활용한 Custom Command 작성
Playwright의 test.extend
기능을 사용하면 test
객체에 Custom Command를 추가할 수 있습니다. 이는 유틸리티 메서드보다 더 간결하게 반복 작업을 처리할 수 있습니다.
a. 디렉터리 구조
project/
├── fixtures/
│ ├── custom.commands.ts # Fixtures 확장
├── tests/
│ ├── login.spec.ts # 테스트 파일
b. Fixtures 확장
custom.commands.ts
Custom Command를 Playwright의 Fixtures로 확장합니다.
import { test as base, Page } from '@playwright/test';
// Custom Fixtures 정의
type CustomFixtures = {
login: (username: string, password: string) => Promise<void>;
};
// 기본 test 객체 확장
export const test = base.extend<CustomFixtures>({
login: async ({ page }, use) => {
// login 메서드 구현
const login = async (username: string, password: string) => {
await page.goto('https://example.com/login');
await page.fill('input[name="username"]', username);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
};
await use(login);
},
});
c. 테스트에서 사용
login.spec.ts
test
객체에 확장된 login
Command를 호출합니다.
import { test, expect } from '../fixtures/custom.commands';
test('Login with custom command', async ({ page, login }) => {
// Custom Command를 사용한 로그인
await login('testuser', 'password123');
// 대시보드 확인
await expect(page).toHaveURL('https://example.com/dashboard');
await expect(page.locator('h1')).toHaveText('Welcome, Test User!');
});
4. Custom Command 설계 시 유용한 팁
- 유틸리티 함수 vs Fixtures
- 유틸리티 함수는 더 간단하며 초기 프로젝트에서 적합합니다.
- Fixtures는
test
객체와 통합되어 확장성이 높으며, 대규모 프로젝트에 적합합니다.
- 재사용 가능성
- Custom Command는 반복적으로 사용하는 작업을 캡슐화하여 테스트 코드에서 직접 호출하지 않아도 되도록 설계하세요.
- 예: 로그인, 세션 초기화, 데이터 정리.
- 매개변수화
- Custom Command가 유연하도록 매개변수를 사용해 다양한 상황을 처리하세요.
- 예: 로그인 시 사용자 역할(관리자, 일반 사용자) 구분.
- 에러 핸들링
- Custom Command 내부에서 예외 처리를 포함하여, 테스트가 실패했을 때 명확한 로그를 제공하세요.
- 테스트 실행 속도 최적화
- Custom Command 내에서 불필요한 작업(예: 불필요한 페이지 이동)을 피하고 필요한 작업만 실행하세요.
5. 확장 가능성
Custom Command는 로그인 외에도 다양한 작업에 활용할 수 있습니다.
- 데이터 생성: API 호출로 테스트 데이터를 생성.
- 상태 확인: 특정 요소가 화면에 표시될 때까지 대기.
- 네트워크 설정: 네트워크 요청 가로채기(Mock API) 구현.
Custom Command는 반복적인 작업을 캡슐화하여 코드의 가독성과 유지보수성을 향상시키는 강력한 도구입니다. 작은 프로젝트에서는 유틸리티 함수로 시작하고, 점차적으로 Playwright의 Fixtures 확장을 활용해 확장성을 더하세요. 이를 통해 더 효율적이고 구조적인 테스트 코드를 작성할 수 있습니다.