본문 바로가기

[nodejs][playwright] Playwright로 E2E(End to End, 종단) 테스트로 사용자의 입장에서 사용자가 사용하는 상황 테스트 작성하기

출입금지 발행일 : 2023-06-07

Playwright로 E2E(End to End, 종단) 테스트로 사용자의 입장에서 사용자가 사용하는 상황 테스트 작성하기

 

Playwright는 무엇인가?

MS에서 개발한 Playwright는 하나의 API로 모든 최신 브라우저(크로미움, 파이어폭스, 웹킷)에서 빠르고 안정적인 자동화를 지원하는 자동화 도구입니다. 이 도구는 다양한 장점을 가지고 있으며, 레거시 Edge나 IE11은 지원하지 않지만 아래와 같은 기능을 제공합니다:

 

1. 다양한 페이지, 도메인, iframe을 포함한 시나리오 지원: Playwright는 여러 페이지나 도메인, iframe을 포함한 복잡한 시나리오를 자동화할 수 있습니다. 이를 통해 다양한 웹 애플리케이션을 효율적으로 테스트하고 제어할 수 있습니다.

2. 엘리먼트 준비 대기: 클릭과 같은 액션을 실행하기 전에 엘리먼트가 준비될 때까지 자동으로 대기할 수 있습니다. 이는 웹 페이지가 동적으로 로드되는 경우 유용하며, 액션을 실행하기 전에 페이지의 상태를 확인할 수 있습니다.

3. 네트워크 활동 가로채기: Playwright는 네트워크 요청을 모킹하거나 가로챌 수 있습니다. 이를 통해 특정 요청에 대한 응답을 조작하거나 가짜 데이터를 주입하여 테스트 환경을 조작할 수 있습니다.

 4. 마우스와 키보드 입력 이벤트: Playwright는 마우스와 키보드의 기본 입력 이벤트를 지원합니다. 이를 통해 테스트 시나리오에서 실제 사용자의 동작을 모방하고 웹 애플리케이션을 제어할 수 있습니다.


Playwright의 설치 방법과 기본적인 사용 방법은 공식 문서를 참고하면 됩니다. 이 글에서는 Playwright의 기본 개념을 알아보고 테스트 러너를 사용하여 테스트를 작성하는 방법을 다룰 예정입니다.

 

브라우저(Browser)

const { chromium } = require('playwright');

const browser = await chromium.launch();

await browser.close();

Playwright는 브라우저(크로미움, 파이어폭스, 웹킷) 인스턴스를 관리하는 도구입니다. Playwright는 브라우저 인스턴스를 시작하고 사용한 후에는 닫는 작업을 수행합니다.

브라우저 인스턴스를 생성하는 것은 비용이 많이 들기 때문에 Playwright는 단일 인스턴스를 통해 여러 브라우저 컨텍스트에서 작업을 수행할 수 있도록 설계되었습니다. 브라우저 컨텍스트는 논리적인 브라우저 창 또는 탭으로 생각할 수 있으며, 각 컨텍스트는 독립적인 실행 환경을 가지고 있습니다. 따라서 여러 개의 컨텍스트를 동시에 실행하면 각각의 컨텍스트에서 독립적으로 웹 페이지를 열고 조작할 수 있습니다.

Playwright의 설계 철학은 가능한 한 단일 브라우저 인스턴스를 재사용하여 비용을 최소화하는 것입니다. 따라서 동일한 브라우저 인스턴스를 여러 브라우저 컨텍스트에서 사용하여 작업을 수행할 수 있으며, 이는 자동화 테스트의 효율성을 높이는 데 도움이 됩니다.

 

컨텍스트(Browser context)

import { devices } from "playwright";

const browser = await chromium.launch();
const context = await browser.newContext();

const iPhone = devices['iPhone 11 Pro'];
const iPad = devices['iPad Pro 11'];

const iPhoneContext = await browser.newContext({
  ...iPhone,
  permissions: ['geolocation'],
  geolocation: { latitude: 52.52, longitude: 13.39 },
  colorScheme: 'dark',
  locale: 'de-DE'
});
const iPadContext = await browser.newContext({
  ...iPad,
  permissions: ['geolocation'],
  geolocation: { latitude: 37.4, longitude: 127.1 },
  // ...
});

브라우저 컨텍스트는 브라우저 인스턴스 내에서 독립적인 세션을 나타냅니다. 이러한 컨텍스트는 브라우저 상태를 분리하고 각각의 테스트를 실행하는 데 사용됩니다. 브라우저 컨텍스트를 생성하는 것은 빠르고 비용이 적게 듭니다.

각 테스트를 새로운 브라우저 컨텍스트에서 실행하는 것이 좋습니다. 이렇게 하면 테스트 간에 브라우저 상태가 분리되어 서로 영향을 주지 않고 독립적으로 실행될 수 있습니다. 예를 들어, 첫 번째 테스트에서 웹 사이트에 로그인하여 세션을 유지하고, 두 번째 테스트에서는 새로운 세션에서 웹 사이트를 방문하여 로그인 상태를 확인할 수 있습니다.

또한, 브라우저 컨텍스트를 사용하여 다른 기기를 시뮬레이션할 수도 있습니다. 예를 들어, 아이폰과 아이패드와 같은 다양한 기기에 대한 브라우저 컨텍스트를 생성하여 각각의 디바이스에서 웹 사이트를 테스트할 수 있습니다. 이를 통해 다양한 디바이스에서의 사용자 경험을 확인하고 반응형 웹 디자인을 검증할 수 있습니다.

 

페이지와 프레임(Pages and frames)

컨텍스트는 컨텍스트 내에서 개별적인 탭 또는 팝업 창을 가지고 있는 페이지를 나타냅니다. 각 페이지는 고유한 URL을 통해 이동하고 페이지의 콘텐츠와 상호작용할 수 있습니다.

페이지를 사용하면 각각의 다른 탭에서 작업하는 것과 유사한 방식으로 동작할 수 있습니다. 예를 들어, 각기 다른 페이지를 탐색하고 각 페이지에서 필요한 동작을 수행할 수 있습니다. 이를 통해 여러 탭 또는 팝업 창을 동시에 조작하고 각각의 페이지에서 원하는 작업을 수행할 수 있습니다.

예를 들어, 한 컨텍스트 내에서 첫 번째 페이지에서 로그인을 수행하고, 두 번째 페이지에서 상품 목록을 탐색하고, 세 번째 페이지에서 결제 프로세스를 실행할 수 있습니다. 각 페이지는 독립적으로 작동하며, 각 페이지에서 필요한 작업을 수행할 수 있습니다. 이를 통해 다양한 페이지 간의 상호작용을 모사하고 테스트 시나리오를 정밀하게 제어할 수 있습니다.

// ...
// 페이지1 생성
const page1 = await context.newPage();

// 브라우저에 URL을 입력하는 것처럼 탐색
await page1.goto('http://example.com');
// 인풋 채우기
await page1.fill('#search', 'query');

// 페이지2 생성
const page2 = await context.newPage();

await page2.goto('https://www.nhn.com');
// 링크를 클릭하여 탐색
await page.click('.nav_locale');
// 새로운 url 출력
console.log(page.url());

페이지는 하나 이상의 프레임 객체를 포함할 수 있습니다. 각 페이지에는 메인 프레임이 있으며, 페이지 레벨의 상호작용(예: 클릭)은 주로 메인 프레임에서 수행됩니다. 또한, iframe 태그를 사용하여 추가적인 프레임을 포함할 수도 있습니다.

프레임을 사용하면 프레임 내부의 엘리먼트를 가져오거나 조작할 수 있습니다. 예를 들어, 특정 프레임의 내부 엘리먼트에 액세스하여 값을 확인하거나 변경할 수 있습니다. 이를 위해 다양한 방법을 사용할 수 있으며, 예제에서는 프레임을 가져오는 방법과 프레임 내부의 엘리먼트와 상호작용하는 방법을 보여줍니다.

예를 들어, 특정 프레임을 식별하고 해당 프레임의 컨텍스트를 가져온 후, 프레임 내부의 엘리먼트를 선택하여 값을 가져오거나 조작할 수 있습니다. 이를 통해 다양한 프레임을 동시에 조작하고 페이지 내의 여러 영역 간에 상호작용할 수 있습니다. 이는 웹 애플리케이션의 다양한 구성 요소 간의 통합 테스트를 수행할 때 유용합니다.

// 프레임의 name 속성으로 프레임 가져오기
const frame = page.frame('frame-login');

// 프레임의 URL로 가져오기
const frame = page.frame({ url: /.*domain.*/ });

// 선택자로 프레임 가져오기
const frameElementHandle = await page.$('.frame-class');
const frame = await frameElementHandle.contentFrame();

// 프레임과 상호작용
await frame.fill('#username-input', 'John');

선택자(Selectors)

Playwright에서는 CSS 선택자, XPath 선택자, id, data-test-id 등을 사용하여 엘리먼트를 검색할 수 있습니다.

가장 일반적으로 사용되는 방법은 CSS 선택자입니다. CSS 선택자는 엘리먼트를 선택하기 위해 요소의 클래스, 태그 이름, 속성 등을 활용합니다. CSS 선택자는 직관적이고 사용하기 쉬우며, 대부분의 경우에 효과적으로 작동합니다.

또한 XPath 선택자를 사용하여 엘리먼트를 검색할 수도 있습니다. XPath는 엘리먼트의 위치, 계층 구조, 속성 등을 사용하여 엘리먼트를 정확하게 식별합니다. XPath는 더 복잡한 선택 기준이 필요한 경우에 유용하게 사용될 수 있습니다.

또한, id나 data-test-id와 같은 HTML 속성을 활용하여 엘리먼트를 찾을 수도 있습니다. 이러한 속성은 특정 엘리먼트를 고유하게 식별하는 데 도움이 됩니다. data-test-id 속성은 테스트 목적으로 사용되며, 엘리먼트를 테스트하는 데 특히 유용합니다.

예제에서는 다양한 선택자를 사용하여 엘리먼트를 검색하는 방법을 보여줄 것입니다. 이를 통해 원하는 엘리먼트를 식별하고 상호작용할 수 있습니다.

// data-test-id 선택자 사용
await page.click('data-test-id=foo');

// CSS, XPath 선택자가 자동으로 탐지된다.
await page.click('div');
await.page.click('//html/body/div');

// 부분 텍스트로 노드 검색
await page.click('text=Hello w');

// 명시적인 CSS, XPath 표기법
await page.click('css=div');
await page.click('xpath=//html/body/div');

// 쉐도우 DOM이 아닌 light DOM만 검색
await page.click('css:light=div');

Playwright에서는 선택자 체이닝을 통해 선택자들을 연결하여 사용할 수 있습니다. 이를 위해 `>>` 구분자를 사용합니다. 선택자로 찾아온 엘리먼트에서 다시 검색하지 않고, `>>` 구분자를 통해 선택자를 묶어서 사용할 수 있는 장점이 있습니다.

선택자 체이닝을 사용하면 한 번에 여러 조건을 결합하여 원하는 엘리먼트를 식별할 수 있습니다. 예를 들어, 특정 클래스를 가진 엘리먼트 중에서 자식 엘리먼트 중 하나를 선택하거나, 특정 속성 값을 가진 엘리먼트의 형제 엘리먼트를 선택할 수 있습니다.

이를 통해 엘리먼트를 좀 더 정확하게 식별하고 필요한 상호작용을 수행할 수 있습니다. 선택자 체이닝은 복잡한 구조의 웹 페이지에서 특정 엘리먼트를 찾는 데 유용하며, 더 간결하고 효율적인 테스트 코드 작성에 도움이 됩니다.

자동 대기(Auto-wating)

Playwright는 엘리먼트가 나타나고 실행 가능해질 때까지 자동으로 대기하는 기능을 제공합니다. 예를 들어, 클릭을 수행하면 다음과 같은 조건에 따라 엘리먼트가 나타날 때까지 자동으로 대기합니다:

1. 주어진 선택자가 DOM에 나타날 때까지 기다립니다.
2. 엘리먼트가 나타날 때까지(예: visibility: hidden이 아닌 경우) 대기합니다.
3. 애니메이션이 완료될 때까지 대기합니다(예: CSS 트랜지션의 완료).

이러한 기능을 통해 엘리먼트를 가져오기 위해 별도의 대기 코드를 작성할 필요가 없습니다. 단순히 엘리먼트를 가져오거나 클릭하는 등의 작업을 수행하면 됩니다. Playwright는 자동으로 대기하며, 엘리먼트가 나타날 때까지 대기한 후 작업을 수행합니다.

이 기능은 Playwright의 강력한 장점 중 하나로, 테스트 코드를 간결하게 유지할 수 있으며, 엘리먼트를 조작하기 위해 별도의 대기 코드를 작성하는 번거로움을 줄여줍니다.

// DOM에 #search 엘리먼트가 나타날 때까지 기다린다.
await page.fill('#search', 'query');

// 에니메이션이 멈출 때까지 기다린 후 클릭한다.
await page.click('#search');


// #search 엘리먼트가 DOM에 나타날 때까지 기다린다.
await page.waitForSelector('#search', { state: 'attached' });

// #promo가 보일 때까지 기다린다. (예를 들어 visibility: visible)
await page.waitForSelector('#promo');


// #details가 보이지 않을 때까지 기다린다. (예를 들어 display: none)
await page.waitForSelector('#details', { state: 'hidden' });

// #promo가 DOM에서 제거 될 때까지 기다린다.
await page.waitForSelector('#promo', { state: 'detached' });

 

반응형

댓글