Next.js 15 + Playwright 2편 – API 테스트 맛보기

2024. 11. 25. 16:33·FrontEnd/Testing

1편에서 E2E로 다이얼로그 여닫기 같은 UI 동작을 눌러 봤다면,
2편은 "페이지를 타고 들어가서 API까지 같이 확인해 보기" 쪽에 더 가깝다.

 

Next.js 15 프로젝트를 만들고 Playwright를 붙이다가,
"특정 페이지에서 API 요청이 진짜 잘 나가는지"까지 같이 보고 싶어졌다.

Playwright 공식 문서를 보다가 API 테스트 기능이 있는 걸 발견했고,
그 과정을 기록해 둔 글이다.

  • Playwright API test 개념 확인
  • baseURL 설정해서 경로만으로 요청 보내기
  • 목록 API, 상세 API 테스트 코드 예시
  • 테스트하면서 헷갈린 포인트 간단 메모

참고한 문서들은 이쪽이다.

  • Playwright API Testing
    • https://playwright.dev/docs/api-testing

1. baseURL 설정으로 경로만 써서 이동하기

처음에는 페이지 이동이나 API 요청에 항상 전체 URL을 적고 있었다.

Playwright 문서를 보다 보니 playwright.config.ts의 use.baseURL을 설정해 두면

  • page.goto("/")
  • request.get("/api/shop")

처럼 경로만 써도 동작하게 만들 수 있다는 걸 뒤늦게 알았다.

설정은 대략 이런 느낌으로 잡았다.

// playwright.config.ts 일부
use: {
  /* Base URL to use in actions like `await page.goto('/')`. */
  baseURL: "http://localhost:3000",

  /* 필요한 경우 trace 설정 등도 여기에서 같이 관리 */
  trace: "on-first-retry",
},

이렇게 해 두면 테스트 코드에서는

  • await page.goto("/")
  • await request.get("/api/shop")

처럼 훨씬 짧게 쓸 수 있어서 편했다.


2. 목록 API 테스트 – 기본 스키마 확인

먼저 상점 목록을 가져오는 API부터 테스트했다.

목표는 크게 네 가지였다.

  • 요청이 200번대 상태 코드로 성공하는지
  • 응답이 배열 형태인지
  • 배열 길이가 0이 아닌지
  • 각 요소가 예상하는 스키마를 가지고 있는지

코드는 이렇게 작성했다.

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

test("상점 목록에서 데이터 확인", async ({ request }) => {
  // API 요청 보내기
  const getShopData = await request.get("/api/shop");

  // 응답 상태 확인 (ok()가 true면 200번대 상태로 간주)
  expect(getShopData.ok()).toBeTruthy();

  // JSON 데이터 변환
  const responseData = await getShopData.json();

  // 데이터가 배열인지 확인 (findMany 형태라 배열 예상)
  expect(Array.isArray(responseData)).toBe(true);

  // 데이터가 비어 있지 않은지 확인
  expect(responseData.length).toBeGreaterThan(0);

  // 첫 번째 요소의 스키마 검증
  expect(responseData[0]).toEqual(
    expect.objectContaining({
      id: expect.any(String),
      title: expect.any(String),
      star: expect.any(Number),
      description: expect.any(String),
      deliveryTime: expect.any(String),
      deliveryFee: expect.any(String),
      minimumOrder: expect.any(String),
      cardImage: expect.any(String),
      coverImage: expect.any(String),
      tag: expect.any(String),
      create_at: expect.any(String),
      update_at: expect.any(String),
    }),
  );
});

여기서 toBeTruthy()를 쓴 이유는

  • ok()가 200대 상태 코드를 모두 true로 보기 때문에
  • 굳이 toBe(true)까지는 강하게 묶지 않아도 됐기 때문이다.

엄밀하게 true인지 보고 싶다면 toBe(true)로 바꿀 수 있고,
실무에서는 팀 코드 스타일에 맞추면 될 것 같다.


3. 페이지 이동 + 상세 API 테스트 – UUID까지 같이 확인

두 번째로는 상점 목록 페이지에서 마지막 상점 상세 페이지로 이동한 다음,
그 상점의 상세 API와 메뉴 API를 같이 검증하는 흐름을 만들었다.

테스트 흐름은 이렇게 잡았다.

  • 홈(/)에서 상점 리스트 페이지(/shop)로 이동
  • 마지막 상점 카드의 링크를 찾아서 href를 읽는다
  • URL에서 마지막 조각을 잘라서 shopId로 사용한다
  • shopId가 UUID 형식인지 정규식으로 검사한다
  • 페이지를 실제로 클릭해서 /shop/{shopId}로 이동하는지 확인한다
  • 같은 shopId로 두 가지 API를 병렬로 호출한다
    • /api/shop?id={shopId}
    • /api/shop/{shopId}
  • 두 응답 모두 ok인지 확인하고, 각각의 스키마를 검증한다

코드는 다음과 같다.

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

test("상점 상세 이동 및 데이터 확인 - 마지막 상점 (uuid)", async ({
  page,
  request,
}) => {
  await page.goto("/");

  // /shop 페이지로 이동하는 링크 클릭
  await page.click('a[href*="shop"]');
  await expect(page).toHaveURL("/shop");

  // 마지막 상점의 링크 선택
  const lastShopLink = page.locator('a[href*="/shop/"]').last();
  const shopUrl = await lastShopLink.getAttribute("href");

  if (!shopUrl) {
    throw new Error("Shop URL is not available");
  }

  // URL에서 shopId 추출
  const shopId = shopUrl.split("/").pop();

  // UUID 정규식 검사 (검색해서 가져와 사용했다)
  const uuidRegex =
    /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;

  if (!uuidRegex.test(shopId!)) {
    throw new Error(`유효하지 않은 포맷: ${shopId}`);
  }

  // 상점 디테일 페이지로 이동 후 URL 확인
  await lastShopLink.click();
  await expect(page).toHaveURL(`/shop/${shopId}`);

  // 상점과 메뉴 정보를 동시에 요청
  const [shopData, menuData] = await Promise.all([
    request.get(`/api/shop?id=${shopId}`),
    request.get(`/api/shop/${shopId}`),
  ]);

  // 응답 상태 확인
  expect(shopData.ok()).toBeTruthy();
  expect(menuData.ok()).toBeTruthy();

  // JSON 변환
  const shop = await shopData.json();
  const menu = await menuData.json();

  // shop 스키마 검증
  expect(shop).toEqual(
    expect.objectContaining({
      id: expect.any(String),
      title: expect.any(String),
      star: expect.any(Number),
      description: expect.any(String),
      deliveryTime: expect.any(String),
      deliveryFee: expect.any(String),
      minimumOrder: expect.any(String),
      cardImage: expect.any(String),
      coverImage: expect.any(String),
      tag: expect.any(String),
      create_at: expect.any(String),
      update_at: expect.any(String),
    }),
  );

  // menu 스키마 검증
  expect(menu).toEqual(
    expect.arrayContaining([
      expect.objectContaining({
        id: expect.any(String),
        category: expect.any(String),
        menu: expect.any(String),
        price: expect.any(Number),
        shopId: expect.any(String),
      }),
    ]),
  );

  console.log("Shop:", shop);
  console.log("Menu:", menu);
});

중간에 UUID 정규식을 가져와서 검사하는 부분은,
"잘못된 URL 구조로 테스트를 통과시키지 말자"는 정도의 안전장치 느낌으로 넣었다.


4. 이번에 새로 알게 된 메서드들 메모

이번 API 테스트를 만들면서, Playwright 쪽에서 특히 인상 깊었던 메서드들을 정리해 둔다.

  • request.get()
    • Playwright의 APIRequestContext로 HTTP 요청을 보내는 메서드
  • response.ok()
    • 응답 상태 코드가 200대인지 확인하는 헬퍼
  • expect()
    • Jest 스타일로 값을 검증하는 어서션 엔트리
  • toBeTruthy()
    • 값이 truthy인지 확인 (ok()와 같이 쓰기 편했다)
  • toBe(true)
    • 엄밀하게 true인지 확인
  • toBeGreaterThan(n)
    • 값이 n보다 큰지 확인 (배열 길이 검증에 사용)
  • toEqual()
    • 값이 예상한 값과 동일한지 비교
  • expect.objectContaining()
    • 객체가 특정 속성들을 포함하고 있는지 부분 비교
  • expect.arrayContaining()
    • 배열이 특정 요소를 포함하고 있는지 비교
  • expect.any(Type)
    • 값의 타입을 느슨하게 검증할 때 사용

API 스키마가 자주 바뀌는 환경에서는
objectContaining, arrayContaining, any 조합이 꽤 유연하게 느껴졌다.


5. 작게 남겨두는 회고

1편에서 UI를 눌러 보다가, 2편에서는 그 아래 API까지 같이 확인하는 흐름을 만들어 봤다.

이번에 느낀 점을 정리하면 대략 이렇다.

  • UI 테스트만으로는 놓치는 부분을 API 테스트가 꽤 잘 메워 준다
  • baseURL을 설정해 두니 코드가 훨씬 읽기 좋아졌다
  • request 픽스처와 expect.objectContaining 조합이 생각보다 강력했다

다음에 비슷한 구조의 프로젝트에서 API를 검증해야 할 일이 생기면,
이 글의 코드 조각들을 그대로 가져다가 살짝만 고쳐 써도 될 것 같다.

저작자표시 변경금지 (새창열림)

'FrontEnd > Testing' 카테고리의 다른 글

Next.js 15 + Playwright 도입 – E2E 테스트랑 조금 친해지기  (0) 2024.11.24
'FrontEnd/Testing' 카테고리의 다른 글
  • Next.js 15 + Playwright 도입 – E2E 테스트랑 조금 친해지기
프론트엔드 개발자 jbeat
프론트엔드 개발자 jbeat
프론트엔드 개발자 블로그인데 일상도 쪼그으믐
  • 프론트엔드 개발자 jbeat
    jbeat 님의 블로그
    프론트엔드 개발자 jbeat
  • 전체
    오늘
    어제
    • 분류 전체보기 (44)
      • FrontEnd (43)
        • TypeScript (6)
        • JavaScript (18)
        • Next.js (3)
        • React (1)
        • Testing (2)
        • Third Party (1)
        • web (10)
        • Tooling (1)
        • coding test (0)
        • A.I (1)
      • 일상 (1)
        • wedding (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 인기 글

  • 태그

    배열
    Next.js
    주니어
    고차함수
    playwright
    WebSocket
    preconnect
    CRDT
    TypeScript
    Utility
    omit
    pick
    CrossOrigin
    javascript
    이터러블
    yjs
    컬렉션
    Android
    타입스크립트
    코테
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
프론트엔드 개발자 jbeat
Next.js 15 + Playwright 2편 – API 테스트 맛보기
상단으로

티스토리툴바