playwright를 이용한 e2e test

오경원·2022년 12월 20일
1

들어가며

많은 전/현직 개발자 분들이라면 운영중인 서비스에 예상하지 못한 이슈/버그가 발견되는 일을 빈번하게 겪어봤을 것이라고 생각합니다. 아무리 개발단에서 철저하게 단위 테스트, 유닛 테스트를 구성하고 실행하여 개발을 한다고 하여도 분명히 요상한 장소, 기능에서 이슈가 터져나옵니다. 이 포스트에서는 이러한 이슈들로 인한 개발에 불편한 경험을 겪으시는 분들을 위해 playwright 라는 프레임워크를 통해 E2E 테스트 자동화를 구성하는 방법에 대해 알아보고자 합니다

E2E 테스트

그렇다면 먼저 E2E 테스트가 무엇인지 부터 생각해볼 필요가 있습니다. E2E test란 뭘까요?

  • E2E testing is a methodology used for ensuring that applications behave as expected and that the flow of data is maintained for all kinds of user tasks and processes
  • E2E testing is a methodology that assesses the working order of a complex product in a start-to-finish process
  • In theory, end-to-end testing (E2E testing) is the process of testing a piece of software from start to finish as it will be used by the actual users

위의 인용문들이 모두 E2E 테스트의 정의입니다. 각각 표현이 약간씩 다르지만 중복되는 말을 모아보면

실제 사용자의 입장에서 소프트웨어가 정상적으로 작동되는지
각각의 기능들을 처음부터 끝까지(end to end) 테스트 하는 것

정도로 E2E 테스트를 간단하게 정리 할 수 있을것 같습니다. 조금 풀어서 설명을 더하자면 개발자의 입장에서 테스트를 하는 것이 아닌 실제 소프트웨어를 사용하는 유저의 입장이 되어서 유저가 소프트웨어를 실행하는 시나리오를 따라 소프트웨어를 테스트 하면서 하나의 기능을 전체적으로 테스트 하는것이 E2E 테스트라고 할수 있을것 같습니다.

playwright

앞에서 E2E 테스트가 무엇인지에 대해 알아보았으니 이제 이 테스트를 자동화 시키기 위한 툴을 알아볼까 합니다. 이번 포스팅에서는 playwright라는 프레임워크를 사용해 테스트 자동화를 구현해 보겠습니다. 먼저 playwright의 공식문서를 보게되면 playwright에 관한 설명과 사용할수 있는 API들이 정리되어있습니다. playwright 프레임워크는 파이썬, 자바스크립트, 타입스크립트등 여러가지 언어로 사용할 수 있는데 저는 자바스크립트를 이용해서 코드를 작성하도록 하겠습니다.

const { test, expect } = require('@playwright/test');

playwright로 테스트를 작성하실 때에는 위의 코드를 항상 추가해서 코드를 작성하여 주세요!

로그인 테스트 작성

웹 서비스에서 이슈 혹은 버그가 발생하였을때 굉장히 큰 비지니스적 임팩트를 가져올 수 있는 로그인 쪽 테스트를 작성해 보도록 하겠습니다.

 //logintest.spec.js
 test(`id/pw login`, async ({page}) => { 
      await page.goto("테스트 사이트 로그인 페이지 url"); //로그인 페이지로 이동
      await page.locator('div.join-btn.cursor').click(); //로그인 버튼 클릭
      await page.getByPlaceholder("아이디").fill("user_id");
      await page.getByPlaceholder("비밀번호").fill("user_pw"); //placeholder를 이용해 입력칸에 정보 삽입
      await page.locator('button.login-btn').click(); //로그인 버튼 클릭(로그인 api 호출)
      await page.waitForResponse("api_response_url"); //로그인 api 응답 대기
      
      const context = page.context();
      const state = await context.storageState();
      const token_value = state.origins[0].localStorage.some((elem) => elem.name === 'token'); 
      await expect(token_value).toBe(true);
      console.log("login success");
      //console.log(token_value);
      page.close();
    })

로그인 테스트 코드를 작성하였습니다. 코드를 크게 두덩이로 나누어 위쪽 덩이는 실질적인 유저의 동작 즉 로그인을 하는 동작을 page라는 클래스의 여러 매소드들을 이용해 구현하였고 아래쪽 덩이에서는 사용자가 로그인을 시도하였을때 발급되는 토큰이 정상적으로 발급되는지와 발급된 토큰을 이용해 로그인 테스트의 성공 여부를 판단하는 코드를 작성하였습니다.

위와 같이 코드를 작성한 뒤에는 터미널에 아래와 같은 명령어를 입력하면 테스트가 실행되게 됩니다.

npx playwright test logintest.spec.js

그외의 테스트 코드

아래에 첨부하는 테스트 코드들은 실제 한 커뮤니티 회사에서 제가 작성하였던 테스트 코드들입니다. 실무에서 작성된 코드이기 때문에 계정정보, url 정보는 모두 제외하였습니다.

hot게시글 리스트 체크

//list_check.spec.js
test.describe("리스트 체크", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("url");
    await page.locator("div.join-btn.cursor").click();
    await page.getByPlaceholder("아이디").fill("user_id");
    await page.getByPlaceholder("비밀번호").fill("user_pw");
    await page.locator("button.login-btn").click();
    await page.waitForResponse("api_response_url");
  });
  test("메인, 게시판 페이지 HOT글 리스트 일치 테스트", async ({ page }) => {
    await page.waitForSelector("a > div.txt");
    const main_element = await page.$$("a > div.txt");
    let mainHotArr = [];
    for (const arrNum in main_element) {
      const elem = await main_element[arrNum].innerHTML();
      mainHotArr.push(elem);
    }
    await page.goto("page_url");
    const selector = "array_selector";
    await page.waitForSelector(selector);
    const postElement = await page.$$(selector);
    let postHotArr = [];
    for (const arrNum in postElement) {
      const elem = await postElement[arrNum].innerHTML();
      postHotArr.push(elem);
    }
    await expect(mainHotArr.toString() === postHotArr.toString()).toBe(true);
  });

한 커뮤니티 사이트의 hot게시글 리스트가 각각의 페이지에서 동일하게 보이는지에 대한 테스트 코드입니다. 위에서 보여드린 로그인 테스트 코드에서는 로그인 테스트 하나만을 테스트하기 때문에 test 함수만 이용했지만 이번 hot게시글 리스트 체크 코드에서는 추후 다른 리스트들의 테스트 코드를 추가하기 위해 test.describe 메소드로 리스트 체크라는 큰 테스트 묶음을 만들었고 각 테스트 진행 전에 이루어져야할 로그인 작업을 test.beforeEach 메소드를 이용해 작성하였습니다.

이 코드를 작성하면서 동기와 비동기 방식의 개념을 다시 한번 정리하고 학습하는 계기가 되었습니다. async와 await가 다음 코드를 실행하는 시점에 대해 고민한 결과 로딩이 길어지는 부분에 waitfor~~~ 메소드를 사용하였고 배열을 받아오는 코드를 작성하는 과정에서 foreach 문은 비동기함수(async)를 기다리지 않는다 라는 것을 깨닫고 for in 문으로 배열위치를 받아와 postHotArr에 해당 배열 위치에 있는 값을 push 해주는 코드를 작성하였습니다.

공유, 스크랩 버튼

//share_scrap_test.spec.js
test.describe("share, scrap test", () => {
    test.beforeEach(async ({ page }) => { 
        await page.goto("loginpage_url");
        await page.locator("div.join-btn.cursor").click();
        await page.getByPlaceholder("아이디").fill("user_id");
        await page.getByPlaceholder("비밀번호").fill("user_pw");
        await page.locator("button.login-btn").click();
        await page.waitForResponse("api_response_url");
    })
    test("공유", async ({ page }) => { 
        await page.goto("page_url");
        await page.locator("button selector").click();
        await page.locator('double check button selector').click();
        await page.keyboard.press("Enter");
        
        const context = page.context();
        await context.grantPermissions(['clipboard-read']); //권한 요청
        let clip = await page.evaluate("navigator.clipboard.readText()");
        await expect(page.url() === clip).toBe(true);
        await page.close();
    })
    test("스크랩", async ({ page }) => {
        await page.goto("page_url");
        await page.waitForSelector("title selector");
        const title = await page.locator("title seletor").innerHTML();
        await page.locator("button selector").click();
        await page.locator("double check button selector").click();
        await page.goto("scrap board page url");
        await page.waitForSelector("title selector");
        const myTitle = await page.locator("title selector").innerHTML();
        
        await expect(myTitle === title).toBe(true);

        await page.locator("button selector").click();
        await page.locator("double check button selector"").click();
        await page.close();
    })
})

같은 커뮤니티 사이트에서 게시물을 공유, 스크랩할때의 테스트 코드입니다. 이번 역시 test.describe를 통해 테스트를 묶고 test.beforeEach 메소드를 통해 각 테스트 전 로그인을 했습니다.

이번 코드에서 중요하게 볼 점은 권한 관련된 내용입니다. 게시물 공유 버튼을 클릭하면 클립보드에 해당 게시물의 url가 복사되게 되는데 이 복사된 url과 게시물의 url을 비교하기 위해 브라우저에 권한을 요청하는 코드를 작성하였습니다. 이때 문제가 발생하는데 위의 코드로는 playwright에서 기본적으로 사용하는 테스트 브라우저 3개중 chromium에서만 권한을 받을수 있습니다. webkit과 firefox에서는 권한을 받아오지 못합니다. 그렇기 때문에 playwright로 브라우저의 권한을 받아와 특정 테스트를 실행하실 때에는 아래와 같이 꼭 프로젝트를 chromium으로 설정하여 실행하시기 바랍니다.

npx playwright test --project=chromium share_scrap_test.spec.js

0개의 댓글