1편에서 단일 제품 자동화를 처음 구축한 과정을 썼다. 이번엔 그 다음에 생긴 문제다.
자동화가 잘 돌아가니까 "우리 제품도 해주실 수 있어요?"가 왔다. 한 팀, 두 팀, 세 팀. 결국 네 개 제품이 됐다.
처음엔 그냥 레포를 복사했다. 빠르니까. 그리고 두 달 뒤에 그 결정을 후회했다.
레포가 4개라는 건 공통 코드도 4벌이라는 뜻이다
playwright.config.ts를 하나 고치면 네 군데 다 반영해야 한다. 당연히 누락이 생긴다. 한 레포에서 고친 게 다른 레포엔 안 들어간 채로 CI가 돌아가고 있었다. 버그를 잡았는데 세 개 레포에선 여전히 같은 버그가 있는 상황.
공통 코드를 별도 common 레포에 모아두고, 변경이 생기면 CI가 각 제품 레포에 자동으로 배포하는 구조로 바꿨다.
+-------------------------------------------+
| common 레포 |
| playwright.config.ts / global-setup.ts |
| common.ts / bdd-scripts/ |
+-------------------------------------------+
│
CI Pipeline (Push 감지)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
+---------+ +---------+ +---------+
| 제품 A | | 제품 B | | 제품 C |
+---------+ +---------+ +---------+
규칙은 딱 하나다. 공통 파일은 반드시 common 레포에서만 수정한다. 제품 레포에서 직접 손대면 다음 CI 실행 때 덮어써진다.
/etc/hosts 없이 도메인-IP 매핑하기
멀티 제품 환경에서 제일 귀찮은 게 서버마다 도메인-IP 매핑이었다. 보통 /etc/hosts를 수정하는데, CI 서버에서 권한 문제가 자주 났다. 환경마다 파일을 따로 관리해야 하는 것도 번거롭고.
Chromium에 --host-resolver-rules 옵션이 있다는 걸 알고 나서는 이 문제가 깔끔하게 해결됐다.
// playwright.config.ts
use: {
launchOptions: {
args: [
`--host-resolver-rules=MAP ${globalThis.domain} ${globalThis.ip}`
],
},
}
OS 레벨 DNS 조회 전에 이 규칙이 먼저 적용된다. 권한 없이도 되고, 제품별로 다른 매핑을 코드로 관리할 수 있다.
로그인을 TC마다 하지 않는 이유
제품이 4개가 되니 각 TC에서 로그인 절차를 밟으면 실행 시간이 눈에 띄게 늘어났다. global-setup.ts에서 딱 한 번 로그인하고 세션을 파일로 저장해두면 된다.
projects: [
{
name: 'product-a',
use: {
baseURL: `https://${globalThis.domain}/product-a/`,
storageState: globalThis.storageState, // auth.json
},
},
{
name: 'product-b',
use: {
baseURL: `https://${globalThis.domain}/product-b/`,
storageState: globalThis.cmStorageState, // cm-auth.json
},
},
]
제품마다 인증 구조가 달라서 storageState 파일을 분리했다. global-setup.ts에서 제품 타입을 감지하고 맞는 로그인 절차를 실행한다.
BDD와 Spec, 굳이 하나만 써야 하나
팀마다 선호가 달랐다. Gherkin 시나리오로 쓰고 싶은 팀도 있고, TypeScript 스펙으로 바로 쓰고 싶은 팀도 있었다. 두 방식을 환경 변수 하나로 전환할 수 있게 했다.
const useBdd = process.env.PLAYWRIGHT_MODE === 'bdd';
const bddProductA = useBdd
? defineBddConfig({
outputDir: '.features-gen/product-a',
features: 'features/product-a/**/*.feature',
steps: 'features/product-a/**/steps/*.ts',
})
: './tests/product-a';
PLAYWRIGHT_MODE=bdd로 실행하면 .feature 파일 기반, 아무것도 없으면 .spec.ts 기반으로 동작한다. Jenkinsfile에서 환경 변수만 바꿔주면 CI에서도 두 모드를 모두 돌릴 수 있다.
그 외
워커 수 자동 결정: 로컬 개발 머신과 CI 서버의 코어 수가 다르다. 워커를 고정하면 한쪽에서 항상 낭비가 생긴다. setGlobalProductInfo()에서 CPU 코어 수를 읽어 적정 값을 계산하게 했다.
리포팅 파이프라인: Allure는 BDD 시나리오 단위로 시각화해주고, JUnit XML은 Jenkins 빌드 결과로 바로 읽힌다. 실패 케이스엔 스크린샷·영상·Trace가 자동 첨부된다.
reporter: [
["line"],
["allure-playwright"],
["html"],
["junit", { outputFile: "test-results/junit.xml" }],
],
screenshot: 'only-on-failure',
video: { mode: 'retain-on-failure' },
trace: 'retain-on-failure',
구조를 잡고 나니 새 제품이 추가돼도 할 일이 단순해졌다. CI 배포 타겟 하나 추가하고, 서버 설정 파일에 접속 정보 추가하고, product.txt에 제품명 한 줄 적으면 된다.
3편에서는 TC를 어떻게 작성하고 Jenkins CI까지 배포하는 실무 흐름을 다룬다.