안녕하세요. 매쓰플랫 QA 최지예입니다.
매쓰플랫은 선생님이 학습지와 교재를 만들고, 학생에게 출제하고, 채점하고, 보고서를 확인하는 서비스입니다. 크게 보면 네 단계지만, 그 안의 화면과 갈래는 훨씬 많습니다. 저는 배포 전에 이 흐름과 그 안의 기능들이 제대로 동작하는지 테스트합니다.
테스트해야 할 영역도 많고, 같은 테스트를 반복해서 해야 하는 경우도 많습니다. 이 반복적인 테스트를 자동화하면서 지금은 QA가 더 중요한 부분에 집중할 수 있는 환경을 만들고 있습니다.
지난 반년간 AI 에이전트와 함께 테스트를 만들고 자동화해 온 과정을 알려드리겠습니다.
시나리오가 돌아가는 동안 액션 단위로 로그가 쌓이고, 통과한 동작이 하나씩 기록됩니다.
배포 전에 같은 화면을 다시 누르는 일
제 일은 판단에서 시작합니다. 기능이 나오기 전에 명세를 읽고 조건별로 어떻게 동작해야 하는지 확인합니다. 사용자가 밟을 순서 중에서 위험한 경로를 고르고, 문제가 보고되면 어떤 조건에서 재현되는지 좁혀 개발자에게 전달합니다. 이번 변경을 배포해도 되는지도 판단합니다.
테스트는 그 판단의 근거를 모으는 과정입니다.
매쓰플랫은 선생님이 실제 수업에서 사용하는 서비스이기에 기능 하나에 문제가 생기면 그날 수업에 영향을 줄 수 있습니다. 그래서 새 기능을 확인한 뒤에는 기존에 잘 동작하던 기능에도 문제가 없는지 다시 테스트해야 합니다.
이미 여러 번 확인한 흐름을 다시 밟는 데 시간이 쏠리면 다른 일에 쓸 시간이 줄어듭니다. 새로 들어온 기능의 명세를 한 번 더 따져보거나 아직 아무도 밟지 않은 경로를 생각해보는 일이 뒤로 밀립니다. 그래서 반복해서 확인해야 하는 부분부터 자동화하기로 했습니다.
첫 번째 시나리오 이후에 생긴 문제
제가 처음 자동화한 것은 매달 자정에 열리는 시험이 정해진 날짜에 정상적으로 출제되는지 확인하는 일이었습니다. 자정에 열리는 시험이라 매달 팀원들이 새벽에 테스트해야 했습니다. 시나리오 하나는 금방 만들었습니다. 화면을 열고, 클릭하고, 결과를 확인하는 코드를 순서대로 쓰면 됐습니다. 실행해보니 동작했습니다.
문제는 그 다음이었습니다. 두 번째 시나리오를 어떻게 붙일지, 화면이 바뀌면 어디를 고쳐야 할지 정해져 있지 않았습니다. 처음부터 구조를 생각하지 않고 만들었기 때문입니다. 하나 만드는 건 어렵지 않은데 계속 늘려갈 방법이 없었습니다. 테스트도 자주 깨졌습니다. 버튼 위치가 조금 바뀌면 깨졌고, 페이지가 조금 늦게 열려도 실패했습니다. 어떤 날은 테스트가 실패해서 원인을 찾아보니 서비스는 정상이고 테스트가 너무 빨리 다음 동작으로 넘어간 경우도 있었습니다. 테스트가 열 번 중 아홉 번만 통과한다면 신뢰하고 계속 사용하기 어렵습니다. 실패할 때마다 사람이 다시 확인해야 하고, 같은 실패가 반복되면 실제 문제인지 테스트 문제인지부터 확인해야 하기 때문입니다.
매쓰플랫은 기능이 많고 사용자가 밟는 경로도 여러 갈래입니다. 스크립트를 복사해서 늘리는 방식은 유지하기 어려웠습니다. 로그인하고 학습지를 만들고 출제하는 동작은 여러 시나리오에서 반복됩니다. 이 코드를 스무 개 파일에 복사해두면 화면이 한 번 바뀌었을 때 스무 곳을 고쳐야 합니다. 화면을 기준으로 코드를 묶는 방식(Page Object)도 제가 만들려는 구조와는 달랐습니다. 제가 반복해서 재사용하고 싶었던 단위는 화면보다 사용자의 행동에 가까웠기 때문입니다. 같은 학습지 만들기가 출제 시나리오에도, 채점 시나리오에도, 오답 학습지 시나리오에도 들어갑니다.
그래서 화면이 아니라 동작을 단위로 묶는 방식으로 바꿨습니다.
단위는 이미 쓰던 문서에 있었습니다
제가 쓰는 테스트 케이스를 다시 봤습니다.
테스트 케이스는 STEP으로 나뉘어 있습니다. 로그인한다, 학년을 고른다, 문제 수를 입력한다, 저장한다. 그리고 같은 STEP이 여러 케이스에 반복해서 나옵니다. 학습지를 만드는 STEP은 출제 케이스에도, 채점 케이스에도, 오답 학습지 케이스에도 들어갑니다. 자동화도 STEP 단위로 나누기로 했습니다. STEP 하나를 함수 하나로 만들어두면 같은 동작을 다시 만들 필요가 없습니다. 시나리오에서는 그 동작의 이름을 순서대로 적으면 됩니다.
전체 구조는 두 부분으로 나뉩니다. 아래에는 화면을 조작하는 동작이 있습니다. 학년을 고르는 동작, 출제 버튼을 누르는 동작, 전체를 정답 처리하는 동작입니다. 위에는 그 동작의 이름을 순서대로 적은 시나리오가 있습니다. 동작을 만드는 데는 코드가 필요하지만, 시나리오를 구성하는 데는 필요 없습니다. 이미 만들어진 동작의 이름을 조합해서 어떤 순서로 검증할지 정하면 됩니다.
실제 시나리오 파일은 이렇게 생겼습니다.
학생 이름 같은 값은 환경변수로 두고 실행할 때 넣습니다. 특정 계정이나 특정 데이터에 묶이지 않게 하기 위해서입니다.
Playwright에는 테스트 러너가 따로 있습니다. 그걸 쓰지 않고 시나리오를 JSON으로 두고 실행 엔진을 직접 만든 이유는 하나입니다. 코드를 직접 작성하지 않아도 동작 이름을 조합해서 시나리오를 만들 수 있어야 했기 때문입니다. 동작 이름을 한글로 둔 것도 같은 이유입니다. 테스트 케이스의 STEP과 동일하게 적혀 있어야 시나리오를 읽고 쓰기 쉽습니다. 실행 엔진은 Node.js에서 Playwright를 직접 호출해서 시나리오 파일의 동작을 순서대로 실행합니다.
동작 안은 다시 세 층입니다.
층 | 하는 일 | 실제 동작 이름 |
버튼 하나 | 화면 요소를 직접 조작 | 학년_선택하기 |
묶은 동작 | 버튼 동작 여러 개를 묶음 | 학습지_빠르게_만들기 |
전체 흐름 | 로그인부터 끝까지 | 학습지_만들고_출제하기_전체 |
위 시나리오의 첫 번째 동작이 맨 아랫줄의 전체 흐름이고, 그 뒤에 오는 동작들은 그보다 작은 단위입니다. 시나리오에서는 필요한 수준의 동작을 골라 부르고, 문제가 생기면 아래 층의 동작을 따로 실행해서 어디서 문제가 생겼는지 확인합니다. 동작은 모두 비슷한 형태의 함수로 만듭니다. 화면과 시나리오가 넘긴 값, 앞선 동작에서 남긴 값을 받습니다.
값은 시나리오에서 지정한 값, 앞 동작이 남긴 값, 기본값 순서로 사용합니다. 그래서 같은 동작을 제목을 정해서 만드는 시나리오와, 앞에서 만든 학습지를 이어서 찾는 시나리오에서 모두 사용할 수 있습니다. 화면 요소를 찾는 코드는 버튼 하나를 누르는 수준의 동작에만 둡니다. 그것들을 묶은 동작과 전체 흐름은 아래 동작의 이름만 부릅니다. 그래서 화면이 바뀌면 그 화면을 직접 건드리는 동작 몇 개만 고치면 되고, 그 동작을 쓰는 시나리오는 손대지 않습니다.
여기에 한 가지 규칙을 더 뒀습니다. 생성이나 출제처럼 무언가를 변경하는 시나리오에는 마지막에 결과 확인을 붙입니다. 위 시나리오도 출제한 뒤 학생 쪽에서 학습지가 실제로 보이는지 확인합니다. 만드는 동작이 에러 없이 끝났다는 것과 실제로 만들어졌다는 것은 다르기 때문입니다. 이 확인이 없으면 아무것도 만들어지지 않았는데도 테스트가 통과할 수 있습니다.
지금은 동작이 약 350개, 시나리오가 약 180개 있습니다.
같은 동작이 여러 시나리오에서 재사용되는 모습.
자동화가 자주 깨졌습니다
시간이 가장 많이 들어간 건 만들어둔 테스트가 일정하게 동작하지 않는 문제였습니다. 접속할 때마다 뜨는 안내 팝업이 있었습니다. 사용자는 한 번 닫으면 그 뒤로 신경 쓰지 않지만, 자동화는 매번 새 브라우저로 접속하기 때문에 매번 다시 만납니다. 팝업이 화면을 덮고 있으면 뒤의 목록을 제대로 확인하지 못해서 테스트가 목록이 없는 것으로 판단하기도 했습니다.
여러 시나리오를 동시에 실행할 때도 문제가 있었습니다. 브라우저가 자원을 나눠 쓰면서 화면 전환이 평소보다 늦어졌고 테스트는 그만큼 기다리지 않았습니다. 이런 문제를 각 동작마다 따로 처리하면 같은 코드가 계속 생깁니다. 그래서 클릭과 입력을 공통 함수로 감쌌습니다.
Playwright는 요소가 보이고 누를 수 있을 때까지 기다려 줍니다. 다만 그렇게 기다렸는데도 실패하면 거기서 끝나버립니다. 실제로는 모달이 잠깐 덮었거나 화면 전환이 늦어서 실패한 클릭이 잠시 뒤에 다시 누르면 되는 경우가 많았습니다. 그래서 실패한 동작은 정해진 횟수만큼 다시 시도하고, 각 시도를 기록으로 남기도록 했습니다. 팝업도 각 동작을 시작하기 전에 정리합니다.
실행 중에는 이런 기록이 남습니다.
몇 번째 시도에서 무엇 때문에 실패했는지 기록에 남습니다. 그래서 나중에 확인할 때 화면을 다시 열지 않아도 어디를 봐야 하는지 알 수 있습니다. 재시도가 실제 문제를 가릴 수도 있습니다. 간헐적으로 눌리지 않는 버튼이 있다면 재시도 끝에 통과해버리기 때문입니다. 그래서 재시도해서 통과한 경우도 그냥 정상으로 넘기지 않고 확인할 수 있도록 기록을 남깁니다.
동시 실행으로 화면 전환이 늦어지는 경우는 공통 재시도 로직으로 대응했습니다. 그래도 한 시나리오에서 생긴 문제가 다른 시나리오까지 끌고 가지 않도록 시나리오 하나를 프로세스 하나로 분리했습니다. 한 시나리오에서 브라우저가 종료돼도 다른 시나리오와 대시보드에는 영향을 주지 않고, 제한 시간을 넘긴 시나리오도 해당 시나리오만 종료합니다. 오래 붙잡고 있었던 문제도 하나 있었습니다.
입력은 됐는데 저장이 안 됐습니다
학습지 제목을 입력하고 저장하는 테스트가 있었습니다. 어느 날부터 뒤에 오는 시나리오들이 계속 실패했습니다. 만들어진 학습지를 제목으로 찾지 못한다는 것이었습니다. 입력 자체는 성공했습니다. 입력 직후 값을 읽어보면 제가 넣은 제목이 그대로 보였습니다. 그런데 저장된 이름은 기본값이었습니다.
처음에는 셀렉터나 저장 동작을 의심했습니다. 확인해보니 원인은 입력 방식이었습니다. Playwright의 fill()은 값을 한 번에 넣고 input 이벤트 하나를 보냅니다. 보통의 React 입력 칸은 이걸로 충분한데, 이 화면에서는 그것만으로 저장에 쓰이는 상태가 갱신되지 않았습니다. 화면에는 값이 보이는데 저장되는 값은 그대로였습니다. 그래서 저장 버튼을 누르면 원래 들고 있던 기본값이 저장됐습니다.
그래서 실제 키 입력에 가깝게 처리하도록 바꿨습니다.
이렇게 바꾸자 여러 시나리오가 같이 해결됐습니다. 제목으로 학습지를 찾던 시나리오뿐 아니라 수정하거나 휴지통에서 복구하는 시나리오도 같은 이유로 실패하고 있었습니다. 사용자에게도 같은 문제가 생기는지는 따로 확인했습니다. 사람이 하듯이 붙여넣기로 제목을 넣고 저장해보니 제대로 저장됐습니다. 서비스 문제가 아니라 자동화가 값을 넣는 방식에서만 생기는 문제였습니다. 그 뒤로는 입력 직후 값은 정상인데 저장된 값만 기본값으로 나오는 경우를 따로 확인하고 있습니다.
자동화의 실패 신호가 늘 서비스 문제를 뜻하지는 않았습니다. 사람은 팝업이 뜨면 닫고, 값을 입력할 때는 타이핑하고, 화면이 늦으면 조금 기다립니다. 자동화에서는 이런 행동을 하나씩 직접 정해줘야 합니다. 그래서 테스트가 실패하면 서비스뿐만 아니라 테스트도 함께 확인합니다. 테스트가 잘못된 경우도 있고 실제 서비스 문제인 경우도 있기 때문입니다. 둘 중 하나라고 미리 정해두면 오히려 확인하는 데 시간이 더 걸립니다.
자동화 범위를 알 수 없었습니다
시나리오가 백 개를 넘어가면서 어디까지 자동화되어 있는지 한눈에 보기 어려워졌습니다. 테스트 개수만 세서는 알 수 없었습니다. 통과율도 마찬가지였습니다. 테스트가 전부 통과해도 애초에 테스트하지 않은 기능이 있다면 알 수 없기 때문입니다.
그래서 테스트를 세는 대신 기능부터 정리했습니다.
기능 영역마다 문서를 하나씩 만들었습니다. 그 기능이 어떻게 동작해야 하는지와 사용자가 밟는 STEP을 정리한 문서로, 정책서라고 부릅니다. 선생님이 쓰는 기능부터 학생 앱까지 영역별로 만들었습니다. 다 적고 보니 정책서 약 50개에 STEP은 약 250개였습니다.
그리고 각 STEP 옆에 자동화 여부를 표시했습니다. 자동화가 STEP 단위로 만들어져 있기 때문에 세는 단위와 실제 구현 단위가 같습니다. 동작 수가 STEP 수보다 많은 이유는 STEP 하나가 버튼 수준의 동작 여러 개로 이루어지기 때문입니다. 현재는 전체 STEP의 약 3분의 2, 약 170개가 자동화되어 있습니다.
자동화된 STEP은 배포 전에 자동으로 확인되고, 비어 있는 STEP은 제가 직접 확인해야 하는 목록입니다. 이렇게 정리하고 나니 아직 자동화하지 않은 부분도 보이기 시작했습니다. 남은 3분의 1이 어디인지 알고 있으면 다음에 무엇을 할지도 정할 수 있습니다.
정책서의 STEP 목록과 STEP별 자동화 여부.
이제 테스트를 만드는 과정도 줄이고 있습니다
테스트 실행은 자동화됐지만 테스트를 만드는 일은 여전히 손으로 했습니다. 남은 3분의 1을 채우는 속도도 결국 제가 화면을 확인하고 동작을 만드는 속도에 달려 있었습니다. 구조를 만들 때부터 AI와 함께 작업했고, 지금은 테스트를 만드는 과정에도 함께하고 있습니다.
바로 테스트를 만들어달라고 하기보다는 먼저 작업 규칙을 문서로 만들어두었습니다. 실행 엔진과 공통 모듈은 수정하지 말 것, 기존 동작의 이름을 바꾸지 말 것, 주소나 계정을 코드에 직접 적지 말 것, 추측하지 말고 실제 화면을 확인한 뒤 만들 것 같은 규칙입니다. 화면에서 요소를 찾는 순서도 정해뒀습니다.
로케이터 우선순위
클래스 이름을 직접 사용하는 방식은 화면이 조금만 바뀌어도 영향을 받기 쉽습니다. 반면 확인 버튼처럼 사용자가 실제로 알아볼 수 있는 이름을 기준으로 찾으면 변경에 조금 더 대응하기 쉽습니다. 그 이름이 화면에서 사라지면 테스트도 같이 실패하니, 검증이 사용자 관점에 가까워지는 효과도 있습니다. testid는 문구나 역할이 바뀌어도 깨지지 않아 가장 안정적이지만, 사용자 관점 검증을 우선하려고 순서는 뒤에 두었습니다.
동작 하나를 새로 만들려면 실제 화면 구조를 확인하고, 기존에 비슷한 동작이 있는지 찾고, 실행해보고, 실패한 지점을 고쳐 다시 실행하는 과정을 여러 번 거칩니다. 이 반복을 AI가 대신 돌립니다. 기존 시나리오를 원하는 지점까지 실행한 뒤 그 화면의 구조를 덤프하는 스캐너를 만들어 두었고, AI 에이전트는 그것으로 실제 화면을 확인한 뒤 동작을 만듭니다. 줄어든 건 만드는 시간보다 찾고 고치는 데 드는 시간입니다.
다만 어떤 기능을 테스트할지 결정하는 것은 여전히 QA의 일입니다. 만들어진 테스트가 실제로 의도한 것을 확인하고 있는지도 직접 봅니다. 개발자가 올린 변경 사항을 읽고 영향받는 시나리오만 골라내는 것도 실험하고 있습니다. 지금은 배포 전에 영역 단위로 묶어서 실행하는데, 바뀐 곳에 맞춰 필요한 것만 돌릴 수 있으면 확인이 더 빨라집니다.
자동화하지 않은 것도 있습니다
처음에는 만든 테스트를 가능한 한 모두 유지하려고 했습니다. 지금은 화면이 바뀌었을 때 더 이상 의미 있는 검증을 하지 않는 테스트는 정리합니다. 통과하더라도 실제로 확인하는 것이 없다면 유지할 이유가 없기 때문입니다.
착수했다가 멈춘 영역도 있습니다. 기술적으로 구현하지 못해서가 아니라, 검증에 필요한 데이터 조건이 없어서 화면이 떴다는 것 정도만 확인할 수 있는 경우입니다. 그럴 때는 억지로 통과하는 테스트를 만들지 않습니다. 왜 멈췄는지 기록해두고 나중에 필요한 조건이 갖춰지면 다시 진행합니다. 자동화에도 한계가 있습니다. 자동화는 제가 알고 있는 것을 확인합니다. 시나리오는 제가 예상한 순서로 화면을 누르고 예상한 결과를 확인합니다. 예상하지 못한 순서에서 어떤 문제가 생기는지는 시나리오에 들어 있지 않습니다.
그래서 모든 테스트가 통과한 날에도 전체 서비스에 문제가 없다고 생각하지 않습니다. 제가 확인하도록 만든 범위 안에서는 문제가 발견되지 않았다는 의미로 봅니다.
마치며
자동화를 시작할 때는 반복해서 테스트하는 시간이 줄어들 거라고 생각했습니다. 실제로 반복되는 테스트에 드는 시간은 많이 줄었습니다. 이전에는 큰 시나리오 하나를 직접 테스트하는 데 20분 정도 걸렸지만, 지금은 180개의 시나리오를 약 40분 안에 실행하고 실패한 건만 확인하는 방식으로 바뀌었습니다. 그만큼 명세를 읽고 조건별 동작을 확인하거나, 아직 확인하지 않은 경로를 생각하는 데 시간을 쓸 수 있게 됐습니다.
전체 시나리오를 실행한 뒤의 리포트. 실패한 동작과 지난 실행과 달라진 결과를 표시합니다.
자동화를 하면서 알게 된 것은, 테스트를 많이 만드는 일보다 어떤 테스트가 필요한지 분명히 아는 일이 더 중요하다는 점이었습니다.
앞으로 해야 할 일도, 해보고 싶은 일도 많습니다.
•
남은 3분의 1을 채우는 일. 지금은 정책서에 적힌 STEP을 기준으로 자동화하고 있습니다. 문서에 없지만 사용자가 실제로 밟는 경로도 찾아서 자동화 범위를 넓혀보려고 합니다.
•
배포 과정에 붙이는 일. 지금은 제가 배포 전에 실행하고 있지만, 개발자가 변경 사항을 올리면 관련 시나리오가 알아서 돌고 그 자리에서 결과를 확인할 수 있는 형태를 만들어보려고 합니다. 앞에서 적은 실험이 그 시작입니다.
•
QA만 쓰는 도구로 남지 않게 하는 일. 코드를 몰라도 시나리오를 만들 수 있게 해둔 이유이기도 합니다. 팀원이 확인 순서를 직접 적고, 개발자가 자기 변경을 검증할 시나리오를 골라 돌려볼 수 있는 데까지 시도해보려고 합니다.
지금까지 만든 구조를 계속 다듬으면서, QA만이 아니라 팀 모두가 품질 확인에 참여할 수 있는 환경을 만들어보고 싶습니다. 읽어주셔서 감사합니다.











