Playwright로 회원가입 과정을 자동 테스트하는 방법

회원가입 기능은 왜 자동 테스트해야 할까?

바이브 코딩으로 만든 웹사이트에서 회원가입 화면이 정상적으로 표시된다고 해서 가입 기능까지 올바르게 작동하는 것은 아니다.

입력한 이메일이 서버로 전달되지 않거나, 비밀번호 확인 값이 검사되지 않거나, 가입 버튼을 여러 번 눌렀을 때 계정이 중복 생성될 수 있다. 회원 데이터는 생성됐지만 화면이 계속 로딩 상태에 머무는 문제도 발생할 수 있다.

이런 오류는 코드를 읽는 것만으로 발견하기 어렵다. 브라우저에서 실제 사용자가 하는 것처럼 회원가입 페이지에 접속하고, 입력란을 채우고, 가입 버튼을 누른 다음 결과를 확인해야 한다.

Playwright는 이러한 브라우저 동작을 자동화할 수 있는 E2E 테스트 도구다. Chromium, Firefox, WebKit을 지원하며 테스트 실행기, assertion, 테스트 격리, 병렬 실행과 디버깅 도구를 함께 제공한다. Playwright 설치 공식 문서

단위 테스트와 회원가입 E2E 테스트의 차이

단위 테스트는 이메일 형식 검사나 비밀번호 강도 계산처럼 작은 함수 하나를 검증한다.

expect(isValidEmail("user@example.com")).toBe(true);

Playwright 회원가입 테스트는 실제 브라우저에서 여러 단계를 연결해 확인한다.

회원가입 페이지 접속
→ 이름과 이메일 입력
→ 비밀번호 입력
→ 약관 동의
→ 가입 버튼 클릭
→ 가입 성공 화면 확인

단위 테스트는 빠르고 오류 원인을 찾기 쉽지만 브라우저와 서버가 제대로 연결됐는지는 알 수 없다. E2E 테스트는 실제 사용자 흐름을 확인할 수 있지만 실행 속도가 더 느리고 테스트 데이터 관리가 필요하다.

두 테스트는 서로 대체하는 관계가 아니다. 이메일 검증과 비밀번호 조건은 단위 테스트로 확인하고, 실제 가입 과정은 Playwright로 확인하는 방식이 좋다.

운영 사이트에서 테스트하면 안 되는 이유

회원가입 자동 테스트는 실행할 때마다 새로운 계정을 생성할 수 있다. 운영 사이트에서 반복 실행하면 다음과 같은 문제가 생길 수 있다.

  • 가짜 회원 데이터가 계속 쌓인다.
  • 실제 가입자 통계가 부정확해진다.
  • 가입 확인 이메일이나 문자 비용이 발생한다.
  • 보안 시스템이 비정상 가입으로 판단할 수 있다.
  • 테스트 계정에 운영 권한이 잘못 부여될 수 있다.
  • 외부 인증 서비스의 사용량이 증가할 수 있다.

따라서 로컬 개발 환경이나 별도로 분리된 테스트 환경에서 실행해야 한다. 데이터베이스와 인증 서비스도 가능하면 운영 환경과 분리한다.

환경 주소는 소스 코드에 직접 고정하지 않고 설정 파일이나 환경변수로 관리하는 것이 안전하다.

Playwright 설치하기

기존 프로젝트에 Playwright를 추가하려면 프로젝트 루트에서 다음 명령을 실행한다.

npm init playwright@latest

설치 과정에서는 다음 항목을 선택하거나 확인한다.

  • TypeScript 또는 JavaScript
  • 테스트 파일을 저장할 폴더
  • 브라우저 설치 여부
  • CI 설정 파일 생성 여부

설치가 끝나면 일반적으로 다음 파일이 생성된다.

playwright.config.ts
tests/
  example.spec.ts

기존 테스트 파일은 설치 과정에서 덮어쓰지 않지만, 프로젝트에 이미 Playwright 설정이 있다면 새로 초기화하지 말고 현재 설정을 먼저 확인해야 한다.

설치된 버전은 다음 명령으로 확인할 수 있다.

npx playwright --version

회원가입 테스트를 위한 기본 설정

playwright.config.ts에는 테스트 폴더, 실행할 브라우저, 기본 URL과 실패 추적 방식을 설정할 수 있다.

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

export default defineConfig({
  testDir: "./tests",
  fullyParallel: true,
  forbidOnly: Boolean(process.env.CI),
  retries: process.env.CI ? 2 : 0,
  reporter: "html",

  use: {
    baseURL:
      process.env.E2E_BASE_URL ??
      "http://127.0.0.1:3000",
    trace: "on-first-retry",
    screenshot: "only-on-failure",
  },

  projects: [
    {
      name: "chromium",
      use: {
        ...devices["Desktop Chrome"],
      },
    },
  ],

  webServer: {
    command: "npm run dev",
    url: "http://127.0.0.1:3000",
    reuseExistingServer: !process.env.CI,
  },
});

baseURL을 설정하면 테스트에서 전체 주소 대신 /signup처럼 상대 경로를 사용할 수 있다.

webServer는 테스트 실행 전에 개발 서버를 시작하고 지정된 주소가 준비될 때까지 기다리는 설정이다. 프로젝트에 따라 실행 명령이 npm run startnpm run preview일 수 있으므로 package.json을 확인해야 한다.

trace: "on-first-retry"는 실패한 테스트를 재시도할 때 브라우저 동작을 기록한다. 기본 URL과 trace를 포함한 주요 옵션은 Playwright 설정 공식 문서에서 확인할 수 있다.

테스트마다 고유한 이메일 만들기

회원가입 테스트에서 같은 이메일을 반복 사용하면 두 번째 실행부터 중복 이메일 오류가 발생한다. 테스트마다 새로운 이메일을 만들어야 한다.

Node.js의 randomUUID를 이용하면 충돌 가능성이 낮은 값을 만들 수 있다.

import { randomUUID } from "node:crypto";

const email = `e2e-${randomUUID()}@test.example.com`;

실제 프로젝트에서는 자신이 관리하는 테스트용 이메일 도메인을 사용해야 한다. 이메일 인증이 필요 없는 환경이라면 외부로 메일이 전달되지 않는 테스트 전용 주소를 사용할 수 있다.

이메일 인증이 필요한 서비스에서는 일반 사용자의 실제 이메일을 사용하지 말고 테스트 메일함이나 인증 서비스의 테스트 기능을 사용해야 한다.

첫 회원가입 성공 테스트 작성하기

다음 경로에 테스트 파일을 만든다.

tests/signup.spec.ts

기본적인 회원가입 테스트는 다음과 같이 작성할 수 있다.

import { randomUUID } from "node:crypto";
import { expect, test } from "@playwright/test";

test("새로운 사용자가 회원가입을 완료한다", async ({
  page,
}) => {
  const email =
    `e2e-${randomUUID()}@test.example.com`;
  const password = "E2e-Test-Password-123!";

  await page.goto("/signup");

  await expect(
    page.getByRole("heading", {
      name: "회원가입",
    }),
  ).toBeVisible();

  await page.getByLabel("이름").fill("테스트 사용자");
  await page.getByLabel("이메일").fill(email);

  await page
    .getByLabel("비밀번호", { exact: true })
    .fill(password);

  await page
    .getByLabel("비밀번호 확인")
    .fill(password);

  await page
    .getByRole("checkbox", {
      name: /이용약관/,
    })
    .check();

  await page
    .getByRole("button", {
      name: "회원가입",
    })
    .click();

  await expect(page).toHaveURL(
    /\/welcome|\/dashboard/,
  );

  await expect(
    page.getByRole("heading", {
      name: /가입 완료|환영/,
    }),
  ).toBeVisible();
});

예시의 입력란 이름, 버튼 문구, 이동 주소는 실제 프로젝트에 맞게 수정해야 한다.

CSS 선택자보다 사용자에게 보이는 이름을 사용하자

AI가 생성한 테스트에는 다음과 같이 긴 CSS 선택자가 포함될 수 있다.

await page
  .locator(
    "#root > div:nth-child(2) > form > button",
  )
  .click();

이 방식은 화면 구조가 조금만 변경돼도 테스트가 깨질 가능성이 높다.

Playwright는 사용자가 인식하는 역할과 이름을 기준으로 요소를 찾는 방법을 권장한다.

await page.getByLabel("이메일").fill(email);

await page
  .getByRole("button", {
    name: "회원가입",
  })
  .click();

폼 입력란은 getByLabel, 버튼과 체크박스는 getByRole, 일반적인 안내 문구는 getByText를 우선적으로 사용할 수 있다.

이 방식이 동작하지 않는다면 입력란에 연결된 label, 버튼의 접근 가능한 이름과 HTML 구조가 올바른지도 확인해야 한다. Playwright는 사용자 관점의 속성과 명시적인 테스트 계약을 우선하는 locator 사용을 권장한다. Playwright locator 공식 문서

가입 버튼을 눌렀다는 사실만 검사하면 안 된다

다음 테스트는 동작을 실행할 뿐 결과를 검증하지 않는다.

await page
  .getByRole("button", {
    name: "회원가입",
  })
  .click();

버튼이 클릭됐더라도 서버 요청이 실패하거나 계정이 생성되지 않았을 수 있다. 회원가입 테스트에서는 최소한 다음 중 두 가지 이상을 확인하는 것이 좋다.

  • 가입 완료 페이지로 이동했는가?
  • 성공 안내 문구가 표시됐는가?
  • 로그인된 사용자 메뉴가 표시됐는가?
  • 가입 직후 로그인 상태가 생성됐는가?
  • 테스트 환경의 사용자 조회 API에서 계정을 찾을 수 있는가?
  • 이메일 인증이 필요한 경우 인증 안내 화면이 표시됐는가?

화면에 나타나는 결과는 Playwright의 web-first assertion으로 확인할 수 있다.

await expect(
  page.getByText("가입이 완료되었습니다."),
).toBeVisible();

Playwright의 locator assertion은 조건이 충족될 때까지 자동으로 다시 확인한다. 비동기 화면을 검사하기 위해 임의의 대기 시간을 추가하는 것보다 안정적이다. Playwright assertion 공식 문서

임의의 시간 대기를 사용하지 말자

AI가 작성한 테스트에서 다음 코드를 자주 볼 수 있다.

await page.waitForTimeout(3000);

이 방식은 서버 응답이 3초보다 느리면 실패하고, 응답이 빠르면 불필요하게 기다리게 된다.

특정 시간만큼 기다리는 대신 실제로 필요한 상태를 검사해야 한다.

await expect(
  page.getByText("가입이 완료되었습니다."),
).toBeVisible();

주소 변경을 기다릴 수도 있다.

await expect(page).toHaveURL("/dashboard");

버튼이 활성화되는 것을 기다려야 한다면 다음처럼 작성한다.

const signupButton = page.getByRole("button", {
  name: "회원가입",
});

await expect(signupButton).toBeEnabled();
await signupButton.click();

약한 비밀번호 검증 테스트 추가하기

성공 테스트만으로는 회원가입 폼의 입력값 검증을 확인할 수 없다.

test("약한 비밀번호는 회원가입할 수 없다", async ({
  page,
}) => {
  await page.goto("/signup");

  await page
    .getByLabel("이메일")
    .fill("weak-password@test.example.com");

  await page
    .getByLabel("비밀번호", { exact: true })
    .fill("1234");

  await page
    .getByLabel("비밀번호 확인")
    .fill("1234");

  await page
    .getByRole("button", {
      name: "회원가입",
    })
    .click();

  await expect(
    page.getByText(
      /비밀번호는.*이상|안전한 비밀번호/,
    ),
  ).toBeVisible();

  await expect(page).toHaveURL(/\/signup/);
});

이 테스트에서는 오류 문구가 표시되는지와 회원가입 페이지에 그대로 남아 있는지를 함께 확인한다.

중요한 점은 화면의 검사만 믿지 않는 것이다. 브라우저 검사는 우회될 수 있으므로 서버에서도 동일한 비밀번호 정책과 입력값 검증을 수행해야 한다.

비밀번호 불일치 테스트

회원가입 폼에는 비밀번호와 비밀번호 확인 값이 다를 때 가입을 막는 기능이 필요하다.

test("비밀번호 확인 값이 다르면 오류가 표시된다", async ({
  page,
}) => {
  await page.goto("/signup");

  await page
    .getByLabel("비밀번호", { exact: true })
    .fill("E2e-Test-Password-123!");

  await page
    .getByLabel("비밀번호 확인")
    .fill("Different-Password-456!");

  await page
    .getByRole("button", {
      name: "회원가입",
    })
    .click();

  await expect(
    page.getByText(/비밀번호가 일치하지 않습니다/),
  ).toBeVisible();
});

테스트가 통과하지 않는다면 오류 문구가 실제 화면과 다른 것인지, 비밀번호 확인 로직이 없는 것인지 구분해서 확인해야 한다.

중복 이메일 테스트

중복 이메일 테스트에는 이미 가입된 계정이 필요하다. 테스트끼리 실행 순서에 의존하게 만들면 안 되므로 첫 번째 테스트에서 만든 계정을 두 번째 테스트가 사용하도록 구성하는 것은 피해야 한다.

안전한 방법은 각 테스트가 시작되기 전에 테스트 전용 API나 데이터 준비 스크립트로 계정을 생성하는 것이다.

test("이미 가입된 이메일은 다시 사용할 수 없다", async ({
  page,
  request,
}) => {
  const email =
    `existing-${randomUUID()}@test.example.com`;

  await request.post("/test-support/users", {
    data: {
      email,
      password: "E2e-Test-Password-123!",
    },
  });

  await page.goto("/signup");
  await page.getByLabel("이메일").fill(email);

  await page
    .getByLabel("비밀번호", { exact: true })
    .fill("E2e-Test-Password-123!");

  await page
    .getByLabel("비밀번호 확인")
    .fill("E2e-Test-Password-123!");

  await page
    .getByRole("button", {
      name: "회원가입",
    })
    .click();

  await expect(
    page.getByText(/이미 사용 중인 이메일/),
  ).toBeVisible();
});

여기에서 /test-support/users는 예시다. 이런 데이터 준비 API는 테스트 환경에서만 활성화해야 하며 운영 서비스에 공개하면 안 된다.

테스트 데이터 정리 방법

회원가입 테스트를 반복 실행하면 테스트 데이터베이스에 계정이 계속 쌓일 수 있다. 다음 중 프로젝트에 맞는 정리 방식을 선택한다.

  • 테스트 실행이 끝날 때 생성한 계정을 삭제한다.
  • 테스트마다 초기화되는 별도 데이터베이스를 사용한다.
  • 테스트 사용자에게 e2e- 같은 식별자를 붙여 주기적으로 삭제한다.
  • 테스트 환경 전체를 배포할 때마다 재생성한다.
  • 인증 서비스가 제공하는 테스트 프로젝트를 별도로 사용한다.

삭제 로직은 일반 사용자에게 제공되는 화면보다 관리자 SDK나 테스트 전용 내부 API를 사용하는 편이 안정적이다. 단, 삭제 권한이나 관리자 키를 브라우저 코드에 포함해서는 안 된다.

테스트 실행과 디버깅 명령어

전체 테스트 실행:

npx playwright test

회원가입 테스트 파일만 실행:

npx playwright test tests/signup.spec.ts

Chromium에서만 실행:

npx playwright test tests/signup.spec.ts \
  --project=chromium

실제 브라우저 화면을 보면서 실행:

npx playwright test tests/signup.spec.ts \
  --headed

UI 모드로 단계별 확인:

npx playwright test --ui

HTML 결과 보고서 열기:

npx playwright show-report

Playwright는 기본적으로 headless 모드에서 테스트를 실행하며 --headed--ui 옵션으로 실행 장면을 확인할 수 있다. Playwright 테스트 실행 공식 문서

실패한 테스트를 Trace Viewer로 확인하기

회원가입 테스트가 CI에서만 실패한다면 trace를 확인하는 것이 좋다. Trace Viewer에서는 각 단계의 화면, locator, 네트워크 요청과 실행 흐름을 확인할 수 있다.

로컬에서 trace를 강제로 기록하려면 다음 명령을 사용할 수 있다.

npx playwright test tests/signup.spec.ts \
  --trace on

테스트 실행 후 HTML 보고서를 열면 실패한 단계와 trace를 확인할 수 있다.

npx playwright show-report

Trace Viewer는 테스트의 각 동작을 앞뒤로 이동하며 당시 화면과 실행 상태를 확인할 수 있는 도구다. Playwright Trace Viewer 공식 문서

AI에게 회원가입 테스트를 요청하는 프롬프트

현재 프로젝트의 회원가입 과정을 Playwright로 테스트해 줘.

조건:
1. 운영 환경이 아니라 로컬 테스트 환경만 사용해 줘.
2. 현재 회원가입 화면의 label, 버튼 이름과 URL을 먼저 확인해 줘.
3. CSS와 XPath보다 getByRole과 getByLabel을 우선해 줘.
4. 테스트마다 고유한 이메일을 생성해 줘.
5. 가입 성공 후 URL과 성공 화면을 모두 검증해 줘.
6. waitForTimeout은 사용하지 마.
7. 약한 비밀번호와 비밀번호 불일치 테스트도 작성해 줘.
8. 테스트 데이터 정리 방법을 별도로 설명해 줘.
9. 운영 코드와 인증 설정은 수정하지 마.
10. 변경할 파일 목록을 먼저 보여 줘.

AI가 생성한 테스트는 실제 화면의 문구, 접근 가능한 이름과 가입 정책을 기준으로 검토해야 한다.

회원가입 자동 테스트 최종 체크리스트

  • 운영 환경이 아닌 테스트 환경에서 실행하는가?
  • 테스트마다 고유한 이메일을 사용하는가?
  • 비밀번호 같은 민감한 값을 코드 저장소에 노출하지 않았는가?
  • 입력란은 getByLabel로 찾고 있는가?
  • 버튼은 getByRole로 찾고 있는가?
  • 긴 CSS 선택자와 XPath를 피했는가?
  • 임의의 waitForTimeout을 사용하지 않았는가?
  • 가입 완료 화면이나 URL을 검증하는가?
  • 약한 비밀번호와 불일치 값도 검사하는가?
  • 테스트 데이터 정리 방법이 있는가?
  • 실패 시 screenshot이나 trace를 확인할 수 있는가?
  • 실제 메일이나 문자 비용이 발생하지 않는가?

마무리

Playwright 회원가입 테스트는 입력란을 자동으로 채우는 스크립트에 그쳐서는 안 된다. 계정이 실제로 생성됐는지, 성공 화면으로 이동했는지, 잘못된 입력을 차단하는지, 테스트 데이터가 남지 않는지까지 확인해야 한다.

처음에는 Chromium 하나에서 정상 회원가입과 비밀번호 오류 테스트만 작성해도 충분하다. 테스트가 안정적으로 실행되면 중복 이메일, 약관 미동의, 이메일 인증, 모바일 화면과 다른 브라우저까지 범위를 넓힐 수 있다.

회원가입은 사용자가 서비스에 들어오는 첫 번째 관문이다. AI가 만든 화면이 보인다는 사실보다 실제 가입 흐름이 반복해서 정상 작동한다는 증거를 남기는 것이 더 중요하다.

댓글 남기기