Playwright의 주요 기능
Playwright는 브라우저 자동화와 테스트를 위한 강력한 도구로, 다양한 작업을 수행할 수 있는 메서드와 API를 제공합니다. 브라우저 열기와 닫기, 페이지 탐색, 폼 상호작용, AJAX 처리, 모바일 및 멀티 브라우저 테스트 등 여러 상황에서 유용하게 사용할 수 있습니다. 특히 evaluate()와 같은 브라우저 내장 API를 활용하면 클라이언트 측 동작을 효과적으로 제어할 수 있습니다.

브라우저 자동화
브라우저 열기와 닫기
다음은 Playwright에서 브라우저를 열고 닫는 간단한 예제 코드입니다. 이 코드는 TypeScript로 작성되었습니다.
import { chromium, Browser, Page } from 'playwright';
async function runBrowserExample() {
let browser: Browser | null = null;
try {
// 브라우저 열기
browser = await chromium.launch({ headless: false }); // headless: false는 브라우저 창을 표시
const context = await browser.newContext();
const page: Page = await context.newPage();
// 웹 페이지 열기
await page.goto('https://example.com');
console.log('Page opened:', await page.title());
// 페이지에서 작업 수행 (예: 스크린샷 저장)
await page.screenshot({ path: 'example.png' });
console.log('Screenshot saved as example.png');
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 닫기
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
runBrowserExample();
코드 설명
chromium.launch()
: Chromium 브라우저를 시작합니다.headless: false
옵션을 사용하면 브라우저 창이 표시됩니다.browser.newContext()
: 브라우저 컨텍스트를 생성하여 쿠키나 세션 데이터를 분리합니다.context.newPage()
: 새로운 페이지를 생성합니다.page.goto()
: 지정된 URL로 이동합니다.page.screenshot()
: 스크린샷을 저장합니다.- 에러 처리 및 리소스 정리:
try-catch-finally
를 사용하여 에러를 처리하고 브라우저를 항상 닫도록 합니다.
이 코드를 기반으로 Playwright의 더 다양한 기능을 확장해 사용할 수 있습니다.
새 탭 열기
Playwright에서 새 탭을 여는 방법은 브라우저 컨텍스트에서 추가적인 페이지를 생성하는 것입니다. 아래는 새 탭을 열고 작업을 수행하는 예제 코드입니다.
새 탭 열기 예제 (TypeScript)
import { chromium, Browser, Page } from 'playwright';
async function openNewTabExample() {
let browser: Browser | null = null;
try {
// 브라우저 시작
browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
// 첫 번째 탭 생성
const page1: Page = await context.newPage();
await page1.goto('https://example.com');
console.log('First tab opened:', await page1.title());
// 새 탭 열기
const page2: Page = await context.newPage();
await page2.goto('https://playwright.dev');
console.log('Second tab opened:', await page2.title());
// 두 탭 간의 상호작용 예제
await page1.bringToFront(); // 첫 번째 탭으로 다시 전환
console.log('Switched back to first tab');
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 종료
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
openNewTabExample();
코드 설명
context.newPage()
: 새로운 탭(페이지)을 엽니다. 컨텍스트 내에서 호출하면 여러 탭을 생성할 수 있습니다.page.goto()
: 새 탭에서 열고 싶은 URL로 이동합니다.page.bringToFront()
: 특정 탭을 활성화합니다. 여러 탭이 있을 때 원하는 탭으로 포커스를 전환합니다.browser.close()
: 브라우저를 닫을 때 모든 탭이 함께 종료됩니다.
주요 포인트
- Playwright에서 탭은 페이지(
Page
)로 표현됩니다. - 새 탭을 생성할 때는 동일한 브라우저 컨텍스트에서
newPage()
를 호출하여 브라우저 세션을 공유할 수 있습니다.
페이지 상호작용
클릭, 입력, 제출
Playwright에서 클릭, 입력, 제출하는 방법은 page.click()
, page.fill()
또는 page.type()
, 그리고 page.press()
또는 page.evaluate()
등을 활용해 구현할 수 있습니다. 아래는 클릭, 입력, 폼 제출 동작을 포함한 TypeScript 예제입니다.
클릭, 입력, 제출 예제 (TypeScript)
import { chromium, Browser, Page } from 'playwright';
async function formInteractionExample() {
let browser: Browser | null = null;
try {
// 브라우저 시작
browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page: Page = await context.newPage();
// 페이지 열기
await page.goto('https://example.com'); // 실제 폼이 있는 URL로 변경
// 입력 필드 찾고 값 입력
await page.fill('input[name="username"]', 'test_user'); // 사용자 이름 입력
await page.fill('input[name="password"]', 'password123'); // 비밀번호 입력
// 버튼 클릭
await page.click('button[type="submit"]'); // 제출 버튼 클릭
// 폼 제출 이후 결과 확인
await page.waitForNavigation(); // 페이지가 이동할 때까지 대기
console.log('Form submitted and new page title:', await page.title());
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 닫기
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
formInteractionExample();
주요 메서드 설명
page.fill(selector, value)
- 지정된 선택자에 값을 입력합니다.
- 입력 필드에 값을 설정할 때 사용됩니다.
page.type(selector, value)
- 지정된 선택자에 값을 한 글자씩 입력합니다. (
fill
과의 차이점은 입력 속도 시뮬레이션) delay
옵션을 추가해 타이핑 속도를 조정할 수 있습니다.await page.type('input[name="username"]', 'test_user', { delay: 100 }); // 100ms 딜레이
- 지정된 선택자에 값을 한 글자씩 입력합니다. (
page.click(selector)
- 지정된 요소(버튼 등)를 클릭합니다.
page.press(selector, key)
- 특정 키(예:
Enter
,Tab
)를 누를 때 사용합니다. await page.press('input[name="password"]', 'Enter');
- 특정 키(예:
page.waitForNavigation()
- 클릭 또는 제출 후 페이지가 로드될 때까지 대기합니다.
폼 제출 방식
- 버튼 클릭으로 제출
<button type="submit">
버튼을 클릭해 제출.
Enter
키로 제출- 입력 필드에서
Enter
키를 누르는 방식. await page.press('input[name="password"]', 'Enter');
- 입력 필드에서
- 스크립트로 폼 제출
page.evaluate()
를 사용하여 DOM에서 직접 폼을 제출.- await page.evaluate(() => {
(document.querySelector('form') as HTMLFormElement).submit();
});
응용
- 폼에 추가 입력이 필요하거나, 체크박스, 드롭다운 등을 다룰 때는 Playwright의 다양한 메서드(
check
,selectOption
등)를 사용하면 됩니다.
체크박스 클릭
await page.check('input[type="checkbox"]');
드롭다운 선택
await page.selectOption('select[name="options"]', 'value1');
체크박스와 드롭다운 다루기
Playwright에서 체크박스와 드롭다운을 다루는 방법은 간단하며, 각각 page.check()
및 page.selectOption()
메서드를 사용합니다. 아래는 체크박스와 드롭다운을 다루는 구체적인 TypeScript 예제입니다.
체크박스와 드롭다운 예제 (TypeScript)
import { chromium, Browser, Page } from 'playwright';
async function handleCheckboxAndDropdown() {
let browser: Browser | null = null;
try {
// 브라우저 시작
browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page: Page = await context.newPage();
// 페이지 열기 (예시로 대체 URL 사용)
await page.goto('https://example.com'); // 실제 폼이 있는 URL로 변경
// 체크박스 다루기
const checkboxSelector = 'input[type="checkbox"][name="agree"]';
if (!(await page.isChecked(checkboxSelector))) {
await page.check(checkboxSelector); // 체크박스 체크
console.log('Checkbox checked');
} else {
await page.uncheck(checkboxSelector); // 체크박스 체크 해제
console.log('Checkbox unchecked');
}
// 드롭다운 다루기
const dropdownSelector = 'select[name="options"]';
await page.selectOption(dropdownSelector, 'value1'); // value 속성이 'value1'인 옵션 선택
console.log('Dropdown option "value1" selected');
// 드롭다운의 현재 선택된 값 출력
const selectedValue = await page.$eval(dropdownSelector, (select) => (select as HTMLSelectElement).value);
console.log('Currently selected option:', selectedValue);
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 종료
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
handleCheckboxAndDropdown();
주요 메서드 설명
체크박스 다루기
page.check(selector)
- 선택된 체크박스를 체크 상태로 만듭니다.
- 체크되지 않은 상태에서만 작동하며, 이미 체크된 경우에는 아무 동작도 하지 않습니다.
page.uncheck(selector)
- 선택된 체크박스를 체크 해제합니다.
- 체크된 상태에서만 작동하며, 이미 체크 해제된 경우에는 아무 동작도 하지 않습니다.
page.isChecked(selector)
- 체크박스가 현재 체크되어 있는지 여부를 확인합니다.
- 반환값은
true
(체크됨) 또는false
(체크되지 않음)입니다.
드롭다운 다루기
-
page.selectOption(selector, value)
:- 드롭다운에서
value
에 해당하는 옵션을 선택합니다. - 옵션을 식별하는 방법:
value
:<option value="value1">Option</option>
에서value1
.label
: 옵션의 텍스트 값.index
: 옵션의 순서(0부터 시작).
await page.selectOption('select[name="options"]', { label: 'Option Text' }); // 라벨로 선택 await page.selectOption('select[name="options"]', { index: 2 }); // 인덱스로 선택
- 드롭다운에서
-
page.$eval(selector, callback)
:- 선택된 드롭다운 값을 가져오는 데 사용됩니다.
const selectedValue = await page.$eval('select[name="options"]', (select) => (select as HTMLSelectElement).value); console.log('Selected value:', selectedValue);
-
여러 옵션 선택 (멀티 셀렉트 지원):
await page.selectOption('select[name="multi-options"]', ['value1', 'value2']);
예시 HTML 구조에 따른 적용
체크박스 예시
<input type="checkbox" name="agree" id="agree">
<label for="agree">I agree to the terms and conditions</label>
드롭다운 예시
<select name="options">
<option value="value1">Option 1</option>
<option value="value2">Option 2</option>
<option value="value3">Option 3</option>
</select>
참고
- 멀티 셀렉트 드롭다운은 옵션 선택 시 배열 형태로
value
값을 전달해야 합니다. - 동적 콘텐츠가 있는 경우, 체크박스나 드롭다운이 로드될 때까지 기다리려면
page.waitForSelector()
를 활용하세요.
스크롤과 화면 캡처
Playwright에서 스크롤과 화면 캡처를 다루는 방법은 간단합니다. page.evaluate()
를 사용하여 페이지를 스크롤하거나, page.screenshot()
를 사용하여 화면을 캡처할 수 있습니다.
아래는 스크롤과 화면 캡처의 예제를 TypeScript로 작성한 코드입니다.
스크롤과 화면 캡처 예제 (TypeScript)
import { chromium, Browser, Page } from 'playwright';
async function scrollAndCapture() {
let browser: Browser | null = null;
try {
// 브라우저 시작
browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page: Page = await context.newPage();
// 페이지 열기
await page.goto('https://example.com'); // 테스트용 페이지
// 스크롤 작업
console.log('Scrolling to the bottom of the page...');
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight); // 페이지 끝까지 스크롤
});
// 스크롤 후 일정 시간 대기 (콘텐츠 로드 기다림)
await page.waitForTimeout(2000);
// 전체 페이지 캡처
await page.screenshot({ path: 'full_page.png', fullPage: true });
console.log('Full page screenshot saved as "full_page.png"');
// 특정 영역만 캡처 (예: 헤더 부분)
const headerSelector = 'header'; // 실제 존재하는 헤더 셀렉터로 변경
await page.locator(headerSelector).screenshot({ path: 'header.png' });
console.log('Header screenshot saved as "header.png"');
// 특정 요소 스크린샷
const elementSelector = 'h1'; // 페이지의 특정 요소
const element = await page.$(elementSelector);
if (element) {
await element.screenshot({ path: 'element.png' });
console.log('Element screenshot saved as "element.png"');
} else {
console.log('Element not found!');
}
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 닫기
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
scrollAndCapture();
주요 메서드 및 설명
스크롤 작업
-
스크롤 전체 페이지
window.scrollTo(x, y)
를 사용하여 페이지를 스크롤합니다.
await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight); // 페이지 하단으로 스크롤 });
-
스크롤 단계별 이동
- 단계적으로 스크롤하여 콘텐츠가 로드되는 것을 시뮬레이션합니다.
await page.evaluate(async () => { for (let i = 0; i < document.body.scrollHeight; i += 500) { window.scrollTo(0, i); await new Promise(resolve => setTimeout(resolve, 500)); // 각 스크롤 후 대기 } });
화면 캡처
-
전체 페이지 캡처
fullPage: true
옵션을 사용하여 전체 페이지를 캡처합니다.
await page.screenshot({ path: 'full_page.png', fullPage: true });
-
특정 요소 캡처
page.locator(selector).screenshot()
또는element.screenshot()
을 사용하여 특정 요소만 캡처합니다.
await page.locator('header').screenshot({ path: 'header.png' });
-
뷰포트 화면 캡처
- 현재 보이는 화면(뷰포트)만 캡처합니다.
await page.screenshot({ path: 'viewport.png' });
-
스크린샷 품질 및 포맷 설정
- 파일 포맷을
jpeg
로 설정하거나, 품질을 조정할 수 있습니다.
await page.screenshot({ path: 'screenshot.jpeg', type: 'jpeg', quality: 80 });
- 파일 포맷을
예제 HTML 구조에 따른 적용
<header>
<h1>Welcome to Example</h1>
</header>
<div style="height: 2000px;"> <!-- 긴 페이지 내용 -->
<p>Scroll down for more content...</p>
</div>
<footer>End of page</footer>
추가 사항
- 스크롤이 끝난 후 콘텐츠가 로드되기 전에 캡처되지 않도록
page.waitForTimeout()
또는page.waitForSelector()
를 적절히 사용하세요. - 페이지 동적 로딩이 있는 경우, 스크롤을 단계적으로 수행하여 완전한 콘텐츠 로딩을 보장할 수 있습니다.
네트워크 요청 다루기
요청 감시
Playwright에서 네트워크 요청을 감시하려면 page.on('request')
와 page.on('response')
이벤트를 활용하면 됩니다. 이를 통해 특정 요청이나 응답 데이터를 로깅하거나, 요청 차단, 요청 수정 등의 작업을 수행할 수 있습니다.
아래는 네트워크 요청 감시와 관련된 TypeScript 예제입니다.
네트워크 요청 감시 예제 (TypeScript)
import { chromium, Browser, Page } from 'playwright';
async function monitorNetworkRequests() {
let browser: Browser | null = null;
try {
// 브라우저 시작
browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page: Page = await context.newPage();
// 네트워크 요청 감시
page.on('request', (request) => {
console.log('➡️ Request:', request.method(), request.url());
});
// 네트워크 응답 감시
page.on('response', async (response) => {
console.log('⬅️ Response:', response.status(), response.url());
});
// 네트워크 요청 실패 감시
page.on('requestfailed', (request) => {
console.error('❌ Request Failed:', request.url(), request.failure()?.errorText);
});
// 페이지 열기
await page.goto('https://example.com');
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 종료
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
monitorNetworkRequests();
주요 메서드 및 이벤트 설명
-
page.on('request', callback)
- 모든 네트워크 요청이 발생할 때 호출됩니다.
- 요청 URL, 메서드(GET, POST 등), 헤더 등을 확인할 수 있습니다.
page.on('request', (request) => { console.log(`Request Method: ${request.method()}, URL: ${request.url()}`); });
-
page.on('response', callback)
- 요청에 대한 응답이 반환될 때 호출됩니다.
- 응답 상태 코드, URL, 바디 데이터를 확인할 수 있습니다.
page.on('response', async (response) => { console.log(`Response Status: ${response.status()}, URL: ${response.url()}`); if (response.url().includes('example')) { console.log('Response Body:', await response.text()); } });
-
page.on('requestfailed', callback)
- 요청이 실패하면 호출됩니다.
- 실패 원인을 출력할 수 있습니다.
page.on('requestfailed', (request) => { console.error(`Failed Request URL: ${request.url()}, Error: ${request.failure()?.errorText}`); });
네트워크 요청 차단 및 수정
특정 요청 차단
route.abort()
를 사용하여 특정 요청을 차단할 수 있습니다.
await page.route('**/*.png', (route) => {
console.log('Blocking PNG request:', route.request().url());
route.abort(); // 요청 차단
});
요청 수정
route.continue()
를 사용하여 요청을 수정할 수 있습니다.
await page.route('**/api/data', (route) => {
const headers = {
...route.request().headers(),
'Authorization': 'Bearer token'
};
route.continue({ headers }); // 요청 헤더 수정
});
요청 필터링
요청 및 응답을 특정 조건에 따라 필터링할 수 있습니다.
특정 URL 요청만 로깅
page.on('request', (request) => {
if (request.url().includes('api')) {
console.log('API Request:', request.url());
}
});
응답 상태 코드 필터링
page.on('response', (response) => {
if (response.status() >= 400) {
console.log('Error Response:', response.status(), response.url());
}
});
실용적인 응용 사례
-
네트워크 성능 분석:
- 모든 요청/응답 시간을 기록하여 성능 문제를 분석합니다.
page.on('response', async (response) => { const timing = response.timing(); console.log(`Response Time: ${timing.responseEnd - timing.requestStart} ms for ${response.url()}`); });
-
API 테스트 자동화:
- 특정 API 요청을 감시하고, 반환 데이터를 확인합니다.
page.on('response', async (response) => { if (response.url().includes('/api/test')) { const data = await response.json(); console.log('API Response Data:', data); } });
요청 가로채기
Playwright에서는 page.route()
를 사용하여 요청을 가로채고, 해당 요청을 차단, 수정 또는 대체할 수 있습니다. 이를 통해 특정 요청에 대해 커스텀 동작을 수행하거나 요청을 조작할 수 있습니다.
아래는 요청 가로채기에 대한 다양한 TypeScript 예제입니다.
요청 가로채기 예제 (TypeScript)
import { chromium, Browser, Page } from 'playwright';
async function interceptRequests() {
let browser: Browser | null = null;
try {
// 브라우저 시작
browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page: Page = await context.newPage();
// 요청 가로채기 설정
await page.route('**/*', (route) => {
const request = route.request();
// 요청 로그 출력
console.log(`Intercepted Request: ${request.method()} ${request.url()}`);
// 특정 요청 차단
if (request.url().endsWith('.png') || request.url().endsWith('.jpg')) {
console.log(`Blocking image request: ${request.url()}`);
return route.abort(); // 요청 차단
}
// 요청 헤더 수정
if (request.url().includes('/api')) {
const modifiedHeaders = {
...request.headers(),
'Authorization': 'Bearer fake-token'
};
console.log(`Modifying headers for API request: ${request.url()}`);
return route.continue({ headers: modifiedHeaders });
}
// 기본 요청 진행
route.continue();
});
// 페이지 열기
await page.goto('https://example.com');
console.log('Page loaded');
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 종료
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
interceptRequests();
주요 메서드 및 사용법
1. 요청 차단
route.abort()
를 사용하여 특정 요청을 차단합니다.
await page.route('**/*.png', (route) => {
console.log(`Blocking PNG request: ${route.request().url()}`);
route.abort(); // 요청 차단
});
2. 요청 수정
route.continue()
를 사용하여 요청을 수정합니다. 예를 들어, 요청 헤더를 변경하거나 POST 데이터를 수정할 수 있습니다.
await page.route('**/api/data', (route) => {
const modifiedHeaders = {
...route.request().headers(),
'Authorization': 'Bearer custom-token'
};
console.log('Modifying request headers:', route.request().url());
route.continue({ headers: modifiedHeaders }); // 요청 헤더 수정
});
3. 요청 대체
route.fulfill()
을 사용하여 요청에 대해 사용자 정의 응답을 제공합니다. 원래 요청을 서버로 보내지 않고 가로채서 직접 응답을 반환할 수 있습니다.
await page.route('**/api/mock', (route) => {
console.log('Intercepting and fulfilling request:', route.request().url());
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ message: 'This is a mock response' })
});
});
실용적인 요청 가로채기 예제
1. 이미지 요청 차단
await page.route('**/*.{png,jpg,jpeg,gif}', (route) => {
console.log(`Blocking image request: ${route.request().url()}`);
route.abort(); // 이미지 요청 차단
});
2. API 요청의 Body 수정
await page.route('**/api/submit', (route) => {
if (route.request().method() === 'POST') {
const modifiedPostData = JSON.stringify({ key: 'modifiedValue' });
console.log('Modifying POST request body:', modifiedPostData);
route.continue({
postData: modifiedPostData
});
} else {
route.continue();
}
});
3. 특정 요청에 대해 Mock 응답 반환
await page.route('**/api/test', (route) => {
console.log('Mocking API response:', route.request().url());
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
message: 'This is a mocked response'
})
});
});
페이지 전체에서 요청 감시 및 가로채기 조합
다양한 조건에 따라 요청을 가로채고 처리합니다.
await page.route('**/*', (route) => {
const url = route.request().url();
const method = route.request().method();
// 요청 로그 출력
console.log(`Request intercepted: ${method} ${url}`);
if (url.includes('/analytics')) {
console.log(`Blocking analytics request: ${url}`);
return route.abort(); // 분석 요청 차단
}
if (url.includes('/api')) {
console.log(`Intercepting API request: ${url}`);
route.continue({
headers: {
...route.request().headers(),
'Authorization': 'Bearer modified-token'
}
});
return;
}
route.continue(); // 기본 요청 진행
});
추가 사항
route.fulfill()
은 API 테스트 또는 네트워크 환경이 불안정할 때 서버 응답을 모의(mock)하는 데 유용합니다.- 요청 가로채기와 관련된 테스트를 반복적으로 실행하려면
browserContext.route()
를 사용하여 페이지 단위가 아닌 브라우저 컨텍스트에서 적용할 수 있습니다.
요청 모킹(Mock)
Playwright에서 요청 모킹(Mock)은 page.route()
를 사용하여 특정 요청을 가로채고, route.fulfill()
로 사용자 정의 응답을 반환함으로써 구현할 수 있습니다. 서버와 실제로 통신하지 않고, 요청에 대해 원하는 응답을 시뮬레이션할 수 있습니다.
아래는 요청 모킹의 다양한 예제를 TypeScript로 작성한 코드입니다.
요청 모킹 기본 예제
import { chromium, Browser, Page } from 'playwright';
async function mockRequestExample() {
let browser: Browser | null = null;
try {
// 브라우저 시작
browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page: Page = await context.newPage();
// 요청 모킹 설정
await page.route('**/api/data', async (route) => {
console.log(`Mocking API request: ${route.request().url()}`);
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
message: 'This is a mocked response',
data: [1, 2, 3, 4, 5]
})
});
});
// 테스트 페이지 열기
await page.goto('https://example.com'); // 실제 API 호출이 포함된 페이지로 변경
} catch (error) {
console.error('An error occurred:', error);
} finally {
// 브라우저 종료
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}
// 함수 실행
mockRequestExample();
주요 메서드 설명
-
page.route()
- 특정 URL 패턴에 해당하는 요청을 가로챕니다.
- 패턴 예시:
**/api/*
→ 모든/api/
요청.https://example.com/api/data
→ 특정 URL 요청.**/*.png
→ 특정 파일 유형 요청.
-
route.fulfill()
- 요청에 대한 사용자 정의 응답을 반환합니다.
- 주요 옵션:
status
: HTTP 상태 코드 (예:200
,404
).contentType
: 응답의 MIME 유형 (예:application/json
,text/html
).body
: 응답 본문.
요청 모킹 고급 예제
조건부 모킹
await page.route('**/api/data', async (route) => {
const request = route.request();
if (request.method() === 'GET') {
// GET 요청 모킹
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: 'GET response data' })
});
} else if (request.method() === 'POST') {
// POST 요청 모킹
const postData = request.postData();
console.log('POST data:', postData);
route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ success: true, message: 'POST request was mocked' })
});
} else {
// 기본 요청 처리
route.continue();
}
});
Delay 추가 (네트워크 지연 시뮬레이션)
await page.route('**/api/data', async (route) => {
console.log('Mocking request with delay:', route.request().url());
await new Promise((resolve) => setTimeout(resolve, 3000)); // 3초 지연
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: 'Delayed response' })
});
});
Mock 파일로 대체
import * as fs from 'fs';
await page.route('**/api/data', async (route) => {
const mockResponse = fs.readFileSync('mock-data.json', 'utf-8'); // JSON 파일 읽기
route.fulfill({
status: 200,
contentType: 'application/json',
body: mockResponse
});
});
원본 요청을 수정하여 Mock 데이터 추가
await page.route('**/api/data', async (route) => {
const originalResponse = await route.fetch(); // 원본 요청 수행
const originalBody = await originalResponse.json();
const modifiedBody = {
...originalBody,
additionalData: 'This data was added by the mock'
};
route.fulfill({
status: originalResponse.status(),
contentType: originalResponse.headers()['content-type'],
body: JSON.stringify(modifiedBody)
});
});
Mocking Use Cases
1. API 테스트 자동화
- 백엔드가 준비되지 않은 상태에서 프런트엔드 개발 및 테스트.
- 서버 부하를 줄이기 위해 테스트 환경에서 Mock 데이터 사용.
2. 에러 처리 시뮬레이션
- 다양한 HTTP 상태 코드를 테스트하여 에러 핸들링을 검증.
await page.route('**/api/data', async (route) => {
route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ success: false, error: 'Internal Server Error' })
});
});
3. 대규모 데이터 응답 테스트
- 클라이언트가 대규모 데이터를 처리하는 성능을 테스트.
await page.route('**/api/large-data', async (route) => {
const largeData = Array(10000).fill({ key: 'value' });
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: largeData })
});
});
요청 모킹과 실제 요청 조합
원본 요청을 계속 수행하면서 특정 요청만 모킹할 수 있습니다.
await page.route('**/*', (route) => {
if (route.request().url().includes('mocked-endpoint')) {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ message: 'This endpoint is mocked' })
});
} else {
route.continue(); // 원본 요청 수행
}
});
동적 컨텐츠 테스트
SPA(Single Page Application) 지원
Playwright는 SPA(Single Page Application) 환경을 완벽히 지원합니다. SPA는 클라이언트 측에서 페이지를 렌더링하고, 대부분의 네트워크 요청이 비동기로 이루어지기 때문에, Playwright를 사용할 때 다음과 같은 추가적인 고려사항이 필요합니다.
주요 과제와 Playwright의 지원 기능
-
동적 콘텐츠 로드:
- SPA는 페이지 전환이나 사용자 액션 시 새로운 데이터를 비동기로 가져와 화면에 렌더링합니다.
- Playwright는
page.waitForSelector()
나page.waitForResponse()
로 비동기 데이터 로드를 기다릴 수 있습니다.
-
URL 경로 변경 감지 (라우팅 처리):
- SPA는 페이지 이동 시 URL이 변경되더라도 전체 페이지가 새로 고쳐지지 않으며, 이는 Playwright의
page.waitForURL()
로 처리할 수 있습니다.
- SPA는 페이지 이동 시 URL이 변경되더라도 전체 페이지가 새로 고쳐지지 않으며, 이는 Playwright의
-
JavaScript 이벤트 처리:
- SPA는 주로 JavaScript를 통해 사용자 인터페이스를 조작하므로, Playwright는 DOM 이벤트 및 상태 변경을 감지하고 조작하는 메서드를 제공합니다.
-
XHR 및 Fetch 요청 감시:
- Playwright는 SPA에서 발생하는 모든 네트워크 요청(XHR, Fetch 등)을 감시하거나 가로챌 수 있습니다.
Playwright로 SPA 테스트하는 방법
1. 비동기 데이터 로드 대기
import { chromium } from 'playwright';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
// SPA 페이지 열기
await page.goto('https://example.com');
// 동적으로 로드된 콘텐츠 대기
await page.waitForSelector('.dynamic-content'); // 동적 콘텐츠가 렌더링될 때까지 대기
const content = await page.textContent('.dynamic-content');
console.log('Loaded Content:', content);
await browser.close();
})();
2. URL 경로 변경 감지
SPA에서는 URL이 변경될 수 있지만 새 페이지가 로드되지는 않습니다. page.waitForURL()
로 경로 변경을 처리할 수 있습니다.
await page.goto('https://example.com');
// 버튼 클릭 후 URL 변경 감지
await page.click('button.navigate');
await page.waitForURL('**/new-route'); // URL이 변경될 때까지 대기
console.log('Navigation completed to new route:', page.url());
3. 네트워크 요청 감시
SPA의 비동기 요청을 감시하거나 응답을 조작하려면 page.route()
나 page.on('response')
를 활용합니다.
// 특정 API 요청 대기
await page.waitForResponse((response) =>
response.url().includes('/api/data') && response.status() === 200
);
// API 요청 Mocking
await page.route('**/api/data', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: [1, 2, 3] }),
});
});
4. DOM 이벤트 처리
SPA는 이벤트 기반으로 작동하므로 DOM 이벤트를 처리할 수 있습니다.
await page.click('button#load-data'); // 버튼 클릭
await page.waitForSelector('.loaded-item'); // 동적으로 추가된 요소 대기
const items = await page.$$('.loaded-item');
console.log(`Loaded items count: ${items.length}`);
5. Fetch 및 XHR 요청 확인
page.on('request')
와 page.on('response')
를 사용하여 SPA의 네트워크 요청을 확인할 수 있습니다.
page.on('request', (request) => {
if (request.url().includes('/api')) {
console.log(`Request made to: ${request.url()}`);
}
});
page.on('response', async (response) => {
if (response.url().includes('/api')) {
console.log(`Response from ${response.url()}:`, await response.json());
}
});
SPA 테스트 팁
-
라우팅 대기 (
page.waitForURL
)- SPA에서는 URL 변경만 감지하고, 별도의 페이지 로드는 발생하지 않으므로
waitForURL
이 유용합니다.
- SPA에서는 URL 변경만 감지하고, 별도의 페이지 로드는 발생하지 않으므로
-
동적 렌더링 콘텐츠 처리
- 비동기로 로드되는 요소는 항상
waitForSelector
로 렌더링을 기다립니다.
- 비동기로 로드되는 요소는 항상
-
네트워크 요청 처리
- SPA에서 데이터 요청을 감시하거나 Mock 응답을 제공하려면
page.route()
와page.waitForResponse()
를 적절히 사용합니다.
- SPA에서 데이터 요청을 감시하거나 Mock 응답을 제공하려면
-
상태 기반 대기
- 단순히 특정 셀렉터를 기다리기보다, 페이지 상태를 직접 확인하는 방법이 필요할 수 있습니다.
await page.waitForFunction(() => { return document.querySelector('.status').textContent === 'Loaded'; });
-
UI 전환 테스트
page.locator()
를 사용하면 동적으로 로드된 요소나 텍스트를 기다리고 쉽게 검증할 수 있습니다.
SPA와 관련된 고급 기능
UI 스냅샷 테스트
- SPA의 특정 상태를 캡처하여 UI 스냅샷을 테스트할 수 있습니다.
await page.goto('https://example.com');
await page.click('button.load-content');
await page.waitForSelector('.dynamic-content');
await page.screenshot({ path: 'ui-snapshot.png', fullPage: true });
SPA 컴포넌트 테스트
- Playwright를 사용해 SPA의 특정 컴포넌트를 개별적으로 테스트할 수 있습니다.
await page.goto('https://example.com');
const component = page.locator('my-component');
await component.click();
await expect(component).toHaveText('Loaded');
SPA 테스트 자동화의 장점
- 속도: 페이지 리로드가 없으므로 빠른 테스트 실행.
- 네트워크 요청 제어: 모든 비동기 요청과 응답을 쉽게 감시하고 조작 가능.
- 정확성: 비동기 렌더링 타이밍에 맞춘 대기로 안정적 테스트 가능.
AJAX 요청 처리
Playwright에서 AJAX 요청은 일반적으로 XMLHttpRequest
또는 fetch
API를 통해 비동기로 이루어집니다. Playwright는 이러한 요청을 감시하거나 제어하기 위한 강력한 기능을 제공합니다. 이를 활용하면 AJAX 요청을 감시, 대기, 가로채기(Mock), 응답 확인 등을 수행할 수 있습니다.
AJAX 요청 처리 주요 방법
-
요청 감시
page.on('request')
와page.on('response')
이벤트를 사용하여 AJAX 요청과 응답을 감시합니다.
-
특정 요청 대기
page.waitForResponse()
를 사용하여 특정 AJAX 요청의 응답을 기다립니다.
-
요청 가로채기(Mock)
page.route()
를 사용하여 요청을 가로채고, Mock 데이터를 반환하거나 요청을 수정합니다.
-
요청 확인
- AJAX 요청에 전달된 데이터와 응답 데이터를 확인합니다.
AJAX 요청 처리 예제 (TypeScript)
1. AJAX 요청 감시
import { chromium } from 'playwright';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
// AJAX 요청 감시
page.on('request', (request) => {
if (request.resourceType() === 'xhr' || request.resourceType() === 'fetch') {
console.log(`➡️ AJAX Request: ${request.method()} ${request.url()}`);
}
});
// AJAX 응답 감시
page.on('response', async (response) => {
if (response.request().resourceType() === 'xhr' || response.request().resourceType() === 'fetch') {
console.log(`⬅️ AJAX Response: ${response.url()} - ${response.status()}`);
const responseBody = await response.text();
console.log('Response Body:', responseBody);
}
});
await page.goto('https://example.com'); // AJAX 요청이 발생하는 페이지
await browser.close();
})();
2. 특정 AJAX 요청 대기
await page.goto('https://example.com');
// 특정 URL에 대한 AJAX 요청 대기
const response = await page.waitForResponse((response) =>
response.url().includes('/api/data') && response.status() === 200
);
console.log('AJAX Response Data:', await response.json());
3. AJAX 요청 가로채기(Mock)
await page.route('**/api/data', (route) => {
console.log(`Intercepting AJAX Request: ${route.request().url()}`);
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: ['Mocked Item 1', 'Mocked Item 2', 'Mocked Item 3']
}),
});
});
4. AJAX 요청의 Body 확인
page.on('request', (request) => {
if (request.resourceType() === 'xhr' || request.resourceType() === 'fetch') {
console.log('AJAX Request Body:', request.postData());
}
});
5. AJAX 요청 변경(Mock 데이터 추가)
await page.route('**/api/data', async (route) => {
const originalResponse = await route.fetch(); // 원본 요청 수행
const originalBody = await originalResponse.json();
const modifiedBody = {
...originalBody,
additionalData: 'Mocked Data',
};
route.fulfill({
status: originalResponse.status(),
contentType: originalResponse.headers()['content-type'],
body: JSON.stringify(modifiedBody),
});
});
6. AJAX 요청 결과 기반 대기
await page.goto('https://example.com');
// AJAX 요청의 결과로 특정 DOM 요소가 렌더링될 때까지 대기
await page.click('#load-data-button');
await page.waitForSelector('.dynamic-content'); // 동적으로 로드된 콘텐츠 대기
console.log('Dynamic Content Loaded:', await page.textContent('.dynamic-content'));
AJAX 요청 처리 활용 사례
1. API 응답 검증
- AJAX 요청 및 응답을 검증하여 클라이언트와 서버 간 데이터 흐름을 확인합니다.
page.on('response', async (response) => {
if (response.url().includes('/api/data')) {
const responseData = await response.json();
console.log('API Response:', responseData);
// 특정 데이터 검증
if (responseData.success) {
console.log('API call successful');
} else {
console.error('API call failed');
}
}
});
2. AJAX 요청 차단
- 특정 요청을 차단하여 네트워크 부하를 줄이거나 테스트 환경을 시뮬레이션합니다.
await page.route('**/api/analytics', (route) => {
console.log('Blocking analytics request:', route.request().url());
route.abort(); // 요청 차단
});
3. AJAX 응답 지연 시뮬레이션
- 네트워크 지연 상황을 시뮬레이션하여 클라이언트의 처리 능력을 테스트합니다.
await page.route('**/api/data', async (route) => {
console.log('Delaying AJAX Response...');
await new Promise((resolve) => setTimeout(resolve, 5000)); // 5초 지연
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: 'Delayed Data' }),
});
});
4. AJAX 요청 기반 DOM 검증
- AJAX 요청의 결과로 페이지가 동적으로 변경될 때, DOM을 검증합니다.
await page.goto('https://example.com');
await page.click('#load-data-button'); // AJAX 요청 트리거
await page.waitForSelector('.loaded-content'); // AJAX 요청 결과로 렌더링된 요소 대기
const content = await page.textContent('.loaded-content');
console.log('Loaded Content:', content);
AJAX 요청 테스트의 주요 포인트
-
동기화 처리:
- AJAX 요청이 비동기로 이루어지므로 항상 적절한 대기 메커니즘(
waitForResponse
,waitForSelector
등)을 사용합니다.
- AJAX 요청이 비동기로 이루어지므로 항상 적절한 대기 메커니즘(
-
네트워크 패턴 관리:
- URL 패턴(
**/api/*
,**/fetch/*
등)을 활용하여 요청을 효율적으로 필터링합니다.
- URL 패턴(
-
Mocking:
- 요청을 모의(Mock)하여 서버 환경 없이 프런트엔드 기능을 테스트하거나 네트워크 에러 상황을 시뮬레이션합니다.
-
요청 상태 코드 검증:
- 응답 상태 코드(
200
,404
,500
)를 확인하여 에러 핸들링 로직을 검증합니다.
- 응답 상태 코드(
멀티 브라우저와 디바이스 테스트
다양한 브라우저 지원
Playwright는 기본적으로 다양한 브라우저를 지원하며, Chromium, Firefox, WebKit을 포함한 주요 브라우저 엔진에서 테스트를 실행할 수 있습니다. 이를 통해 브라우저 호환성 테스트를 쉽게 수행할 수 있습니다.
아래는 다양한 브라우저를 지원하기 위한 주요 정보와 TypeScript 예제입니다.
Playwright에서 지원하는 브라우저
-
Chromium (Google Chrome, Microsoft Edge 기반)
- 경량 브라우저 테스트나 일반적인 웹 테스트에 주로 사용.
- Playwright는 Chromium 기반 브라우저에서
headless
와headful
모드를 모두 지원.
-
Firefox
- Firefox 엔진을 기반으로 테스트를 실행.
- Mozilla 특유의 렌더링 및 기능을 테스트.
-
WebKit (Apple Safari 기반)
- WebKit 엔진을 기반으로 하며, macOS와 iOS의 브라우저 환경을 테스트 가능.
- Safari 호환성을 확인하기에 적합.
브라우저별 테스트 실행 예제 (TypeScript)
1. 단일 브라우저에서 테스트 실행
import { chromium } from 'playwright';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log('Page Title:', await page.title());
await browser.close();
})();
2. 여러 브라우저에서 테스트 실행
import { chromium, firefox, webkit, BrowserType } from 'playwright';
(async () => {
const browsers: { [key: string]: BrowserType } = { chromium, firefox, webkit };
for (const [browserName, browserType] of Object.entries(browsers)) {
console.log(`Testing on: ${browserName}`);
const browser = await browserType.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(`${browserName} - Page Title:`, await page.title());
await browser.close();
}
})();
3. 브라우저 간 공통 설정
테스트 환경이나 브라우저 설정이 공통적으로 필요할 경우, 이를 재사용할 수 있습니다.
import { chromium, firefox, webkit, BrowserType } from 'playwright';
async function testBrowser(browserType: BrowserType) {
const browser = await browserType.launch({
headless: false,
slowMo: 50 // 느린 동작으로 실행 (디버깅 시 유용)
});
const context = await browser.newContext({
viewport: { width: 1280, height: 720 } // 모든 브라우저에서 동일한 뷰포트 설정
});
const page = await context.newPage();
await page.goto('https://example.com');
console.log('Page Title:', await page.title());
await browser.close();
}
(async () => {
await testBrowser(chromium);
await testBrowser(firefox);
await testBrowser(webkit);
})();
브라우저별 고유 설정
Playwright는 브라우저별로 고유한 설정을 지원합니다. 아래는 각 브라우저에 적합한 설정 예제입니다.
1. Chromium 설정
await chromium.launch({
headless: false,
args: ['--no-sandbox', '--disable-setuid-sandbox'] // Chrome 옵션 설정
});
2. Firefox 설정
await firefox.launch({
headless: true,
firefoxUserPrefs: {
'browser.startup.homepage': 'https://example.com',
'network.proxy.type': 0 // Firefox 네트워크 프록시 설정
}
});
3. WebKit 설정
await webkit.launch({
headless: false,
timeout: 30000 // WebKit의 기본 실행 시간 초과 설정
});
브라우저 간 테스트 결과 비교
Playwright는 브라우저 간 차이를 쉽게 비교할 수 있도록 도와줍니다.
DOM 확인 테스트
const browsers = { chromium, firefox, webkit };
for (const [name, browserType] of Object.entries(browsers)) {
const browser = await browserType.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
const elementText = await page.textContent('h1');
console.log(`${name} - H1 Text: ${elementText}`);
await browser.close();
}
스크린샷 비교 테스트
import { chromium, firefox, webkit } from 'playwright';
const browsers = { chromium, firefox, webkit };
(async () => {
for (const [name, browserType] of Object.entries(browsers)) {
const browser = await browserType.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: `${name}.png` }); // 브라우저별 스크린샷 저장
console.log(`${name} screenshot saved`);
await browser.close();
}
})();
CI/CD에서 브라우저별 테스트 실행
Playwright는 CI/CD 환경에서 여러 브라우저를 병렬로 테스트하는 데 최적화되어 있습니다.
GitHub Actions 예시
name: Playwright Tests
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
browser: [chromium, firefox, webkit] # 여러 브라우저 테스트
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Run Playwright tests
run: npx playwright test --project=${{ matrix.browser }}
Playwright의 장점
- 모든 주요 브라우저 지원:
- Chromium, Firefox, WebKit을 모두 지원하여 폭넓은 브라우저 테스트 가능.
- Cross-Browser 테스트:
- 동일한 테스트 코드를 사용하여 여러 브라우저 간 호환성 확인.
- 병렬 실행 지원:
- 병렬 실행으로 테스트 시간을 단축.
- 특정 브라우저 최적화:
- 각 브라우저에 특화된 설정을 제공.
모바일 디바이스 에뮬레이션
Playwright는 모바일 디바이스 에뮬레이션을 지원하며, 다양한 화면 크기, 해상도, 입력 장치(터치 등)를 설정할 수 있습니다. 이를 통해 데스크톱과 모바일 환경 간의 호환성을 테스트하거나, 모바일 중심의 UI를 검증할 수 있습니다.
모바일 디바이스 에뮬레이션 기본 설정
Playwright는 내장된 디바이스 프리셋 목록을 제공합니다. 예를 들어, iPhone 11
, Pixel 4
등의 사전 설정된 모바일 디바이스 환경을 사용할 수 있습니다.
1. 사전 설정된 디바이스 사용
import { chromium, devices } from 'playwright';
(async () => {
const browser = await chromium.launch({ headless: false });
// iPhone 11 프리셋 가져오기
const iPhone = devices['iPhone 11'];
// iPhone 11 환경으로 브라우저 컨텍스트 생성
const context = await browser.newContext({
...iPhone, // 디바이스 설정
});
const page = await context.newPage();
await page.goto('https://example.com');
console.log('Page title:', await page.title());
await browser.close();
})();
2. 사용자 정의 모바일 설정
사전 설정된 디바이스 외에도 사용자 정의 설정으로 디바이스를 에뮬레이션할 수 있습니다.
import { chromium } from 'playwright';
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
viewport: { width: 375, height: 812 }, // 화면 크기 (iPhone X 크기)
deviceScaleFactor: 3, // 디스플레이 해상도 배율
isMobile: true, // 모바일 모드 활성화
hasTouch: true, // 터치 입력 지원
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1', // 모바일 User-Agent
});
const page = await context.newPage();
await page.goto('https://example.com');
console.log('Page title:', await page.title());
await browser.close();
})();
주요 설정 옵션
-
viewport
:- 디바이스 화면 크기를 설정합니다.
- 예:
{ width: 375, height: 812 }
(iPhone X)
-
deviceScaleFactor
:- 디스플레이 해상도 배율을 설정합니다.
- 예:
2
(레티나 디스플레이)
-
isMobile
:- 모바일 디바이스 여부를 지정합니다.
-
hasTouch
:- 터치 입력을 활성화합니다.
-
userAgent
:- 디바이스의 User-Agent를 설정합니다.
- 예: iPhone, Android 등에서 사용하는 User-Agent 문자열.
Playwright 디바이스 프리셋 목록
Playwright는 사전 설정된 모바일 디바이스 목록을 제공합니다. 이 목록은 devices
객체에서 확인할 수 있습니다. 몇 가지 주요 디바이스는 다음과 같습니다:
import { devices } from 'playwright';
console.log(devices);
/* 출력 예시:
{
'iPhone 11': {...},
'Pixel 4': {...},
'iPad Pro 11': {...},
'Galaxy S9+': {...},
...
}
*/
iPhone 11
iPhone X
Pixel 4
iPad Pro 11
Galaxy S9+
Samsung Galaxy Tab S4
모바일 디바이스 에뮬레이션 예제
1. 여러 디바이스에서 테스트
여러 디바이스 환경을 반복적으로 테스트하려면 아래처럼 설정할 수 있습니다.
import { chromium, devices } from 'playwright';
(async () => {
const browser = await chromium.launch({ headless: false });
const deviceList = ['iPhone 11', 'Pixel 4', 'Galaxy S9+'];
for (const deviceName of deviceList) {
const device = devices[deviceName];
console.log(`Testing on ${deviceName}`);
const context = await browser.newContext({ ...device });
const page = await context.newPage();
await page.goto('https://example.com');
console.log(`${deviceName} - Page title:`, await page.title());
await context.close();
}
await browser.close();
})();
2. 스크린샷 비교 (모바일 vs 데스크톱)
모바일 디바이스와 데스크톱 환경의 화면을 비교합니다.
import { chromium, devices } from 'playwright';
(async () => {
const browser = await chromium.launch();
// 데스크톱 환경
const desktopContext = await browser.newContext();
const desktopPage = await desktopContext.newPage();
await desktopPage.goto('https://example.com');
await desktopPage.screenshot({ path: 'desktop.png' });
// 모바일 환경 (iPhone 11)
const iPhone = devices['iPhone 11'];
const mobileContext = await browser.newContext({ ...iPhone });
const mobilePage = await mobileContext.newPage();
await mobilePage.goto('https://example.com');
await mobilePage.screenshot({ path: 'mobile.png' });
console.log('Screenshots saved: desktop.png, mobile.png');
await browser.close();
})();
CI/CD에서 모바일 테스트 자동화
GitHub Actions 또는 CI/CD 환경에서 모바일 디바이스 에뮬레이션을 포함한 테스트를 실행할 수 있습니다.
GitHub Actions 설정 예시
name: Playwright Mobile Tests
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
device: [ 'iPhone 11', 'Pixel 4', 'Galaxy S9+' ] # 테스트할 디바이스
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Run Playwright mobile tests
run: npx playwright test --project=${{ matrix.device }}
모바일 디바이스 테스트에서 주의할 점
-
뷰포트 크기와 레이아웃:
- UI 요소가 모바일 화면에 맞게 표시되는지 확인.
- 글꼴 크기, 버튼 간격, 터치 대상 등을 검증.
-
반응형 디자인 확인:
- 브라우저 창 크기를 조정하거나 모바일 디바이스 프리셋을 사용해 반응형 디자인 테스트.
-
터치 이벤트:
- Playwright의
hasTouch
옵션을 통해 터치 기반 이벤트를 테스트.
- Playwright의
-
네트워크 속도 시뮬레이션:
- 모바일 네트워크 환경(예: 3G, 4G)을 시뮬레이션하려면
context.setOffline()
또는context.setNetworkConditions()
를 사용.
- 모바일 네트워크 환경(예: 3G, 4G)을 시뮬레이션하려면
추가 시나리오
-
네트워크 조건 에뮬레이션
- 느린 네트워크 속도를 테스트:
await context.setNetworkConditions({ download: 500 * 1024, // 500kbps upload: 500 * 1024, latency: 100 // 100ms });
-
오프라인 모드 테스트
- 네트워크가 없는 상태를 시뮬레이션:
await context.setOffline(true);
브라우저 내장 API 활용
Playwright에서는 브라우저 내장 API를 활용해 DOM 조작, JavaScript 실행, 또는 브라우저 내부 상태 확인 및 변경이 가능합니다. 이를 통해 사용자 동작을 시뮬레이션하거나, 특정 상태에서의 동작을 테스트할 수 있습니다.
다음은 Playwright에서 브라우저 내장 API를 사용하는 방법과 다양한 활용 사례를 소개합니다.
브라우저 내장 API 활용 방법
Playwright는 page.evaluate()
와 page.evaluateHandle()
를 통해 브라우저 컨텍스트 내에서 JavaScript를 실행할 수 있습니다.
-
page.evaluate(pageFunction, ...args)
- 브라우저 내에서 JavaScript 코드를 실행하고 결과를 반환합니다.
- 페이지 외부에서 데이터나 함수를 전달할 수 있습니다.
-
page.evaluateHandle(pageFunction, ...args)
- 브라우저 내 객체 핸들(참조)을 반환합니다. DOM 노드나 복잡한 객체를 조작할 때 유용합니다.
브라우저 내장 API 활용 예제
1. DOM 조작
await page.evaluate(() => {
// DOM 요소 찾기 및 변경
const element = document.querySelector('h1');
if (element) {
element.textContent = 'Hello from Playwright!';
}
});
2. LocalStorage 및 SessionStorage 조작
LocalStorage에 데이터 설정 및 읽기:
// 데이터 설정
await page.evaluate(() => {
localStorage.setItem('key', 'value');
});
// 데이터 읽기
const value = await page.evaluate(() => localStorage.getItem('key'));
console.log('LocalStorage Value:', value);
SessionStorage에 데이터 설정 및 읽기:
await page.evaluate(() => {
sessionStorage.setItem('sessionKey', 'sessionValue');
});
const sessionValue = await page.evaluate(() => sessionStorage.getItem('sessionKey'));
console.log('SessionStorage Value:', sessionValue);
3. Cookie 설정 및 읽기
Cookie 설정:
await page.context().addCookies([
{
name: 'testCookie',
value: '12345',
domain: 'example.com',
path: '/',
httpOnly: false,
secure: true,
sameSite: 'Lax',
},
]);
Cookie 읽기:
const cookies = await page.context().cookies();
console.log('Cookies:', cookies);
4. JavaScript 경고창(Alerts) 처리
Alert 창 감지 및 메시지 확인:
page.on('dialog', async (dialog) => {
console.log('Dialog message:', dialog.message());
await dialog.accept(); // 경고창 확인
});
await page.evaluate(() => alert('This is a test alert'));
5. Geolocation 설정
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 }); // 샌프란시스코 좌표
await page.goto('https://example.com');
6. Network API 활용
fetch
API로 데이터 가져오기:
const data = await page.evaluate(async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
return await response.json();
});
console.log('Fetched Data:', data);
7. Performance API 활용
페이지 로드 성능 측정:
const performanceTiming = await page.evaluate(() => JSON.stringify(window.performance.timing));
console.log('Performance Timing:', JSON.parse(performanceTiming));
특정 작업의 실행 시간 측정:
const duration = await page.evaluate(() => {
const start = performance.now();
// 긴 작업 수행
for (let i = 0; i < 1e6; i++);
return performance.now() - start;
});
console.log('Operation Duration:', duration, 'ms');
8. Clipboard API 활용
클립보드에 텍스트 복사:
await page.evaluate(async () => {
await navigator.clipboard.writeText('Copied text from Playwright');
});
console.log('Text copied to clipboard.');
클립보드에서 텍스트 읽기:
const clipboardText = await page.evaluate(async () => {
return await navigator.clipboard.readText();
});
console.log('Clipboard Text:', clipboardText);
9. Device Orientation 및 Motion API 활용
Device Orientation 설정:
await page.evaluate(() => {
window.dispatchEvent(new DeviceOrientationEvent('deviceorientation', {
alpha: 90, // 회전 각도
beta: 45,
gamma: 0,
}));
});
10. Custom Events 및 Event Dispatch
사용자 정의 이벤트 생성 및 디스패치:
await page.evaluate(() => {
const event = new CustomEvent('myCustomEvent', { detail: { key: 'value' } });
window.dispatchEvent(event);
});
// 이벤트 리스너 등록 및 처리 확인
await page.evaluate(() => {
window.addEventListener('myCustomEvent', (e) => {
console.log('Custom Event Triggered:', e.detail);
});
});
브라우저 내장 API 활용 시 주의사항
-
안전한 DOM 접근:
- 페이지 로드가 완료된 후에
evaluate
를 호출해야 합니다. 필요한 경우page.waitForSelector()
나page.waitForLoadState()
를 사용합니다.
- 페이지 로드가 완료된 후에
-
비동기 작업:
- 브라우저 내 비동기 코드는
async/await
를 사용해야 하며, 이를 Playwright에서 처리할 때도 동일합니다.
- 브라우저 내 비동기 코드는
-
Cross-Origin 제한:
- Playwright에서 브라우저 내
fetch
나XMLHttpRequest
를 호출할 때, Cross-Origin 제한이 적용될 수 있습니다.
- Playwright에서 브라우저 내
-
성능 측정:
- 성능 측정을 위해
window.performance
를 활용할 때, 클라이언트와 서버 간의 네트워크 속도를 고려해야 합니다.
- 성능 측정을 위해