Playwright으로 E2E 테스트 해보기

dosilv·2025년 1월 23일
0
post-thumbnail

팀에서 E2E 테스트를 위주로 테스트 코드를 조금씩 작성해보기로 했는데, 업무에 치여서 손 놓고 있다가... 이제야 시도해 본 간단한 Playwright 테스트 기록 📝

🧪 playwright 설치 및 세팅

// npm
npm init playwright@latest

// yarn
yarn create playwright

// pnpm
pnpm create playwright

위 명령어로 playwright을 설치하고, playwright.config.ts 파일을 원하는 설정으로 수정한다. 나는 아래처럼 기본적인 세팅만 해줬다.

import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./e2e",
  use: {
    headless: true,
    screenshot: "only-on-failure",
    video: "retain-on-failure",
    trace: "retain-on-failure",
    baseURL: {테스트할 URL},
  },
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
  ],
});
  • headless: ui(브라우저)를 띄우지 않고 백그라운드에서 테스트
    • 나는 default값인 true로 명시해두고, 브라우저를 띄우고 싶을 경우 npx playwright --headed 명령어로 실행함
  • screenshot/video/trace: 스크린샷, 비디오, 액션/이벤트에 관한 로깅 저장 옵션
    • "only-on-failure", "retaion-on-failure"로 지정해 실패했을 경우만 남기고, 불필요한 파일은 삭제하도록 함👀
  • baseURL: 테스트 시작 시 진입점이 될 URL
  • projects: 테스트 환경이 될 브라우저, 디바이스를 지정
    • Chrome, Firefox, Safari, Microsoft Edge 등 대부분의 브라우저뿐만 아니라 아니라 데스크탑, 모바일도 기기별로 세분화되어 있음 (크로스 브라우징에 탁월!)
    • 여러 개를 등록해도 기본적으로 병렬 실행되기 때문에 테스트 시간 단축 가능!


🧪 codegen mode 활용해 테스트 작성하기

브라우저에서 수행하는 유저의 동작(클릭, 입력 등)을 자동으로 테스트 코드로 변환해 주는 기능으로, e2e 테스트 작성에 매우 매우! 유용하다.

아래 명령어를 실행하면 브라우저가 뜨는데, 해당 브라우저에서 테스트 시나리오대로 행동을 재현하면 된다.

npx playwright codegen {테스트할 URL}

예시로 만든 페이지를 위해 다음과 같은 시나리오를 작성해 보았다.

  1. 게시물을 클릭하면 게시물 다이얼로그가 뜬다.
  2. 댓글 추가 버튼을 누르면 댓글 추가 다이얼로그가 뜬다.
  3. 댓글을 작성하고 확인을 누르면 해당 내용으로 댓글이 달린다.
  4. 삭제 버튼을 누르면 댓글이 삭제된다.
  5. 닫기 버튼을 누르면 게시물 다이얼로그가 닫힌다.
  6. 게시물의 태그를 누르면 해당 태그를 가진 게시물들로 필터링된다.

그리고 codegen으로 띄운 브라우저에서 아래처럼 시나리오대로 실행을 한다. 중간중간 컨트롤러의 눈 아이콘👀을 누르고 요소를 선택해주는 건 해당 요소가 화면에 보여야 함을 명시적으로 나타내기 위해서이다.

그럼 이렇게 코드가 자동으로 생성된다! 어메이징...✨

생성된 코드를 복사해서 각 케이스별 설명과 필요한 검증을 추가해 테스트 코드를 완성했다. playwright 사용은 처음이긴 하지만 엘리먼트를 가져오는 문법이 css selector나 html querySelector와 유사해서 어렵지 않게 작성할 수 있었다.

import { test, expect } from "@playwright/test";

test.describe("test example", () => {
  let page;

  test("페이지 진입을 확인한다.", async ({ browser }) => {
    page = await browser.newPage();
    await page.goto("/");
    await expect(page.getByText("test page🧪✨")).toBeVisible();
  });

  test("게시물을 클릭하면 게시물 다이얼로그가 뜬다.", async () => {
    await page.getByText("Dave watched as the forest").click();
    await expect(page.getByRole("dialog", { name: "Dave watched as the forest" })).toBeVisible();
  });

  test("댓글 추가 버튼을 누르면 댓글 추가 다이얼로그가 뜬다.", async () => {
    await page.getByRole("button", { name: "댓글 추가" }).click();
    await expect(page.getByRole("dialog", { name: "새 댓글 추가" })).toBeVisible();
  });

  test("댓글을 작성하고 확인을 누르면 해당 내용으로 댓글이 달린다.", async () => {
    await page.getByRole("textbox", { name: "댓글 내용" }).click();
    await page.getByRole("textbox", { name: "댓글 내용" }).fill("랄랄라~~~");
    await page.getByRole("button", { name: "확인" }).click();
    await expect(page.locator("div").filter({ hasText: /^emilys:랄랄라~~~$/ }).nth(1)).toBeVisible();
  });

  test("삭제 버튼을 누르면 댓글이 삭제된다.", async () => {
    await page.getByRole("button").nth(3).click();
    await expect(page.locator("div").filter({ hasText: /^emilys:랄랄라~~~$/ }).nth(1)).not.toBeVisible();
  });

  test("닫기 버튼을 누르면 게시물 다이얼로그가 닫힌다.", async () => {
    await page.getByRole("button", { name: "닫기" }).click();
    await expect(page.getByRole("dialog", { name: "Dave watched as the forest" })).not.toBeVisible();
  });

  test("게시물의 태그를 누르면 해당 태그를 가진 게시물들로 필터링된다.", async () => {
    await page.getByText("magical").first().click();
    await expect(page.getByRole("combobox").filter({ hasText: "magical" })).toBeVisible();
    expect(page.locator("tr").all.length).toBe(page.getByText("magical").all.length);
  });
});

page를 전역변수로 빼준 이유는 동일한 page context를 공유하면서 흐름대로 테스트를 진행해야 하기 때문이다. 사실상 하나의 test에 묶어서 작성해도 될 것 같지만, 케이스를 명확하게 하기 위해 일일이 나눴다.

그리고 실행 결과 🥳



🧪 크로스 브라우징 테스트

개인적으로 playwright의 킥이라고 느낀 부분!🌟 크로스 브라우징 테스트를 추가적으로 진행해보았다.

export default defineConfig({
  ...
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
    {
      name: "Mobile Safari", // 📱 추가! 
      use: { ...devices["iPhone 12"] },
    },
  ],
});

컨피그의 projects에 모바일 사파리 설정을 추가하고 다시 테스트를 실행시켰다. 그 결과..!

크롬+데스크탑에서는 성공하는 테스트가 모바일에선 와다다 실패하는 걸 볼 수 있다.
디버깅을 위해 아래 명령어로 trace를 확인해보자!

npx exec playwright show-trace {해당 traces.zip 파일명}

보자마자 알 수 있는 실패 이유는... 사이즈가 작은 모바일 화면에서 UI가 와장창 깨졌기 때문~~~ (당연함. 반응형 고려 안함>.<;;)
못생긴 건 둘째고, 게시물 타이틀이 날라가버려서 아예 클릭을 할 수 없었던 것이다.

위처럼 적당히 레이아웃을 고쳐주니 그제야 테스트에 통과했다.(병렬 실행👍🏻) 반응형 UI뿐만 아니라 다른 브라우저에서 발생할 수 있는 문제들도 동시에 E2E 테스트를 할 수 있다는 점에서 실무에서(특히 B2C 서비스라면) 아주 유용할 듯! github Action과도 연동할 수 있으니 더 공부해서 부디 잘... 써먹어 봐야겠다 😗

References

https://playwright.dev/docs/test-configuration
https://ui.toast.com/posts/ko_20210818

profile
DevelOpErUN 성장일기🌈

0개의 댓글

관련 채용 정보